/*************************************/
/*************************************/
/*      Kotoba Kards Design System   */
/*************************************/
/*************************************/
/* Ports the mobile app's visual language to the web.
 *
 * The two products already shared a brand -- same #A40021 crimson, same Worstveld display face
 * for the wordmark. What the web lacked was everything underneath it: the app builds every
 * surface from soft #F6F6F6 panels at a 12px radius with no border and no shadow, at a much
 * larger type scale, while the web was rendering stock Bootstrap defaults.
 *
 * This file is loaded LAST so its :root block wins over Bootstrap's. It is organised as:
 *   1. Tokens              -- lifted from the app's constants/colors.ts + tailwind.config.js
 *   2. Bootstrap remap     -- expresses the tokens through Bootstrap's own --bs-* variables,
 *                             so the ~134 existing .card sites and every .btn restyle themselves
 *   3. Component overrides -- where a Bootstrap default is not close enough to the app
 *   4. New components      -- patterns the app has that Bootstrap has no equivalent for
 *   5. Legacy aliases      -- .btn-brand / .hero-section / .callout etc., so the pages that have
 *                             not been rebuilt still inherit the redesign
 *
 * Source of truth for every value is the app at ../kotoba-kards. */


/*************************************/
/*             1. Tokens             */
/*************************************/
:root {
	/* Colour -- app constants/colors.ts and tailwind.config.js */
	--kk-primary: #A40021;
	--kk-secondary: #7A0019;
	--kk-dark: #212529;
	--kk-surface: #F6F6F6;      /* the app calls this "shading"; every card uses it */
	--kk-bg: #FFFFFF;
	--kk-disabled: #8F8F8F;
	--kk-gray: #A9A9A9;         /* placeholder text */
	--kk-border: #d1d5db;       /* gray-300 */
	--kk-border-soft: #e5e7eb;  /* gray-200 -- progress track, table rules */
	--kk-muted: #6b7280;        /* gray-500 -- lock reasons, captions */
	--kk-success: #22c55e;
	--kk-danger: #C41E3A;
	--kk-danger-dark: #842029;
	--kk-danger-light: #F8D7DA;

	/* Shape. The app uses rounded-xl (12px) for cards, buttons, chips and callouts alike,
	   and a deliberately squarer 4px on text inputs. */
	--kk-radius: 12px;
	--kk-radius-input: 4px;
	--kk-radius-modal: 16px;
	--kk-radius-pill: 999px;

	/* The app is flat: this is the only shadow in the entire codebase, and only modals use it. */
	--kk-shadow-modal: 0 2px 4px rgba(0, 0, 0, .25);

	/* Space -- 4px scale */
	--kk-gutter: 8px;
	--kk-card-pad: 24px;
	--kk-btn-pad-y: 16px;
	--kk-section-gap: 40px;

	/* Type. The app's scale is tuned for a phone at arm's length; applied literally to a 1440px
	   desktop it looks oversized, so these clamp -- a phone browser gets the app's exact numbers
	   (48 / 30 / 20px) and desktop scales down. */
	--kk-page-title: clamp(2rem, 5vw, 3rem);
	--kk-card-title: clamp(1.5rem, 3vw, 1.875rem);
	--kk-btn-label: clamp(1.125rem, 2vw, 1.25rem);
	--kk-stat-value: clamp(2rem, 6vw, 2.25rem);
}


/*************************************/
/*        2. Bootstrap remap         */
/*************************************/
:root {
	--bs-primary: var(--kk-primary);
	--bs-primary-rgb: 164, 0, 33;
	--bs-body-bg: var(--kk-bg);
	--bs-body-color: var(--kk-dark);
	--bs-secondary-color: var(--kk-muted);
	--bs-border-color: var(--kk-border);
	--bs-border-radius: var(--kk-radius);
	--bs-border-radius-sm: var(--kk-radius-input);
	--bs-border-radius-lg: var(--kk-radius-modal);
	--bs-link-color: var(--kk-primary);
	--bs-link-hover-color: var(--kk-secondary);
	--bs-link-color-rgb: 164, 0, 33;
	--bs-link-hover-color-rgb: 122, 0, 25;
	/* Noto Sans JP is already loaded for Japanese content. Putting it last means system-ui still
	   wins for Latin while Japanese glyphs render from a font we actually control. */
	--bs-body-font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue",
		Arial, "Noto Sans", sans-serif, "Noto Sans JP", "Apple Color Emoji", "Segoe UI Emoji";
}

body {
	background-color: var(--kk-bg);
	color: var(--kk-dark);
}

.text-muted {
	color: var(--kk-muted)!important;
}


/*************************************/
/*     3. Component overrides        */
/*************************************/

/* --- Cards ---------------------------------------------------------------
   The single most important rule in this file. In the app a card is a soft grey panel with
   NO border and NO shadow -- separation comes purely from #F6F6F6 against #FFFFFF plus an
   8px gutter. Bootstrap's default card is white-on-white with a hairline border, which is
   why nothing on the web read as a card. */
.card {
	--bs-card-spacer-x: var(--kk-card-pad);
	--bs-card-spacer-y: var(--kk-card-pad);
	--bs-card-bg: var(--kk-surface);
	--bs-card-border-width: 0;
	--bs-card-border-radius: var(--kk-radius);
	--bs-card-inner-border-radius: var(--kk-radius);
}

.card-title {
	font-size: var(--kk-card-title);
	font-weight: 700;
	line-height: 1.2;
}

/* Cards routinely show a single large number under the title (words learned, characters due).
   The app renders these at 36px bold rather than Bootstrap's thin 3.5rem display face. */
.card .display-4 {
	font-size: var(--kk-stat-value);
	font-weight: 700;
	line-height: 1.1;
}

/* --- Buttons -------------------------------------------------------------
   Bootstrap 5.3 drives every button from --bs-btn-* custom properties, so the brand buttons
   are defined by setting those rather than the !important overrides this used to need. */
.btn {
	--bs-btn-border-radius: var(--kk-radius);
	--bs-btn-font-weight: 600;
	font-weight: 600;
}

.btn-brand,
.btn-primary {
	--bs-btn-color: #fff;
	--bs-btn-bg: var(--kk-primary);
	--bs-btn-border-color: var(--kk-primary);
	--bs-btn-hover-color: #fff;
	--bs-btn-hover-bg: var(--kk-secondary);
	--bs-btn-hover-border-color: var(--kk-secondary);
	--bs-btn-active-color: #fff;
	--bs-btn-active-bg: var(--kk-secondary);
	--bs-btn-active-border-color: var(--kk-secondary);
	--bs-btn-disabled-color: #fff;
	--bs-btn-disabled-bg: var(--kk-disabled);
	--bs-btn-disabled-border-color: var(--kk-disabled);
	--bs-btn-disabled-opacity: 1;
}

.btn-brand-dark {
	--bs-btn-color: #fff;
	--bs-btn-bg: var(--kk-secondary);
	--bs-btn-border-color: var(--kk-secondary);
	--bs-btn-hover-color: #fff;
	--bs-btn-hover-bg: var(--kk-primary);
	--bs-btn-hover-border-color: var(--kk-primary);
	--bs-btn-active-color: #fff;
	--bs-btn-active-bg: var(--kk-primary);
	--bs-btn-active-border-color: var(--kk-primary);
	--bs-btn-disabled-color: #fff;
	--bs-btn-disabled-bg: var(--kk-disabled);
	--bs-btn-disabled-border-color: var(--kk-disabled);
	--bs-btn-disabled-opacity: 1;
}

.btn-outline-brand {
	--bs-btn-color: var(--kk-primary);
	--bs-btn-bg: #fff;
	--bs-btn-border-color: var(--kk-primary);
	--bs-btn-hover-color: var(--kk-secondary);
	--bs-btn-hover-bg: var(--kk-surface);
	--bs-btn-hover-border-color: var(--kk-secondary);
	--bs-btn-active-color: #fff;
	--bs-btn-active-bg: var(--kk-primary);
	--bs-btn-active-border-color: var(--kk-primary);
	--bs-btn-disabled-color: var(--kk-disabled);
	--bs-btn-disabled-border-color: var(--kk-disabled);
	--bs-btn-disabled-opacity: 1;
}

/* Bootstrap 5 dropped .btn-block; it is still used ~20 times here. Kept in
   bootstrap-override.css, sized to match the app's full-width controls. */
.btn.btn-block {
	padding-top: .75rem;
	padding-bottom: .75rem;
	font-size: var(--kk-btn-label);
}

/* --- Forms ---------------------------------------------------------------
   Inputs are deliberately squarer than buttons in the app: 4px against the button's 12px. */
.form-control,
.form-select {
	border-radius: var(--kk-radius-input);
	border-color: var(--kk-border);
	color: var(--kk-dark);
}

.form-control::placeholder {
	color: var(--kk-gray);
	opacity: 1;
}

.form-control:focus,
.form-select:focus {
	border-color: var(--kk-primary);
	box-shadow: 0 0 0 .2rem rgba(164, 0, 33, .25);
}

.form-check-input:checked {
	background-color: var(--kk-primary);
	border-color: var(--kk-primary);
}

.form-check-input:focus {
	box-shadow: 0 0 0 .25rem rgba(164, 0, 33, .25);
}

/* Quiz answer validation. The learning and quiz pages already put Bootstrap's own .is-valid /
   .is-invalid on their inputs, so these retune those rather than introducing new class names. */
.form-control.is-valid,
.was-validated .form-control:valid {
	border-color: var(--kk-success);
	color: #15803d;
}

.form-control.is-invalid,
.was-validated .form-control:invalid {
	border-color: var(--kk-danger);
	color: var(--kk-danger-dark);
}

.form-control.is-valid:focus {
	box-shadow: 0 0 0 .2rem rgba(34, 197, 94, .25);
}

.form-control.is-invalid:focus {
	box-shadow: 0 0 0 .2rem rgba(196, 30, 58, .25);
}

/* Multiple-choice answers (the grammar lesson review). The app renders each option as a
   full-width crimson-bordered row rather than a bare radio beside a label. */
.kk-choices .form-check {
	padding: .875rem 1rem .875rem 2.25rem;
	margin-bottom: .75rem;
	border: 1px solid var(--kk-primary);
	border-radius: var(--kk-radius);
}

.kk-choices .form-check-input {
	margin-left: -1.5rem;
}

.kk-choices .form-check-label {
	display: block;
	width: 100%;
	font-size: 1.125rem;
	cursor: pointer;
}

/* --- Progress ------------------------------------------------------------
   12px tall, fully rounded, gray-200 track with a crimson fill. Used identically on the
   dashboard, the hubs and inside every learning session. */
.progress,
.progress-stacked {
	--bs-progress-height: 12px;
	--bs-progress-bg: var(--kk-border-soft);
	--bs-progress-border-radius: var(--kk-radius-pill);
	--bs-progress-bar-bg: var(--kk-primary);
}

.progress-bar {
	border-radius: var(--kk-radius-pill);
}

/* --- Modals --------------------------------------------------------------
   The one place the app uses a shadow. */
.modal-content {
	border: 0;
	border-radius: var(--kk-radius-modal);
	box-shadow: var(--kk-shadow-modal);
}

.modal-header,
.modal-footer {
	border-color: var(--kk-border-soft);
}

/* --- Tabs and pills ------------------------------------------------------ */
.nav-tabs {
	--bs-nav-tabs-border-color: var(--kk-border);
	--bs-nav-tabs-link-active-color: var(--kk-dark);
	--bs-nav-tabs-link-active-border-color: var(--kk-border) var(--kk-border) var(--kk-bg);
	--bs-nav-link-color: var(--kk-primary);
	--bs-nav-link-hover-color: var(--kk-secondary);
}

.nav-pills {
	--bs-nav-pills-link-active-bg: var(--kk-primary);
	--bs-nav-pills-border-radius: var(--kk-radius);
	--bs-nav-link-color: var(--kk-primary);
}

.nav-pills .nav-link {
	border: 1px solid var(--kk-primary);
}

/* --- Tables -------------------------------------------------------------- */
.table {
	--bs-table-border-color: var(--kk-border-soft);
}

.table-dark {
	--bs-table-bg: var(--kk-dark);
}

/* --- Badges -------------------------------------------------------------- */
.badge {
	--bs-badge-border-radius: var(--kk-radius-pill);
	font-weight: 600;
}


/*************************************/
/*        4. New components          */
/*************************************/

/* --- Icons ---------------------------------------------------------------
   Everything from functions/icons.php. Inherits colour so a single glyph works on crimson,
   on white and in the muted state, the way the app tints its 24px sprites. */
.kk-icon {
	display: inline-block;
	vertical-align: -.125em;
	flex-shrink: 0;
}

.kk-spin {
	animation: kk-spin 1s linear infinite;
}

@keyframes kk-spin {
	to { transform: rotate(360deg); }
}

/* Loading spinner. Replaces <i class="fa fa-spinner fa-spin"></i>, which appeared 27 times
   across 11 files while Font Awesome was never loaded on any page -- so every one of those
   spinners was invisible. Deliberately CSS-only rather than an SVG from functions/icons.php,
   because several of these live inside JS string literals where a PHP call cannot reach. */
.kk-spinner {
	display: inline-block;
	width: 1em;
	height: 1em;
	vertical-align: -.125em;
	border: .15em solid currentColor;
	border-right-color: transparent;
	border-radius: 50%;
	animation: kk-spin .75s linear infinite;
}

/* Correct / incorrect glyphs in results tables, matching the app's ✓ and ✕. */
.kk-mark {
	font-size: 1.25rem;
	line-height: 1;
}

@media (prefers-reduced-motion: reduce) {
	.kk-spin,
	.kk-spinner { animation: none; }
}

/* --- Funnel progress -----------------------------------------------------
   Account -> Free Trial -> Start Learning, rendered by includes/funnel-progress.php.

   Laid out with flex rather than a grid so the connector between two steps can be a plain
   ::after on the step itself. Labels wrap to a second line on narrow screens instead of the
   row scrolling sideways -- three short words fit at 320px, and a progress indicator that
   needs scrolling to read defeats the point of having one. */
.kk-funnel {
	display: flex;
	align-items: flex-start;
	justify-content: center;
	gap: 0;
	list-style: none;
	margin: 0 0 1.5rem;
	padding: 0;
	counter-reset: none;
}

.kk-funnel-step {
	display: flex;
	flex-direction: column;
	align-items: center;
	flex: 1 1 0;
	max-width: 11rem;
	position: relative;
	text-align: center;
	gap: .5rem;
}

/* The connector sits between this step's marker and the next one's. Anchored to the marker's
   vertical centre so it does not drift when a label wraps to two lines. */
.kk-funnel-step:not(:last-child)::after {
	content: "";
	position: absolute;
	top: 1rem;
	left: calc(50% + 1.5rem);
	right: calc(-50% + 1.5rem);
	height: 2px;
	background-color: var(--kk-border-soft);
}

.kk-funnel-step.kk-funnel-complete:not(:last-child)::after {
	background-color: var(--kk-primary);
}

.kk-funnel-marker {
	display: flex;
	align-items: center;
	justify-content: center;
	width: 2rem;
	height: 2rem;
	border-radius: var(--kk-radius-pill);
	border: 2px solid var(--kk-border-soft);
	background-color: var(--kk-bg);
	color: var(--kk-muted);
	font-weight: 700;
	line-height: 1;
	flex-shrink: 0;
}

.kk-funnel-label {
	font-size: .875rem;
	color: var(--kk-muted);
	line-height: 1.25;
}

.kk-funnel-current .kk-funnel-marker {
	border-color: var(--kk-primary);
	background-color: var(--kk-primary);
	color: #fff;
}

.kk-funnel-current .kk-funnel-label {
	color: var(--kk-dark);
	font-weight: 700;
}

.kk-funnel-complete .kk-funnel-marker {
	border-color: var(--kk-primary);
	background-color: var(--kk-primary);
	color: #fff;
}

.kk-funnel-complete .kk-funnel-label {
	color: var(--kk-dark);
}

/* --- Plan cards ----------------------------------------------------------
   The two subscription options on select-plan.php. The whole card is the click target, not
   just the radio, so the hit area matches what the eye reads as one choice.

   .kk-plan-featured is the yearly option: a crimson border plus a badge straddling the top
   edge. The badge is positioned rather than being a flow element so it can overlap the border
   without the card needing padding it would not otherwise want. */
.kk-plan {
	position: relative;
	display: block;
	background-color: var(--kk-surface);
	border: 2px solid transparent;
	border-radius: var(--kk-radius);
	padding: var(--kk-card-pad);
	cursor: pointer;
	width: 100%;
	text-align: left;
}

/* The crimson border means SELECTED, and nothing else. The featured card is marked by its badge
   and by being listed first -- if it also carried a permanent border, both cards would look
   active whenever the learner picked the monthly one. Same split as the mobile screen. */
.kk-plan:has(input:checked) {
	border-color: var(--kk-primary);
}

/* Keyboard focus has to remain visible: the radio itself is the focus target and it is small,
   so the ring goes on the card. */
.kk-plan:has(input:focus-visible) {
	outline: 2px solid var(--kk-primary);
	outline-offset: 2px;
}

/* Reserves room for the badge that straddles the top edge, so it cannot collide with the
   radio row beneath it. */
.kk-plan-featured {
	margin-top: .75rem;
}

.kk-plan-badge {
	position: absolute;
	top: 0;
	left: 50%;
	transform: translate(-50%, -50%);
	background-color: var(--kk-primary);
	color: #fff;
	border-radius: var(--kk-radius-pill);
	padding: .25rem 1rem;
	font-size: .8125rem;
	font-weight: 700;
	line-height: 1.2;
	white-space: nowrap;
}

.kk-plan-price {
	font-size: 2rem;
	font-weight: 700;
	line-height: 1.1;
}

.kk-plan-save {
	color: var(--kk-primary);
	font-weight: 700;
}

/* --- Trial timeline ------------------------------------------------------
   "How your trial works" on select-plan.php: Today / For 7 Days / After 7 Days. A list rather
   than three cards, because the point is the sequence. */
.kk-timeline {
	list-style: none;
	margin: 0;
	padding: 0;
}

.kk-timeline li {
	position: relative;
	padding: 0 0 1.25rem 1.75rem;
}

.kk-timeline li:last-child {
	padding-bottom: 0;
}

.kk-timeline li::before {
	content: "";
	position: absolute;
	left: 0;
	top: .35rem;
	width: .75rem;
	height: .75rem;
	border-radius: 50%;
	background-color: var(--kk-primary);
}

/* The rule joining one dot to the next. Not drawn on the last item, so the sequence ends
   rather than trailing off. */
.kk-timeline li:not(:last-child)::after {
	content: "";
	position: absolute;
	left: .3125rem;
	top: 1.1rem;
	bottom: .25rem;
	width: 2px;
	background-color: var(--kk-border-soft);
}

.kk-timeline strong {
	display: block;
}

/* --- Panel ---------------------------------------------------------------
   The app's card, available without Bootstrap's .card wrapper markup. */
.kk-panel {
	background-color: var(--kk-surface);
	border-radius: var(--kk-radius);
	padding: var(--kk-card-pad);
}

.kk-panel__title {
	font-size: var(--kk-card-title);
	font-weight: 700;
	line-height: 1.2;
	margin: 0 0 .75rem;
}

.kk-panel__note {
	color: var(--kk-muted);
	font-size: .9375rem;
	margin: .25rem 0 0;
}

.kk-panel__note a {
	color: var(--kk-primary);
	text-decoration: underline;
}

/* --- Block button --------------------------------------------------------
   The app's signature control: a full-width crimson bar with the label (and an optional
   24px icon) on the left and the count right-aligned INSIDE the button, rather than
   trailing the label as a separate pill badge. */
.kk-btn-block {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .75rem;
	width: 100%;
	padding: var(--kk-btn-pad-y) 1rem;
	border: 1px solid var(--kk-primary);
	border-radius: var(--kk-radius);
	background-color: var(--kk-primary);
	color: #fff;
	font-size: var(--kk-btn-label);
	font-weight: 600;
	line-height: 1.2;
	text-align: left;
	text-decoration: none;
	transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
}

.kk-btn-block:hover,
.kk-btn-block:focus {
	background-color: var(--kk-secondary);
	border-color: var(--kk-secondary);
	color: #fff;
}

.kk-btn-block:focus-visible {
	outline: 0;
	box-shadow: 0 0 0 .2rem rgba(164, 0, 33, .35);
}

.kk-btn-block__label {
	display: flex;
	align-items: center;
	gap: .5rem;
}

.kk-btn-block__count {
	font-variant-numeric: tabular-nums;
}

.kk-btn-block--outline {
	background-color: #fff;
	color: var(--kk-primary);
}

.kk-btn-block--outline:hover,
.kk-btn-block--outline:focus {
	background-color: var(--kk-surface);
	border-color: var(--kk-secondary);
	color: var(--kk-secondary);
}

.kk-btn-block.disabled,
.kk-btn-block:disabled,
.kk-btn-block[aria-disabled="true"] {
	background-color: var(--kk-disabled);
	border-color: var(--kk-disabled);
	color: #fff;
	pointer-events: none;
}

/* Stacked controls sit 12px apart in the app, primary first then outline. */
.kk-btn-block + .kk-btn-block,
.kk-btn-block + .kk-panel__note + .kk-btn-block {
	margin-top: .75rem;
}

/* --- Session controls ----------------------------------------------------
   Every learning and quiz screen in the app ends in the same stack: a full-width filled primary
   with a full-width outline secondary 12px beneath it, never side by side. The web used
   `d-grid gap-3 d-md-block`, which stacked them on phones but scattered them into inline pills
   on desktop.

   The buttons keep their .btn classes and their ids, because the session JS toggles .d-none on
   those ids to step through Show English -> Review -> Submit -> Next. Bootstrap's .d-none is
   !important, so it still wins over the flex display here. */
.kk-controls {
	display: flex;
	flex-direction: column;
	gap: .75rem;
	width: 100%;
	max-width: 420px;
	margin-left: auto;
	margin-right: auto;
}

.kk-controls .btn {
	width: 100%;
	padding: var(--kk-btn-pad-y) 1rem;
	font-size: var(--kk-btn-label);
}

/* --- Page header ---------------------------------------------------------
   The crimson band under the navbar. The existing markup is
   <div class="row py-5 hero-section">, and py-5 is 3rem = 48px, which already matches the
   app's py-12 header exactly -- so only the title treatment needed to change. */
.hero-section,
.kk-page-header {
	background-color: var(--kk-primary);
	color: #fff;
}

.hero-section .display-4,
.kk-page-header .display-4,
.kk-page-header__title {
	font-size: var(--kk-page-title);
	font-weight: 700;
	line-height: 1.15;
}

/* Plain links only. This used to match every <a> in the band, including <a class="btn">, and
   .hero-section a (0,1,1) outranks Bootstrap's .btn (0,1,0) -- so the white won and the two
   .btn-light buttons on study-topic.php and writing-study-topic.php rendered white text on a
   near-white #f8f9fa fill. Buttons keep the text colour their own variant sets. */
.hero-section a:not(.btn),
.kk-page-header a:not(.btn) {
	color: #fff;
}

.kk-page-header__eyebrow {
	opacity: .8;
}

/* --- Greeting ------------------------------------------------------------
   Mascot beside こんにちは. The app opens on this; the web never showed the mascot once
   logged in, which is most of why it read as a colder product. */
.kk-greeting {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 1.25rem;
	padding: 1.25rem 0;
}

/* The dashboard greeting IS the hero, so the band's own py-5 supplies the vertical space and
   the greeting should not add a second helping. Text colour is inherited from .hero-section. */
.hero-section .kk-greeting {
	padding: 0;
}

.kk-greeting__mascot {
	height: 130px;
	object-fit: contain;
	flex-shrink: 0;
}

.kk-greeting__hello {
	font-size: 3rem;
	font-weight: 700;
	line-height: 1.2;
	margin: 0;
}

.kk-greeting__name {
	font-size: 2.5rem;
	line-height: 1.2;
	margin: 0;
}

/* --- Stat tile ----------------------------------------------------------- */
.kk-stat {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	height: 100%;
	background-color: var(--kk-surface);
	border-radius: var(--kk-radius);
	padding: 1rem;
	text-align: center;
}

.kk-stat__label {
	font-weight: 700;
	font-size: 1rem;
	margin: 0;
}

.kk-stat__value {
	font-size: var(--kk-stat-value);
	line-height: 1.1;
	margin: .25rem 0 0;
}

/* --- Chip ----------------------------------------------------------------
   Replaces .vocab-category / .grammar-category / .writing-category, which were three
   byte-identical copies of the same rule in three different page <style> blocks. */
.kk-chip,
.vocab-category,
.grammar-category,
.writing-category {
	display: inline-block;
	padding: .375rem .75rem;
	border: 1px solid var(--kk-primary);
	border-radius: var(--kk-radius);
	color: var(--kk-primary);
	background-color: #fff;
	text-decoration: none;
}

.kk-chip--active {
	background-color: var(--kk-primary);
	color: #fff;
}

/* --- Callout ------------------------------------------------------------- */
.kk-callout,
.callout {
	padding: 1rem;
	border: 1px solid var(--kk-primary);
	border-left-width: 4px;
	border-radius: var(--kk-radius);
	font-size: 1.125rem;
	text-align: center;
}

/* --- Progress label row --------------------------------------------------
   "Track - Topic" on the left, "72%" bold on the right, sitting directly above the bar. */
.kk-progress-label {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: .75rem;
	margin: .5rem 0 .25rem;
	font-size: 1.125rem;
}

.kk-progress-label__value {
	font-weight: 700;
}

/* --- Topic progress row --------------------------------------------------
   The app lists study topics as a plain row: title on the left, percentage on the right in
   crimson, separated by a hairline rule. The web was rendering each one as a bordered chip
   wrapped around a green Bootstrap progress bar with the number printed inside the fill, which
   became unreadable at low percentages -- hence the `overflow-visible text-dark` special case
   that used to be needed below 1%. */
.kk-topic-row {
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .625rem 0;
	border-bottom: 1px solid var(--kk-border);
	text-decoration: none;
	color: var(--kk-dark);
}

.kk-topic-row:hover {
	color: var(--kk-primary);
}

.kk-topic-row__title {
	flex: 1 1 auto;
	min-width: 0;
}

.kk-topic-row__bar {
	flex: 0 0 6rem;
	height: 8px;
	background-color: var(--kk-border-soft);
	border-radius: var(--kk-radius-pill);
	overflow: hidden;
}

.kk-topic-row__fill {
	display: block;
	height: 100%;
	background-color: var(--kk-primary);
	border-radius: var(--kk-radius-pill);
}

.kk-topic-row__value {
	flex: 0 0 auto;
	min-width: 3rem;
	text-align: right;
	font-weight: 600;
	color: var(--kk-primary);
	font-variant-numeric: tabular-nums;
}

/* --- Level donut (vocabulary hub) ----------------------------------------
   Replaces the clickable level grid, which was the entry point to studying vocabulary by
   level -- functionality grammar and writing never had.

   Levels 0-10 are binned into six ordered bands in vocabulary.php. The ring is one crimson
   ramp, light to dark, because level is a magnitude, not an identity: eleven categorical
   hues would be wrong even if eleven slices were readable. The legend carries the numbers,
   so the two palest steps never have to be told apart by colour alone. */
.kk-donut {
	max-width: 220px;
	margin: 0 auto;
}

.kk-donut svg {
	display: block;
	width: 100%;
	height: auto;
	/* Dash segments start at 3 o'clock; a donut should start at 12. */
	transform: rotate(-90deg);
	transform-origin: center;
}

.kk-donut__track {
	fill: none;
	stroke: var(--kk-border-soft);
	stroke-width: 28;
}

.kk-donut__segment {
	fill: none;
	stroke-width: 28;
}

.kk-donut-legend {
	list-style: none;
	margin: 1.25rem 0 0;
	padding: 0;
}

.kk-donut-legend li {
	display: flex;
	align-items: center;
	gap: .5rem;
	padding: .25rem 0;
	font-size: .9375rem;
}

.kk-donut-legend__swatch {
	width: .75rem;
	height: .75rem;
	border-radius: 3px;
	flex-shrink: 0;
}

/* Text keeps its own ink -- the swatch is what carries identity. */
.kk-donut-legend__label {
	color: var(--kk-dark);
}

.kk-donut-legend__value {
	margin-left: auto;
	font-weight: 600;
	color: var(--kk-dark);
}

.kk-donut-legend__pct {
	font-weight: 400;
	color: var(--kk-muted);
}

/* --- Section heading -----------------------------------------------------
   Replaces the <hr style="border-color: var(--brand)"> rules used as section dividers. */
.kk-section {
	margin-top: var(--kk-section-gap);
}

.kk-section__title {
	font-size: var(--kk-card-title);
	font-weight: 700;
	margin: 0 0 1rem;
}

/* --- Error banner -------------------------------------------------------- */
.kk-alert-danger {
	background-color: var(--kk-danger-light);
	color: var(--kk-danger-dark);
	border-radius: var(--kk-radius-input);
	padding: .75rem 1rem;
	text-align: center;
}

/* --- Unverified email banner ---------------------------------------------
   Was a second full <nav class="bg-dark"> sandwiched between two <hr> elements that each
   carried the same four inline style declarations. It is one strip, so it is one rule. */
.kk-verify-banner {
	background-color: var(--kk-dark);
	color: #fff;
	padding: .75rem 1rem;
	border-top: 1px solid rgba(255, 255, 255, .65);
	border-bottom: 1px solid rgba(255, 255, 255, .65);
}

.kk-verify-banner a {
	color: #fff;
}

/* --- Bottom tab bar ------------------------------------------------------
   Phone-width only. The top navbar hides its section links below 768px and they live here
   instead, mirroring the app's tab bar: 70px tall, white, hairline top border, 24px glyphs
   tinted crimson when active. */
.kk-tabbar {
	display: none;
}

@media (max-width: 767.98px) {
	.kk-tabbar {
		position: fixed;
		left: 0;
		right: 0;
		bottom: 0;
		z-index: 1030;
		display: flex;
		align-items: stretch;
		height: calc(70px + env(safe-area-inset-bottom, 0px));
		padding-bottom: env(safe-area-inset-bottom, 0px);
		background-color: #fff;
		border-top: 1px solid var(--kk-border-soft);
	}

	.kk-tabbar__item {
		flex: 1 1 0;
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
		gap: .25rem;
		padding: .5rem .25rem;
		color: var(--kk-dark);
		text-decoration: none;
		font-size: .75rem;
		line-height: 1.1;
		text-align: center;
	}

	.kk-tabbar__item.is-active {
		color: var(--kk-primary);
	}

	/* Keeps the fixed bar from covering the end of a long page. */
	body.has-kk-tabbar {
		padding-bottom: calc(70px + env(safe-area-inset-bottom, 0px));
	}
}

/* --- "Get the app" banner ------------------------------------------------
   Fixed to the top for visitors on iOS or Android; see includes/app-banner.php for which pages
   mount it and why. Unlike .kk-tabbar this has NO breakpoint: the decision is which device you
   are on, not how wide the window is, because the button leads to an app store. Both the banner
   and the body padding are switched on from script, so a desktop visitor never gets either.

   z-index sits above .kk-tabbar (1030) and Bootstrap's sticky/fixed range so it is never
   half-covered, but below modals (1055). */
.kk-app-banner[hidden] {
	display: none;
}

.kk-app-banner {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	z-index: 1035;
	display: flex;
	align-items: center;
	gap: .75rem;
	padding: .625rem 1rem;
	padding-top: calc(.625rem + env(safe-area-inset-top, 0px));
	background-color: var(--kk-primary);
	color: #fff;
}

.kk-app-banner__text {
	flex: 1 1 auto;
	min-width: 0;
	line-height: 1.25;
}

.kk-app-banner__text strong {
	display: block;
	font-size: .9375rem;
}

/* The second line is the first thing to go on a narrow phone -- the headline and the button are
   what the banner is for, and three elements on one row is already tight at 320px. */
.kk-app-banner__sub {
	display: block;
	font-size: .75rem;
	opacity: .85;
}

@media (max-width: 400px) {
	.kk-app-banner__sub {
		display: none;
	}
}

.kk-app-banner__cta {
	flex: 0 0 auto;
	padding: .375rem .875rem;
	border-radius: var(--kk-radius-pill);
	background-color: #fff;
	color: var(--kk-primary);
	font-size: .875rem;
	font-weight: 600;
	text-decoration: none;
	white-space: nowrap;
}

.kk-app-banner__cta:hover,
.kk-app-banner__cta:focus {
	background-color: var(--kk-surface);
	color: var(--kk-secondary);
}

.kk-app-banner__close {
	flex: 0 0 auto;
	/* 44px is Apple's minimum touch target; the glyph alone is far too small to hit reliably. */
	width: 44px;
	height: 44px;
	margin-right: -.5rem;
	padding: 0;
	border: 0;
	background: transparent;
	color: #fff;
	font-size: 1.5rem;
	line-height: 1;
	opacity: .85;
}

.kk-app-banner__close:hover,
.kk-app-banner__close:focus {
	opacity: 1;
}

/* Mirrors .has-kk-tabbar at the other end: reserves the space the fixed banner covers, so the
   navbar below it is not hidden. The navbar is static rather than fixed-top, so it moves down
   with the rest of the page. */
body.has-kk-app-banner {
	padding-top: calc(3.25rem + env(safe-area-inset-top, 0px));
}

@media (max-width: 400px) {
	body.has-kk-app-banner {
		padding-top: calc(2.75rem + env(safe-area-inset-top, 0px));
	}
}


/*************************************/
/*         5. Page layout            */
/*************************************/
/* Everything below was previously duplicated across 24 per-page <style> blocks. The same
   "narrow centred column" rule appeared in fifteen of them under different ids, and the auth
   pages carried five byte-identical copies of the same centring block. The ids are unique per
   page, so collecting the selectors here is safe and means one place to change the widths. */

/* --- Session column ------------------------------------------------------
   The learning, review, fine-tuning and quiz screens are all a single narrow column. */
#vocab-word,
#writing-character,
#answer-section,
#definition-row,
#experience-points-row {
	width: 100%;
	max-width: 400px;
	margin: auto;
}

#test-your-knowledge-section,
#placement-test-section,
#results {
	width: 100%;
	max-width: 600px;
	margin: auto;
}

#questionnaire {
	width: 100%;
	max-width: 700px;
	margin: auto;
}

#test-your-knowledge,
#placement-test {
	width: 100%;
	max-width: 800px;
	margin: auto;
}

/* The standalone character display on the writing quiz and fine-tuning screens (not the
   flashcard -- that lives in flip-cards.css). Sized to the app's 90px kanji. */
#character {
	font-size: 90px;
	line-height: 1.1;
}

/* --- Question / result box -----------------------------------------------
   Was `.form-question` with a #dee2e6 border on signup and an rgba(108,117,125,1) border on
   get-started, plus a third copy on the placement test. One outlined card at the app's radius. */
.form-question,
#placement-test-section,
#results {
	border: 1px solid var(--kk-border);
	border-radius: var(--kk-radius);
	padding: 1.5rem;
}

/* --- Auth / billing pages ------------------------------------------------
   login, signup, forgot-password, reset-password, game-sync, payment-issue and select-plan all
   carried their own copy of this. */
#logo-row,
#header-row,
#form-row,
#alert-row {
	width: 100%;
	max-width: 600px;
	margin: auto;
}

/* select-plan.php is the one two-column page among these forms, so 600px squeezes the plan
   cards and the trial panel into ~300px each. 1200px is deliberately two 600px halves: each
   column ends up exactly as wide as the single-column form every other page above still gets.
   Scoped to .kk-plan-page (set on the container in select-plan.php) because #form-row is shared
   with login, signup, forgot-password, reset-password, game-sync and payment-issue, which are
   all single column and must stay at 600px. All four rows move together -- widening #form-row
   alone would leave #header-row's top border spanning half the content beneath it. */
.kk-plan-page #logo-row,
.kk-plan-page #header-row,
.kk-plan-page #form-row,
.kk-plan-page #alert-row {
	max-width: 1200px;
}

@media only screen and (min-width: 768px) {
	#login-container,
	#signup-container {
		display: flex!important;
		justify-content: center!important;
		align-items: center!important;
	}

	#login-row,
	#signup-row {
		width: 100%;
	}

	/* settings.php, separating its two panes */
	.border-right-md {
		border-right: 1px solid var(--kk-border);
	}
}

/* --- Reading popovers (read-chapter.php) --------------------------------- */
.popover-body {
	padding: 0;
}

.popover-first-row {
	border: 1px solid var(--kk-dark);
	border-radius: .5rem .5rem 0 0;
}

/* --- Badge grid (achievements.php) ---------------------------------------
   Matches the app's badges screen: a three-across grid of 12px-radius tiles, earned ones on the
   #F6F6F6 surface with a crimson border, locked ones grey with only the icon dimmed. The name
   and description stay legible rather than the whole tile being faded to 60%. */
.kk-badge-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
	gap: .75rem;
}

.badge-card {
	display: flex;
	flex-direction: column;
	align-items: center;
	height: 100%;
	border-radius: var(--kk-radius);
	padding: .75rem;
	text-align: center;
}

.badge-card.earned {
	background-color: var(--kk-surface);
	border: 1px solid var(--kk-primary);
}

.badge-card.locked {
	background-color: #f3f4f6;
	border: 1px solid var(--kk-border-soft);
	color: var(--kk-muted);
}

.badge-icon {
	font-size: 32px;
	line-height: 1.2;
}

.badge-card.locked .badge-icon {
	opacity: .3;
}


/*************************************/
/*        6. Legacy aliases          */
/*************************************/
/* Used across pages that have not been rebuilt yet, so they inherit the redesign untouched. */
.bg-brand {
	background-color: var(--kk-primary);
	color: #fff;
}

.bg-brand-dark {
	background-color: var(--kk-secondary);
	color: #fff;
}

.text-brand {
	color: var(--kk-primary)!important;
}

.text-brand-dark {
	color: var(--kk-secondary)!important;
}

.jp-xl {
	font-size: 1.35rem;
}

.kana-character:focus {
	background-color: var(--kk-primary);
	color: #fff!important;
}
