/* ============================================================================
   tokens.css - the Dasow house style, ported to vanilla CSS.

   SOURCE OF TRUTH
   ---------------
   /Users/andrewhaleem/Projects/Invoicing-SaaS/frontend/src/styles/tokens.css
   (SignOff), which is itself the reconciled copy of the Dasow Internal System
   tokens and the official brand palette. Names and values below are the same,
   minus the Tailwind machinery, so the Academy, SignOff and the Internal
   System read as one house.

   PALETTE RULES
   -------------
   --color-primary is THE interactive colour: buttons, links, focus, active nav.
   Grape is the deep anchor for gradients and brand grounds only.
   Aqua supports: progress rings, done badges, success.
   Orange is used sparingly: the kids tag, the certificate seal, small emphasis.
   Linen is the warm band colour in light mode, never the page ground.
   Aqua and orange as small text go through --color-aqua-text /
   --color-orange-text, which hold the hue and shift luminance to pass AA in
   both themes.

   THEME
   -----
   :root holds the DARK values as the cascade base. Light is declared as the
   override and is ALSO the default: the light block answers
   :root:not([data-theme]) so a document that never got an attribute is light.
   The OS preference is not consulted. A person who wants dark presses the
   toggle once; theme-boot.js reads that choice from localStorage["theme.v2"]
   before first paint.
   ========================================================================= */

:root {
  /* --- Core, dark ------------------------------------------------------- */
  --color-background: hsl(240 10% 3.9%);
  --color-foreground: hsl(0 0% 98%);
  --color-card: hsl(240 10% 3.9%);
  --color-card-foreground: hsl(0 0% 98%);
  --color-popover: hsl(240 10% 3.9%);
  --color-popover-foreground: hsl(0 0% 98%);
  --color-primary: hsl(253 91% 58%);
  --color-primary-foreground: hsl(0 0% 98%);
  --color-secondary: hsl(240 3.7% 15.9%);
  --color-secondary-foreground: hsl(0 0% 98%);
  --color-muted: hsl(240 3.7% 15.9%);
  --color-muted-foreground: hsl(240 5% 64.9%);
  --color-accent: hsl(240 3.7% 15.9%);
  --color-accent-foreground: hsl(0 0% 98%);
  --color-destructive: hsl(0 62.8% 30.6%);
  --color-destructive-foreground: hsl(0 0% 98%);
  --color-border: hsl(240 3.7% 15.9%);
  --color-input: hsl(240 3.7% 15.9%);
  --color-ring: hsl(240 4.9% 83.9%);

  /* Long-form body copy: foreground is too hot, muted-foreground too cold. */
  --color-copy: hsl(240 5% 78%);
  /* A control boundary needs 3:1; the decorative hairline does not qualify. */
  --color-border-strong: hsl(240 5% 48%);
  /* Primary as TEXT: same hue, lifted until a 14px label passes. */
  --color-primary-text: hsl(253 100% 78%);
  --color-primary-soft: hsl(253 60% 14%);

  /* --- Official Dasow brand palette ------------------------------------- */
  --color-brand-grape: #552c87;
  --color-brand-aqua: #69cad9;
  --color-brand-orange: #f47523;
  --color-brand-linen: #eeece3;
  /* Ink for text sitting ON a brand fill. Aqua and orange are light in both
     themes, so this is pinned to the near-black ground, not to the theme. */
  --color-brand-ink: hsl(240 10% 3.9%);

  --color-aqua-text: #69cad9;
  --color-orange-text: #f47523;
  --color-success: #69cad9;
  --color-warning: #f47523;
  --color-destructive-text: hsl(0 85% 72%);

  /* --- Type ------------------------------------------------------------- */
  --font-heading: "Montserrat", "Segoe UI", ui-sans-serif, system-ui, sans-serif;
  --font-sans: "Segoe UI", "Microsoft Tai Le", ui-sans-serif, system-ui,
    -apple-system, Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  --text-hero: clamp(2.5rem, 1.6rem + 3.6vw, 4rem);
  --text-hero--line-height: 1.06;
  --text-hero--letter-spacing: -0.03em;
  --text-display: clamp(1.875rem, 1.3rem + 2.4vw, 2.75rem);
  --text-display--line-height: 1.12;
  --text-display--letter-spacing: -0.025em;
  --text-title: clamp(1.25rem, 1.1rem + 0.9vw, 1.625rem);
  --text-title--line-height: 1.22;
  --text-title--letter-spacing: -0.018em;
  --text-eyebrow: 0.75rem;
  --text-eyebrow--line-height: 1;
  --text-eyebrow--letter-spacing: 0.14em;

  /* --- Shape ------------------------------------------------------------ */
  --radius-sm: 0.375rem;
  --radius-md: 0.75rem;
  --radius-lg: 1rem;
  --radius-xl: 1.5rem;
  --radius-2xl: 2rem;
  --radius-3xl: 2.5rem;

  --shadow-card: 0 1px 2px rgb(0 0 0 / 0.4);
  --shadow-pop: 0 10px 30px -8px rgb(0 0 0 / 0.55);

  /* --- Motion and measure ----------------------------------------------- */
  --ease-out-soft: cubic-bezier(0.16, 1, 0.3, 1);
  --container-shell: 76rem;

  /* Decorative art opacity, per theme. */
  --pattern-opacity: 0.14;

  color-scheme: dark;
}

/* ----------------------------------------------------------------------------
   LIGHT - the override, and the DEFAULT.

   Two selectors on purpose. [data-theme="light"] is an explicit choice;
   :not([data-theme]) is the absence of one, which must not mean dark. Only
   what changes is redeclared: primary and the four brand hues are identical
   across themes, what moves is the luminance of their *text* variants.
   ------------------------------------------------------------------------- */
:root[data-theme="light"],
:root:not([data-theme]) {
  --color-background: hsl(0 0% 100%);
  --color-foreground: hsl(240 10% 3.9%);
  --color-card: hsl(0 0% 100%);
  --color-card-foreground: hsl(240 10% 3.9%);
  --color-popover: hsl(0 0% 100%);
  --color-popover-foreground: hsl(240 10% 3.9%);
  --color-primary: hsl(253 91% 58%);
  --color-primary-foreground: hsl(0 0% 98%);
  --color-secondary: hsl(240 4.8% 95.9%);
  --color-secondary-foreground: hsl(240 5.9% 10%);
  --color-muted: hsl(240 4.8% 95.9%);
  --color-muted-foreground: hsl(240 4% 42%);
  --color-accent: hsl(240 4.8% 95.9%);
  --color-accent-foreground: hsl(240 5.9% 10%);
  --color-destructive: hsl(0 84.2% 60.2%);
  --color-destructive-foreground: hsl(0 0% 98%);
  --color-border: hsl(240 5.9% 90%);
  --color-input: hsl(240 5.9% 90%);
  --color-ring: hsl(240 5.9% 10%);

  --color-copy: hsl(240 6% 30%);
  --color-border-strong: hsl(240 5% 55%);
  --color-primary-text: hsl(253 75% 47%);
  --color-primary-soft: hsl(253 100% 96%);

  /* Aqua and orange are 2.0:1 and 2.9:1 on white. Dropped to a readable
     luminance, hue held. */
  --color-aqua-text: hsl(191 48% 30%);
  --color-orange-text: hsl(22 92% 34%);
  --color-success: hsl(191 48% 30%);
  --color-warning: hsl(22 92% 34%);
  --color-destructive-text: hsl(0 72% 42%);

  --shadow-card: 0 1px 2px rgb(24 24 27 / 0.05), 0 1px 3px rgb(24 24 27 / 0.04);
  --shadow-pop: 0 10px 30px -8px rgb(24 24 27 / 0.14), 0 2px 6px rgb(24 24 27 / 0.05);

  --pattern-opacity: 0.08;

  color-scheme: light;
}

/* ----------------------------------------------------------------------------
   PER-COURSE ACCENT

   data-accent survives in the content, but it no longer repaints the
   interactive colour: a grape button and an orange button on the same grid
   taught people that the colour meant something it did not. It is now a soft
   tint on the card art and the ink of the course-page eyebrow, nothing else.
   ------------------------------------------------------------------------- */
[data-accent="grape"] { --course-accent: var(--color-brand-grape); --course-ink: var(--color-primary-text); }
[data-accent="aqua"] { --course-accent: var(--color-brand-aqua); --course-ink: var(--color-aqua-text); }
[data-accent="orange"] { --course-accent: var(--color-brand-orange); --course-ink: var(--color-orange-text); }

/* ----------------------------------------------------------------------------
   LEGACY ALIASES

   The lesson, quiz, verify and prose CSS was written against the first
   Academy palette. Rather than rewrite files this redesign does not touch,
   the old names are thin aliases onto the tokens above. Nothing new should
   use them.
   ------------------------------------------------------------------------- */
:root {
  --bg: var(--color-background);
  --bg-raised: var(--color-card);
  --bg-sunken: var(--color-secondary);
  --fg: var(--color-foreground);
  --fg-soft: var(--color-copy);
  --muted: var(--color-muted-foreground);
  --line: var(--color-border);
  --line-soft: var(--color-border);
  --accent: var(--color-primary);
  --accent-ink: var(--color-primary-text);
  --on-accent: var(--color-primary-foreground);
  --grape: var(--color-brand-grape);
  --grape-ink: var(--color-primary-text);
  --aqua: var(--color-brand-aqua);
  --aqua-ink: var(--color-aqua-text);
  --orange: var(--color-brand-orange);
  --orange-ink: var(--color-orange-text);
  --ok: var(--color-success);
  --bad: var(--color-destructive-text);
  --display: var(--font-heading);
  --text: var(--font-sans);
  --mono: var(--font-mono);
  --shadow: var(--shadow-card);
}

/* Any element the client hides with the hidden attribute stays hidden, even
   when its own rule sets display (course cards are flex, chips inline-flex). */
[hidden]{display:none!important}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{margin:0;background:var(--color-background);color:var(--color-foreground);font-family:var(--font-sans);font-size:17px;line-height:1.6;-webkit-font-smoothing:antialiased;font-feature-settings:"rlig" 1,"calt" 1;min-height:100dvh;display:flex;flex-direction:column}
img,svg{max-width:100%;display:block}
main{flex:1}

/* Touch. `manipulation` drops the 300ms wait for a second tap on everything
   that is pressed rather than read, and the tap flash is set on purpose
   instead of left to whatever grey the platform picks: the site already
   answers a press with :hover and :active, and the two together read as a
   flicker on iOS. */
a,button,summary,label,select,[role="button"],input[type="checkbox"],input[type="radio"],input[type="submit"],input[type="file"]{touch-action:manipulation}
html{-webkit-tap-highlight-color:transparent}

/* Both sticky headers are 64px, so anything an anchor lands on has to start
   below one. #main is the skip link's target and :target covers every heading
   and section a link in the footer, the rail or the copy points at. */
:target,#main{scroll-margin-top:84px}
.wrap{width:100%;max-width:1040px;margin:0 auto;padding:0 20px}
.wrap.wide{max-width:var(--container-shell)}
main.prose{max-width:70ch;padding-top:32px;padding-bottom:64px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* First thing in the tab order on every page, off screen until it has focus.
   #main is the empty marker layout() drops between the header and the page,
   so a keyboard lands on the content rather than walking the nav again. */
.skip-link{position:absolute;top:0;left:-9999px;z-index:100;background:var(--color-primary);color:var(--color-primary-foreground);font-family:var(--font-heading);font-weight:600;font-size:14px;padding:10px 16px;border-radius:0 0 var(--radius-md) 0;text-decoration:none}
.skip-link:focus{left:0}
#main{display:block;height:0}

/* Type. Headings are Montserrat 600 in sentence case: the SignOff register,
   not the Internal System's uppercase terminal one. */
h1,h2,h3,h4{font-family:var(--font-heading);font-weight:600;line-height:1.15;margin:0 0 .4em;text-wrap:balance;color:var(--color-foreground)}
h1{font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing)}
h2{font-size:var(--text-title);line-height:var(--text-title--line-height);letter-spacing:var(--text-title--letter-spacing);margin-top:2em}
h3{font-size:18px;margin-top:1.4em}
h4{font-size:var(--text-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
p{margin:0 0 1em}
a{color:var(--color-primary-text);text-decoration-thickness:1px;text-underline-offset:2px}
a:hover{text-decoration-thickness:2px}
ul,ol{padding-left:22px;margin:0 0 1em}
li{margin:.25em 0}
li::marker{color:var(--color-primary)}
code{font-family:var(--font-mono);font-size:.88em;background:var(--color-secondary);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:1px 5px}
pre{margin:0 0 1em}
table{border-collapse:collapse;width:100%;font-size:15px}
th,td{text-align:left;vertical-align:top;padding:9px 12px;border-bottom:1px solid var(--color-border)}
th{font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);letter-spacing:.08em;text-transform:uppercase;color:var(--color-muted-foreground)}
.tablewrap,.prose table{overflow-x:auto}
.eyebrow{font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);line-height:var(--text-eyebrow--line-height);letter-spacing:var(--text-eyebrow--letter-spacing);text-transform:uppercase;color:var(--course-ink,var(--color-primary-text));margin:0 0 .75em}
.eyebrow a{color:inherit;text-decoration:none}
.lede{font-size:19px;color:var(--color-copy);max-width:60ch}
.meta{font-size:14px;color:var(--color-muted-foreground);display:flex;flex-wrap:wrap;align-items:center;gap:10px}

/* Controls */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;font-family:var(--font-heading);font-weight:600;font-size:14px;color:var(--color-primary-foreground);background:var(--color-primary);border:1px solid var(--color-primary);border-radius:var(--radius-md);padding:11px 20px;text-decoration:none;cursor:pointer;line-height:1.2;transition:filter .15s var(--ease-out-soft),border-color .15s var(--ease-out-soft)}
.btn:hover{filter:brightness(1.08);text-decoration:none}
.btn.ghost{background:transparent;color:var(--color-foreground);border-color:var(--color-border-strong)}
.btn.ghost:hover{filter:none;border-color:var(--color-primary)}
.btn.small{padding:6px 12px;font-size:13px;border-radius:var(--radius-sm)}
.btn.block{width:100%;min-height:48px;border-radius:var(--radius-xl);font-size:15px}
.btn[disabled]{opacity:.55;cursor:default}
:focus-visible{outline:2px solid var(--color-ring);outline-offset:2px;border-radius:var(--radius-sm)}
input[type=text],input[type=email]{font:inherit;color:var(--color-foreground);background:var(--color-card);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:11px 14px;min-width:0;max-width:100%}
input[type=text]:focus,input[type=email]:focus{border-color:var(--color-primary)}
label{display:inline-flex;flex-direction:column;gap:6px;font-size:14px;color:var(--color-copy)}
::selection{background:var(--color-primary);color:var(--color-primary-foreground)}

/* Header. 64px, translucent over the page ground, hairline bottom.
   position:sticky is also what makes the bar the containing block for the
   mobile drawer, which is absolutely positioned against it below. */
.site-head{position:sticky;top:0;z-index:20;background:color-mix(in srgb,var(--color-background) 95%,transparent);backdrop-filter:blur(12px);border-bottom:1px solid var(--color-border)}
.site-head .wrap{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:64px}
/* margin-left:auto rather than the wrap's space-between doing it: with three
   children space-between would centre the links and strand the toggle on its
   own at the right edge. The links belong beside the controls. */
.site-nav{display:flex;align-items:center;gap:6px;font-size:14px;margin-left:auto}
.site-nav a{color:var(--color-copy);text-decoration:none;font-weight:600;padding:8px 10px;border-radius:var(--radius-sm)}
.site-nav a:hover{color:var(--color-foreground)}
/* The theme toggle and the hamburger, which never leave the bar. */
.head-actions{display:flex;align-items:center;gap:2px;flex:none}
/* No button until nav.js says there is a script to run it. Without one the
   links stay on the page as a second row and the button would be furniture
   that does nothing. */
.nav-toggle{display:none;place-items:center;width:36px;height:36px;flex:none;padding:0;background:transparent;border:0;border-radius:var(--radius-sm);color:var(--color-copy);cursor:pointer}
.nav-toggle:hover{color:var(--color-foreground);background:var(--color-secondary)}
.nav-toggle svg{width:20px;height:20px}
/* One state, read from the attribute the button already has to carry, so the
   glyph cannot disagree with what a screen reader is told. */
.nav-toggle .i-close{display:none}
.nav-toggle[aria-expanded="true"] .i-menu{display:none}
.nav-toggle[aria-expanded="true"] .i-close{display:block}

/* ── The header below 720px ──────────────────────────────────────────────
   720px is where the bar a SIGNED-IN learner sees stops fitting: lockup,
   Courses, Tracks, an XP pill, Admin, Sign in and the theme toggle. A phone
   is nowhere near it, and a small tablet is right on the line, so the drawer
   takes over well before anything has to wrap.

   Two layouts live in here. Without the bundle, `.site-nav` is a wrapped row
   under the bar: every link is on the page, reachable, and no worse than a
   long header. Once nav.js adds `js-nav` to the document the same element
   becomes a drawer hanging off the bottom edge of the bar, and the hamburger
   appears to work it. Nothing is hidden until the script that can show it
   again is running. */
@media (max-width:719px){
  .site-head .wrap{flex-wrap:wrap;gap:6px 12px;min-height:0;padding-top:10px;padding-bottom:6px}
  .site-nav{order:3;width:100%;flex-wrap:wrap;gap:0 2px;margin-left:-10px}

  :root.js-nav .nav-toggle{display:grid}
  :root.js-nav .site-head .wrap{flex-wrap:nowrap;min-height:64px;padding-top:0;padding-bottom:0}
  :root.js-nav .site-nav{
    position:absolute;left:0;right:0;top:100%;order:0;width:auto;margin:0;
    display:grid;justify-items:stretch;gap:2px;padding:10px 20px 18px;
    /* Opaque, unlike the bar above it. The bar is a hairline over the page
       and can afford to be translucent; the drawer sits ON the page and has
       to be read against whatever the hero happens to be. */
    background:var(--color-background);
    border-bottom:1px solid var(--color-border);
    box-shadow:var(--shadow-pop);
    /* visibility, not display: it is what keeps the closed drawer out of the
       tab order and out of the accessibility tree while still allowing the
       open and close to be animated.

       The visibility step is timed by hand rather than eased with the rest.
       Closing delays it by the length of the fade so the drawer is still
       there to be seen leaving; OPENING flips it in the same style
       recalculation the class arrives in, because a link inside a drawer that
       is still computing as hidden cannot take focus, and moving focus into
       the drawer is the first thing nav.js does. */
    opacity:0;visibility:hidden;transform:translateY(-8px);
    transition:opacity .18s var(--ease-out-soft),transform .18s var(--ease-out-soft),visibility 0s linear .18s
  }
  :root.js-nav .site-nav.is-open{opacity:1;visibility:visible;transform:none;transition:opacity .18s var(--ease-out-soft),transform .18s var(--ease-out-soft),visibility 0s}
  :root.js-nav .site-nav a{font-size:16px;padding:12px 12px;border-radius:var(--radius-md)}
  :root.js-nav .site-nav a:hover{background:var(--color-secondary)}
  :root.js-nav .site-nav a+a{border-top:1px solid var(--color-border);border-radius:0}
  :root.js-nav .site-nav a:last-child{border-radius:0 0 var(--radius-md) var(--radius-md)}
  /* The chip is status rather than a destination, so it sits above the links
     with a rule under it rather than pretending to be one of them. */
  :root.js-nav .site-nav .xp-chip{justify-self:start;margin:2px 0 10px 12px}
}

/* The lockup: the real Dasow mark on a white tile, product name over the
   master brand. The bowl of the D is opaque white in the art, so the white
   tile is what keeps the counters reading on a dark ground. */
.brand{display:inline-flex;align-items:center;gap:10px;text-decoration:none;color:var(--color-foreground)}
.brand:hover{text-decoration:none}
.mark{display:grid;place-items:center;flex:none;width:32px;height:32px;background:#fff;box-shadow:0 0 0 1px rgb(0 0 0 / .1);border-radius:var(--radius-md)}
.mark img{width:78%;height:78%;object-fit:contain}
.mark.sm{width:24px;height:24px;border-radius:var(--radius-sm)}
.brand-text{display:flex;flex-direction:column;line-height:1}
.brand-name{font-family:var(--font-heading);font-weight:600;font-size:20px;letter-spacing:-.02em}
.brand-by{margin-top:2px;font-size:.625rem;font-weight:500;text-transform:uppercase;letter-spacing:.14em;color:var(--color-muted-foreground)}

#theme-toggle{display:grid;place-items:center;width:32px;height:32px;flex:none;background:transparent;border:0;color:var(--color-muted-foreground);border-radius:var(--radius-sm);cursor:pointer;padding:0}
#theme-toggle:hover{color:var(--color-foreground);background:var(--color-secondary)}
#theme-toggle svg{width:18px;height:18px}
/* Show the icon for where the toggle takes you: a moon on a light page, a
   sun on a dark one. Keyed off the attribute so it is right at first paint. */
#theme-toggle .i-sun{display:none}
:root[data-theme="dark"] #theme-toggle .i-sun{display:block}
:root[data-theme="dark"] #theme-toggle .i-moon{display:none}

/* The footer is the site map for anyone who reached the bottom: three short
   groups, then the copyright and the note about how the courses are made. */
.site-foot{border-top:1px solid var(--color-border);padding:36px 0 32px;font-size:13.5px;color:var(--color-muted-foreground)}
.site-foot p{margin:0;max-width:70ch}
.foot-groups{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:24px 32px;margin:0 0 28px}
.foot-group h2{margin:0 0 10px;font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);line-height:1.2;letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
.foot-group ul{list-style:none;margin:0;padding:0}
.foot-group li{margin:0 0 8px}
.foot-group li::marker{content:none}
.foot-group a{color:var(--color-copy);text-decoration:none}
.foot-group a:hover{color:var(--color-primary-text);text-decoration:underline}
.foot-note{display:flex;flex-direction:column;gap:8px;padding-top:20px;border-top:1px solid var(--color-border)}
.foot-copy{font-family:var(--font-heading);font-weight:600;letter-spacing:.04em;color:var(--color-foreground)}

.notice,.error{border-left:3px solid var(--color-brand-aqua);background:var(--color-secondary);padding:12px 16px;border-radius:0 var(--radius-md) var(--radius-md) 0;margin:0 0 1em;font-size:15px}
.error{border-left-color:var(--color-destructive)}
.notice p:last-child,.error p:last-child{margin:0}
.hidden{display:none!important}

/* ── House primitives, shared with SignOff ─────────────────────────────── */

/* A panel reads as an object sitting ON the page: a lit top edge, a
   grounded body and a real shadow. */
.panel{position:relative;border-radius:var(--radius-xl);background-image:linear-gradient(to bottom,color-mix(in oklab,var(--color-foreground) 5%,var(--color-card)),var(--color-card));border:1px solid var(--color-border);box-shadow:0 1px 0 0 color-mix(in oklab,var(--color-foreground) 8%,transparent) inset,var(--shadow-pop)}

.chip{display:inline-flex;align-items:center;gap:.375rem;border-radius:999px;border:1px solid var(--color-border);background:color-mix(in oklab,var(--color-card) 70%,transparent);padding:.25rem .625rem;font-family:var(--font-mono);font-size:.6875rem;letter-spacing:.08em;text-transform:uppercase;color:var(--color-copy)}
.chip.orange{border-color:color-mix(in srgb,var(--color-brand-orange) 45%,transparent);color:var(--color-orange-text)}
.chip.aqua{border-color:color-mix(in srgb,var(--color-brand-aqua) 45%,transparent);color:var(--color-aqua-text)}

/* The brand board ramp: grape into the interactive violet into aqua. A fill
   behind large type or art, never under body copy. */
.brand-gradient{background-image:linear-gradient(118deg,var(--color-brand-grape) 0%,var(--color-primary) 46%,var(--color-brand-aqua) 100%)}
.brand-rule{background-image:linear-gradient(90deg,var(--color-brand-grape),var(--color-primary) 46%,var(--color-brand-aqua))}

/* A recessed full-width band. Linen in light, the neutral wash in dark: a
   pure-white page reads clinical for a whole section. */
.band-warm{--band-ground:var(--color-secondary);background:var(--band-ground)}
:root[data-theme="light"] .band-warm,:root:not([data-theme]) .band-warm{--band-ground:var(--color-brand-linen)}

/* One entrance for the whole site: 12px rise, 700ms, one curve. */
[data-reveal]{opacity:0;transform:translate3d(0,12px,0);transition:opacity 700ms var(--ease-out-soft),transform 700ms var(--ease-out-soft);transition-delay:var(--reveal-delay,0ms)}
[data-reveal="in"]{opacity:1;transform:none}
.no-reveal [data-reveal]{opacity:1;transform:none}
@media (prefers-reduced-motion:reduce){[data-reveal]{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){*,*::before,*::after{animation:none!important;transition:none!important}}

/* Progress ring. Aqua is the supporting accent and this is what it is for. */
.ring{--pct:0;position:relative;width:46px;height:46px;border-radius:50%;background:conic-gradient(var(--color-brand-aqua) calc(var(--pct)*1%),var(--color-border) 0);display:grid;place-items:center;flex:none}
.ring::after{content:"";position:absolute;inset:5px;border-radius:50%;background:var(--color-card)}
.ring span{position:relative;z-index:1;font-family:var(--font-heading);font-weight:600;font-size:11px;font-variant-numeric:tabular-nums;color:var(--color-copy)}
.ring.big{width:64px;height:64px}
.ring.big span{font-size:13px}

/* Course page stop list */
.stops{list-style:none;padding:0;margin:0 0 1.5em;display:grid;gap:8px}
.stops li{display:grid;grid-template-columns:32px 1fr auto;gap:12px;align-items:center;background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-md);padding:12px 14px;box-shadow:var(--shadow-card)}
.stops li a{color:var(--color-foreground);text-decoration:none;display:grid;gap:2px}
.stops li a b{font-family:var(--font-heading);font-weight:600;font-size:15px}
.stops li a span{font-size:14px;color:var(--color-copy)}
.stops li a:hover b{text-decoration:underline}
.badge{width:28px;height:28px;border-radius:50%;border:2px solid var(--color-border);display:grid;place-items:center;font-family:var(--font-heading);font-weight:600;font-size:12px;color:var(--color-muted-foreground);transition:background .2s,border-color .2s}
.stops li.done .badge,.lesson-nav li.done .badge{background:var(--color-brand-aqua);border-color:var(--color-brand-aqua);color:var(--color-brand-ink);font-size:0}
.stops li.done .badge::before,.lesson-nav li.done .badge::before{content:"✓";font-size:12px}
.lesson-nav li.done .badge::before{font-size:10px}
.tag{font-family:var(--font-heading);font-weight:600;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--color-copy);border:1px solid var(--color-border);border-radius:999px;padding:3px 8px;white-space:nowrap}

/* Lesson layout */
.lesson-wrap{display:grid;grid-template-columns:1fr;gap:24px;padding-top:24px;padding-bottom:96px}
.lesson-nav{order:2;font-size:14px}
.lesson-nav ol{list-style:none;padding:0;margin:0;display:grid;gap:4px}
.lesson-nav li{display:grid;grid-template-columns:24px 1fr;gap:8px;align-items:center}
.lesson-nav li .badge{width:22px;height:22px;font-size:10px}
.lesson-nav li a{color:var(--color-copy);text-decoration:none;padding:4px 6px;border-radius:var(--radius-sm)}
.lesson-nav li.current a{color:var(--color-foreground);font-weight:600;background:var(--color-secondary)}
.lesson-nav li a:hover{color:var(--color-foreground)}
@media (min-width:900px){
  .lesson-wrap{grid-template-columns:240px minmax(0,1fr);gap:48px}
  .lesson-nav{order:0;position:sticky;top:84px;align-self:start}
}
main.lesson{max-width:70ch;min-width:0}
.lesson-actions{margin:2em 0 1em}
.pager{display:flex;justify-content:space-between;gap:16px;padding-top:16px;border-top:1px solid var(--color-border);font-family:var(--font-heading);font-weight:600;font-size:14px}
/* The feedback disc is fixed to the bottom right corner on a phone, and the
   next stop is the one link on the page that lives in that corner too. The
   pager keeps a gutter the width of the button so the two can never land on
   each other, wherever the reader happens to stop scrolling. */
@media (max-width:559px){.pager{padding-right:54px}}

/* Prompt cards and asides */
.try{background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-lg);margin:0 0 14px;overflow:hidden;box-shadow:var(--shadow-card)}
.try-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;border-bottom:1px solid var(--color-border);font-family:var(--font-heading);font-weight:600;font-size:14px}
.copy{font:inherit;font-size:12px;font-weight:600;font-family:var(--font-heading);color:var(--color-primary-text);background:transparent;border:1px solid var(--color-border-strong);border-radius:var(--radius-sm);padding:4px 10px;cursor:pointer}
.copy:hover{border-color:var(--color-primary)}
.prompt{margin:0;padding:14px;font-family:var(--font-mono);font-size:14px;line-height:1.55;white-space:pre-wrap;word-break:break-word;background:var(--color-secondary);color:var(--color-foreground)}
.card{border-left:3px solid var(--color-primary);background:var(--color-secondary);padding:12px 16px;border-radius:0 var(--radius-md) var(--radius-md) 0;margin:0 0 16px}
.card.watchout{border-left-color:var(--color-brand-orange)}
.card.where{border-left-color:var(--color-brand-aqua)}
/* The card heading is an h3 so a stop page never skips from h2 to h4; it
   keeps the eyebrow look the h4 rule gave it. */
.card h3{font-size:var(--text-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground);margin:0 0 4px}
h2+.card>h3,h3+.card>h3{display:none}
.card p:last-child{margin:0}

/* Checks and quiz */
.check,.quiz{margin-top:2em}
fieldset{border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:14px 16px;margin:0 0 14px;background:var(--color-card);box-shadow:var(--shadow-card)}
legend{font-family:var(--font-heading);font-weight:600;font-size:15px;padding:0 6px}
/* flex-direction is spelled out because the base `label` rule stacks its
   children; an option is a radio next to its text, not above it. */
.opt{display:flex;flex-direction:row;align-items:flex-start;gap:10px;padding:8px 10px;border-radius:var(--radius-sm);border:1px solid transparent;cursor:pointer;font-size:15px;color:var(--color-foreground)}
.opt:hover{background:var(--color-secondary)}
.opt input{margin-top:4px;accent-color:var(--color-primary)}
.opt.correct{border-color:var(--color-success);color:var(--color-success)}
.opt.wrong{border-color:var(--color-destructive-text);color:var(--color-destructive-text)}
.why{font-size:14px;color:var(--color-copy);margin:8px 0 0;padding-top:8px;border-top:1px dashed var(--color-border)}
.score{font-family:var(--font-heading);font-weight:700;font-size:var(--text-display);line-height:1;margin:0 0 .3em;font-variant-numeric:tabular-nums}
.score.pass{color:var(--color-success)}
.score.fail{color:var(--color-destructive-text)}
.confetti-canvas{position:fixed;inset:0;width:100%;height:100%;pointer-events:none;z-index:50}

/* Agreement (kids) */
.agree{background:var(--color-secondary);border:2px dashed var(--color-brand-orange);border-radius:var(--radius-lg);padding:16px;margin:0 0 16px;display:grid;gap:12px}
.agree ol{margin:0}
.agree.signed{border-style:solid}
.agree-signed{font-family:var(--font-heading);font-weight:600;margin:0}

/* Cheat sheet */
table.cheat th:first-child{width:38%}

/* Verify */
.verify-card{background:var(--color-card);border:1px solid var(--color-border);border-top:3px solid var(--color-primary);border-radius:var(--radius-xl);padding:24px;box-shadow:var(--shadow-pop)}
.verify-card dl{display:grid;grid-template-columns:max-content 1fr;gap:6px 18px;margin:0}
.verify-card dt{font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);letter-spacing:.08em;text-transform:uppercase;color:var(--color-muted-foreground);padding-top:3px}
.verify-card dd{margin:0}
.id{font-family:var(--font-mono);font-size:13px;color:var(--color-muted-foreground)}

/* Watch first */
.watch{margin:0 0 1.5em;border-radius:var(--radius-2xl);overflow:hidden;background:var(--color-brand-ink);box-shadow:var(--shadow-pop)}
.watch video{display:block;width:100%;height:auto;aspect-ratio:16/9;background:#000}
.watch figcaption{font-family:var(--font-heading);font-weight:600;font-size:13px;letter-spacing:.04em;color:var(--color-muted-foreground);padding:8px 14px;background:var(--color-card)}
.watch.watched figcaption::before{content:"✓ ";color:var(--color-aqua-text)}
.watch.trailer{max-width:720px}
/* Exercises */
.exercise{margin-top:2em;background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:20px 22px;box-shadow:var(--shadow-card)}
.exercise h2{margin-top:0}
.exercise-prompt{font-weight:600}
.exercise-result{min-height:1.5em;margin:12px 0 0;font-size:15px;color:var(--color-copy)}
.exercise-result.ok{color:var(--color-aqua-text);font-weight:600}
.exercise-result.hint{color:var(--color-orange-text)}
.exercise.done{border-color:var(--color-brand-aqua)}
.exercise-actions{margin:12px 0 0}
.pick-grid{display:grid;gap:12px}
@media (min-width:700px){.pick-grid{grid-template-columns:1fr 1fr}}
.pick-card{text-align:left;font:inherit;color:var(--color-foreground);background:var(--color-secondary);border:2px solid var(--color-border);border-radius:var(--radius-lg);padding:14px;cursor:pointer;display:grid;gap:6px;transition:border-color .15s,transform .15s}
.pick-card:not(.correct):not(.wrong):hover{border-color:var(--color-primary)}
.pick-label{font-family:var(--font-heading);font-weight:600;font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--color-muted-foreground)}
.pick-text{font-family:var(--font-mono);font-size:14px;white-space:pre-wrap}
/* Scoped to .exercise: .chip, .correct and .wrong already mean something else
   on the home page cards and in the quick check, and an unscoped rule here
   repainted those. */
.exercise .chip{font:inherit;font-size:15px;text-transform:none;letter-spacing:normal;color:var(--color-foreground);background:var(--color-card);border:2px solid var(--color-border);border-radius:999px;padding:8px 14px;cursor:pointer;transition:border-color .15s,transform .15s}
.exercise .chip:not(.correct):not(.wrong):hover{border-color:var(--color-primary)}
.exercise .chip.wide{border-radius:var(--radius-lg);text-align:left;display:block;width:100%;margin:6px 0}
.order-placed{list-style:decimal;padding-left:26px;margin:0 0 12px;display:grid;gap:6px}
.order-slot{color:var(--color-muted-foreground);font-size:14px;border:1px dashed var(--color-border);border-radius:999px;padding:6px 12px;list-style:none;margin-left:-26px}
.order-pool{display:flex;flex-wrap:wrap;gap:8px}
.build-template{font-family:var(--font-mono);font-size:15px;line-height:2.1;background:var(--color-secondary);border-radius:var(--radius-lg);padding:14px}
.build-blank{font:inherit;font-size:14px;border:2px solid var(--color-primary);border-radius:8px;padding:2px 6px;background:var(--color-card);color:var(--color-foreground)}
.spot-text{margin:0;padding:14px;background:var(--color-secondary);border-radius:var(--radius-lg);line-height:2.1}
.spot-span{font:inherit;color:var(--color-foreground);background:transparent;border:2px solid var(--color-border);border-radius:8px;padding:1px 6px;cursor:pointer}
.spot-span:not(.correct):not(.wrong):hover{border-color:var(--color-primary)}
.scenario-step{font-family:var(--font-heading);font-weight:600;font-size:12px;letter-spacing:.1em;text-transform:uppercase;color:var(--color-muted-foreground);margin:0 0 4px}
.scenario-situation{font-size:17px}
.scenario-why{margin:10px 0 0;color:var(--color-copy)}
/* Graded feedback sits after every :hover rule above, and those hover rules
   exclude .correct and .wrong, so the answer colour holds while the pointer
   rests on the thing the learner just tapped. */
.exercise .correct{border-color:var(--color-brand-aqua);background:color-mix(in srgb,var(--color-brand-aqua) 18%,transparent);animation:pop .35s cubic-bezier(.16,1,.3,1)}
.exercise .wrong{border-color:var(--color-destructive)}
.shake{animation:shake .4s}
@keyframes shake{20%{transform:translateX(-5px)}40%{transform:translateX(5px)}60%{transform:translateX(-3px)}80%{transform:translateX(3px)}}
@keyframes pop{0%{transform:scale(.96)}60%{transform:scale(1.03)}100%{transform:scale(1)}}
@media (prefers-reduced-motion:reduce){.exercise .shake,.exercise .correct{animation:none}}

/* ============================================================================
   pages.css - home, sign-in and account.

   Everything here is composition. The colours, radii, shadows and the entrance
   all come from tokens.css; nothing below invents a value.
   ========================================================================= */

/* ── Shared art ────────────────────────────────────────────────────────────
   The pattern strip (grape molecule links and sparkles on transparent) is the
   one decorative texture. It never carries meaning, so it sits under content
   at the per-theme opacity and is always aria-hidden by being a background. */
.pattern{position:absolute;inset:0;pointer-events:none;background:url("/img/brand/pattern-strip.webp") repeat-x 50% 100%/auto 200px;opacity:var(--pattern-opacity)}

/* A CSS-only stand-in for art that has not been generated yet. The build
   decides which one renders; both occupy the same box so the page does not
   move when a file lands. */
.is-fallback{background-color:color-mix(in srgb,var(--course-accent,var(--color-primary)) 12%,var(--color-card));background-image:linear-gradient(118deg,color-mix(in srgb,var(--color-brand-grape) 24%,transparent) 0%,color-mix(in srgb,var(--color-primary) 18%,transparent) 46%,color-mix(in srgb,var(--color-brand-aqua) 24%,transparent) 100%)}

/* ── Home: hero ───────────────────────────────────────────────────────── */
.hero{position:relative;overflow:hidden;padding:48px 0 56px}
.hero-grid{position:relative;display:grid;gap:36px;align-items:center}
@media (min-width:900px){
  .hero{padding:72px 0 80px}
  .hero-grid{grid-template-columns:7fr 5fr;gap:48px}
}
.hero h1{font-size:var(--text-hero);line-height:var(--text-hero--line-height);letter-spacing:var(--text-hero--letter-spacing);margin-bottom:.35em}
.hero .lede{margin-bottom:0}
.hero-actions{display:flex;flex-wrap:wrap;gap:12px;margin:28px 0 0}
.trust{display:flex;flex-wrap:wrap;gap:8px;list-style:none;padding:0;margin:24px 0 0}
.trust li{margin:0}

.hero-art-wrap{position:relative}
.hero-bloom{position:absolute;inset:-10% -8%;border-radius:var(--radius-3xl);filter:blur(56px);opacity:.34;pointer-events:none}
.art{position:relative;overflow:hidden;border-radius:var(--radius-3xl);box-shadow:var(--shadow-pop);border:1px solid var(--color-border);aspect-ratio:16/10;background:var(--color-secondary)}
.art img{width:100%;height:100%;object-fit:cover}

/* ── Home: continue strip ─────────────────────────────────────────────── */
#continue{padding-top:8px;padding-bottom:0}
#continue h2{margin-top:0}

/* ── Home: course grid ────────────────────────────────────────────────── */
.course-section{padding:40px 0 8px}
.course-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:20px}
.course-card{display:flex;flex-direction:column;background:var(--color-card);border:1px solid var(--color-border);border-radius:var(--radius-2xl);box-shadow:var(--shadow-card);overflow:hidden;color:var(--color-foreground);text-decoration:none;transition:transform .2s var(--ease-out-soft),border-color .2s var(--ease-out-soft),box-shadow .2s var(--ease-out-soft)}
a.course-card:hover{transform:translateY(-2px);border-color:color-mix(in oklab,var(--color-primary) 20%,transparent);box-shadow:var(--shadow-pop);text-decoration:none}
.card-art{position:relative;display:block;aspect-ratio:16/10;overflow:hidden;background:var(--color-secondary)}
.card-art img{width:100%;height:100%;object-fit:cover}
.card-art.is-fallback::after{content:"";position:absolute;inset:0;background:url("/img/brand/pattern-strip.webp") repeat-x 50% 100%/auto 72px;opacity:var(--pattern-opacity)}
.card-art .ring{position:absolute;top:12px;right:12px;box-shadow:0 0 0 4px var(--color-card)}
.card-body{flex:1;display:flex;flex-direction:column;gap:8px;padding:18px 20px 20px}
.course-card h3{margin:0;font-size:var(--text-title);line-height:var(--text-title--line-height);letter-spacing:var(--text-title--letter-spacing)}
.course-card h3 a{color:inherit;text-decoration:none}
.course-card h3 a:hover{color:var(--color-primary-text)}
.course-card p{margin:0;font-size:15px;color:var(--color-copy)}
/* The card's last line: how long on the left, what you come away with on the
   right, over a hairline. Two items rather than a run of middle dots, and
   they wrap to two lines on a narrow card rather than squeezing. Scoped with
   :has() to the catalogue card, because the continue strip and the account
   page print their own .meta inside the same card and are left as they are. */
.course-card .meta{margin-top:auto;padding-top:8px;font-size:13px}
.course-card .meta:has(.card-outcome){justify-content:space-between;gap:4px 12px;padding-top:12px;border-top:1px solid var(--color-border)}
.card-outcome{font-family:var(--font-heading);font-weight:600;color:var(--color-copy)}
.course-card .btn{align-self:flex-start;margin-top:4px}
.card-chips{display:flex;flex-wrap:wrap;gap:6px}

/* ── Home: shared section rhythm ───────────────────────────
   Every band on the home page uses the same top padding and the same heading
   plus lede pair, so the page reads as one column of sections rather than a
   pile of components.

   The section head is set at the display size rather than the title size. A
   card heading is already --text-title, so at the old size a section and the
   cards under it were the same weight and the page had no spine: everything
   was equally important, which is the same as nothing being important. */
/* Top and bottom only: every .section is also a .wrap, and the shorthand
   used to zero the wrap's 20px gutters, which left section headings flush
   against the screen edge on a phone and on any window under ~1256px. */
.section{padding-top:56px;padding-bottom:8px}
.section > h2,.how h2,.certificate-grid h2,.who-grid h2,.faq-head h2{margin:0 0 10px;font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing)}
.section-lede{margin:0 0 28px;max-width:56ch;color:var(--color-copy);font-size:16px}

/* A short bar of the brand ramp over the first line of a section. The gradient
   itself is .brand-rule from components.css; this only gives it a size and the
   space under it. Decorative, so it is aria-hidden in the markup. */
.section-mark{display:block;width:44px;height:3px;border-radius:999px;margin:0 0 18px}
.hero .section-mark{margin-bottom:20px}

/* A section that owns a full-width band rather than sitting on the page
   ground: the band supplies its own vertical space, since .section's padding
   belongs to the column inside it. */
.section-band{padding:60px 0}
@media (min-width:900px){.section-band{padding:76px 0}}

/* ── Home: start at your level ───────────────────────── */
.level-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
.level-card{display:flex;flex-direction:column;gap:6px;padding:20px;border:1px solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-card);box-shadow:var(--shadow-card);color:var(--color-foreground);text-decoration:none;transition:transform .2s var(--ease-out-soft),border-color .2s var(--ease-out-soft),box-shadow .2s var(--ease-out-soft)}
a.level-card:hover{transform:translateY(-2px);border-color:color-mix(in oklab,var(--color-primary) 24%,transparent);box-shadow:var(--shadow-pop);text-decoration:none}
.level-card h3{margin:0;font-size:19px;letter-spacing:-.01em}
.level-card .who{margin:0;font-size:14px;color:var(--color-primary-text)}
.level-card p{margin:0;font-size:14px;color:var(--color-copy)}
.level-card .meta{margin-top:auto;padding-top:10px;font-size:13px}
/* A rung with no course yet is still a link. It just does not ask for the eye. */
.level-card.is-empty{opacity:.72}

/* ── Home: tracks ─────────────────────────────────── */
.track-list{list-style:none;margin:0;padding:0;border-top:1px solid var(--color-border)}
.track-row{display:grid;gap:12px;padding:20px 0;border-bottom:1px solid var(--color-border)}
@media (min-width:760px){.track-row{grid-template-columns:1fr auto;align-items:center;gap:32px}}
.track-row h3{margin:0 0 4px;font-size:18px;letter-spacing:-.01em}
/* The one place the track accent is allowed to show: a rule down the left. */
.track-row > div:first-child{border-left:3px solid var(--course-accent,var(--color-primary));padding-left:14px}
.track-row p{margin:0;font-size:14px;color:var(--color-copy);max-width:62ch}
.track-levels{display:flex;flex-wrap:wrap;gap:8px}
.track-levels .chip{text-decoration:none;color:var(--color-primary-text);border-color:color-mix(in oklab,var(--color-primary) 32%,transparent)}
.track-levels .chip:hover{background:var(--color-primary-soft);text-decoration:none}

/* ── Home: the certificate ──────────────────────────── */
.certificate-grid{display:grid;gap:28px;align-items:center}
@media (min-width:900px){.certificate-grid{grid-template-columns:6fr 5fr;gap:48px}}
.certificate-grid p{color:var(--color-copy);max-width:56ch}
.certificate-art{position:relative;overflow:hidden;border-radius:var(--radius-2xl);border:1px solid var(--color-border);box-shadow:var(--shadow-pop);aspect-ratio:1400/990;background:var(--color-secondary)}
.certificate-art img{width:100%;height:100%;object-fit:cover}

/* ── Home: tell us what to build ─────────────────────── */
.feedback-strip{margin-top:56px;padding:40px 0}
.feedback-strip .wrap{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:20px}
.feedback-strip h2{margin:0 0 6px;font-size:var(--text-title)}
.feedback-strip p{margin:0;color:var(--color-copy);font-size:15px;max-width:52ch}

/* ── Home: how it works ───────────────────────────────────────────────── */
.how{margin-top:40px;padding:56px 0}
.how-grid{display:grid;gap:28px}
@media (min-width:760px){.how-grid{grid-template-columns:repeat(3,1fr);gap:32px}}
.how h2{margin:0 0 28px}
.how h3{margin:12px 0 6px;font-size:19px}
.how p{margin:0;color:var(--color-copy);font-size:15px}

/* ══ Home page, reorganised for a first-time visitor ═══════════════════════
   Everything below belongs to the sections the home page gained when it was
   reordered: the counted figures under the hero, the compact track grid, the
   who-makes-this panel, the FAQ and the closing call to action. Same rules as
   the rest of this file: every colour, radius, shadow and curve comes from
   tokens.css, both themes read because nothing here names a colour of its
   own, and no rule transitions `all`.
   ────────────────────────────────────────────────────────────────────────── */

/* ── Home: the counted figures, in the band under the hero ─────────────────
   A ledger strip rather than a set of cards: full-bleed hairlines above and
   below, one column per figure, and a hairline between columns. It is the
   only place on the page that is ruled like this, so it reads as the proof
   under the promise rather than as one more section. A definition list, one
   number per term, so the label belongs to the figure. Numbers are tabular
   so the row does not wobble when a course lands and 41 becomes 42. Each cell
   carries its own [data-reveal] with a short stagger, left to right. */
.numbers{border-top:1px solid var(--color-border)}
/* How it works is the next band and brings its own edge, so the two meet
   flush rather than leaving a strip of page ground between them. */
.numbers + .how{margin-top:0}
.numbers-row{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));margin:0}
.numbers-row > div{display:grid;align-content:start;gap:8px}
.numbers-row dt{font-family:var(--font-heading);font-weight:700;font-size:clamp(2rem,1.5rem + 1.8vw,3rem);line-height:1;letter-spacing:-.035em;font-variant-numeric:tabular-nums;color:var(--color-foreground)}
.numbers-row dd{margin:0;font-size:14px;line-height:1.4;max-width:20ch;color:var(--color-muted-foreground)}
/* A phone gets two by two: the rule between the columns and the one between
   the rows are the same hairline, so the four read as one block. */
@media (max-width:759px){
  .numbers-row > div{padding:20px 12px 20px 0}
  .numbers-row > div:nth-child(even){padding-left:18px;border-left:1px solid var(--color-border)}
  .numbers-row > div:nth-child(n+3){border-top:1px solid var(--color-border)}
}
@media (min-width:760px){
  .numbers-row{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr)}
  .numbers-row > div{padding:30px 28px}
  .numbers-row > div:first-child{padding-left:0}
  .numbers-row > div + div{border-left:1px solid var(--color-border)}
}

/* ── How it works: the shape of a course, under the heading ─────────────── */
.how h2{margin-bottom:8px}
.how .section-lede{margin:0 0 32px}

/* ── How it works: a path, not three cards ────────────────────────────────
   Three steps on one line, each a node, a heading and a sentence. The line is
   one connector per step, drawn from that step's node centre to the next
   one's, so it never needs measuring. The nodes are filled with the band's
   own ground (--band-ground, from .band-warm) so they sit over the line
   rather than having it run through them. On a phone the path turns
   vertical: the nodes down the left, the words beside them. The .how-grid
   rules above are the About page's and are left alone. */
.how-path{--node:22px;--how-gap:48px;--how-row:30px;list-style:none;margin:0;padding:0;display:grid;gap:var(--how-row)}
.how-step{position:relative;margin:0;padding-left:calc(var(--node) + 20px)}
.how .how-step h3{margin:0 0 6px;font-size:19px;line-height:1.3}
.how .how-step p{max-width:36ch}
.how-node{position:absolute;z-index:1;left:0;top:2px;width:var(--node);height:var(--node);border-radius:50%;border:2px solid var(--color-primary);background:var(--band-ground,var(--color-background))}
.how-step.is-end .how-node{background:var(--color-brand-aqua);border-color:var(--color-aqua-text)}
.how-step:not(:last-child)::after{content:"";position:absolute;z-index:0;left:calc(var(--node) / 2 - 1px);top:calc(2px + var(--node) / 2);width:2px;height:calc(100% + var(--how-row));background:var(--color-primary);transform-origin:top}
.how-step:nth-last-child(2)::after{background:linear-gradient(to bottom,var(--color-primary),var(--color-brand-aqua))}
@media (min-width:760px){
  .how-path{grid-template-columns:repeat(3,minmax(0,1fr));gap:0 var(--how-gap)}
  .how-step{padding-left:0;padding-top:calc(var(--node) + 22px)}
  .how-node{top:0}
  .how-step:not(:last-child)::after{left:calc(var(--node) / 2);top:calc(var(--node) / 2 - 1px);width:calc(100% + var(--how-gap));height:2px;transform-origin:left}
  .how-step:nth-last-child(2)::after{background:linear-gradient(90deg,var(--color-primary),var(--color-brand-aqua))}
}
/* The draw. Only with motion allowed: under reduced motion the connectors
   are simply there, and the global rule in base.css already shows every
   [data-reveal] at once. Each connector starts a beat after its own step
   lands, which is when the next step starts to rise. Without an observer
   (.no-reveal) nothing is left undrawn. */
@media (prefers-reduced-motion:no-preference){
  .how-step[data-reveal]:not(:last-child)::after{transform:scaleY(0);transition:transform 600ms var(--ease-out-soft) calc(var(--reveal-delay,0ms) + 240ms)}
}
@media (prefers-reduced-motion:no-preference) and (min-width:760px){
  .how-step[data-reveal]:not(:last-child)::after{transform:scaleX(0)}
}
@media (prefers-reduced-motion:no-preference){
  .how-step[data-reveal="in"]:not(:last-child)::after,.no-reveal .how-step:not(:last-child)::after{transform:none}
}

/* ── Home: the track grid ─────────────────────────────────────────────────
   Twenty-six rows of blurb is a wall. On the home page a track is its name
   and how much of its ladder is built; the blurbs and the rungs are one click
   away at /tracks/. */
.track-tiles{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(15rem,1fr));gap:10px}
.track-tiles li{margin:0}
.track-tile{display:flex;align-items:baseline;justify-content:space-between;gap:12px;height:100%;padding:13px 16px;border:1px solid var(--color-border);border-left:3px solid var(--course-accent,var(--color-primary));border-radius:var(--radius-md);background:var(--color-card);box-shadow:var(--shadow-card);color:var(--color-foreground);text-decoration:none;transition:background-color .2s var(--ease-out-soft),box-shadow .2s var(--ease-out-soft)}
a.track-tile:hover{background:var(--color-secondary);box-shadow:var(--shadow-pop);text-decoration:none}
.track-tile h3{margin:0;font-size:16px;line-height:1.25;letter-spacing:-.01em}
.track-tile-count{flex:none;font-family:var(--font-mono);font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--color-muted-foreground);font-variant-numeric:tabular-nums}
.section-more{margin:20px 0 0;font-size:15px}

/* ── Home: who makes this ─────────────────────────────────────────────── */
.who-grid{display:grid;gap:28px;align-items:start}
@media (min-width:900px){.who-grid{grid-template-columns:7fr 5fr;gap:48px}}

.who-grid p{color:var(--color-copy);max-width:60ch}
.who-actions{display:flex;flex-wrap:wrap;gap:12px;margin:24px 0 0}

/* On the home page the section is a stacked statement rather than a split:
   the certificate band right before it is already copy beside a picture, and
   two splits in a row read as the same section twice. The heading, then the
   first paragraph set a size up as the statement, the reason under it, the
   two links, and the three facts as a row of notes across the full width.
   Notes rather than cards: a rule over each and no box, so they read as the
   footing of the statement, not as three more things to click. The .who-grid
   rules above are the About page's. */
.who-stack{max-width:64ch}
.who-stack p{color:var(--color-copy)}
.who-stack .who-lead{font-size:clamp(1.125rem,1rem + .5vw,1.3125rem);line-height:1.55;color:var(--color-foreground)}
.who-notes{list-style:none;margin:44px 0 0;padding:0;display:grid;gap:20px}
.who-notes li{margin:0;padding-top:14px;border-top:1px solid var(--color-border-strong);font-size:15px;line-height:1.55;color:var(--color-copy);max-width:44ch}
@media (min-width:760px){.who-notes{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:32px}}

/* ── Home: the FAQ ────────────────────────────────────────────────────────
   Answers, not disclosure widgets. Seven short ones read faster open than
   they do behind seven clicks, and a crawler reads the same words the
   FAQPage block claims. */
.faq-layout{display:grid;gap:8px 56px}
.faq-head .section-lede{margin-bottom:0}
.faq-grid{display:grid;gap:26px 48px;margin-top:20px}
@media (min-width:760px){.faq-grid{grid-template-columns:1fr 1fr}}
/* The heading takes a column of its own and rides down with the answers: it
   is one question being answered seven ways, not seven headings in a list. */
@media (min-width:1040px){
  .faq-layout{grid-template-columns:19rem minmax(0,1fr)}
  .faq-head{position:sticky;top:96px}
  .faq-grid{grid-template-columns:1fr 1fr;margin-top:0}
}
.faq-item h3{margin:0 0 6px;font-size:17px;letter-spacing:-.01em}
.faq-item p{margin:0;font-size:15px;color:var(--color-copy);max-width:56ch}

/* ── Home: the closing call to action ───────────────────────────────────── */
.closing{padding-top:64px;padding-bottom:80px}
.closing .panel{display:grid;justify-items:center;gap:14px;padding:44px 24px;text-align:center}
.closing h2{margin:0;max-width:22ch;font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing)}
.closing p{margin:0;max-width:52ch;color:var(--color-copy)}
.closing-actions{display:flex;flex-wrap:wrap;justify-content:center;gap:12px;margin:10px 0 0}

/* ── Courses and tracks pages ────────────────────────────
   The catalogue is server rendered in full and courses.js only hides and
   shows, so nothing here is allowed to depend on the filter having run. */
/* Top and bottom only, for the same reason as .section: these mains are
   also .wrap, and the shorthand zeroed its gutters. */
.courses-main,.track-main{padding-top:32px;padding-bottom:64px}
.courses-head,.track-head{max-width:60ch;margin-bottom:28px}
.courses-head h1,.track-head h1{font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing);margin:.15em 0 .3em}

.filter-bar{position:sticky;top:64px;z-index:5;display:flex;flex-wrap:wrap;align-items:center;gap:12px 16px;margin:0 0 32px;padding:14px 0;background:var(--color-background);border-bottom:1px solid var(--color-border)}
.filter-chips{display:flex;flex-wrap:wrap;gap:8px}
.filter-chip{display:inline-flex;align-items:center;border-radius:999px;border:1px solid var(--color-border);background:var(--color-card);padding:.3rem .75rem;font-family:var(--font-mono);font-size:.6875rem;letter-spacing:.08em;text-transform:uppercase;color:var(--color-copy);text-decoration:none;cursor:pointer}
.filter-chip:hover{border-color:var(--color-border-strong);text-decoration:none}
.filter-chip.is-on{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-foreground)}
.filter-controls{display:flex;flex-wrap:wrap;gap:10px;flex:1;min-width:min(100%,18rem)}
/* The strong border, not the hairline: a control's edge has to reach 3:1
   against the page, and --color-input is the decorative 1.3:1 line. The
   placeholder is set from the tokens rather than left to the browser's
   grey, which measured 4.3:1 on the dark ground. */
.filter-bar select,.filter-bar input{font:inherit;font-size:14px;color:var(--color-foreground);background:var(--color-card);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:.45rem .7rem}
.filter-bar input::placeholder{color:var(--color-muted-foreground);opacity:1}
.filter-bar input{flex:1;min-width:9rem}
.filter-bar select:focus-visible,.filter-bar input:focus-visible{outline:2px solid var(--color-primary);outline-offset:1px}
#courses-count{margin:0;white-space:nowrap}

.track-group{margin:0 0 44px}
.track-group h2{margin:0 0 6px;font-size:var(--text-title);letter-spacing:var(--text-title--letter-spacing)}
.track-group h2 a{color:inherit;text-decoration:none}
.track-group h2 a:hover{color:var(--color-primary-text)}
.track-group .section-lede{margin-bottom:18px}

/* A rung of a track ladder. The empty ones stay, so the climb reads whole. */
.ladder{list-style:none;margin:0;padding:0;display:grid;gap:20px}
.rung{display:grid;gap:12px;padding:20px;border:1px solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-card)}
@media (min-width:820px){.rung{grid-template-columns:1fr minmax(300px,26rem);align-items:center;gap:32px}.rung > .course-card{grid-row:span 2}.rung-head,.rung-blurb{grid-column:1}}
.rung.is-empty{background:transparent;border-style:dashed}
.rung-head{display:flex;flex-wrap:wrap;align-items:center;gap:10px}
/* The level chip is the rung's heading, so the h2 rules have to give up the
   size, the top margin and the trailing space the flex row would inherit. */
.rung-head h2.chip{margin:0}
.rung-head .who{margin:0;font-size:14px;color:var(--color-primary-text)}
.rung-blurb{margin:0;font-size:14px;color:var(--color-copy);max-width:60ch}
.rung .empty{margin:0}

/* A button that has to sit inside a sentence. */
.linkish{font:inherit;color:var(--color-primary-text);background:none;border:0;padding:0;cursor:pointer;text-decoration:underline}

/* ── Course page: level, track and prerequisites ─────── */
.course-chips{display:flex;flex-wrap:wrap;gap:8px;margin:0 0 20px}
.course-chips .chip{text-decoration:none;color:var(--color-primary-text);border-color:color-mix(in oklab,var(--color-primary) 32%,transparent)}
.course-chips .chip:hover{background:var(--color-primary-soft);text-decoration:none}
.start-with{list-style:none;margin:0 0 24px;padding:0;display:grid;gap:10px}
.start-with li{display:grid;gap:2px;padding:12px 14px;border:1px solid var(--color-border);border-left:3px solid var(--color-primary);border-radius:var(--radius-md);background:var(--color-card)}
.start-with span{font-size:14px;color:var(--color-copy)}

/* ── Sign-in ──────────────────────────────────────────────────────────── */
.auth-strip{display:flex;align-items:center;justify-content:space-between;gap:16px;min-height:64px}
.auth-strip .brand-name{font-size:15px}
.auth-main{display:flex;align-items:center;padding:16px 0 64px}
.auth-grid{display:grid;gap:32px;width:100%}
@media (min-width:900px){
  .auth-grid{grid-template-columns:1fr 1fr;gap:56px;align-items:center}
  .auth-main{padding:0 0 48px}
}
.auth-form{width:100%;max-width:24rem;margin-inline:auto}
.auth-form h1{font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing)}
.auth-form .lede{font-size:16px;margin-bottom:24px}
.auth-form form{display:grid;gap:16px;margin:0 0 12px}
.auth-form label{width:100%}
.auth-form input[type=email]{width:100%}
.auth-form #magic-status{font-size:14px;color:var(--color-copy);margin:0}
.auth-art{position:relative;overflow:hidden;border-radius:var(--radius-3xl);box-shadow:var(--shadow-pop);border:1px solid var(--color-border);aspect-ratio:16/10;background:var(--color-secondary)}
.auth-art img{width:100%;height:100%;object-fit:cover}
@media (min-width:900px){.auth-art{aspect-ratio:4/5}}

/* ── Account ──────────────────────────────────────────────────────────── */
.account{padding-top:40px;padding-bottom:72px}
.account-head{margin:0 0 32px}
.account-head h1{font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing);margin-bottom:.2em}
.account-head .lede{font-size:16px;margin:0}
.account h2{margin-top:44px;margin-bottom:16px}
.account section:first-of-type h2{margin-top:0}
.empty{color:var(--color-muted-foreground);font-size:15px}

.cert-list{list-style:none;padding:0;margin:0;display:grid;gap:16px}
.cert-list li{padding:20px 22px;display:grid;gap:10px}
.cert-list li b{font-family:var(--font-heading);font-weight:600;font-size:var(--text-title);line-height:1.25}
.cert-list .actions{display:flex;flex-wrap:wrap;gap:8px;align-items:center}
/* certActions() ships its own .actions wrapper; flatten it so all four
   controls sit on one row rather than three plus one. */
.cert-list .actions .actions{display:contents}
.seal{align-self:start;justify-self:start}

.not-started{list-style:none;padding:0;margin:0;display:grid;gap:2px}
.not-started li{margin:0}
.not-started a{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 4px;border-bottom:1px solid var(--color-border);color:var(--color-foreground);text-decoration:none;font-size:15px}
.not-started a:hover{color:var(--color-primary-text)}
.not-started a span{color:var(--color-muted-foreground)}

.settings{margin-top:44px;padding:22px 24px}
.settings h2{margin:0 0 16px;font-size:19px;color:var(--color-muted-foreground)}
.settings .form-row{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;margin:0 0 16px}
.settings .actions{display:flex;flex-wrap:wrap;gap:10px;margin:20px 0 0}
.form-row{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;margin:0 0 1em}

/* The signed-out branch of /account/ is the same composition as /auth/, just
   inside the page shell rather than replacing it. */
#account-root .auth-grid{gap:32px}

/* ── Game layer ───────────────────────────────────────────────────────── */
/* XP, level, streak and badges are all derived from progress the site
   already stores. Nothing here is a new fact about a learner, so it stays
   quiet: one pill in the header, one panel on the account page. */
.xp-chip{display:inline-flex;align-items:center;gap:.35em;border-radius:999px;background:var(--color-primary-soft);color:var(--color-primary-text);padding:5px 11px;font-size:12px;font-weight:600;line-height:1;white-space:nowrap}
/* display:inline-flex would beat the UA [hidden] rule, so the empty pill
   would flash on every load before the first paint decides it has nothing
   to say. */
.xp-chip[hidden]{display:none}
.xp-chip .xp-n{font-family:var(--font-mono);font-weight:600;font-variant-numeric:tabular-nums}
/* The separator belongs to the level, so a chip that drops the level on a
   narrow screen never ends in a dangling dot. The two rules that used to drop
   the level at 600px and the whole chip at 420px are gone: below 720px the
   chip is in the nav drawer, where it has a whole row to itself and there is
   nothing to make room for. */
.xp-chip .xp-level::before{content:"·";margin-right:.35em}

.game-panel{padding:22px 24px;margin:0 0 8px}
/* .account section:first-of-type h2 zeroes the top margin of the first
   section's heading, and the panel is now that section, so this has to match
   its weight and come later rather than lose the space under the streak. */
.account section.game-panel h2,.game-panel h2{margin:24px 0 12px;font-size:15px;color:var(--color-muted-foreground)}
.game-head{display:flex;flex-wrap:wrap;align-items:baseline;justify-content:space-between;gap:8px}
.game-head p{margin:0}
.game-xp{display:flex;align-items:center;gap:10px}
.game-xp b{font-family:var(--font-heading);font-size:var(--text-title);line-height:1.1;letter-spacing:var(--text-title--letter-spacing)}
.game-panel .streak{margin:12px 0 0;font-size:14px;color:var(--color-copy)}

.level-bar{margin-top:14px;height:10px;border-radius:999px;background:var(--color-secondary);border:1px solid var(--color-border);overflow:hidden}
.level-bar span{display:block;height:100%;width:var(--pct,0%);border-radius:999px;background:linear-gradient(90deg,var(--color-primary),var(--color-brand-aqua));transition:width .6s var(--ease-out-soft)}

.badge-wall{list-style:none;margin:0;padding:0;display:grid;gap:12px;grid-template-columns:repeat(auto-fill,minmax(150px,1fr))}
/* .badge is the numbered circle in the stops list everywhere else, so every
   rule here is scoped to the wall and has to undo that shape. */
.badge-wall .badge{width:auto;height:auto;display:grid;justify-items:center;gap:6px;padding:14px 12px;text-align:center;border-radius:var(--radius-lg);border:1px solid var(--color-border);background:var(--color-card);font-family:var(--font-sans);font-size:13px;font-weight:400;color:var(--color-foreground);transition:border-color .2s,background .2s}
.badge-wall .badge:not(.earned){opacity:.6}
.badge-wall .badge.earned{border-color:color-mix(in srgb,var(--color-primary) 55%,transparent);background:var(--color-primary-soft)}
.badge-wall .badge b{font-family:var(--font-heading);font-weight:600;font-size:14px;line-height:1.2}
.badge-wall .badge span{font-size:12px;color:var(--color-muted-foreground);line-height:1.35}
.badge-wall .badge.earned span{color:var(--color-aqua-text)}
.badge-wall .sparkle{width:26px;height:26px;fill:var(--color-muted-foreground)}
.badge-wall .badge.earned .sparkle{fill:var(--color-brand-aqua)}

.toast-host{position:fixed;left:50%;bottom:24px;transform:translateX(-50%);z-index:60;display:flex;flex-direction:column-reverse;align-items:center;gap:8px;pointer-events:none}
/* The card itself never eats a click: it floats over the page for a few
   seconds and the only thing in it worth pressing is the next-stop link. */
.toast{pointer-events:none;display:flex;align-items:center;gap:14px;max-width:min(92vw,30rem);padding:12px 16px;border-radius:var(--radius-lg);border:1px solid var(--color-border);background:var(--color-card);box-shadow:var(--shadow-pop);font-size:14px;color:var(--color-foreground);opacity:0;transform:translateY(12px);transition:opacity .25s var(--ease-out-soft),transform .25s var(--ease-out-soft)}
.toast.show{opacity:1;transform:none}

.badge-card{pointer-events:none;position:fixed;top:80px;right:20px;z-index:60;display:flex;align-items:center;gap:12px;max-width:min(92vw,22rem);padding:14px 16px;border-radius:var(--radius-lg);border:1px solid color-mix(in srgb,var(--color-primary) 55%,transparent);background:var(--color-card);box-shadow:var(--shadow-pop);opacity:0;transform:translateX(16px);transition:opacity .3s var(--ease-out-soft),transform .3s var(--ease-out-soft)}
.badge-card.show{opacity:1;transform:none}
.badge-card-art{flex:none;display:grid;place-items:center;width:40px;height:40px;border-radius:50%;background:var(--color-primary-soft)}
.badge-card .sparkle{width:22px;height:22px;fill:var(--color-brand-aqua)}
.badge-card b{display:block;font-family:var(--font-heading);font-weight:600;font-size:14px;margin-bottom:2px}
.badge-card>span:last-child>span{font-size:13px;color:var(--color-copy);line-height:1.35}

@media (prefers-reduced-motion:reduce){.toast,.badge-card,.level-bar span,.badge-wall .badge{transition:none}}

/* ---- Course journey map -------------------------------------------------
   The stop list is the map. On a wide screen journey.js adds .is-drawn and
   places the same list items along a winding SVG path; below 900px, and on any
   page whose bundle never ran, none of this applies and the list is the list
   it has always been. Nodes: done is aqua with a check (the shared .badge
   rule), next is violet and pulsing, the rest are muted. */
.journey{position:relative;margin:0 0 1.5em}
.journey-path,.journey-end{display:none}

@media (min-width:900px){
  /* The box is reserved at the viewBox's ratio before journey.js runs, so the
     page does not jump when the list stops taking flow height. */
  /* An inline-size container so a label can be sized against the map rather
     than against the viewport: the box is the thing the geometry scales with.
     The map is a diagram, not a paragraph, so it also steps out of the 70ch
     prose column to the page column (.wrap: 1040px less its 20px padding),
     which is what buys the eight labels enough width to be worth reading. */
  .journey{aspect-ratio:800/360;margin-bottom:2.5em;container-type:inline-size;width:min(1000px,calc(100vw - 40px));margin-left:50%;transform:translateX(-50%)}
  .journey.is-drawn .journey-path{display:block;position:absolute;inset:0;width:100%;height:100%;overflow:visible}
  .journey-track{fill:none;stroke:var(--color-border);stroke-width:3;stroke-linecap:round;stroke-dasharray:2 11}
  /* Undrawn until journey.js has measured it, so a browser that cannot
     measure a path shows no trail rather than a full one. */
  .journey-trail{fill:none;stroke:var(--color-brand-aqua);stroke-width:3;stroke-linecap:round;stroke-dasharray:9999;stroke-dashoffset:9999;transition:stroke-dashoffset .8s var(--ease-out-soft)}
  /* The still life sits at the end of the path: from just right of the last
     node to the right edge, centred on that node's line. journey.js writes
     --end-x and --end-y from the geometry, so nothing here repeats a number. */
  .journey.is-drawn .journey-end{display:block;position:absolute;right:0;top:var(--end-y);width:calc(100% - var(--end-x) - 26px);height:auto;aspect-ratio:8/5;transform:translateY(-50%);object-fit:cover;border-radius:var(--radius-md);border:1px solid var(--color-border);box-shadow:var(--shadow-card)}

  .journey.is-drawn .stops{display:block;position:absolute;inset:0;margin:0}
  /* Each stop is a zero-size anchor point on the curve; the badge and the
     label hang off it, so nothing has to be re-measured when the box scales. */
  .journey.is-drawn .stops li{position:absolute;left:var(--x);top:var(--y);display:block;width:0;height:0;padding:0;gap:0;background:none;border:0;border-radius:0;box-shadow:none}
  .journey.is-drawn .stops li .badge{position:absolute;left:-21px;top:-21px;width:42px;height:42px;font-size:14px;background:var(--color-card);transition:background .25s,border-color .25s,color .25s,transform .4s var(--ease-out-soft)}
  /* Restated because the map's own .badge rule above outranks the shared
     .stops li.done one: aqua fill, the number swapped for a check. */
  .journey.is-drawn .stops li.done .badge{background:var(--color-brand-aqua);border-color:var(--color-brand-aqua);color:var(--color-brand-ink);font-size:0;transform:scale(1.06)}
  .journey.is-drawn .stops li.done .badge::before{font-size:16px}
  .journey.is-drawn .stops li.next .badge{background:var(--color-primary);border-color:var(--color-primary);color:var(--color-primary-foreground);animation:journey-pulse 2s var(--ease-out-soft) infinite}

  /* The label is hidden with opacity, never display:none, so a screen reader
     still reads every stop title and summary. The transparent disc over the
     node keeps the link clickable while the words are invisible.
     The width is the whole game: half a label plus the neighbouring 42px
     circle (44.5px once it is done and scaled) has to stay inside one node
     pitch, or a visible title covers the next node and steals its click.
     journey.js writes --pitch as a percentage of the box width, so the label
     follows the geometry at every size and nothing is typed twice. */
  .journey.is-drawn .stops li a{position:absolute;left:0;transform:translateX(-50%);width:100px;width:min(170px,calc(var(--pitch,9) * 2cqi - 52px));display:block;text-align:center;opacity:0;pointer-events:none;transition:opacity .2s var(--ease-out-soft)}
  /* Below the node in the upper half of the box, above it in the lower half:
     journey.js writes data-side from the same points it draws the path from. */
  .journey.is-drawn .stops li[data-side=below] a{top:30px}
  .journey.is-drawn .stops li[data-side=above] a{bottom:30px}
  /* The disc overlaps the near edge of the label box, so a pointer travelling
     from the node to the title never crosses a gap and never loses :hover. It
     swings with the label, or the bottom of a node would answer to the badge
     rather than to the link. */
  .journey.is-drawn .stops li a::before{content:"";position:absolute;left:50%;width:56px;height:60px;margin-left:-28px;border-radius:28px;pointer-events:auto}
  .journey.is-drawn .stops li[data-side=below] a::before{top:-58px}
  .journey.is-drawn .stops li[data-side=above] a::before{bottom:-58px}
  /* The last node stands against the still life, so its label swings left of
     the node instead of running over the photo. Above the line was the first
     try and it does not work: the crest of the path (nodes 6 and 7) is
     directly up and left of the last node, so the title lands on a circle
     instead of on the picture. The label box reaches back up to the node
     through its padding, so the diagonal from node to title has no gap, and
     the disc sits on the node rather than 30px above it. */
  .journey.is-drawn .stops li:last-child[data-side] a{left:auto;right:0;top:0;bottom:auto;transform:none;padding-top:30px}
  .journey.is-drawn .stops li:last-child[data-side] a::before{left:auto;right:-28px;margin-left:0;top:-28px;bottom:auto}
  /* Two lines at most, then an ellipsis. One line was the first plan and it
     cut half the titles to nothing at this width ("Connectors and ..."); a
     second line costs 18px of empty space and no clearance at all, since it
     is the width that keeps a label off its neighbour. The full title is in
     the accessibility tree either way, and in the list below 900px. */
  .journey.is-drawn .stops li a b{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:2;line-clamp:2;overflow:hidden;font-size:14px;line-height:1.3;text-wrap:balance}
  /* The summary has no room beside a 42px node; like the tag it stays in the
     page for a screen reader and comes back in the list under 900px. */
  .journey.is-drawn .stops li a span{position:absolute;width:1px;height:1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
  /* The next stop's label is always on show, so the one the reader is
     actually pointing at has to sit above it, not under it. */
  .journey.is-drawn .stops li.next{z-index:3}
  .journey.is-drawn .stops li:hover,.journey.is-drawn .stops li:focus-within{z-index:4}
  .journey.is-drawn .stops li:hover a,.journey.is-drawn .stops li:focus-within a,.journey.is-drawn .stops li.next a{opacity:1;pointer-events:auto}
  .journey.is-drawn .stops li.next a b{color:var(--color-primary-text)}
  /* The tag has no room on the map; it stays in the page for a screen reader. */
  .journey.is-drawn .stops li .tag{position:absolute;width:1px;height:1px;padding:0;border:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
}

@keyframes journey-pulse{
  0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--color-primary) 45%,transparent)}
  70%{box-shadow:0 0 0 14px transparent}
  100%{box-shadow:0 0 0 0 transparent}
}

/* Rings ease to a new value instead of snapping. --pct has to stay
   inheritable and accept a percentage as well as a number: .level-bar sets it
   on the parent as a percentage and reads it in the child, and registering it
   any other way would silently break that. Browsers without @property ignore
   the whole thing and keep the old jump. */
@property --pct{syntax:'<number> | <percentage>';inherits:true;initial-value:0}
.ring{transition:--pct .6s var(--ease-out-soft)}

@media (prefers-reduced-motion:reduce){
  .ring{transition:none}
  .journey-trail{transition:none}
  .journey.is-drawn .stops li .badge,.journey.is-drawn .stops li a{transition:none}
  .journey.is-drawn .stops li.next .badge{animation:none}
}

/* --- Company and legal pages ------------------------------------------- */

.contact-card{overflow-wrap:anywhere;font-style:normal;margin:0 0 20px;padding:18px 20px;border:1px solid var(--color-border);border-left:3px solid var(--color-primary);border-radius:0 var(--radius-md) var(--radius-md) 0;background:var(--color-card);font-size:15px;line-height:1.7}
.contact-card p{margin:0 0 10px}
.contact-card p:last-child{margin:0}
.contact-name{font-family:var(--font-heading);font-weight:600;letter-spacing:.04em;color:var(--color-foreground)}

/* The contact page is an address card beside a form, so it gets the page
   column rather than the 70ch a page of reading wants. */
.contact-grid{display:grid;gap:24px;padding-top:44px;padding-bottom:16px;align-items:start}
@media (min-width:820px){
  .contact-grid{grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);gap:32px;padding-top:56px}
  /* The form is the taller column by a long way, so the details ride down
     beside it rather than scrolling off and leaving the reader to guess the
     phone number. 96px is the sticky header plus the usual clearance. */
  .contact-grid > .address-card{position:sticky;top:96px}
}
.contact-grid h2{margin-top:0}
.contact-aside{font-size:14px;color:var(--color-muted-foreground)}

/* A form field is a label, a control and a slot for one message about it.
   The slot is always in the DOM so aria-describedby has something to point
   at before anything has gone wrong. */
.field{display:flex;flex-direction:column;gap:6px;margin:0 0 18px}
.field label{font-family:var(--font-heading);font-weight:600;font-size:14px;color:var(--color-foreground);flex-direction:row;gap:6px}
.field-note{font-family:var(--font-sans);font-weight:400;color:var(--color-muted-foreground)}
.field input,.field textarea{font:inherit;font-size:16px;width:100%;color:var(--color-foreground);background:var(--color-card);border:1px solid var(--color-border-strong);border-radius:var(--radius-md);padding:11px 14px}
.field textarea{resize:vertical;min-height:140px;line-height:1.55}
.field input:focus,.field textarea:focus{border-color:var(--color-primary)}
.field input[aria-invalid=true],.field textarea[aria-invalid=true]{border-color:var(--color-destructive-text)}
.field-error{font-size:13.5px;color:var(--color-destructive-text);min-height:1px}
.field-error:empty{display:none}
/* The honeypot. Off screen rather than display:none, because a bot that
   reads the stylesheet skips a hidden field and fills a visible one. */
.field-trap{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.form-actions{margin:4px 0 0}
.form-status{margin:12px 0 0;font-size:15px;color:var(--color-destructive-text)}
.form-status:empty{display:none}
.form-status.is-ok{color:var(--color-aqua-text)}

/* The no-JavaScript path. The API redirects back to /contact/?sent=1#sent,
   and :target is what reveals the right paragraph without a line of script. */
.form-flash{display:none;margin:0 0 18px;padding:12px 16px;border-radius:0 var(--radius-md) var(--radius-md) 0;font-size:15px;background:var(--color-secondary);border-left:3px solid var(--color-brand-aqua)}
.form-flash-bad{border-left-color:var(--color-destructive)}
.form-flash:target{display:block}

/* ══ Site pages: About, Contact, Privacy, Terms, Verify ═══════════════════
   These five are built out of the home page's vocabulary rather than as a
   document with a heading on top: a warm hero band, sections on the page
   ground, panels, tiles and one closing call to action. Everything below is
   composition only. Every colour is a token, both themes read because no
   rule names a colour of its own, no rule transitions `all`, and every
   control keeps the shared focus ring.
   ────────────────────────────────────────────────────────────────────── */

/* The page owns the gap to the footer, so no section has to end with one. */
.site-page{padding-bottom:8px}

/* The site pages borrow the home page's section head: display size over a
   short bar of the brand ramp. .section > h2 already covers the sections that
   carry that class; these are the two bands that do not. */
.about-creed h2,.contact-reasons h2{margin:0 0 10px;font-size:var(--text-display);line-height:var(--text-display--line-height);letter-spacing:var(--text-display--letter-spacing)}

/* ── The hero band ────────────────────────────────────────────────────────
   The same opening as the home page: eyebrow, one line, one lede, on the
   linen band with the pattern strip under it. The band is what says "the
   page starts here", so nothing below it needs a rule across the top. */
.page-hero{position:relative;overflow:hidden;padding:44px 0 48px;border-bottom:1px solid var(--color-border)}
@media (min-width:900px){.page-hero{padding:76px 0 80px}}
.page-hero-inner{position:relative;max-width:60rem}
.page-hero h1{margin:0 0 .3em;font-size:var(--text-hero);line-height:var(--text-hero--line-height);letter-spacing:var(--text-hero--letter-spacing);max-width:20ch}
.page-hero .lede{margin:0;max-width:54ch}

/* ── The counted figures ──────────────────────────────────────────────────
   Hairline gaps rather than borders, so four cards read as one block. The
   numbers are tabular: a course landing must not make the row wobble. */
.stat-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:1px;margin:0 0 28px;padding:0;background:var(--color-border);border:1px solid var(--color-border);border-radius:var(--radius-xl);overflow:hidden}
.stat-row > div{display:grid;align-content:start;gap:6px;padding:20px 22px;background:var(--color-card)}
.stat-row dt{font-family:var(--font-heading);font-weight:700;font-size:34px;line-height:1;letter-spacing:-.03em;font-variant-numeric:tabular-nums;color:var(--color-foreground)}
.stat-row dd{margin:0;font-size:13px;line-height:1.35;color:var(--color-muted-foreground)}

/* ── A small fact, pulled out of the prose ────────────────────────────────
   Used on About for what you walk away with, and on the legal pages for the
   four facts a reader came to look up: retention, the cookie, the container,
   the certificate. Aqua, because it is support rather than a warning. */
.callout{margin:24px 0 0;padding:16px 18px;border:1px solid var(--color-border);border-left:3px solid var(--color-brand-aqua);border-radius:0 var(--radius-md) var(--radius-md) 0;background:var(--color-secondary)}
.callout-label{margin:0 0 6px;font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
.callout p{margin:0 0 8px;font-size:15px;color:var(--color-copy)}
.callout p:last-child{margin:0}

/* ── About: who we are, our vision, our mission ───────────────────────────
   Three panels of the same shape, because the three are equal: an About page
   that ranks them has already decided which one the reader cares about. Each
   is a label, one line that could be said out loud, and the paragraph that
   earns it. The lead line is set at the heading face so the three read across
   the row before any of the bodies do. */
.creed-grid{display:grid;gap:16px}
@media (min-width:900px){.creed-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:20px}}
.creed-panel{display:grid;align-content:start;gap:10px;padding:24px 24px 22px}
.creed-panel .eyebrow{margin:0}
.creed-lead{margin:0;font-family:var(--font-heading);font-weight:600;font-size:19px;line-height:1.28;letter-spacing:-.012em;color:var(--color-foreground);text-wrap:balance}
.creed-panel p:last-child{margin:0;font-size:15px;color:var(--color-copy)}

/* ── About: the studio panel beside the copy ───────────────────────────── */
.fact-panel{padding:22px 24px}
.fact-head{display:flex;align-items:center;gap:12px}
.fact-head h3{margin:0;font-size:15px;letter-spacing:.12em;color:var(--color-foreground)}
.fact-list{margin:12px 0 0}
.fact-list > div{display:grid;gap:3px;padding:13px 0;border-top:1px solid var(--color-border)}
.fact-list dt{font-family:var(--font-heading);font-weight:600;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
.fact-list dd{margin:0;font-size:15px;line-height:1.5;color:var(--color-copy);overflow-wrap:anywhere}

/* Four steps rather than three. The 760px rule above puts the how-grid on
   three columns, so this pairs them first and opens to four late. */
@media (min-width:760px){.how-grid-4{grid-template-columns:repeat(2,1fr);gap:32px 40px}}
@media (min-width:1100px){.how-grid-4{grid-template-columns:repeat(4,1fr)}}
.how-grid .chip{font-variant-numeric:tabular-nums}
.how-grid-4 h3{margin-top:10px}
.how-written .section-more{margin-top:36px;padding-top:24px;border-top:1px solid var(--color-border);font-size:15px;color:var(--color-copy)}

/* ── The quiet panel: what this is not ────────────────────────────────────
   A dashed edge and no shadow: it is a caveat, so it must not compete with
   the panels that are offering something. */
.quiet-panel{padding:26px 24px;border:1px dashed var(--color-border-strong);border-radius:var(--radius-xl);background:color-mix(in oklab,var(--color-secondary) 55%,transparent)}
.quiet-panel h2{margin:0 0 14px;font-size:var(--text-title);line-height:var(--text-title--line-height)}
.quiet-panel p{margin:0 0 12px;max-width:66ch;font-size:15px;color:var(--color-copy)}
.quiet-panel p:last-child{margin:0}
/* The heading steps out to its own column rather than leaving the panel two
   thirds empty at the right of a wide screen. */
@media (min-width:900px){
  .quiet-panel{display:grid;grid-template-columns:13rem minmax(0,1fr);gap:12px 40px;padding:32px 34px}
  .quiet-panel h2{margin:0}
}

/* ── Contact: the address card ────────────────────────────────────────────
   A label column and a value column, one hairline per row. Every value that
   a device can act on is a link: the map, the dialler and the mailbox. */
.address-card{padding:24px 24px 22px}
.address-head{display:flex;align-items:center;gap:12px;padding-bottom:18px;border-bottom:1px solid var(--color-border)}
.address-head h2{margin:0;font-size:16px;letter-spacing:.12em;color:var(--color-foreground)}
.address-kicker{margin:3px 0 0;font-size:13px;color:var(--color-muted-foreground)}
.address-lines{font-style:normal;margin:0}
.address-row{display:grid;grid-template-columns:4.5rem minmax(0,1fr);gap:4px 16px;align-items:start;margin:0;padding:14px 0;border-bottom:1px solid var(--color-border)}
.address-row:last-child{padding-bottom:0;border-bottom:0}
/* On a narrow phone the label column costs the value 72px it does not have,
   and "Costa Mesa, California" breaks over two lines for no reason. Under
   420px the label sits above its value instead. */
@media (max-width:419px){.address-row{grid-template-columns:1fr;gap:3px}.address-label{padding-top:0}}
.address-label{padding-top:3px;font-family:var(--font-heading);font-weight:600;font-size:11px;letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
/* Underlined on hover and on focus rather than always: in a card of six rows
   six underlines is noise, and the weight plus the value column is what says
   these are the actionable things. */
.address-row a{color:var(--color-foreground);font-weight:600;text-decoration:none;overflow-wrap:anywhere}
.address-row a:hover,.address-row a:focus-visible{color:var(--color-primary-text);text-decoration:underline}
.address-map{display:block;margin-top:4px;font-family:var(--font-sans);font-weight:400;font-size:12.5px;color:var(--color-muted-foreground)}
.address-hours{display:grid;justify-items:start;gap:8px;font-size:15px;color:var(--color-copy)}

/* Whether the studio is open right this minute. contact.js writes it from
   Pacific time, so it ships hidden and the page never guesses. */
.open-now{display:inline-flex;align-items:center;gap:7px;padding:3px 10px;border-radius:999px;border:1px solid var(--color-border);background:var(--color-card);font-family:var(--font-heading);font-weight:600;font-size:12px;line-height:1.5}
.open-now.is-open{color:var(--color-aqua-text);border-color:color-mix(in srgb,var(--color-brand-aqua) 45%,transparent);background:color-mix(in srgb,var(--color-brand-aqua) 12%,transparent)}
.open-now.is-shut{color:var(--color-muted-foreground)}
.open-dot{flex:none;width:7px;height:7px;border-radius:50%;background:currentColor}

/* ── Contact: the form card ───────────────────────────────────────────── */
.form-card{padding:24px 24px 22px}
.form-card > h2{margin:0 0 6px;font-size:var(--text-title);line-height:var(--text-title--line-height)}
.form-card-lede{margin:0 0 22px;font-size:15px;color:var(--color-copy)}
.form-card .form-actions{margin:6px 0 0}
.form-card .form-actions .btn{min-height:46px;padding-inline:26px}
@media (max-width:559px){.form-card .form-actions .btn{width:100%}}
/* The small print at the foot of either card is set off by a rule, so it
   reads as a footnote rather than as one more line of the thing above it. */
.address-card .contact-aside,.form-card .contact-aside{margin:18px 0 0;padding-top:16px;border-top:1px solid var(--color-border)}

/* The confirmation replaces the form: the job is done, so the fields are no
   longer the thing on screen. It ships in the markup and only `hidden` moves,
   which is what keeps the words the same in both paths. */
.sent-card{display:grid;justify-items:center;gap:10px;padding:34px 24px;text-align:center;border:1px solid color-mix(in srgb,var(--color-brand-aqua) 40%,transparent);border-radius:var(--radius-xl);background:color-mix(in srgb,var(--color-brand-aqua) 8%,transparent)}
.sent-card h3{margin:0;font-size:var(--text-title);line-height:var(--text-title--line-height)}
.sent-card p{margin:0;max-width:36ch;font-size:15px;color:var(--color-copy)}
.sent-tick{display:grid;place-items:center;width:46px;height:46px;border-radius:50%;background:var(--color-brand-aqua);color:var(--color-brand-ink)}
.sent-tick svg{width:24px;height:24px}
.sent-actions{margin:8px 0 0}

/* ── The legal pages ──────────────────────────────────────────────────────
   Prose at a reading measure, with the contents beside it on a wide screen
   and as a row of chips above it on a narrow one. The list and the sections
   are rendered from the same array, so an anchor cannot point at nothing. */
.legal-grid{display:grid;gap:28px;padding-top:40px;padding-bottom:8px;align-items:start}
@media (min-width:1000px){.legal-grid{grid-template-columns:15rem minmax(0,1fr);gap:56px;padding-top:56px}}
.legal-toc{font-size:14px}
.legal-toc-head{margin:0 0 10px;font-family:var(--font-heading);font-weight:600;font-size:var(--text-eyebrow);letter-spacing:.12em;text-transform:uppercase;color:var(--color-muted-foreground)}
.legal-toc ol{list-style:none;margin:0;padding:0}
.legal-toc li{margin:0}
.legal-toc a{display:block;color:var(--color-copy);text-decoration:none;line-height:1.35}
.legal-toc a:hover{color:var(--color-primary-text)}
@media (max-width:999px){
  .legal-toc ol{display:flex;flex-wrap:wrap;gap:6px}
  .legal-toc a{padding:6px 12px;border:1px solid var(--color-border);border-radius:999px;background:var(--color-card);font-size:13px}
  .legal-toc a:hover{border-color:var(--color-primary)}
}
@media (min-width:1000px){
  /* 84px is the sticky header plus the same 20px every anchor target clears. */
  .legal-toc{position:sticky;top:84px;max-height:calc(100dvh - 7rem);overflow:auto}
  .legal-toc a{padding:7px 12px;border-left:2px solid var(--color-border)}
  .legal-toc a:hover{border-left-color:var(--color-primary);background:var(--color-secondary)}
}
.legal-body{max-width:65ch;min-width:0}
/* The heading a contents link lands on has to clear the sticky header. */
.legal-body section{scroll-margin-top:84px}
.legal-body section + section{margin-top:2.6em}
.legal-body h2{margin:0 0 .5em;font-size:var(--text-title);line-height:var(--text-title--line-height);letter-spacing:var(--text-title--letter-spacing)}
.legal-body h3{margin:1.7em 0 .4em;font-size:17px;color:var(--color-foreground)}
.legal-body p,.legal-body li{color:var(--color-copy);font-size:16px}
.legal-body p:last-child{margin-bottom:0}
.legal-date{margin:26px 0 0;font-size:14px;color:var(--color-muted-foreground)}
.legal-date .chip{background:var(--color-card)}

/* ── Verify ───────────────────────────────────────────────────────────────
   One narrow column: a code goes in at the top and the answer appears under
   it, in the same place every time. */
.verify-wrap{display:grid;gap:20px;max-width:44rem;padding-top:44px;padding-bottom:24px}
@media (min-width:900px){.verify-wrap{padding-top:56px}}
.verify-form{padding:22px 24px}
.verify-label{display:block;margin:0 0 8px;font-family:var(--font-heading);font-weight:600;font-size:14px;color:var(--color-foreground)}
.verify-row{display:flex;flex-wrap:wrap;gap:10px}
/* Monospace and tracked out, because a code is read one character at a time
   and O against 0 is the mistake this page exists to catch. */
#verify-code{flex:1 1 15rem;padding:12px 14px;font-family:var(--font-mono);font-size:18px;letter-spacing:.14em;text-transform:uppercase}
#verify-code::placeholder{color:var(--color-muted-foreground);text-transform:none}
.verify-row .btn{flex:none;min-height:48px;padding-inline:26px}
@media (max-width:479px){.verify-row .btn{width:100%}}
.verify-hint{margin:10px 0 0;font-size:13.5px;color:var(--color-muted-foreground)}
#verify-error{margin:10px 0 0}
.verify-example{font-family:var(--font-mono);font-size:.9em;color:var(--color-foreground);white-space:nowrap}

.verify-idle{padding:20px 22px;border:1px dashed var(--color-border-strong);border-radius:var(--radius-xl)}
.verify-idle p{margin:0;font-size:15px;color:var(--color-copy)}
.verify-idle .verify-idle-lead{margin:0 0 8px;font-family:var(--font-heading);font-weight:600;color:var(--color-foreground)}

.verify-looking{display:flex;align-items:center;gap:12px;margin:0;padding:22px 24px;border:1px solid var(--color-border);border-radius:var(--radius-xl);background:var(--color-card);font-size:15px;color:var(--color-copy)}
.verify-spinner{flex:none;width:18px;height:18px;border-radius:50%;border:2px solid var(--color-border);border-top-color:var(--color-primary);animation:verify-spin .8s linear infinite}
@keyframes verify-spin{to{transform:rotate(1turn)}}

.verify-verdict{display:flex;align-items:center;gap:12px;margin:0 0 18px;font-family:var(--font-heading);font-weight:600;font-size:var(--text-title);line-height:1.2;letter-spacing:var(--text-title--letter-spacing)}
.verify-verdict.is-valid{color:var(--color-aqua-text)}
.verify-tick{display:grid;place-items:center;flex:none;width:34px;height:34px;border-radius:50%;background:var(--color-brand-aqua);color:var(--color-brand-ink)}
.verify-tick svg{width:19px;height:19px}
/* components.css gives the card a violet top edge; a certificate that checked
   out gets the aqua one, which is the colour done means everywhere else. */
.verify-state[data-state="valid"] .verify-card{border-top-color:var(--color-brand-aqua)}
.verify-card dd a{font-weight:600}
.verify-foot{margin:20px 0 0;padding-top:14px;border-top:1px solid var(--color-border);font-size:13px;color:var(--color-muted-foreground)}

/* Not found is not an error: the page worked, the code did not match. Orange
   is the small-emphasis colour, and the list underneath is what to try. */
.verify-miss{padding:24px;border:1px solid var(--color-border);border-top:3px solid var(--color-brand-orange);border-radius:var(--radius-xl);background:var(--color-card);box-shadow:var(--shadow-card)}
.verify-miss.is-down{border-top-color:var(--color-border-strong)}
.verify-miss ul{margin:0 0 16px;font-size:15px;color:var(--color-copy)}
/* The verdict keeps its own size: this rule is for the sentences under it. */
.verify-miss p:not(.verify-verdict){font-size:15px;color:var(--color-copy)}
.verify-miss p:last-child{margin:0}
/* The code is the subject of this page, so it is read at full strength here
   rather than at the muted weight the account list gives it. */
.verify-card .id,.verify-verdict .id{color:var(--color-foreground)}

/* ----------------------------------------------------------------------------
   admin.css - the dashboard at /admin/.

   Every colour is a token, so the page follows the theme toggle the same way
   the rest of the site does. The charts are inline SVG drawn by admin.js and
   are styled here rather than carrying their own fills, which is what lets a
   sparkline inherit the colour of the tile it sits in.

   Two things stick: the site header (64px, base.css) and, directly under it,
   the dashboard header carrying the window, the timestamp and Refresh. Every
   offset below is measured from that pair, which is why the numbers repeat.
   ------------------------------------------------------------------------- */

.admin{padding-bottom:4rem}

/* --- the sticky header ---------------------------------------------------- */

.admin-head{position:sticky;top:64px;z-index:15;background:color-mix(in srgb,var(--color-background) 95%,transparent);backdrop-filter:blur(12px);border-bottom:1px solid var(--color-border)}
.admin-head-in{display:flex;flex-wrap:wrap;gap:.75rem 1.5rem;align-items:center;justify-content:space-between;padding-block:.7rem;padding-left:calc(20px + env(safe-area-inset-left,0px));padding-right:calc(20px + env(safe-area-inset-right,0px))}
.admin-head-title .eyebrow{margin:0 0 .15rem}
.admin-head-title h1{margin:0;font-size:1.25rem;line-height:1.2;letter-spacing:-.02em}
.admin-tools{display:flex;flex-wrap:wrap;gap:.5rem .75rem;align-items:center}
.admin-range{display:flex;gap:.4rem}
.admin-updated{margin:0;font-size:.8125rem;color:var(--color-muted-foreground);font-variant-numeric:tabular-nums;min-width:9ch}

/* --- the rail and the body ------------------------------------------------ */

.admin-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:1.5rem;padding-top:1.75rem}
@media (min-width:60rem){.admin-grid{grid-template-columns:11rem minmax(0,1fr);gap:2rem;align-items:start}}

.admin-rail ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;gap:.4rem}
@media (min-width:60rem){.admin-rail{position:sticky;top:9.5rem}.admin-rail ol{flex-direction:column;gap:.15rem}}
.admin-rail a{display:block;padding:.35rem .6rem;border-radius:var(--radius-sm);color:var(--color-muted-foreground);text-decoration:none;font-size:.875rem}
.admin-rail a:hover{background:var(--color-muted);color:var(--color-foreground)}
.admin-rail a[aria-current]{background:var(--color-muted);color:var(--color-foreground);font-weight:600}

.admin-body{display:grid;gap:3rem;min-width:0}
/* Clears both sticky headers, so a deep link never lands under them. */
.admin-sec{scroll-margin-top:9.5rem;min-width:0}
.admin-sec h2{margin:0 0 .75rem;font-size:var(--text-title)}
.admin-sec h3{margin:1.75rem 0 .5rem;font-size:1rem}
.admin-sec .empty{color:var(--color-muted-foreground)}
.admin-lead{margin:0 0 .9rem;color:var(--color-copy);font-size:.875rem;max-width:70ch}

/* --- tiles and charts ----------------------------------------------------- */

.tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(11rem,1fr));gap:.75rem}
.tile{border:1px solid var(--color-border);border-radius:var(--radius-md);padding:.85rem 1rem;background:var(--color-card);display:grid;gap:.15rem;align-content:start}
.tile-label{font-size:.75rem;letter-spacing:.04em;text-transform:uppercase;color:var(--color-muted-foreground)}
.tile-value{font-family:var(--font-heading);font-size:1.75rem;line-height:1.1;font-variant-numeric:tabular-nums}
.tile-note{font-size:.75rem;color:var(--color-muted-foreground);font-variant-numeric:tabular-nums}
.tile .spark{color:var(--color-primary);width:100%;height:38px;margin-top:.25rem}

.admin-figure{margin:1.5rem 0 0;display:grid;gap:.4rem}
.admin-figure .spark{color:var(--color-primary);width:100%;height:96px}
.admin-figure figcaption{font-size:.8125rem;color:var(--color-muted-foreground);font-variant-numeric:tabular-nums}

.admin-pair{display:grid;grid-template-columns:minmax(0,1fr);gap:.5rem 2rem;margin-top:.5rem}
@media (min-width:48rem){.admin-pair{grid-template-columns:repeat(2,minmax(0,1fr))}}

.bars{width:100%;height:auto;max-width:32rem;display:block}
.bars .bar-track{fill:var(--color-muted)}
.bars .bar{fill:var(--color-primary);opacity:.75}
.bars .bar-label,.bars .bar-value{font-family:var(--font-sans);font-size:11px;fill:var(--color-foreground)}
.bars .bar-value{fill:var(--color-muted-foreground)}

/* --- tables --------------------------------------------------------------- */

.admin-scroll{overflow-x:auto;-webkit-overflow-scrolling:touch}
.admin-table{width:100%;border-collapse:collapse;font-size:.875rem}
.admin-table th,.admin-table td{text-align:left;padding:.45rem .6rem;border-bottom:1px solid var(--color-border);white-space:nowrap}
.admin-table th{font-weight:600;color:var(--color-muted-foreground);font-size:.75rem;letter-spacing:.04em;text-transform:uppercase}
.admin-table td.num,.admin-table th.num{text-align:right;font-variant-numeric:tabular-nums}
.admin-table tr.clickable{cursor:pointer}
.admin-table tr.clickable:hover{background:var(--color-muted)}
.admin-table tr.clickable:focus-visible{background:var(--color-muted);outline:2px solid var(--color-ring);outline-offset:-2px}
.admin-table tr.flagged td:first-child{box-shadow:inset 2px 0 0 0 var(--color-warning)}

/* A sortable heading is a real button: pressing it with the keyboard has to
   do what clicking it does, and the arrow says which way it is pointing. */
.th-sort{display:inline-flex;align-items:center;gap:.25rem;background:none;border:0;padding:0;margin:0;cursor:pointer;font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit}
.th-sort:hover{color:var(--color-foreground)}
.th-sort.on{color:var(--color-foreground)}
.th-sort:focus-visible{outline:2px solid var(--color-ring);outline-offset:2px;border-radius:var(--radius-sm)}
.th-arrow{font-size:.8em;line-height:1}
.admin-table th.num .th-sort{flex-direction:row-reverse}

/* The one flag on the courses table: read by people, never quizzed. */
.flag{display:inline-block;margin-left:.4rem;padding:.05rem .4rem;border:1px solid var(--color-warning);border-radius:999px;color:var(--color-warning);font-size:.6875rem;letter-spacing:.02em;text-transform:none;vertical-align:middle}

.admin-filters{display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:.9rem}

.admin-search input{min-width:16rem}
.admin-detail-host:empty{display:none}
.admin-detail{margin-top:1rem;padding:1rem 1.1rem}
.admin-detail-head{display:flex;align-items:center;justify-content:space-between;gap:1rem;margin-top:0}
.admin-detail h3{margin-top:1.25rem}
.admin-locked{max-width:32rem;padding:1.5rem}
ul.plain{list-style:none;margin:0;padding:0;display:grid;gap:.35rem}

/* ============================================================================
   feedback.css - the floating report button and its dialog.

   Every colour is a token from tokens.css, so the dialog is right in both
   themes without a second palette. The native <dialog> supplies the top
   layer and the focus trap; what is styled here is the surface, the form
   rhythm and the backdrop.
   ========================================================================= */

/* A floating button is furniture, not a call to action: it sits over the
   reader's page on every screen of the site, so it is sized to be findable
   and nothing more. A 30px pill on a wide screen, and a 40px disc on a phone
   where the pager, the quiz and the closing panel all want the bottom of the
   viewport. 40px is the smallest touch target worth shipping, so the disc
   does not shrink any further. */
.feedback-fab {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 12px;
  line-height: 1;
  color: var(--color-primary-foreground);
  background: var(--color-primary);
  border: 1px solid var(--color-primary);
  border-radius: 999px;
  padding: 8px 13px 8px 11px;
  cursor: pointer;
  box-shadow: var(--shadow-pop);
  transition: transform .15s var(--ease-out-soft), filter .15s;
}
.feedback-fab-icon { width: 14px; height: 14px; flex: none; }
.feedback-fab:hover { transform: translateY(-1px); filter: brightness(1.08); }
.feedback-fab:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

/* The lesson pager, the quiz and the closing panel all sit low on the page on
   a phone, so the button drops its label and becomes a disc in the corner.
   The label is clipped rather than removed: it is still read aloud, and the
   aria-label on the button pins the same word as the accessible name. */
@media (max-width: 559px) {
  .feedback-fab {
    right: 14px;
    bottom: 14px;
    width: 40px;
    height: 40px;
    padding: 0;
    gap: 0;
    justify-content: center;
    border-radius: 50%;
  }
  .feedback-fab-icon { width: 18px; height: 18px; }
  .feedback-fab-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
}

.feedback-dialog {
  width: min(34rem, calc(100vw - 32px));
  max-height: calc(100dvh - 48px);
  /* A long form inside scrolls itself; contain keeps that scroll off the page
     underneath once it reaches an end. */
  overflow: auto;
  overscroll-behavior: contain;
  padding: 0;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-xl);
  background: var(--color-card);
  color: var(--color-card-foreground);
  box-shadow: var(--shadow-pop);
}
.feedback-dialog::backdrop { background: rgb(0 0 0 / 0.45); }
.feedback-dialog > form,
.feedback-dialog > .feedback-done { padding: 22px 24px 20px; }
.feedback-dialog h2 {
  font-family: var(--font-heading);
  font-weight: 700;
  font-size: var(--text-title);
  line-height: var(--text-title--line-height);
  letter-spacing: var(--text-title--letter-spacing);
  margin: 0 0 16px;
}

.feedback-types { display: flex; gap: 10px; margin: 0 0 18px; }
.feedback-type {
  flex: 1;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 13px;
  color: var(--color-copy);
  background: var(--color-secondary);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  padding: 11px 12px;
  cursor: pointer;
  transition: border-color .15s, color .15s, background .15s;
}
.feedback-type:hover { color: var(--color-foreground); border-color: var(--color-border-strong); }
.feedback-type[aria-pressed="true"] {
  color: var(--color-primary-foreground);
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.feedback-type:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

.feedback-row { display: grid; gap: 6px; margin: 0 0 14px; }
.feedback-row[hidden] { display: none; }
.feedback-row label {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 12px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-muted-foreground);
}
.feedback-row input[type="text"],
.feedback-row input[type="email"],
.feedback-row textarea,
.feedback-row select {
  font: inherit;
  font-size: 15px;
  width: 100%;
  color: var(--color-foreground);
  background: var(--color-background);
  border: 1px solid var(--color-input);
  border-radius: var(--radius-md);
  padding: 10px 12px;
}
.feedback-row textarea { resize: vertical; min-height: 6.5em; line-height: 1.5; }
.feedback-row input:focus-visible,
.feedback-row textarea:focus-visible,
.feedback-row select:focus-visible { outline: 2px solid var(--color-primary); outline-offset: 0; border-color: var(--color-primary); }
.feedback-row input[readonly] { color: var(--color-muted-foreground); background: var(--color-secondary); }
.feedback-row input[type="file"] {
  font: inherit;
  font-size: 13px;
  color: var(--color-copy);
  border: 1px dashed var(--color-border-strong);
  border-radius: var(--radius-md);
  padding: 10px 12px;
  width: 100%;
}
.feedback-hint { font-size: 12px; color: var(--color-muted-foreground); }

.feedback-note { font-size: 12px; color: var(--color-muted-foreground); margin: 0 0 10px; }
.feedback-error { font-size: 13px; font-weight: 600; color: var(--color-destructive-text); margin: 0; min-height: 1.2em; }

.feedback-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 14px; }
.feedback-done p { color: var(--color-copy); margin: 0; }

@media (prefers-reduced-motion: reduce) {
  .feedback-fab { transition: none; }
}

/* ----------------------------------------------------------------------------
   ADMIN TRIAGE

   The list on /admin/. The analytics track owns that page's shell and its own
   stylesheet; the triage table styles ride here with the rest of the feedback
   feature so the two tracks do not both edit one file.
   ------------------------------------------------------------------------- */

.fb-empty { color: var(--color-muted-foreground); font-size: 14px; }

.fb-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.fb-table th {
  text-align: left;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--color-muted-foreground);
  border-bottom: 1px solid var(--color-border);
  padding: 8px 10px;
}
.fb-table td { border-bottom: 1px solid var(--color-border); padding: 10px; vertical-align: top; }
.fb-table tr.fb-detail-row td { border-bottom: 1px solid var(--color-border); background: var(--color-secondary); }

.fb-type {
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--color-copy);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 3px 8px;
  white-space: nowrap;
}
.fb-sev {
  display: inline-block;
  margin-left: 6px;
  font-family: var(--font-heading);
  font-weight: 600;
  font-size: 11px;
  border-radius: 999px;
  padding: 3px 8px;
  white-space: nowrap;
  color: var(--color-muted-foreground);
  border: 1px solid var(--color-border);
}
.fb-sev.high { color: var(--color-orange-text); border-color: var(--color-orange-text); }
.fb-sev.critical { color: var(--color-destructive-text); border-color: var(--color-destructive-text); }

.fb-title {
  font: inherit;
  font-weight: 600;
  text-align: left;
  color: var(--color-foreground);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.fb-title:hover { text-decoration: underline; }
.fb-title:focus-visible { outline: 2px solid var(--color-ring); outline-offset: 2px; }

.fb-page { font-family: var(--font-mono); font-size: 12px; color: var(--color-muted-foreground); word-break: break-all; }
.fb-guest { font-size: 11px; color: var(--color-muted-foreground); }
.fb-meta { font-size: 12px; color: var(--color-muted-foreground); }

.fb-table select {
  font: inherit;
  font-size: 13px;
  color: var(--color-foreground);
  background: var(--color-background);
  border: 1px solid var(--color-input);
  border-radius: var(--radius-sm);
  padding: 5px 8px;
}

.fb-detail { display: grid; gap: 10px; max-width: 60ch; }
.fb-desc { margin: 0; color: var(--color-copy); line-height: 1.55; }
.fb-detail img { max-width: 100%; border: 1px solid var(--color-border); border-radius: var(--radius-md); }
.fb-event-list { margin: 0; padding-left: 20px; font-size: 13px; color: var(--color-copy); display: grid; gap: 3px; }
.fb-event-list span { color: var(--color-muted-foreground); }
