/* Shared design tokens + dev-tooling styles for all 04-prototype/learner/** screens.
   Source: ../../../02-foundations/tokens/tokens.md (the colours and elevations between the GENERATED markers are written by
   06-tooling/tokens.py from tokens.json; the rest of the token layer is hand-kept and checked). Include via
   <link rel="stylesheet" href="../shared/theme.css"> (one level up from a
   <module>/<screen>.html file), AFTER the Google Fonts <link> and BEFORE
   any page-specific <style> block so page rules can still override per-screen
   specifics (e.g. body background, .phone breakpoints, .theme-lab position). */
@import url('https://unpkg.com/@phosphor-icons/web@2.1.1/src/regular/style.css');
@import url('https://unpkg.com/@phosphor-icons/web@2.1.1/src/fill/style.css');
@import url('https://unpkg.com/@phosphor-icons/web@2.1.1/src/duotone/style.css');
/* script fallbacks (tokens §10): the Latin UI fonts have no Indic/Arabic/CJK glyphs, so Noto Sans families follow them in every stack */
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Tamil:wght@400;500;600;700&family=Noto+Sans+Devanagari:wght@400;500;600;700&family=Noto+Sans+Telugu:wght@400;500;600;700&family=Noto+Sans+Kannada:wght@400;500;600;700&family=Noto+Sans+Malayalam:wght@400;500;600;700&family=Noto+Sans+Bengali:wght@400;500;600;700&family=Noto+Sans+Arabic:wght@400;500;600;700&family=Noto+Sans:wght@400;500;600;700&display=swap');

/* Tokens mirror ../../../02-foundations/tokens/tokens.md §1 (WCAG AA-checked).
   The look (2026-10-03, "agentic", second pass): a periwinkle-tinted canvas (bg) lit from the top (bg-glow) with white
   panels (surface) in light mode; a near-black navy canvas with navy glass panels and luminous blue edges in dark mode; an
   electric blue primary (no glow on buttons since 2026-10-03, the user's call); gold for rewards; one electric-blue hero surface per screen with a gold rim (hero-glow).
   Colour on a panel comes from its accent (icon, 3 dp accent bar), never from a pastel fill.

   TOKEN LAYER (the next two parts, up to the END marker) is also copied verbatim into 02-foundations/tokens/tokens.css by
   06-tooling/tokens.py, for every other surface. It has two parts:
   1. GENERATED from tokens.json (the doc tables): the themed colours, category colours and elevations. Do not edit it; edit tokens.md and run
      `python3 06-tooling/tokens.py --write`.
   2. Hand-kept, because the doc tables do not hold them: gradients and color-mix recipes, the prototype-only colours (tokens.json `extras`),
      the font stacks. Edit these here; tokens.py checks and exports them. */
/* ==== BEGIN TOKEN LAYER ==== */
/* >>>>>>>> BEGIN GENERATED from 02-foundations/tokens/tokens.json (06-tooling/tokens.py): do not edit by hand >>>>>>>> */
/* blue, light: everything the doc defines, plus the category colours and the elevations (tokens §1, §4.2) */
:root {
  --bg: #eef2ff;
  --surface: #ffffff;
  --text-primary: #0f172a;
  --text-secondary: #525f7f;
  --outline: #6f7da6;
  --border: #d9dff5;
  --scrim: rgba(10,15,36,.55);
  --media-ink: #000000;
  --on-media: #ffffff;
  --success: #1f7a2e;
  --success-container: #e3f6e6;
  --warning: #a85a06;
  --warning-container: #fff1df;
  --error: #c82c2c;
  --error-container: #fdeaea;
  --info: #1763c9;
  --info-container: #e2ecfd;
  --reward: #b8780a;
  --reward-container: #fff1cf;
  --reward-ink: #7a4f00;
  --primary: #2b5bff;
  --on-primary: #ffffff;
  --secondary: #5b7cff;
  --tertiary: #7c3aed;
  --hero-solid: #2246e0;
  --cat-blue-grey: #546e7a;
  --cat-deep-orange: #e64a19;
  --cat-red: #e53935;
  --cat-purple: #8e24aa;
  --cat-cyan: #00acc1;
  --cat-green: #43a047;
  --cat-pink: #d81b60;
  --elev-1: 0 1px 3px rgba(0, 0, 0, .12);
  --elev-2: 0 3px 6px rgba(0, 0, 0, .15);
  --elev-3: 0 10px 20px rgba(0, 0, 0, .15);
  --elev-4: 0 15px 25px rgba(0, 0, 0, .15);
}
/* blue, dark: the roles that differ; the same set twice, for the system setting and for the explicit choice */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #070b1f;
    --surface: #10173a;
    --text-primary: #f5f7ff;
    --text-secondary: #aab5dc;
    --outline: #7f8ec4;
    --border: #232c5c;
    --scrim: rgba(3,6,20,.7);
    --success: #5ccf6b;
    --success-container: #11321f;
    --warning: #ffb95c;
    --warning-container: #3a2a0c;
    --error: #ff7b7b;
    --error-container: #3d1a22;
    --info: #6fb2ff;
    --info-container: #13295a;
    --reward: #ffc94f;
    --reward-container: #3a2b08;
    --reward-ink: #ffd36a;
    --primary: #5f86ff;
    --on-primary: #070b1f;
    --secondary: #4f6bff;
    --tertiary: #a78bfa;
  }
}
:root[data-theme="dark"] {
  --bg: #070b1f;
  --surface: #10173a;
  --text-primary: #f5f7ff;
  --text-secondary: #aab5dc;
  --outline: #7f8ec4;
  --border: #232c5c;
  --scrim: rgba(3,6,20,.7);
  --success: #5ccf6b;
  --success-container: #11321f;
  --warning: #ffb95c;
  --warning-container: #3a2a0c;
  --error: #ff7b7b;
  --error-container: #3d1a22;
  --info: #6fb2ff;
  --info-container: #13295a;
  --reward: #ffc94f;
  --reward-container: #3a2b08;
  --reward-ink: #ffd36a;
  --primary: #5f86ff;
  --on-primary: #070b1f;
  --secondary: #4f6bff;
  --tertiary: #a78bfa;
}
/* other colour themes, light (also applies in dark until the dark rule below restates it) */
:root[data-color-theme="orange"] {
  --primary: #b84600;
  --secondary: #f57c00;
  --tertiary: #e65100;
  --hero-solid: #b84600;
}
:root[data-color-theme="teal"] {
  --primary: #00796b;
  --secondary: #009688;
  --tertiary: #00695c;
  --hero-solid: #00695c;
}
:root[data-color-theme="violet"] {
  --primary: #6c47ff;
  --secondary: #8b5cf6;
  --tertiary: #ec4899;
  --hero-solid: #5130dc;
}
/* other colour themes, dark */
@media (prefers-color-scheme: dark) {
  :root[data-color-theme="orange"]:not([data-theme="light"]) {
    --primary: #fb8c00;
    --on-primary: #1a0f00;
    --secondary: #ffa726;
    --tertiary: #f4511e;
    --hero-solid: #b84600;
  }
  :root[data-color-theme="teal"]:not([data-theme="light"]) {
    --primary: #26a69a;
    --on-primary: #00201c;
    --secondary: #4db6ac;
    --tertiary: #00796b;
    --hero-solid: #00695c;
  }
  :root[data-color-theme="violet"]:not([data-theme="light"]) {
    --primary: #b39dff;
    --on-primary: #140a33;
    --secondary: #a78bfa;
    --tertiary: #f472b6;
    --hero-solid: #5130dc;
  }
}
:root[data-color-theme="orange"][data-theme="dark"] {
  --primary: #fb8c00;
  --on-primary: #1a0f00;
  --secondary: #ffa726;
  --tertiary: #f4511e;
  --hero-solid: #b84600;
}
:root[data-color-theme="teal"][data-theme="dark"] {
  --primary: #26a69a;
  --on-primary: #00201c;
  --secondary: #4db6ac;
  --tertiary: #00796b;
  --hero-solid: #00695c;
}
:root[data-color-theme="violet"][data-theme="dark"] {
  --primary: #b39dff;
  --on-primary: #140a33;
  --secondary: #a78bfa;
  --tertiary: #f472b6;
  --hero-solid: #5130dc;
}
/* <<<<<<<< END GENERATED <<<<<<<< */

/* ---- hand-kept, light: prototype-only colours and recipes ---- */
:root {
  --swatch-blue: #2b5bff; --swatch-orange: #b84600; --swatch-teal: #00796b; --swatch-violet: #6c47ff;   /* each theme's primary in this mode, for the Appearance picker (tokens §1.1); the per-theme blocks below leave them alone */
  --on-reward: #1a1300;   /* text on a solid gold fill; --reward, its container and its ink are generated above (tokens §1.3). Gold is for rewards (XP, streak, badges). Not --accent: that name is the per-card colour set inline on cards */
  --edge: color-mix(in srgb, var(--primary) 16%, var(--surface));
  --bg-glow: radial-gradient(70% 320px at 50% 0, color-mix(in srgb, var(--primary) 14%, transparent), transparent 100%);   /* the canvas is lit from the top, like the reference */
  --on-error: #ffffff;
  --field-bg: #ffffff;
  --page-bg: #e6ebff;
  --shadow-ink: #000000;   /* the colour every custom shadow is mixed from (toast, menus, pickers, dialogs): color-mix(in srgb, var(--shadow-ink) n%, transparent) */
  --elev-frame: 0 30px 80px color-mix(in srgb, #222e48 14%, transparent);   /* the desktop phone-frame mock only, not a product elevation */
  /* hero: the one electric-blue surface a screen may use (career path card, welcome card, group room); white text on it,
     a white inner edge, a gold rim and a blue glow around it (the lit hexagon of the reference) */
  --hero: linear-gradient(135deg, #2550f0 0%, #2246e0 45%, #1a2fb0 100%); --on-hero: #ffffff; --on-hero-muted: rgba(255,255,255,.82); --hero-edge: rgba(255,255,255,.26); --hero-reward: #ffe08a;
  --hero-glow: 0 0 0 1.5px color-mix(in srgb, var(--hero-reward) 65%, transparent), 0 16px 36px color-mix(in srgb, var(--hero-solid) 34%, transparent);
  --primary-grad: linear-gradient(135deg, var(--primary), color-mix(in srgb, var(--primary) 72%, var(--tertiary)));
  --glow: none;   /* 2026-10-03 (user): no glow shadow on buttons; the fill alone carries the emphasis */
  --font-family: "Poppins", "Noto Sans", "Noto Sans Tamil", "Noto Sans Devanagari", "Noto Sans Telugu", "Noto Sans Kannada", "Noto Sans Malayalam", "Noto Sans Bengali", "Noto Sans Arabic", "Segoe UI", sans-serif;
  --font-scale: 1;
}

/* ---- hand-kept, dark mode: the same prototype-only colours and recipes ---- */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --swatch-blue: #5f86ff; --swatch-orange: #fb8c00; --swatch-teal: #26a69a; --swatch-violet: #b39dff;
    --on-reward: #1a1300;
    --edge: color-mix(in srgb, var(--primary) 36%, var(--surface));
    --bg-glow: radial-gradient(70% 320px at 50% 0, color-mix(in srgb, var(--primary) 26%, transparent), transparent 100%);
    --on-error: #2b0a0a;
    --field-bg: #0d1431; --page-bg: #04071a;
    --hero: linear-gradient(135deg, #2550f0 0%, #2246e0 45%, #1a2fb0 100%); --hero-edge: rgba(255,255,255,.3);
    --hero-glow: 0 0 0 1.5px color-mix(in srgb, var(--hero-reward) 70%, transparent), 0 18px 44px color-mix(in srgb, var(--hero-solid) 50%, transparent);
    --glow: none;
  }
}
:root[data-theme="dark"] {
    --swatch-blue: #5f86ff; --swatch-orange: #fb8c00; --swatch-teal: #26a69a; --swatch-violet: #b39dff;
    --on-reward: #1a1300;
    --edge: color-mix(in srgb, var(--primary) 36%, var(--surface));
    --bg-glow: radial-gradient(70% 320px at 50% 0, color-mix(in srgb, var(--primary) 26%, transparent), transparent 100%);
    --on-error: #2b0a0a;
    --field-bg: #0d1431; --page-bg: #04071a;
    --hero: linear-gradient(135deg, #2550f0 0%, #2246e0 45%, #1a2fb0 100%); --hero-edge: rgba(255,255,255,.3);
    --hero-glow: 0 0 0 1.5px color-mix(in srgb, var(--hero-reward) 70%, transparent), 0 18px 44px color-mix(in srgb, var(--hero-solid) 50%, transparent);
    --glow: none;
}

/* ---- hand-kept: the hero surface follows the colour theme (2026-10-04): the same deep gradient in light and dark, white text 4.5:1 or better on every stop; Blue keeps the electric blue above (the matching --hero-solid is generated above) ---- */
:root[data-color-theme="orange"] { --hero: linear-gradient(135deg, #c24d00 0%, #b84600 45%, #8a3300 100%); }
:root[data-color-theme="teal"] { --hero: linear-gradient(135deg, #007d6e 0%, #00695c 45%, #004d44 100%); }
:root[data-color-theme="violet"] { --hero: linear-gradient(135deg, #6c47ff 0%, #5130dc 45%, #3a1fb0 100%); }

/* ---- font family options from tokens.md ---- */
:root[data-font="inter"] { --font-family: "Inter", "Noto Sans", "Noto Sans Tamil", "Noto Sans Devanagari", "Noto Sans Telugu", "Noto Sans Kannada", "Noto Sans Malayalam", "Noto Sans Bengali", "Noto Sans Arabic", "Segoe UI", sans-serif; }
:root[data-font="jakarta"] { --font-family: "Plus Jakarta Sans", "Noto Sans", "Noto Sans Tamil", "Noto Sans Devanagari", "Noto Sans Telugu", "Noto Sans Kannada", "Noto Sans Malayalam", "Noto Sans Bengali", "Noto Sans Arabic", "Segoe UI", sans-serif; }
:root[data-font="dmsans"] { --font-family: "DM Sans", "Noto Sans", "Noto Sans Tamil", "Noto Sans Devanagari", "Noto Sans Telugu", "Noto Sans Kannada", "Noto Sans Malayalam", "Noto Sans Bengali", "Noto Sans Arabic", "Segoe UI", sans-serif; }
/* ==== END TOKEN LAYER ==== */

* { box-sizing: border-box; }
/* buttons are unstyled by default: every button in the prototypes gets its look from a class (Flutter widgets carry their own).
   This is the only element reset in the system and it stays at type-selector specificity, so any class (.btn, .vis, .tg …) wins.
   No shared file or page adds a second reset such as `.page button { padding: 0 }`: a reset with a class in it outranks the
   component classes and silently strips their padding (tokens doc §8). */
button { border: 0; padding: 0; background: none; font: inherit; color: inherit; cursor: pointer; }
[hidden] { display: none !important; }

/* ---------- shared icon button ---------- */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 48px; height: 48px;
  border-radius: 999px;
  color: var(--text-primary);
  background: transparent;
  transition: background-color 150ms ease;
}
.icon-btn:hover { background: var(--surface); }
.icon-btn svg { width: 24px; height: 24px; }

/* ---------- shared entrance animation ---------- */
.rise {
  opacity: 0;
  transform: translateY(10px);
  animation: rise 300ms cubic-bezier(0.16, 1, 0.3, 1) forwards; /* standard */
}
@keyframes rise { to { opacity: 1; transform: translateY(0); } }

/* ---------- shared spinners ---------- */
.spinner {
  width: 16px; height: 16px;
  border-radius: 999px;
  border: 2px solid color-mix(in srgb, currentColor 40%, transparent);
  border-top-color: currentColor;
  animation: spin 700ms linear infinite;
  display: none;
}
.spinner-lg {
  width: 32px; height: 32px;
  border-radius: 999px;
  border: 3px solid var(--border);
  border-top-color: var(--primary);
  animation: spin 700ms linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* ---------- shared buttons ---------- */
.btn-primary {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%;
  min-height: 48px;
  border-radius: 8px;
  background: var(--primary-grad);
  box-shadow: var(--glow);
  color: var(--on-primary);
  font-size: calc(14px * var(--font-scale));
  font-weight: 600;
  transition: transform 150ms ease, opacity 150ms ease;
}
.btn-primary:active { transform: scale(0.98); }
.btn-primary[disabled] { opacity: 0.6; pointer-events: none; }
.btn-primary.loading .spinner { display: inline-block; }
.btn-primary.loading .btn-label { opacity: 0.85; }

.btn-social {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  width: 100%;
  min-height: 48px;
  border-radius: 8px;
  border: 1px solid var(--outline);
  background: var(--field-bg);
  color: var(--text-primary);
  font-size: calc(14px * var(--font-scale));
  font-weight: 600;
  transition: background-color 150ms ease, border-color 150ms ease;
}
.btn-social:hover { background: var(--surface); border-color: #c9cdd6; }

/* GitHub's own brand guidelines permit an inverted (white) mark on dark surfaces */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .github-mark { fill: #ffffff; }
}
:root[data-theme="dark"] .github-mark { fill: #ffffff; }
.btn-social svg { width: 20px; height: 20px; }

.divider { display: flex; align-items: center; gap: 12px; color: var(--text-secondary); font-size: calc(12px * var(--font-scale)); }
.divider::before, .divider::after { content: ""; flex: 1; height: 1px; background: var(--border); }

/* font-size scale (Small/Medium/Large from tokens.md), applied via
   --font-scale so the Theme Lab's Size control has a visible effect */
.fs-10 { font-size: calc(10px * var(--font-scale)); }
.fs-12 { font-size: calc(12px * var(--font-scale)); }
.fs-14 { font-size: calc(14px * var(--font-scale)); }
.fs-16 { font-size: calc(16px * var(--font-scale)); }
.fs-24 { font-size: calc(24px * var(--font-scale)); }
.fs-18 { font-size: calc(18px * var(--font-scale)); }
.fs-20 { font-size: calc(20px * var(--font-scale)); }
.fs-32 { font-size: calc(32px * var(--font-scale)); }
.fs-28 { font-size: calc(28px * var(--font-scale)); }

/* ==================================================================
   Theme Lab — dev-only control to preview all token combinations.
   NOT part of the product UI, do not convert to Flutter (see
   ../README.md "Flutter conversion guide"). Markup is injected by
   ../shared/theme-lab.js, these are just its styles.
   ================================================================== */
.theme-lab { position: fixed; top: 12px; inset-inline-end: 12px; z-index: 35; font-family: var(--font-family); } /* above content and the FAB (29), below the scrim and sheets (40, 41) */
.theme-lab-toggle {
  width: 36px; height: 36px;
  border-radius: 999px;
  border: 1px dashed var(--border);
  background: var(--surface);
  color: var(--text-primary);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.theme-lab-panel {
  position: absolute; top: 44px; right: 0;
  width: 272px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px;
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);
  display: none;
  flex-direction: column;
  gap: 10px;
}
.theme-lab-panel.open { display: flex; }
.theme-lab-row { display: flex; flex-direction: column; gap: 6px; }
.theme-lab-row > span {
  font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--text-secondary);
}
.theme-lab-seg { display: flex; gap: 4px; flex-wrap: wrap; }
.theme-lab-seg button {
  flex: 1 1 auto; min-width: 52px;
  padding: 8px 10px; min-height: 36px;
  white-space: nowrap;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text-secondary);
  font-size: 12px; font-weight: 600;
  cursor: pointer;
}
.theme-lab-seg button.active {
  background: var(--primary); border-color: var(--primary); color: var(--on-primary);
}
/* extra, screen-specific rows (e.g. 02-social-sign-in's "Simulate outcome") get
   a dashed separator so they read as distinct from the universal token rows */
.theme-lab-row.theme-lab-row--extra { border-top: 1px dashed var(--border); padding-top: 10px; }

/* ==================================================================
   Legal modal — shared shell for auth's 03-terms/04-privacy/
   05-cookies screens (Terms/Privacy/Cookie docs). Opened/closed via
   ../shared/legal-modal.js. Doc: semi-transparent backdrop (opacity 0.5),
   max-width 600px on tablet, full-width on mobile, lg (16px) corner radius,
   sticky header (title + last-updated + divider).
   ================================================================== */
.legal-backdrop {
  position: fixed; inset: 0;
  background: rgba(10, 14, 24, 0.5);
  opacity: 0;
  transition: opacity 300ms ease;
  z-index: 50;
}
.legal-backdrop.open { opacity: 1; }

.legal-modal {
  position: fixed;
  left: 50%;
  bottom: 0;
  transform: translate(-50%, 100%);
  width: 100%;
  max-width: 600px;
  max-height: 88vh;
  background: var(--bg);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -12px 40px rgba(10, 20, 50, 0.25);
  transition: transform 300ms ease;
  z-index: 51;
  display: flex;
  flex-direction: column;
}
.legal-modal.open { transform: translate(-50%, 0); }
@media (min-width: 600px) {
  .legal-modal {
    top: 50%; bottom: auto;
    transform: translate(-50%, -50%) scale(0.96);
    max-height: 80vh;
    border-radius: 16px;
    opacity: 0;
    transition: transform 300ms ease, opacity 300ms ease;
  }
  .legal-modal.open { transform: translate(-50%, -50%) scale(1); opacity: 1; }
}

.legal-header {
  flex-shrink: 0;
  padding: 20px 20px 16px;
  border-bottom: 1px solid var(--border);
  transition: box-shadow 150ms ease;
}
.legal-header.scrolled { box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); }
.legal-title-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.legal-updated { margin-top: 4px; color: var(--text-secondary); }

.legal-body { flex: 1; overflow-y: auto; padding: 20px; }
.legal-body h2 {
  margin-top: 22px;
  color: var(--primary);
  font-weight: 700;
}
.legal-body h2:first-child { margin-top: 0; }
.legal-body p { margin-top: 8px; line-height: 1.6; }
.legal-body p.legal-intro { font-weight: 600; }
.legal-body p.legal-sub { margin-top: 14px; font-weight: 600; }
.legal-body ul { margin-top: 6px; padding-inline-start: 18px; list-style: disc; color: var(--text-secondary); }
.legal-body li { margin-top: 4px; line-height: 1.5; }
.legal-body .legal-closing {
  margin-top: 22px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  color: var(--text-secondary);
  font-style: italic;
}

/* ---- accessibility: honour reduce-motion (tokens §5) ---- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 1ms !important; animation-iteration-count: 1 !important; transition-duration: 1ms !important; scroll-behavior: auto !important; }
  .rise { opacity: 1; transform: none; }
}

/* icons: Phosphor (see shared/icons.js). .mi is only a sizing hook now */
.mi, .mio { font-size: 24px; line-height: 1; display: inline-block; flex-shrink: 0; font-style: normal; user-select: none; }

/* mi-clones: icon-font sizes mirroring the svg size rules */
.icon-btn .mi { font-size: 24px;  }
.btn-social .mi { font-size: 20px;  }
/* mi-clones */

a.icon-btn, .icon-btn, .mi { text-decoration: none; }


/* shared Avatar base (PbAvatar) */
.pb-avatar { display: inline-flex; align-items: center; justify-content: center; border-radius: 999px; font-weight: 700; flex-shrink: 0; overflow: hidden; }
/* tonal round button (Flutter IconButton.filledTonal) and the one count badge. Moved here from shell.css on 2026-10-03 so pushed screens (Chat) use the same
   button as the shell's top bar. Variants: .fill = IconButton.filled (the positive action: accept, add, send), .danger (destructive), .sm = 36 dp visual in a row
   of dense actions. The 44 dp circle sits in a 48 dp touch target (the transparent ::after). Icon-only: always carry aria-label + title. */
.tbtn { position: relative; width: 44px; height: 44px; flex-shrink: 0; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; background: var(--surface); box-shadow: inset 0 0 0 1px var(--edge); color: var(--text-primary); text-decoration: none; transition: background-color 150ms ease, transform 150ms ease, box-shadow 150ms ease; }
.tbtn::after { content: ""; position: absolute; inset: -2px; }
.tbtn:hover { background: color-mix(in srgb, var(--primary) 12%, var(--surface)); }
.tbtn:active { transform: scale(.94); }
.tbtn .ico, .tbtn .mi { font-size: 22px; }
.tbtn.fill { background: var(--primary-grad); color: var(--on-primary); box-shadow: var(--glow); }
.tbtn.fill:hover { background: var(--primary-grad); filter: brightness(1.06); }
.tbtn.danger { color: var(--error); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--error) 45%, var(--surface)); }
.tbtn.danger:hover { background: color-mix(in srgb, var(--error) 12%, var(--surface)); }
.tbtn.on { background: color-mix(in srgb, var(--success) 16%, var(--surface)); color: var(--success); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--success) 50%, var(--surface)); }
.tbtn.pending { background: color-mix(in srgb, var(--primary) 12%, var(--surface)); color: var(--primary); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 40%, var(--surface)); }   /* waiting on someone else: invite sent (a clock); tap cancels */
.tbtn[disabled] { opacity: .45; pointer-events: none; }
.tbtn.hero { background: var(--on-hero); color: var(--hero-solid); box-shadow: 0 6px 18px rgba(0, 0, 0, .18); }   /* filled icon button on a hero surface: white on the blue (like .btn-hero) */
.tbtn.hero:hover { background: var(--on-hero); filter: brightness(.96); }
.tbtn.hero-out { background: rgba(255, 255, 255, .12); color: var(--on-hero); box-shadow: inset 0 0 0 1px var(--hero-edge); }
.tbtn.hero-out:hover { background: rgba(255, 255, 255, .22); }
.tbtn.media { background: color-mix(in srgb, var(--on-media) 14%, transparent); color: var(--on-media); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--on-media) 22%, transparent); }   /* round icon button over video, photos and viewers: translucent on-media fill (Lesson player controls) */
.tbtn.media:hover { background: color-mix(in srgb, var(--on-media) 24%, transparent); }
.tbtn.save.on { background: color-mix(in srgb, var(--primary) 12%, var(--surface)); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--primary) 40%, var(--surface)); color: var(--primary); }   /* a save toggle (the save bookmark on a course row): saved = a pink filled bookmark on the tonal circle, never the done tint of .tbtn.on */
.tbtn.save.on:hover { background: color-mix(in srgb, var(--primary) 12%, var(--surface)); }
.tbtn.sm { width: 36px; height: 36px; } .tbtn.sm .mi { font-size: 18px; } .tbtn.sm::after { inset: -6px; }   /* the 36 dp circle in a 48 dp target too */
.pb-badge { position: absolute; top: 0; inset-inline-end: 0; min-width: 14px; height: 14px; padding: 0 3px; border-radius: 999px; background: var(--error); color: var(--on-error); font-size: 10px; line-height: 14px; font-weight: 700; letter-spacing: -0.02em; text-align: center; border: 2px solid var(--bg); box-sizing: content-box; pointer-events: none; transform: translate(15%, -15%); }
.pb-badge.one { padding: 0; width: 14px; }
@media (prefers-reduced-motion: reduce) { .tbtn { transition: none; } .tbtn:active { transform: none; } }

/* ---------- wheel picker (tokens §7; Flutter CupertinoPicker): 40 dp rows snapping to a tinted centre band, 200 dp tall. Columns are .wheel inside
   .wheel-wrap; shared/wheel.js (PBWheel) fills and reads them. Moved from profile-setup/01-about-you.html on 2026-10-03 (the education sheet uses it too) ---------- */
.wheel-wrap { position: relative; display: flex; height: 200px; border-radius: 12px; background: var(--surface); overflow: hidden; }
.wheel-wrap::before { content: ""; position: absolute; inset-inline-start: 8px; inset-inline-end: 8px; top: 80px; height: 40px; border-radius: 8px; background: color-mix(in srgb, var(--primary) 12%, transparent); pointer-events: none; }
.wheel-wrap::after { content: ""; position: absolute; inset: 0; pointer-events: none; background: linear-gradient(var(--surface), transparent 38%, transparent 62%, var(--surface)); }
.wheel { flex: 1; overflow-y: scroll; scroll-snap-type: y mandatory; padding: 80px 0; scrollbar-width: none; }
.wheel::-webkit-scrollbar { display: none; }
.wheel div { height: 40px; line-height: 40px; text-align: center; scroll-snap-align: center; font-size: calc(16px * var(--font-scale)); color: var(--text-secondary); }
.wheel div.sel { color: var(--text-primary); font-weight: 600; }

/* ---------- labelled field: the Input recipe (tokens §7), used by the setup steps (stepper.css pages) and the profile sheets. Moved from stepper.css on 2026-10-03 ---------- */
.f-label { display: block; font-size: calc(14px * var(--font-scale)); font-weight: 600; margin: 24px 0 8px; }
.f-label:first-child { margin-top: 0; }
.field { display: flex; align-items: center; gap: 8px; min-height: 52px; padding: 0 12px; border: 1px solid var(--outline); border-radius: 8px; background: var(--field-bg); transition: border-color 150ms ease, box-shadow 150ms ease; }
.field.focused { border: 2px solid var(--primary); padding: 0 11px; box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 10%, transparent); }
.field.error { border-color: var(--error); }
.field.ok { border-color: var(--success); }
.field input { flex: 1; min-width: 0; border: none; outline: none; background: transparent; color: var(--text-primary); font: inherit; font-size: calc(14px * var(--font-scale)); height: 100%; min-height: 48px; }
.field input::placeholder { color: var(--text-secondary); }
.field .affix { color: var(--text-secondary); font-weight: 600; font-size: calc(14px * var(--font-scale)); }
.help { margin: 6px 0 0; min-height: 18px; font-size: calc(12px * var(--font-scale)); line-height: 1.5; color: var(--text-secondary); }
.help:empty { display: none; }
.help.error { color: var(--error); } .help.ok { color: var(--success); }
.field.shake { animation: shake 300ms ease; }
@keyframes shake { 20%,60% { transform: translateX(-4px); } 40%,80% { transform: translateX(4px); } }


/* ---------- switch (tokens §7 Switch; Flutter Switch): 44 × 26 dp track, text secondary @ 30% → primary, a white thumb, a 48 dp target. The one switch
   for every screen (moved here from profile.css on 2026-10-03 when Settings needed it); role="switch" + aria-checked carry the state ---------- */
.sw { position: relative; width: 44px; height: 26px; flex-shrink: 0; border-radius: 999px; background: color-mix(in srgb, var(--text-secondary) 30%, transparent); transition: background-color 150ms ease; }
.sw::after { content: ''; position: absolute; top: 3px; inset-inline-start: 3px; width: 20px; height: 20px; border-radius: 999px; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .3); transition: transform 150ms ease; }
.sw[aria-checked="true"] { background: var(--primary); } .sw[aria-checked="true"]::after { transform: translateX(18px); } [dir="rtl"] .sw[aria-checked="true"]::after { transform: translateX(-18px); }
.sw::before { content: ''; position: absolute; inset: -11px; }   /* 48 dp target */
.sw:disabled { opacity: .45; cursor: default; }   /* a switch that can't apply yet (App lock with no screen lock on the phone; a category under a muted push) */

/* ---------- toast (SnackBar) and bottom sheet (showModalBottomSheet / Dialog from 600 dp) ----------
   One definition for every screen. The app shell lifts the toast above its tab bar (shell.css). */
.toast { position: fixed; inset-inline: 0; margin-inline: auto; width: max-content; bottom: 24px; transform: translateY(20px); background: var(--text-primary); color: var(--bg); padding: 12px 16px; border-radius: 12px; font-size: calc(14px * var(--font-scale)); opacity: 0; transition: opacity 150ms ease, transform 150ms ease; z-index: 60; }
.toast.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
.toast:not(.show) { pointer-events: none; }   /* a hidden toast is transparent and must not catch taps meant for a sheet or the page under it (found 2026-10-03: it swallowed a sheet's middle button) */
.toast { display: flex; align-items: center; flex-wrap: wrap; column-gap: 12px; white-space: normal; max-width: min(560px, calc(100vw - 32px)); box-sizing: border-box; }   /* the message wraps; a long one sends the action to its own line, never off the screen (Prepare QA 2026-10-03: Undo sat past a 320 dp screen at 1.43) */
.toast button { flex-shrink: 0; margin-inline-start: auto; min-height: 48px; margin-block: -12px; padding: 0 8px; font-weight: 700; color: color-mix(in srgb, var(--primary) 55%, var(--bg)); }
.sheet2-backdrop { position: fixed; inset: 0; background: var(--scrim); opacity: 0; pointer-events: none; transition: opacity 150ms ease; z-index: 40; }
.sheet2-backdrop.open { opacity: 1; pointer-events: auto; }
.sheet2 { position: fixed; inset-inline: 0; margin: 0 auto; bottom: 0; transform: translateY(100%); width: 100%; max-width: 480px; background: var(--surface); box-shadow: 0 -1px 0 var(--edge); border-radius: 20px 20px 0 0; padding: 12px 16px 24px; transition: transform 300ms cubic-bezier(.16, 1, .3, 1); z-index: 41; box-sizing: border-box; max-height: 88vh; overflow: auto; }
.sheet2.open { transform: translateY(0); }
.sheet2 h2 { font-size: calc(18px * var(--font-scale)); font-weight: 600; margin: 0 0 8px; }
/* the sheet's confirmation row (Cancel · Clear all, Not now · Turn on, Reset · Done): two equal buttons; .sticky keeps it in view in a long sheet. Shared since 2026-10-03 (was nt-foot, pu-sheet-foot) */
.sheet-foot { display: flex; gap: 8px; margin-top: 20px; } .sheet-foot .btn { flex: 1; }
.sheet-foot.sticky { position: sticky; bottom: -24px; margin-bottom: -24px; background: inherit; padding: 8px 0 28px; }   /* sticks at the sheet's very edge: at bottom 0 it stopped above the sheet's 24 dp bottom padding and scrolled fields showed through below it (Events 03, 2026-10-04); the space under the buttons is the same 28 dp */
.grab { width: 36px; height: 4px; border-radius: 999px; background: var(--border); margin: 0 auto 12px; }
@media (min-width: 600px) { .sheet2 { bottom: auto; top: 50%; border-radius: 16px; transform: translateY(-40%); opacity: 0; pointer-events: none; transition: transform 300ms cubic-bezier(.16, 1, .3, 1), opacity 150ms ease; } .sheet2.open { transform: translateY(-50%); opacity: 1; pointer-events: auto; } .sheet2 .grab { display: none; } }

/* ---- brand mark: transparent PNG, navy parts turn light in dark mode (Flutter: two assets or one SVG recoloured by ColorScheme).
   No box, no tint, no padding around it: the mark carries its own shape. ---- */
.pb-logo { display: inline-block; width: 80px; height: 80px; background: url(assets/logo.png) center / contain no-repeat; flex-shrink: 0; }
@media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) .pb-logo { background-image: url(assets/logo-dark.png); } }
:root[data-theme="dark"] .pb-logo { background-image: url(assets/logo-dark.png); }

/* ---- RTL: directional icons mirror (Flutter: Directionality-aware icons / Transform.flip) ---- */
[dir="rtl"] .ph-arrow-left, [dir="rtl"] .ph-arrow-right, [dir="rtl"] .ph-caret-right, [dir="rtl"] .ph-caret-left, [dir="rtl"] .ph-arrow-clockwise { display: inline-block; transform: scaleX(-1); }
[dir="rtl"] .rise { transform: translateY(10px); }
