/* =================================================================
   TOKENS — design system variables, theming, resets, base elements
   -----------------------------------------------------------------
   Loaded first: every other file in this directory consumes these
   custom properties, so they need to exist before anything else is
   parsed. See the full token-architecture explanation inline below.
================================================================= */
/* =================================================================
   LEARNERS WALL DESIGN SYSTEM
   -----------------------------------------------------------------
   Token architecture (three layers)

     1. Primitives — raw, theme-independent values with no meaning
        of their own (--blue-600, --gray-900, and so on). Never used
        directly in component CSS; only ever referenced by aliases.

     2. Alias (semantic) tokens — the layer that actually changes
        between themes. Named by role, not by color (--color-primary,
        --color-text, --color-surface), each pointing at a primitive.
        Light theme's values live on :root; dark theme's values (see
        the "Theming" block right after this one) redefine the exact
        same alias names, so nothing that consumes them needs to know
        which theme is active.

     3. Legacy aliases — the original token names this codebase has
        used throughout its history (--primary, --ink, --bg,
        --surface, --border, --accent, --primary-dark). Kept as thin
        pass-throughs onto the semantic layer rather than renamed
        everywhere, since retiring them would mean touching every
        rule in this file for no behavioral difference. New rules
        should prefer the semantic names in layer 2; these remain
        for the thousands of existing rules already written against
        them.

     --primary-tint has been retired (no longer part of this system)
     in favor of the semantic --color-primary-subtle, which the same
     17 "active/selected/hover" rules that used to reference
     --primary-tint now use instead.

   Typography
     Noto Sans for everything — headings, body, and UI chrome — loaded
     from Google Fonts. --mono is reserved for code only.

   Naming convention (BEM-influenced)
     .block            standalone component, e.g. .pager, .faq
     .block__element   a part of that component, e.g. .pager__link
     .block--modifier   a variant, e.g. .pager--top
     .is-*             JS/state-toggled classes, e.g. .is-active, .is-open
     .js-*             JS hook classes with no styling of their own

   Component structure
     Every component's rules are grouped under one banner comment,
     in the order they appear on the page: topbar → layout → sidebar
     → article → pager/faq/tags → toc-rail → footer → responsive.
================================================================= */
:root {
  /* ---- Layer 1: primitives ------------------------------------ */
  /* Brand accent ramp. --indigo-600 was the target design's own
     indigo-600 (#4F46E5); per explicit request it's now #0E172B (a
     near-black navy) instead — the names are kept as-is rather than
     renamed, same reasoning as the legacy-alias layer further down:
     retiring "indigo" here would mean touching every rule that
     references it for no behavioral difference. --indigo-700 (the
     hover/pressed shade) is recalculated as a lighten of the new
     base rather than reusing its old darken-based relationship,
     since darkening an already near-black color further would be
     barely distinguishable. */
  --indigo-50:  #EEF2FF;
  --indigo-600: #0E172B;
  --indigo-700: #394151;
  --gray-0:    #FFFFFF;
  --gray-50:   #F4F5F3;
  --gray-200:  #E3E6EF;
  --gray-600:  #5B5E66;
  --gray-900:  #202124;
  --teal-700:  #16785F;

  /* Dark-theme primitives — a separate, deliberately-chosen ramp
     rather than the light ramp's values reused at different
     opacities, since a good dark surface/text pairing isn't just an
     inverted light one. */
  --indigo-50-dark:  #2A2559;
  --indigo-400-dark: #818CF8;
  --gray-950-dark: #16171A;
  --gray-900-dark: #202124;
  --gray-800-dark: #2C2D30;
  --gray-700-dark: #3C3D40;
  --gray-400-dark: #9AA0A6;
  --gray-50-dark:  #E8EAED;

  /* Status primitives — light-theme values, sourced directly from
     the hardcoded colors previously scattered across pages.css and
     components.css for quiz feedback, mistake warnings, and Bug
     Checker results. Consolidated here as named primitives rather
     than left as inline hex, which is what let them silently bypass
     the entire theme system before — a hardcoded #FDF3F3 has no way
     to know a dark theme exists. */
  --red-text:    #B23A2F;
  --red-bg:      #FDF3F3;
  --red-border:  #E8A9A9;
  --green-text:  #1F7A4C;
  --green-bg:    #F2FBF6;
  --green-border: #7FCB9E;
  --amber-text:  #8F6010;
  --amber-bg:    #FFF7E6;
  --amber-border: #F0DFC0;

  /* Status primitives — dark-theme values. Not the light versions at
     a different opacity: a pale pink/green/amber background reads as
     a jarring bright patch on a dark page regardless of opacity, so
     these use the same dark-surface-plus-bright-text pairing Material
     Design's own dark theme uses for the same problem — a
     desaturated, dark-tinted background with a lighter, more
     saturated text/border color on top, rather than light colors
     dimmed down. */
  --red-text-dark:    #F28B82;
  --red-bg-dark:      #3C1F1D;
  --red-border-dark:  #5C2E2A;
  --green-text-dark:  #81C995;
  --green-bg-dark:    #1E3A2C;
  --green-border-dark: #2D5940;
  --amber-text-dark:  #FDD663;
  --amber-bg-dark:    #3D3319;
  --amber-border-dark: #5C4D26;

  /* Loading-skeleton shimmer — two close, subtly different grays that
     animate across a placeholder while real content loads. Needs its
     own dark pair rather than reusing --color-bg/--color-surface:
     those two are already fairly close to each other in dark theme
     (deliberately, for a calm background), which would make the
     shimmer's animated motion barely visible — these are chosen to
     keep a clearly visible (if still subtle) shimmer contrast in
     dark mode too. */
  --shimmer-1: #ECECF2;
  --shimmer-2: #F5F5FA;
  --shimmer-1-dark: #2C2D30;
  --shimmer-2-dark: #3C3D40;

  /* ---- Layer 2: semantic (alias) tokens — light theme default -- */
  --color-primary:        var(--indigo-600);
  --color-primary-hover:  var(--indigo-700);
  --color-primary-subtle: var(--indigo-50);
  --color-text:            var(--gray-900);
  --color-text-muted:      var(--gray-600);
  --color-bg:              var(--gray-50);
  --color-surface:         var(--gray-0);
  --color-border:          var(--gray-200);
  /* Raw R,G,B (no rgb()/# wrapper) rather than another var() alias
     of --color-surface — rgba() needs comma-separated numeric
     channels to layer its own alpha on top, and can't extract them
     back out of an already-composed hex/rgb() value. Exists
     specifically for .topbar.is-scrolled's glassmorphism effect (see
     layout.css), which needs a translucent version of the current
     theme's actual surface color, not a hardcoded white that ignores
     dark theme entirely. */
  --color-surface-rgb: 255, 255, 255;

  /* Status semantic tokens — the names every component rule should
     actually use, rather than reaching for --red-text/--red-bg
     (the primitives) directly. Kept separate from the primitive
     layer for the same reason the color primitives are: this layer
     is what gets redefined per-theme (see the dark-theme blocks
     below and in responsive.css), so component CSS written against
     these names never needs to know which theme is active. */
  --color-error-text:    var(--red-text);
  --color-error-bg:      var(--red-bg);
  --color-error-border:  var(--red-border);
  --color-success-text:  var(--green-text);
  --color-success-bg:    var(--green-bg);
  --color-success-border: var(--green-border);
  --color-warning-text:  var(--amber-text);
  --color-warning-bg:    var(--amber-bg);
  --color-warning-border: var(--amber-border);
  --color-shimmer-1: var(--shimmer-1);
  --color-shimmer-2: var(--shimmer-2);

  /* ---- Layer 3: legacy aliases (pass-throughs, unchanged names) - */
  --primary:      var(--color-primary);
  --primary-dark: var(--color-primary-hover);
  --ink:          var(--color-text);
  --ink-soft:     var(--color-text-muted);
  --bg:           var(--color-bg);
  --surface:      var(--color-surface);
  --border:       var(--color-border);
  --accent:       var(--primary); /* one unified brand color across every course, rather than a different tint per course */
  --accent-ink:   var(--color-text);
  --teal:         var(--teal-700);

  /* Matches the reference file's font-sans exactly (Tailwind's own
     default system-font stack, read directly out of this project's
     compiled tailwind.css) — no webfont request needed for body text,
     so "Noto Sans" (previously loaded from Google Fonts alongside
     Space Mono, see header.php) is gone: every visitor now gets their
     OS's native UI font instead of a page-specific one. */
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  --mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  /* Space Mono, loaded from Google Fonts (see the <link> in
     header.php) rather than self-hosted — unlike SaxMono, Space Mono
     is actually on Google Fonts, so there's no need for the
     self-hosting workaround that was necessary before. A monospace
     fallback stack, matching --mono above, means the brand name still
     reads reasonably even before the font loads. Kept as its own
     distinct wordmark font (unlike --sans above) since the logo
     itself is out of scope for the current pass. */
  --font-brand: "Space Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

/* -------------------------------------------------------------------
   Theming: light (default), dark, and "device default"
   ---------------------------------------------------------------
   Three ways a theme gets chosen, in increasing priority:
     1. :root's own values above — the light theme, used whenever
        nothing else overrides it.
     2. prefers-color-scheme: dark — respected automatically when the
        visitor's OS/browser is set to dark and they have never made
        an explicit choice on this site (no data-theme attribute set
        at all). This is "device default."
     3. :root[data-theme="dark"] / :root[data-theme="light"] — an
        explicit choice the visitor made via the theme toggle,
        persisted in localStorage by app.js and applied by setting
        this attribute on <html> before first paint. This always
        wins over both the above, in either direction — a visitor on
        a dark OS can still explicitly pick light, and vice versa.
------------------------------------------------------------------- */


:root[data-theme="dark"] {
  --color-primary:        var(--indigo-400-dark);
  --color-primary-hover:  #A5B4FC;
  --color-primary-subtle: var(--indigo-50-dark);
  --color-text:            var(--gray-50-dark);
  --color-text-muted:      var(--gray-400-dark);
  --color-bg:              var(--gray-950-dark);
  --color-surface:         var(--gray-900-dark);
  --color-surface-rgb: 32, 33, 36;
  --color-border:          var(--gray-700-dark);
  --color-error-text:     var(--red-text-dark);
  --color-error-bg:       var(--red-bg-dark);
  --color-error-border:   var(--red-border-dark);
  --color-success-text:   var(--green-text-dark);
  --color-success-bg:     var(--green-bg-dark);
  --color-success-border: var(--green-border-dark);
  --color-warning-text:   var(--amber-text-dark);
  --color-warning-bg:     var(--amber-bg-dark);
  --color-warning-border: var(--amber-border-dark);
  --color-shimmer-1: var(--shimmer-1-dark);
  --color-shimmer-2: var(--shimmer-2-dark);
}
/* body's own background (see the body rule above) is deliberately
   var(--color-surface), not var(--bg), so light theme reads as pure
   white as requested — but that means dark theme needs body's
   background pointed explicitly back at --bg here, or it would pick
   up --color-surface's own (lighter "card") dark value instead of
   the intended darker page backdrop. */
html[data-theme="dark"] body,
html[data-theme="dark"] { background: var(--color-bg); }
html[data-theme="dark"] .themeswitch__icon-light { display: none; }
html[data-theme="dark"] .themeswitch__icon-dark { display: inline-flex; }

* { box-sizing: border-box; }

html {
  scroll-behavior: smooth;
  /* Without this, the vertical scrollbar only appears when a given
     page's content is tall enough to need it — and since it eats
     into the viewport's available width when it does, every
     margin: 0 auto centered container (the header, footer, .layout,
     .styleguide) shifts slightly sideways depending on whether THAT
     specific page happens to be tall enough to trigger it. On the
     quiz level page in particular, different questions render
     different amounts of content (a code block, more or fewer
     answer options, longer prompt text), so different questions can
     independently cross that height threshold — which is what
     caused the whole page, sidebar included, to visibly shift by a
     different amount from one question to the next. Reserving the
     scrollbar's space unconditionally (whether a given page actually
     needs to scroll or not) keeps the available width — and
     everything centered within it — perfectly consistent across
     every page and every question, regardless of content height.
     scrollbar-gutter is the modern, purpose-built property for this;
     overflow-y: scroll is the fallback for browsers that don't
     support it yet. */
  scrollbar-gutter: stable;
  overflow-y: scroll;
  /* Defensive, alongside body's own background: on a page short
     enough that body doesn't fill the full viewport height, html's
     own (previously unset, browser-default-white) background could
     still show through below it — this closes that gap too, rather
     than relying on body's background alone to always be enough.
     Matches body's own var(--color-surface)/white-for-light choice
     (see body's own comment below and its dark-theme overrides) so a
     short page's sliver of html background, if it ever shows, isn't
     a visibly different shade from body sitting right above it. */
  background: var(--color-surface);
}

html {
  overflow-x: hidden;
}

body {
  margin: 0;
  /* The mobile off-canvas nav drawer (#topbar-nav, .toplinks) is kept
     at its full layout width and moved off-screen with a transform
     when closed, rather than being removed from layout entirely —
     needed so its own open/close transition has something to
     animate. Without a clip boundary somewhere, that full-width
     drawer still counts toward the page's scrollable area even while
     invisible off to the side, letting the whole page be dragged
     sideways on touch devices. The clip lives on <html> above, not
     here on <body> as first tried: overflow-x here computes body's
     own overflow-y to a non-visible value too per the CSS overflow
     spec, which turns body into a scroll container in its own right
     — and .topbar's position: sticky (see layout.css) sticks to its
     nearest scrolling ancestor, so it would silently stick to body
     scrolling off with the page instead of the actual viewport.
     <html> doesn't have that same conflict, so the fix belongs there. */
  /* Slate-50 (#F8FAFC) here, not pure white — confirmed by directly
     sampling pixel values from a supplied mockup image rather than
     guessing, which showed this exact color consistently across the
     entire page backdrop (in front of which the mockup's own cards
     render white, creating the card-vs-page depth the design is
     going for). This supersedes an earlier decision (previously
     documented directly in this comment) that had set light theme to
     pure white instead — kept here as: that choice was reasonable in
     isolation, but more recent, specific, pixel-verified feedback
     against an actual target design takes precedence over a general
     prior preference when the two conflict. Dark theme is unaffected
     either way, since it already overrides this back to its own
     --bg value below (see the two dark-theme blocks later in this
     file and in responsive.css) regardless of what light theme uses. */
  background: #F8FAFC;
  color: var(--ink);
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.65;
}
body.has-popup-open { overflow: hidden; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }
/* Safety net for any <a> styled as a solid-fill button with Tailwind's
   text-white utility (e.g. "Try It Editor", "Try It Yourself"): Tailwind
   v4 wraps its utilities in @layer utilities, and an unlayered rule like
   the bare `a` selector just above always outranks a layered one
   regardless of specificity, so text-white was silently losing to this
   file's own accent-blue link color on every such button. This rule is
   itself unlayered and a combined element+class selector, so it beats
   both the layered utility (via the layer rule) and the plain `a`
   selector (via specificity) — fixing every current button link this
   pattern applies to sitewide, and any future one, in one place instead
   of a one-off override per button. */
a.text-white { color: #fff; }
/* Solid-fill brand CTA buttons ("Try It Editor" in the header, "Try It
   Yourself" on exercise cards): themed off the --accent design token
   instead of a hardcoded Tailwind bg-slate-900/hover:bg-slate-700 pair,
   so they follow the same brand color as every other accent-colored
   element (links, progress bar, quiz highlights) and repaint correctly
   in dark theme too. */
a.btn-accent { background: var(--accent); }
a.btn-accent:hover { background: var(--color-primary-hover); }
/* Header "Try It Editor" button specifically, per explicit request —
   hardcoded indigo rather than the site's --accent token (near-black
   site-wide), scoped to this one button only; "Try It Yourself" on
   exercise cards (the other a.btn-accent) is unaffected. */
a.toplinks__tool-link.btn-accent { background: #4F46E5; }
a.toplinks__tool-link.btn-accent:hover { background: #4338CA; }
a:focus-visible, button:focus-visible, input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
