/* Clean & minimal — one clear focal point (the word), generous whitespace,
   understated color. Mobile-first: base styles target small screens, the
   word size scales with clamp() rather than a breakpoint.

   Palette (2026-08-14, Andy's request) — Sky Blue #87CEEB, Steel Blue
   #4682B4, Cadet Blue #5F9EA0, Light Steel Blue #B0C4DE, Slate Gray
   #708090, all used deliberately rather than just swapping one accent:
   Steel Blue is the light-mode accent, Slate Gray is muted text (a direct
   match — it already reads as "muted gray-blue"), Light Steel Blue is what
   the background/border tints are derived from, and Sky Blue/Cadet
   Blue/Slate Gray anchor the three difficulty-badge tiers below (easy/
   medium/hard) as a light-to-dark progression instead of reusing the
   correct/incorrect red-green tokens the way easy/hard used to. See the
   dark-mode block further down for why raw Sky Blue isn't the dark-mode
   accent despite the tidy light/dark split that would make. */

:root {
  --color-bg: #f3f6fa;
  --color-surface: #ffffff;
  --color-text: #26313c;
  --color-text-muted: #708090;
  --color-accent: #4682b4;
  /* Bug List #49 fix (2026-09-09, Andy's own spec, not a design-review
     guess) — a second deliberate accent, scoped narrowly to primary-action
     buttons (Start Quiz, Sign in with Google, Send friend request) and the
     streak badge only. Everything else (links, .pos, difficulty badges,
     the gold favorite star) keeps using --color-accent/its own token,
     unchanged. White-on-#c1622d contrast checked (4.16:1) — same ballpark
     as the existing white-on-Steel-Blue .btn (4.11:1), not a new
     regression, per the spec's own flagged note. */
  --color-cta: #c1622d;
  --color-border: #d9e2ec;
  --color-error-border: #e3c4bb;
  --color-error-bg: #f7ece9;
  --color-error-text: #a14b3f;
  --color-success: #3f7d5c;
  --color-success-bg: #e8f2ec;
  --color-easy: #2e7ba6;
  --color-easy-bg: #e6f4fa;
  --color-medium: #3d6f72;
  --color-medium-bg: #e3efef;
  --color-hard: #4a5762;
  --color-hard-bg: #e7eaed;
  /* Added 2026-08-30 (logo update — jar+pen icon redesign, decided in
     (C) Scoping.md's 2026-08-30 entry): the pen tip/dot's raw Cadet Blue
     from the palette doesn't map cleanly onto any existing semantic
     --color-* token (--color-medium is a contrast-adjusted derivative,
     not the same hex), so this is a new one, pinned to the exact hex the
     design source SVGs (01 Design/icon-pen-outline-*.svg) already use. */
  --color-icon-pen-tip: #5f9ea0;
  --radius: 14px;
  /* Bug List #68 fix (2026-09-11) — no shared type scale existed;
     `grep -oE "font-size:\s*[0-9.]+(rem|px|em)" static/style.css` found
     14 distinct literal values in use (full frequency table in the Bug
     List entry). These six tokens are sized around the *existing*
     dominant cluster (0.8rem/0.85rem/0.9rem/0.95rem are already the four
     most common values, 0.9rem alone appearing 13 times) rather than
     inventing new numbers — additive only, adopted opportunistically as
     new/touched rules use them going forward. Not a one-shot migration:
     the 14 existing literal declarations are untouched by this fix. */
  --font-size-xs: 0.7rem;
  --font-size-sm: 0.8rem;
  --font-size-base: 0.85rem;
  --font-size-md: 0.9rem;
  --font-size-lg: 0.95rem;
  --font-size-xl: 1.1rem;
  --content-width: 640px;
  --history-columns: 1fr;
  --quiz-columns: 1fr;
}

/* Desktop breakpoint — widens the shared header/main column and lets
   History and the Quiz's answer options use the extra room as a grid
   (auto-fit/minmax below, not a fixed column count, so it degrades
   gracefully at in-between widths like a small tablet). Tokens
   (--content-width/--history-columns/--quiz-columns) keep header, main,
   History and Quiz in lockstep off one decision instead of three. */
@media (min-width: 768px) {
  :root {
    --content-width: 900px;
    --history-columns: repeat(auto-fit, minmax(260px, 1fr));
    --quiz-columns: 1fr 1fr;
  }
}

/* Dark mode — same token set, no per-rule changes needed since every rule
   below reads colors through these variables. Two ways in:
   1) OS/browser preference (prefers-color-scheme), unless the visitor has
      explicitly picked light via the switch (data-theme="light").
   2) Explicit override via the header switch (data-theme="dark"), which
      wins regardless of OS preference. Persisted in localStorage by the
      script in base.html, not a cookie — pure presentation, nothing
      server-side needs it, so no reason to break stateless-by-default. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-bg: #1a2128;
    --color-surface: #232b34;
    --color-text: #edf1f5;
    --color-text-muted: #93a2af;
    /* Not raw Sky Blue (#87CEEB) — too pale to keep white text readable on
       solid-fill accent backgrounds (nav bubble, .btn, the theme-switch
       track). A brightened Steel Blue instead: same lightening approach
       the old accent used going light->dark, just applied to the new
       palette's steel blue. Sky Blue itself shows up below as the "easy"
       badge's text color instead, where it's text-on-a-light-chip, not
       white-on-solid. */
    --color-accent: #6ea5cd;
    /* Bug List #49 fix (2026-09-09) — dark-mode pairing of --color-cta
       above. This prefers-color-scheme block is a real repeat-offender
       spot for missed dark tokens (see Bug List #4's own comment just
       below), so it's added here explicitly rather than assumed covered
       by the :root[data-theme="dark"] block alone. */
    --color-cta: #d98b62;
    --color-border: #333e48;
    --color-error-border: #5c372c;
    --color-error-bg: #2a1c18;
    --color-error-text: #e0917f;
    --color-success: #7fc79e;
    --color-success-bg: #16261e;
    --color-easy: #9ad9f5;
    --color-easy-bg: #1c3444;
    --color-medium: #8fc2c2;
    --color-medium-bg: #1c2f2f;
    --color-hard: #a6b4c0;
    /* Bug List #4 fix (2026-08-29) — same fix as the :root[data-theme="dark"]
       block below; this one (the prefers-color-scheme media-query
       fallback for a user who's never touched the toggle) was missed by
       the first pass since its old_string match required identical
       indentation, and this block is indented differently. Caught during
       external verification against the live site after deploy — the old
       value was still being served. */
    --color-hard-bg: #333c46;
    /* Dark-mode pen tip/dot, matching icon-pen-outline-dark.svg's #8fc2c2
       (the dark-theme equivalent of Cadet Blue this project's dark
       palette already uses elsewhere — same value as --color-medium's
       dark variant, kept as its own token since the light value isn't a
       --color-medium match either, see the :root declaration above). */
    --color-icon-pen-tip: #8fc2c2;
  }

  /* Card elevation, dark variant (Build Task 21a) — a black shadow at the
     same opacity used against a light background barely reads on a dark
     one, so dark mode gets its own deeper/darker pair rather than
     inheriting the light-mode values. */
  :root:not([data-theme="light"]) .card {
    box-shadow:
      0 1px 2px rgba(0, 0, 0, 0.3),
      0 8px 24px rgba(0, 0, 0, 0.35);
  }

  :root:not([data-theme="light"]) .glance-chip {
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.25);
  }
}

:root[data-theme="dark"] {
  --color-bg: #1a2128;
  --color-surface: #232b34;
  --color-text: #edf1f5;
  --color-text-muted: #93a2af;
  --color-accent: #6ea5cd;
  /* Bug List #49 fix (2026-09-09) — explicit-override twin of the
     prefers-color-scheme --color-cta above. */
  --color-cta: #d98b62;
  --color-border: #333e48;
  --color-error-border: #5c372c;
  --color-error-bg: #2a1c18;
  --color-error-text: #e0917f;
  --color-success: #7fc79e;
  --color-success-bg: #16261e;
  --color-easy: #9ad9f5;
  --color-easy-bg: #1c3444;
  --color-medium: #8fc2c2;
  --color-medium-bg: #1c2f2f;
  --color-hard: #a6b4c0;
  /* Bug List #4 fix (2026-08-29): was #262c33, barely distinguishable
     from --color-bg (#1a2128) — bumped to a clearly lighter/bluer shade
     (close to --color-border) so the "hard" pill actually reads as a
     colored pill in dark mode, not near-invisible text. */
  --color-hard-bg: #333c46;
  --color-icon-pen-tip: #8fc2c2;
}

/* Explicit-override twin of the prefers-color-scheme .card shadow above —
   same reasoning, needed here too since data-theme="dark" can apply
   regardless of OS preference. */
:root[data-theme="dark"] .card {
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.3),
    0 8px 24px rgba(0, 0, 0, 0.35);
}

/* Cross-document View Transitions — animates full-page navigations between
   Today/History/Quiz/Scores (all four share this stylesheet via
   base.html, which is required for both the outgoing and incoming page to
   opt in). Real browser support as of 2026: Chrome/Edge 126+, Safari
   18.2+, Firefox partial (146-151). Deliberately not feature-detected —
   this is progressive enhancement by nature; unsupported browsers just
   navigate normally, exactly like before this rule existed. See
   .nav-active below for the specific element this animates (the nav
   bubble); everything else gets the browser's default cross-fade. */
@view-transition {
  navigation: auto;
}

* {
  box-sizing: border-box;
}

/* Design fix (2026-08-26, Andy: "a lot of empty space on the page") —
   subtle background treatment so empty space on short pages (Today,
   mainly) reads as intentional breathing room, not an unfinished page.
   Built from --color-accent via color-mix() rather than a second set of
   hardcoded dark-mode hex values, so it automatically follows whichever
   theme is active with no separate override block needed (same trick
   already used for .privacy-policy etc. reusing existing tokens). Very
   low opacity, fixed (doesn't scroll with content) so it doesn't repeat
   awkwardly on a long page like /privacy. */
body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, system-ui, sans-serif;
  background-color: var(--color-bg);
  background-image: radial-gradient(
    ellipse 900px 500px at 50% 0%,
    color-mix(in srgb, var(--color-accent) 7%, transparent),
    transparent 70%
  );
  background-attachment: fixed;
  color: var(--color-text);
  margin: 0;
  padding: 0;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Mobile-first, matching this file's own pattern elsewhere (base styles =
   phone, @media (min-width: 768px) below = desktop enhancement) — added
   2026-08-27, fixing a real bug: header/nav had zero responsive handling
   at all, so on a narrow phone the logo+avatar+4 nav links+toggle simply
   overflowed past the screen edge instead of adapting, forcing a
   horizontal swipe to reach Scores/the theme toggle (caught by Andy on
   his actual phone, not a simulated viewport). Stacked + wrapped + centered
   by default; restored to the original single-row layout at 768px+ below. */
/* Header divider (Build Task 21a, 2026-08-29) — a full-width line under
   the header separating it from page content, per Andy's reference
   screenshot. header itself stays a max-width column same as before; the
   border lives on a wrapping element so it spans the full viewport width,
   not just the content column. */
.header-wrap {
  border-bottom: 1px solid var(--color-border);
}

header {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  max-width: var(--content-width);
  margin: 0 auto;
  padding: 1.5rem 1rem 1.25rem;
  text-align: center;
}

header h1 {
  /* clamp(), same pattern as .word — scales with viewport instead of
     jumping at a breakpoint, so it's properly big on desktop without
     overwhelming a narrow phone screen. */
  font-size: clamp(1.6rem, 4vw, 2.1rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  margin: 0;
}

header h1 a {
  color: var(--color-text);
  text-decoration: none;
}

/* Screen-reader-only text — visually hidden but still announced. Standard
   "clip" pattern (not display:none, which screen readers skip entirely). */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Signed-in avatar badge (added 2026-08-26, replacing a plain name +
   "Sign out" that used to sit in nav) — grouped with the logo via .brand
   rather than nav, per Andy's request. */
.brand {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Jar+pen icon badge (Build Task 21a, 2026-08-29; geometry replaced
   2026-08-30 — see (C) Scoping.md's 2026-08-30 logo-update entry) — a
   small mark next to the "LexiJar" wordmark, positioned to its right
   (Andy's direct correction — first draft had it on the left). Separate
   from Task 17's mason-jar+wordmark logo (tried live on the web and
   reverted the same day, kept only on the mobile app) — Andy is treating
   this one as refinable/replaceable later, not a final decision.

   2026-08-30: swapped from a filled colored-circle badge with a white
   glyph inside to the flat outline-only jar+pen mark already decided as
   canonical for the mobile header — no background/border-radius any
   more, matching that outline treatment exactly (no filled backdrop was
   the whole point of the mobile version too). Colors now come from
   var(--color-accent)/var(--color-text-muted)/var(--color-icon-pen-tip)
   directly on the SVG's stroke attributes (see base.html), so this still
   follows the theme automatically with no separate light/dark image
   files, same reasoning as before. */
.brand-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.9rem;
  height: 1.9rem;
  flex-shrink: 0;
}

.user-badge {
  position: relative;
  /* tabindex="0" in the markup makes this focusable, so :focus-within
     below reveals the menu for keyboard users too, not just a mouse
     hover — a hover-only menu would be unreachable without a pointer. */
}

.user-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background: var(--color-accent);
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: default;
  user-select: none;
}

.user-menu {
  display: none;
  position: absolute;
  /* Flush against the avatar (no gap), not calc(100% + Nrem) — a gap here
     is a classic hover-dropdown bug: moving the mouse from the avatar
     toward the menu crosses empty space that isn't part of .user-badge's
     hoverable area, so :hover drops before the mouse arrives and the menu
     closes out from under you (exactly what Andy hit). The 0.6rem padding
     below provides the same visual breathing room without a real gap to
     cross. */
  top: 100%;
  /* right: 0, not left: 0 (Build Task 21a, 2026-08-29) — the avatar moved
     to the far right edge of the nav, so a left-anchored dropdown would
     open off past the viewport edge on a phone-width screen. Opening
     leftward from the avatar keeps it fully on-screen instead. */
  right: 0;
  z-index: 10;
  min-width: 11rem;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(70, 130, 180, 0.15);
  padding: 0.6rem;
  /* Deliberately no margin-top here either — margin has the exact same
     "empty space the mouse has to cross" problem as the old `calc(100% +
     0.4rem)` top offset did. The box-shadow above provides enough visual
     separation from the avatar without a real gap. */
}

.user-badge:hover .user-menu,
.user-badge:focus-within .user-menu {
  display: block;
}

.user-menu-name {
  margin: 0 0 0.5rem;
  padding: 0 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-text);
  /* Long Google display names shouldn't blow out the menu's fixed width. */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Build Task 23a (2026-08-31) — matches .user-menu-name's spacing, muted
   since it's secondary info under the name, not the name itself. */
.user-menu-username {
  margin: 0 0 0.5rem;
  padding: 0 0.5rem;
  font-size: 0.8rem;
  color: var(--color-text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.user-menu a,
.user-menu .user-menu-btn {
  display: block;
  margin: 0;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 0.9rem;
  padding: 0.4rem 0.5rem;
  border-radius: calc(var(--radius) - 6px);
  transition: color 0.15s ease, background-color 0.15s ease;
}

.user-menu a:hover,
.user-menu .user-menu-btn:hover {
  color: var(--color-accent);
  background: var(--color-bg);
}

/* Bug List #63 fix (2026-09-10) — Sign out is now a real <button> inside a
   <form> (for CSRF), not an <a>. Resets browser button chrome so it's
   visually indistinguishable from the other .user-menu items above: full
   width, left-aligned text, no default border/background, inherits font
   from the menu instead of the OS control font. */
.user-menu-form {
  margin: 0;
}

.user-menu .user-menu-btn {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  font: inherit;
  cursor: pointer;
}

/* Build Task 30b (2026-09-10), Bug List #54 — the one destructive item in
   this menu; reuses --color-error-text, same "stay inside the existing
   palette" reasoning as .btn--danger above. */
.user-menu a.user-menu-danger {
  color: var(--color-error-text);
}

.user-menu a.user-menu-danger:hover {
  color: var(--color-error-text);
  background: var(--color-bg);
  opacity: 0.85;
}

nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.5rem;
}

/* Pipe-divider style (Build Task 21a, 2026-08-29, Andy's reference
   screenshot) — replaces the filled "bubble" active-state below with
   plain text links separated by "|" characters and an underline on the
   active page. .nav-divider is a plain span between each <a>, not a
   ::before/::after pseudo-element, so base.html's Jinja loop over nav
   items doesn't need to special-case the first/last one. */
.nav-divider {
  color: var(--color-border);
}

nav a {
  position: relative;
  color: var(--color-text-muted);
  text-decoration: none;
  font-size: 1rem;
  padding: 0.3rem 0.15rem;
  border-bottom: 2px solid transparent;
  transition: color 0.15s ease, border-color 0.15s ease;
}

nav a:hover:not(.nav-active) {
  color: var(--color-accent);
}

/* Current-page indicator — an underline, not a filled pill (Build Task
   21a supersedes the original filled-bubble treatment below Andy's
   reference screenshot). aria-current="page" (set in base.html alongside
   this class) carries the same info to screen readers, which don't see
   border color. */
.nav-active {
  color: var(--color-accent);
  border-bottom-color: var(--color-accent);
}

/* Bug List #3 fix (2026-08-29): the view-transition-name used to sit
   directly on .nav-active (the whole <a>), so the browser's
   cross-document View Transition morphed the entire link's text
   bounding box between pages instead of just sliding an underline.
   It now lives on this dedicated, empty absolutely-positioned span
   (rendered only inside the active link — see base.html), sized to
   just the underline bar, so only the bar itself is captured and
   morphed. */
.nav-underline {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -2px;
  height: 2px;
  background: var(--color-accent);
  /* Only one element ever has this class per page — satisfies the API's
     requirement that a view-transition-name be unique within a document.
     Because the outgoing page's underline and the incoming page's
     underline share this same name, the browser morphs position/size
     between them automatically (no keyframes needed for the basic
     slide) instead of just cross-fading two unrelated underlines. */
  view-transition-name: nav-bubble;
}

/* Desktop: restore the original single-row layout (logo left, nav right)
   now that there's room for it — the stacked/centered/wrapped rules above
   are the mobile-first base, this is the enhancement, same pattern as
   --content-width's own @media (min-width: 768px) block near the top of
   this file. */
@media (min-width: 768px) {
  header {
    flex-direction: row;
    justify-content: space-between;
    text-align: left;
  }

  nav {
    justify-content: flex-end;
  }
}

/* Light/dark switch — checkbox styled as a pill toggle. Wiring
   (localStorage read/write, initial checked state) lives in base.html.
   Bug List #10 fix (2026-08-29): moved from the header <nav> into
   <footer class="site-footer">'s .site-footer-links, next to Privacy
   Policy — no separate margin needed here now, .site-footer-links'
   existing gap already spaces it from the link before it, same as every
   other item in that row. */
.theme-switch {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
}

.theme-switch input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.theme-switch-track {
  position: relative;
  display: inline-block;
  width: 2.5rem;
  height: 1.4rem;
  background: var(--color-border);
  border-radius: 999px;
  transition: background 0.15s ease;
}

.theme-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1.1rem;
  height: 1.1rem;
  background: var(--color-surface);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.65rem;
  line-height: 1;
}

/* Sun/moon riding on the thumb — the thumb itself is the state indicator,
   so which icon shows is never ambiguous (no separate "which side is
   active" question the way track-flanking icons would raise). */
.theme-switch-thumb::before {
  content: "\2600\FE0F";
}

.theme-switch input:checked + .theme-switch-track {
  background: var(--color-accent);
}

.theme-switch input:checked + .theme-switch-track .theme-switch-thumb {
  transform: translateX(1.1rem);
}

.theme-switch input:checked + .theme-switch-track .theme-switch-thumb::before {
  content: "\1F319";
}

.theme-switch input:focus-visible + .theme-switch-track {
  outline: 2px solid var(--color-accent);
  outline-offset: 2px;
}

main {
  max-width: var(--content-width);
  margin: 0 auto;
  /* Top padding bumped 0.5rem -> 1.75rem (Build Task 21a, Andy's direct
     request) — breathing room between the new header divider and the
     first card, which otherwise butted right up against it. */
  padding: 1.75rem 1rem 3rem;
}

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 2rem 1.5rem;
  /* Elevation (Build Task 21a, 2026-08-29) — Andy's direct call comparing
     his reference mockup against an earlier flat-shadow draft: cards
     should read as sitting above the page background, not flush with it.
     Two stacked shadows (tight for edge definition, soft/wide for the
     "floating" feel), Steel-Blue-tinted rather than plain black to match
     the rest of this palette. */
  box-shadow:
    0 1px 2px rgba(70, 130, 180, 0.06),
    0 8px 24px rgba(70, 130, 180, 0.10);
}

.eyebrow {
  font-size: 0.8rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  margin: 0 0 0.75rem;
}

/* Small explanatory line above a page's main content (Build Task 18,
   2026-08-29) — normal case/weight, unlike .eyebrow, since this is a full
   sentence explaining a feature, not a section label. */
.page-blurb {
  color: var(--color-text-muted);
  font-size: 0.9rem;
  margin: 0 0 1rem;
}

/* Landing page hero + feature grid (Build Task 33, 2026-09-11 — web
   pivot). New page, new classes — nothing here replaces or reuses an
   existing rule, unlike the word-card markup on landing.html itself,
   which deliberately does reuse .card/.word/.badge-row/etc. All colors
   via existing var()s, so light/dark theming is automatic, same as
   everywhere else in this file. */
.hero {
  text-align: center;
  padding: 1rem 0 2.5rem;
}

.hero-title {
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  font-weight: 700;
  letter-spacing: 0.2px;
  margin: 0.25rem 0 1rem;
}

.hero-blurb {
  color: var(--color-text-muted);
  font-size: 1rem;
  line-height: 1.55;
  max-width: 42rem;
  margin: 0 auto 1.75rem;
}

.store-badges {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
}

/* Deliberately NOT a real link — Build Task 33's own scope decision,
   Andy's explicit call: no live download URL yet (Apple isn't public,
   Android doesn't exist as a project). Styled to read clearly as
   "not yet available" rather than a disabled-looking broken button —
   dashed border, muted text, no hover affordance. */
.store-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 1.1rem;
  border: 1.5px dashed var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text-muted);
}

.store-badge-icon {
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 6px;
  background: var(--color-border);
  flex-shrink: 0;
}

.store-badge-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.2;
}

.store-badge-eyebrow {
  font-size: 0.7rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.store-badge-label {
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-text);
}

.feature-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  margin-top: 1.5rem;
}

@media (min-width: 640px) {
  .feature-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.feature-item {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 1.25rem 1.5rem;
}

.feature-eyebrow {
  font-weight: 700;
  color: var(--color-accent);
  margin: 0 0 0.35rem;
}

.feature-body {
  color: var(--color-text-muted);
  font-size: 0.92rem;
  line-height: 1.5;
  margin: 0;
}

/* Site footer (added 2026-08-26, Task 14's privacy policy; rebuilt Build
   Task 21a, 2026-08-29, per Andy's reference screenshot) — small icon +
   brand name on the left, About/Terms/Privacy links on the right.
   Deliberately no divider above this footer (Andy's explicit call — only
   the header keeps its divider). Reuses the existing muted-text color
   rather than introducing a new one. */
.site-footer {
  max-width: var(--content-width);
  margin: 1.5rem auto 0;
  padding: 1.25rem 1rem 2rem;
  font-size: 0.85rem;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.site-footer-brand {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 700;
  color: var(--color-text-muted);
}

.site-footer-brand .brand-icon {
  width: 1.4rem;
  height: 1.4rem;
}

.site-footer-links {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.site-footer a {
  color: var(--color-text-muted);
}

.site-footer a:hover {
  color: var(--color-accent);
}

/* Privacy policy (added 2026-08-26) — the only long-form, multi-heading
   page in the app; every other .card is short. Just enough spacing so
   headings/paragraphs/list don't run together. */
.privacy-policy h3 {
  margin: 1.75rem 0 0.5rem;
}

.privacy-policy p,
.privacy-policy ul {
  margin: 0 0 0.75rem;
}

.privacy-policy ul {
  padding-left: 1.25rem;
}

/* Card header layout (Bug List #4 fix, 2026-08-29; regrouped per Bug
   List #46, 2026-09-09 — Andy's own call on an external reviewer's
   complaint). The old structure pinned the date to the left edge and the
   difficulty/streak badges + favorite star to the right edge of two
   separate rows (.card-top + .word-row), via space-between/
   margin-left:auto — which left a wide "canyon" whenever the date/word
   text was short, since both sides were forced apart regardless of
   actual content length.

   New structure, the reviewer's own suggested fix: date + word + badges
   all cluster together as one metadata block (in natural document flow,
   no flex-justify tricks needed since nothing needs pushing to an edge
   anymore); the top-right corner is reserved strictly for actionable
   icons — currently just the favorite star on Today's card. word.html's
   public/signed-out shared-word page has no icons at all, so its
   .card-top there just holds the date alone, same as before. See
   today.html/word.html for the markup. */
.card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.75rem;
}

.card-top .date-label {
  margin: 0;
  /* Real fix (2026-08-30, Andy): the original "word of the day text is
     too small" request (Bug List #13) turned out to mean this label
     ("WORD OF THE DAY · date"), not the big word below it — clarified
     after Andy saw the deployed #13 fix and pointed at the actual gap.
     Scoped to .date-label specifically (not the shared .eyebrow class
     other pages reuse for unrelated labels — History's date rows, "Recent
     words," quiz-status copy) so this doesn't resize any of those. */
  font-size: 1rem;
}

/* Bug List #46 fix (2026-09-09): the favorite star is now .card-top's
   only right-side occupant (previously it lived at the bottom of
   .badge-stack) — its own default inline margin-left (see .favorite-star
   below) isn't needed when it has no sibling badge to its left anymore. */
.card-top > .favorite-star {
  margin-left: 0;
}

/* Bug List #46 fix (2026-09-09): difficulty/streak badges moved out of
   the old .badge-stack (which stacked them above the star on the card's
   right edge) into their own row directly under the word, as part of the
   same left-side metadata cluster the reviewer asked for. */
.badge-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.4rem 0 1.1rem;
}

/* Difficulty badge (Build Task 12) — small pill, normal case (overrides the
   eyebrow's uppercase/letter-spacing so "easy/medium/hard" reads normally). */

.difficulty-badge {
  display: inline-block;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
  font-size: 0.7rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  margin-left: 0.4rem;
  vertical-align: middle;
}

/* .badge-row (Bug List #4 fix) uses its own gap for spacing between
   badges, so the badge's default inline margin-left would just add
   unwanted extra space in front of the first (and only, currently)
   badge in the row. (Bug List #46, 2026-09-09: the old third selector
   here, .badge-stack > .favorite-star, is gone along with .badge-stack
   itself — see .card-top > .favorite-star above for its replacement.) */
.badge-row > .difficulty-badge,
.badge-row > .streak-badge {
  margin-left: 0;
}

/* Dedicated tokens (not a reuse of --color-success/--color-error) — the
   old version borrowed the correct/incorrect quiz colors for easy/hard,
   which conflated "how hard is this word" with "did you get it right,"
   two unrelated ideas that happened to share a color. Also fixes medium
   having been hardcoded with no dark-mode variant at all. */
.difficulty-badge--easy {
  background: var(--color-easy-bg);
  color: var(--color-easy);
}

.difficulty-badge--medium {
  background: var(--color-medium-bg);
  color: var(--color-medium);
}

.difficulty-badge--hard {
  background: var(--color-hard-bg);
  color: var(--color-hard);
}

/* Streak badge (Build Task 18b, 2026-08-29) — same pill shape as
   .difficulty-badge. Originally reused --color-accent rather than a new
   palette entry; Bug List #49 (2026-09-09, Andy's own spec) deliberately
   moved it onto the new --color-cta terracotta token instead, since the
   streak badge was explicitly named as one of the two things this new
   accent should be scoped to. */
.streak-badge {
  display: inline-block;
  text-transform: none;
  letter-spacing: normal;
  font-weight: 600;
  font-size: 0.7rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  margin-left: 0.4rem;
  vertical-align: middle;
  background: color-mix(in srgb, var(--color-cta) 16%, var(--color-surface));
  color: var(--color-cta);
}

/* Favorite star (Build Task 18c/18d, 2026-08-29). Plain button, no
   background pill (unlike the badges above) — a star reads correctly on
   its own without one, and this sits right next to a difficulty badge and
   streak badge already, so a third pill shape would be visual noise.
   Muted gray by default, filled gold-ish when active — deliberately not
   reusing --color-accent (blue) here, since blue is already "the app's
   accent" everywhere else and a favorite star reads better in a warm
   color that's unambiguously distinct from "difficulty" or "streak." */
.favorite-star {
  position: relative;
  display: inline-block;
  vertical-align: middle;
  margin-left: 0.4rem;
  border: none;
  background: none;
  padding: 0;
  /* Bug List #4 fix (2026-08-29): bumped from 1rem — Andy's direct
     "the star should be slightly bigger" request once the badge-stack
     layout gave it its own line to sit on. */
  font-size: 1.35rem;
  line-height: 1;
  cursor: pointer;
  color: var(--color-border);
  transition: color 0.15s ease, transform 0.1s ease;
}

.favorite-star:hover,
.favorite-star:focus-visible {
  color: var(--color-text-muted);
}

/* Hover/focus tooltip (2026-08-29, Andy: "make favoriting a little more
   clear... hover over the star shows an animation revealing the text").
   content: attr(data-tooltip) pulls the label straight from the button's
   own data attribute (kept up to date by favorites.js on every toggle),
   so there's one source of truth for the text rather than duplicating it
   in CSS. Pure CSS, no JS needed for the reveal itself. */
.favorite-star::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: var(--color-text);
  color: var(--color-bg);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.25rem 0.55rem;
  border-radius: 6px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.15s ease, transform 0.15s ease;
  z-index: 5;
}

.favorite-star:hover::after,
.favorite-star:focus-visible::after {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

.favorite-star--active,
.favorite-star--active:hover,
.favorite-star--active:focus-visible {
  color: #d4a017;
}

.favorite-star:active {
  transform: scale(0.9);
}

.favorite-star:disabled {
  cursor: default;
  opacity: 0.6;
}

/* Today's discovery strips (Bug List #40 fix, 2026-09-09) — "Saved
   words," "On this day," and "Recent words" used to be three separate,
   always-stacked sections (.glance-strip/.callback-strip, now removed —
   only today.html ever used them), pushing the quiz-status card further
   down the page every time all three had content. Andy's own pick, among
   the options an
   external design review raised (carousel vs. a segmented toggle vs. an
   accordion): one swipeable carousel. Plain CSS scroll-snap for the
   swipe itself (no new dependency) — carousel.js only adds the dot
   indicators and click-to-jump, and no-ops entirely if there's just one
   page (nothing to swipe between). Each page keeps reusing
   .glance-list/.glance-chip/.callback-text exactly as before — this is a
   wrapper change, not a rebuild of what's inside each page. */
.carousel {
  margin: 1.75rem 0;
}

.carousel-track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  gap: 0.75rem;
  /* Hide the scrollbar — this is meant to read as a swipeable carousel
     (dots below communicate position), not a normal scrolling list. */
  scrollbar-width: none;
}

.carousel-track::-webkit-scrollbar {
  display: none;
}

.carousel-page {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
}

.carousel-dots {
  display: flex;
  justify-content: center;
  gap: 0.4rem;
  margin-top: 0.6rem;
}

.carousel-dot {
  width: 6px;
  height: 6px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--color-border);
  cursor: pointer;
  transition: background-color 0.15s ease, transform 0.15s ease;
}

.carousel-dot:hover {
  background: var(--color-accent);
}

.carousel-dot--active {
  background: var(--color-accent);
  transform: scale(1.2);
}

.callback-text {
  font-size: 0.9rem;
  color: var(--color-text-muted);
  margin: 0;
}

.callback-text strong {
  color: var(--color-text);
}

.glance-list {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0 0 0.6rem;
  padding: 0;
}

.glance-chip {
  display: flex;
  align-items: center;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: 999px;
  padding: 0.35rem 0.5rem 0.35rem 0.85rem;
  font-size: 0.9rem;
  /* Small elevation (Build Task 21a) — same reasoning as .card, scaled
     down to match a chip's much smaller size. */
  box-shadow: 0 1px 2px rgba(70, 130, 180, 0.05), 0 4px 10px rgba(70, 130, 180, 0.06);
}

/* Build Task 22d (2026-08-30) — archive-filter.js sets a chip's `hidden`
   attribute to filter it out. `.glance-chip`'s own `display: flex` above
   has equal specificity to the browser's default `[hidden] { display:
   none }` UA rule, and author rules beat UA rules on a tie — so without
   this, a "hidden" chip would still render. */
.glance-chip[hidden] {
  display: none;
}

:root[data-theme="dark"] .glance-chip {
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25), 0 4px 10px rgba(0, 0, 0, 0.25);
}

.glance-chip-word {
  color: var(--color-text);
}

.glance-chip .difficulty-badge {
  margin-left: 0.5rem;
}

.glance-more {
  /* Bug List #5 fix (2026-08-29): now a flex item inside .glance-list
     itself (was a separate <p> below the whole <ul>), so it sits inline
     after the last chip and wraps naturally with the rest of the row. */
  display: flex;
  align-items: center;
  font-size: 0.9rem;
}

.glance-more a {
  color: var(--color-text-muted);
}

.glance-more a:hover {
  color: var(--color-accent);
}

/* Build Task 22b (2026-08-30) — the CTA at the bottom of a shared word
   page (word.html), pointing a recipient who's never opened the app
   back to Today. */
.share-cta {
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
  font-size: 0.9rem;
}

.share-cta a {
  color: var(--color-accent);
  font-weight: 600;
}

/* Quiz status/CTA card (design fix, 2026-08-26) — a second, visually
   quieter card below today's word: no shadow, muted border, so it reads
   as secondary rather than competing with the hero word card above it. */
.card--status {
  box-shadow: none;
  border-color: var(--color-border);
  background: color-mix(in srgb, var(--color-surface) 60%, var(--color-bg));
}

.card--status .btn {
  margin-top: 0.25rem;
}

/* Bug List #45 fix (2026-09-09): a targeted rule for inert "come back
   later" waiting-state cards (Quiz/MidWeek Checkin's locked, completed,
   forfeited states) — center-aligns text/links so an inactive resting
   screen reads as deliberate rather than a dense-reading card missing
   content. Scoped to this class only, not .card globally, since .card is
   also used by live quiz questions and completed-score breakdowns that
   should stay left-aligned/dense.
   Applying text-align: center here is enough — it cascades to inline
   content in child <p> elements (including the "See the full breakdown"
   link on the completed state), no extra rule needed for that link. */
.card--waiting {
  text-align: center;
}

/* Full archive on /history (added 2026-08-27) — year > month disclosure,
   native <details>/<summary> (same pattern as .score-details on Scores),
   month's word chips reuse .glance-list/.glance-chip from Today's "Recent
   words" strip for visual consistency across the app. */
.archive {
  margin: 2rem 0;
}

.archive-year {
  margin-bottom: 0.5rem;
}

.archive-year > summary {
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: 700;
  padding: 0.5rem 0;
}

.archive-year > summary:hover {
  color: var(--color-accent);
}

/* Months indented under their year, so the hierarchy reads at a glance
   without needing a second visual treatment (border/background) per
   level — indentation alone is enough for two levels. */
.archive-month {
  margin: 0.25rem 0 0.25rem 1.25rem;
}

.archive-month > summary {
  cursor: pointer;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--color-text-muted);
  padding: 0.35rem 0;
}

.archive-month > summary:hover {
  color: var(--color-accent);
}

.archive-word-list {
  margin: 0.5rem 0 0.75rem;
}

/* Build Task 22d (2026-08-30) — search + difficulty filter controls above
   the Full Archive, matching the note-form textarea's border/bg language
   (see that block's own comment) rather than inventing a new input style. */
.archive-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem;
  margin: 0.75rem 0 1rem;
}

.archive-search {
  flex: 1 1 14rem;
  padding: 0.5rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.9rem;
  box-sizing: border-box;
}

.archive-search:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.archive-difficulty-filter {
  display: flex;
  gap: 0.4rem;
}

.archive-difficulty-btn {
  padding: 0.35rem 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  color: var(--color-text-muted);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
}

.archive-difficulty-btn:hover {
  color: var(--color-accent);
}

.archive-difficulty-btn--active {
  background: var(--color-accent);
  border-color: var(--color-accent);
  color: #fff;
}

.archive-difficulty-btn--active:hover {
  color: #fff;
}

/* Build Task 23f (2026-08-31) — "Saved only" toggle, matching the
   difficulty pills' spacing/sizing language without borrowing their pill
   shape (a checkbox+label reads more honestly as a toggle than a button
   that isn't part of the same mutually-exclusive group). */
.archive-saved-toggle {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--color-text-muted);
  cursor: pointer;
}

.archive-no-results {
  color: var(--color-text-muted);
  font-size: 0.9rem;
  margin: 0.5rem 0 1rem;
}

/* Generic button — for links that should read as an action (e.g. the
   quiz's "already completed" card pointing at the Scores page), not a
   plain inline hyperlink. */
.btn {
  display: inline-block;
  padding: 0.6rem 1.25rem;
  border-radius: 8px;
  background: var(--color-accent);
  color: #fff;
  font-weight: 600;
  font-size: 0.95rem;
  text-decoration: none;
  border: 1px solid var(--color-accent);
  cursor: pointer;
  transition: opacity 0.15s ease;
}

.btn:hover,
.btn:focus-visible {
  opacity: 0.88;
}

.btn--small {
  padding: 0.35rem 0.85rem;
  font-size: 0.85rem;
}

/* Bug List #49 fix (2026-09-09) — terracotta CTA modifier, opted into
   explicitly by the three named primary actions (Start Quiz, Sign in with
   Google, Send friend request) rather than flipping .btn's own background
   — .btn is reused by many navigational/secondary links (e.g. "See your
   score history →", "Accept" a friend request) that should stay on the
   existing Steel Blue treatment, so scoping via an added class keeps
   terracotta a deliberate "the one thing to do here" signal instead of
   spreading everywhere .btn already appears. */
.btn--cta {
  background: var(--color-cta);
  border-color: var(--color-cta);
}

/* Word notes (Build Task 18e, 2026-08-29) — sits inside .word-card, below
   the definition/example. Deliberately understated (border-only textarea,
   no card-within-a-card) since it's a secondary action on a page whose
   whole point is the word itself.

   Animated reveal (2026-08-29, Andy: "I love animations, can we make the
   text box animate out") — .note-form is always in the DOM; collapsed by
   default (invisible, non-interactive) and only the .note-form--open
   class (added/removed by favorites.js) reveals it, so the
   reveal/collapse is a real CSS transition rather than a hard
   display:none swap. Transitions defined once here, on the base class,
   not on --open — a transition only plays when a *property* changes on
   an already-rendered element, so a page loading straight into --open
   (an existing note) never animates on load, only genuine toggles do.

   Reworked from max-height to grid-template-rows (Bug List #70 fix,
   2026-09-11) — max-height's `0 → <generous fixed value>` technique
   forces the browser to recompute layout every animation frame
   (max-height is a layout property) and needed a guessed-generous fixed
   value (32rem) since the real content height isn't known ahead of time.
   `grid-template-rows: 0fr → 1fr` animates the row *track size* instead,
   which sidesteps both problems — no guessed max value, and (per the
   CSS Grid spec's <flex> track-sizing algorithm) a smoother, GPU-cheaper
   interpolation than the layout-thrash-prone max-height approach. This
   needs exactly one grid item filling the row for the 0fr→1fr math to
   collapse/expand cleanly, hence the new `.note-form-inner` wrapper
   (base.html's note_widget() macro) holding everything that used to be
   .note-form's direct children — every actual class/attribute a script
   or CSS rule looks for is unchanged, this only adds one wrapper level. */
.note-form {
  display: grid;
  grid-template-rows: 0fr;
  opacity: 0;
  pointer-events: none;
  transition: grid-template-rows 0.3s ease, opacity 0.2s ease;
}

.note-form-inner {
  /* Fixed 2026-08-29 (Andy: the textarea's focus outline looked "cut off"
     on the left/right), preserved here across the max-height→grid rework
     — overflow: hidden below (needed to clip this element's content
     while its parent's grid row track animates toward 0fr) was also
     clipping the textarea's own :focus-visible outline, which sits 1px
     outside its border. A small horizontal padding gives the outline
     room to render before hitting the clip boundary; the matching
     negative margin keeps the textarea's actual edges aligned with the
     label/blurb text above it, so nothing visually shifts. min-height: 0
     keeps this grid item's own intrinsic content height from fighting
     the 0fr collapse (belt-and-suspenders alongside overflow: hidden,
     which already resets automatic minimum size per the CSS Sizing
     spec). */
  overflow: hidden;
  min-height: 0;
  padding: 0 3px;
  margin: 0 -3px;
}

.note-form--open {
  grid-template-rows: 1fr;
  opacity: 1;
  pointer-events: auto;
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--color-border);
  transition: grid-template-rows 0.35s ease, opacity 0.3s ease 0.05s;
}

/* Collapsed entry point (2026-08-29, Andy: keep the card clean when
   there's no note yet) — a small dashed-underline button rather than the
   full blurb+textarea, so a word with no note doesn't visually compete
   with the definition above it. Animates out the same way .note-form
   animates in (Andy: "get the add note button/text to go away once the
   note is pressed") — collapses via max-height/opacity, not a hard hide,
   so the two elements read as one continuous swap rather than a jump. */
/* Restyled Build Task 21a (2026-08-29) — Andy's direct feedback on the
   preview: "more of a button." Kept the collapsed-button *pattern* (his
   separate, explicit call — the reference mockup's always-open input
   field was declined), just gave it a real button look: a bordered pill
   sitting below the definition, not a full-width dashed top-border strip
   styled like plain text. Height/opacity still animate via max-height,
   same mechanism as before, just measured against the pill's own compact
   size instead of a full-width strip. */
/* Bug List #70 fix (2026-09-11) — same max-height → grid-template-rows
   rework as .note-form above, applied to this button. `inline-grid`
   (not `grid`) preserves the original `inline-block` shrink-to-fit
   sizing (a full block-level grid container would otherwise stretch to
   its parent's width). margin-top/padding/border-color stay animated
   directly on the button's own box model, same as before — only the
   button's *content* height now comes from the row-track animation via
   the new .note-add-btn-text wrapper (base.html), which is the single
   grid item the 0fr→1fr technique needs. */
.note-add-btn {
  display: inline-grid;
  grid-template-rows: 1fr;
  width: auto;
  margin-top: 1rem;
  padding: 0.5rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-bg);
  color: var(--color-accent);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  opacity: 1;
  transition: grid-template-rows 0.25s ease, opacity 0.2s ease, margin-top 0.25s ease, padding 0.25s ease, border-color 0.25s ease, background-color 0.15s ease;
}

.note-add-btn-text {
  overflow: hidden;
  min-height: 0;
}

.note-add-btn--hidden {
  grid-template-rows: 0fr;
  opacity: 0;
  margin-top: 0;
  padding: 0 1rem;
  border-color: transparent;
  pointer-events: none;
}

.note-add-btn:hover,
.note-add-btn:focus-visible {
  color: var(--color-accent);
  background: var(--color-border);
}

.note-label {
  display: block;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text-muted);
  margin-bottom: 0.35rem;
}

/* Explainer line (2026-08-29, Andy: "add a blurb telling the user what
   the notes feature is for") — sits between the label and the textarea,
   small/muted so it reads as a hint, not a heading. */
.note-blurb {
  font-size: 0.8rem;
  color: var(--color-text-muted);
  margin: 0 0 0.5rem;
}

.note-form textarea {
  width: 100%;
  min-height: 4.5rem;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.95rem;
  resize: vertical;
  box-sizing: border-box;
}

.note-form textarea:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* Save/Edit toggle (2026-08-29, Andy) — readonly state (a note already
   exists, not currently being edited) reads visually "settled": no resize
   handle, muted border, no focus ring since it can't be focused-to-type. */
.note-form textarea[readonly] {
  resize: none;
  border-color: transparent;
  background: color-mix(in srgb, var(--color-accent) 6%, var(--color-bg));
  cursor: default;
}

/* "Edit" button — a lighter, outline treatment (not the solid accent
   .btn look) so it doesn't compete visually with primary actions like
   "Save note" or the quiz CTA in the card below; editing an existing note
   is a lower-commitment action than the first save. */
.note-edit-btn {
  background: none;
  color: var(--color-accent);
  border-color: var(--color-accent);
}

.note-edit-btn:hover,
.note-edit-btn:focus-visible {
  background: color-mix(in srgb, var(--color-accent) 10%, transparent);
  opacity: 1;
}

/* Neutral outline button — Cancel, in both the "cancel an edit" and
   "back out of delete confirmation" senses. Lower visual weight than
   .btn or .note-edit-btn since it's always a step-back action, never the
   thing being encouraged. */
.btn--ghost {
  background: none;
  color: var(--color-text-muted);
  border-color: var(--color-border);
}

.btn--ghost:hover,
.btn--ghost:focus-visible {
  background: color-mix(in srgb, var(--color-text-muted) 10%, transparent);
  color: var(--color-text);
  opacity: 1;
}

/* Destructive button — Delete, and its "Yes, delete" confirmation.
   Reuses --color-error-text (already defined for light/dark) rather than
   introducing a new red, same "stay inside the existing palette"
   reasoning the logo pass and streak badge already used. */
.btn--danger {
  background: var(--color-error-text);
  border-color: var(--color-error-text);
  color: #fff;
}

.btn--danger:hover,
.btn--danger:focus-visible {
  opacity: 0.85;
}

/* Build Task 30b (2026-09-10) — first .btn usage in this app that starts
   genuinely disabled (delete_account.html's type-to-confirm gate), so no
   prior need for this existed. Generic, applies to any .btn. */
.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Action buttons (Build Task 18e, 2026-08-29: Cancel + Delete-with-
   confirmation added per Andy) — all five possible buttons plus the
   delete-confirmation text live in the DOM together always; data-mode on
   the parent .note-form (set by favorites.js) decides which are visible.
   Keeps the JS to "flip an attribute," no DOM building/innerHTML needed. */
.note-actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.note-actions > * {
  display: none;
}

.note-form[data-mode="editing"] .note-save-btn,
.note-form[data-mode="editing"] .note-cancel-btn {
  display: inline-block;
}

.note-form[data-mode="saved"] .note-edit-btn,
.note-form[data-mode="saved"] .note-delete-btn {
  display: inline-block;
}

.note-form[data-mode="confirm-delete"] .note-confirm-text,
.note-form[data-mode="confirm-delete"] .note-confirm-delete-btn,
.note-form[data-mode="confirm-delete"] .note-cancel-btn {
  display: inline-block;
}

.note-confirm-text {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin-right: 0.15rem;
}

.note-form-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  margin-top: 0.5rem;
  gap: 0.75rem;
}

.note-char-count {
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.note-char-count--limit {
  color: var(--color-error-text);
}

.note-save-status {
  font-size: 0.85rem;
  color: var(--color-text-muted);
  margin: 0.4rem 0 0;
  min-height: 1.1em;
}

.word {
  /* Real fix (2026-08-30, Andy): Bug List #13's bump (clamp(2rem, 8vw,
     2.75rem) -> clamp(2.25rem, 9vw, 3.25rem)) turned out to be aimed at
     the wrong element — Andy actually meant the "WORD OF THE DAY" label
     above this (see .date-label), not the word itself, and asked for
     this dialed back down a little once he saw both side by side live.
     Landed a touch below the original pre-#13 size, not a full revert. */
  font-size: clamp(2rem, 8vw, 2.6rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin: 0 0 0.25rem;
}

.pos {
  font-style: italic;
  color: var(--color-accent);
  font-size: 0.95rem;
  margin: 0 0 1.25rem;
}

.definition {
  font-size: 1.15rem;
  margin: 0 0 1rem;
}

.example {
  font-style: italic;
  color: var(--color-text-muted);
  border-left: 3px solid var(--color-border);
  padding-left: 1rem;
  margin: 0;
}

.card--error {
  border-color: var(--color-error-border);
  background: var(--color-error-bg);
}

.card--error .eyebrow {
  color: var(--color-error-text);
}

/* History list (Build Task 5) — same card language as the word-card, at a
   smaller, list-friendly scale. */

.history-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: var(--history-columns);
  /* Back to Grid's default (stretch), deliberately — cards in a row now
     match each other's height, which is what makes History's grid look
     neat instead of ragged (cards were sizing to their own content,
     differing with each definition's line count). This does NOT bring
     back the earlier Scores bug (an open card's tall content stretching
     its short neighbors) — that's now prevented below by giving an open
     card grid-column: 1 / -1, which puts it alone in its own row with no
     siblings left to stretch against, regardless of align-items. */
  gap: 0.75rem;
}

/* Scores-page-only (History's <li> never contains .score-details): once a
   card's word breakdown is open, give it the full row width instead of
   staying squeezed into one grid column — both so the breakdown has room
   to lay out as its own grid (below) and so it isn't sharing a cramped
   column with unrelated cards. Auto-placement bumps it to its own row;
   normal, expected grid behavior, not a bug. */
.history-item:has(.score-details[open]) {
  grid-column: 1 / -1;
}

.history-item {
  padding: 1.25rem 1.5rem;
  /* Bug List #50 fix (2026-09-10): .history-list's Grid align-items:
     stretch (deliberate, see the comment above) makes cards in a row
     match the tallest row-mate's height — which left shorter cards with
     genuine but arbitrary-looking blank space below their content
     (confirmed: .history-item's own padding was never the issue, it's
     already identical everywhere). Flex column + margin-top: auto on
     the trailing element turns that leftover stretched space into one
     consistent gap directly above whatever a card's last element is
     (.note-widget, the reveal button/locked text on a blurred card
     with no note widget, or Friends'/Scores'/Favorites' own trailing
     element — all share this .history-item class) instead of a void
     whose size varies by row-mate. */
  display: flex;
  flex-direction: column;
}

.history-item > *:last-child {
  margin-top: auto;
}

.history-item--today {
  border-color: var(--color-accent);
  /* Stays the full-width focal point even once the rest of the week
     drops into a multi-column grid on wider screens. */
  grid-column: 1 / -1;
}

.history-word {
  /* Bug List #13 fix (2026-08-29): bumped from 1.35rem — same request as
     .word above, extended to History/Scores/Saved's smaller word
     headings too (Andy's call: "all word headings", not just Today's). */
  font-size: 1.55rem;
  font-weight: 700;
  margin: 0 0 0.4rem;
}

.pos-inline {
  font-style: italic;
  color: var(--color-accent);
  font-weight: 400;
  font-size: 0.9rem;
}

.history-definition {
  margin: 0;
  color: var(--color-text);
}

/* Blurred definition (Build Task 19, 2026-08-29, Andy) — this week's
   already-passed days, hidden from casual browsing before Sunday's quiz.
   user-select: none so the "answer" can't just be text-selected/copied
   around the blur either — consistent with this being an honor-system
   nudge, not a security boundary, but no reason to leave the laziest
   workaround open. */
.history-definition--blurred {
  filter: blur(5px);
  user-select: none;
}

.definition-reveal-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin-top: 0.35rem;
  background: none;
  border: none;
  padding: 0;
  font-size: 0.8rem;
  font-weight: 600;
  /* Bug List #42 fix (2026-09-09) — plain --color-text-muted computed to
     4.05:1 against the light-mode card surface (#708090 on #ffffff),
     under WCAG AA's 4.5:1 threshold for this size text (confirmed via a
     real contrast calculation, not eyeballed). A 20% mix toward
     --color-text lands at 5.08:1 light / ~6.6-7.5:1 dark (dark already
     passed AA before this change, at 5.47:1+ — this just keeps both
     themes consistent off one rule via color-mix(), same pattern this
     file already uses elsewhere for theme-following derived colors,
     rather than hardcoding a second light/dark pair). Scoped to this rule
     and .definition-locked below only — --color-text-muted itself is used
     too pervasively elsewhere to bump globally without checking every
     other use. */
  color: color-mix(in srgb, var(--color-text) 20%, var(--color-text-muted));
  cursor: pointer;
}

.definition-reveal-btn:hover,
.definition-reveal-btn:focus-visible {
  color: var(--color-accent);
}

/* Locked state (Build Task 19, 2026-08-29) — Sunday specifically, no
   reveal button at all. Same visual weight as .definition-reveal-btn so
   the layout doesn't shift depending on which day it is, but not a
   button (nothing to click) — plain text with the same muted color. */
.definition-locked {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  margin: 0.35rem 0 0;
  font-size: 0.8rem;
  font-weight: 600;
  /* Bug List #42 fix (2026-09-09) — same contrast fix and reasoning as
     .definition-reveal-btn above. */
  color: color-mix(in srgb, var(--color-text) 20%, var(--color-text-muted));
}

/* Word note, read-only display (Build Task 18e, 2026-08-29) — same idea as
   .example (a distinct, muted secondary line), used on history.html and
   favorites.html to show a note saved from today.html's editable form. */
.history-note {
  margin: 0.4rem 0 0;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  font-style: italic;
}

/* Weekly quiz (Build Task 6) */

.quiz-score {
  text-align: right;
}

.quiz {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.quiz-word {
  font-size: 1.5rem;
  margin: 0 0 1rem;
}

.quiz-options {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Fixed 2-up at desktop width (not auto-fit like History) — these are
     4 same-role choices, not a variable-length list, so a stable 2x2
     reads better than a count that shifts with each option's text
     length. Driven by --quiz-columns so it stays in lockstep with the
     dev view-switcher instead of its own separate breakpoint. */
  grid-template-columns: var(--quiz-columns);
  gap: 0.5rem;
}

.quiz-option {
  width: 100%;
  text-align: left;
  padding: 0.75rem 1rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}

.quiz-option:hover:not(:disabled) {
  border-color: var(--color-accent);
}

.quiz-option:disabled {
  cursor: default;
}

.quiz-option--correct {
  border-color: var(--color-success);
  background: var(--color-success-bg);
  color: var(--color-success);
}

.quiz-option--incorrect {
  border-color: var(--color-error-border);
  background: var(--color-error-bg);
  color: var(--color-error-text);
}

/* Hint elimination (Build Task 28f, 2026-09-08) — deliberately distinct
   from .quiz-option--incorrect (that means "you picked this and it was
   wrong"; this means "the hint removed it before you picked anything"),
   so the two states never read as the same thing. */
.quiz-option--eliminated {
  opacity: 0.45;
  text-decoration: line-through;
  cursor: default;
}

/* Per-question "Use Hint" control (Build Task 28f) — .btn--ghost already
   gives it the right "secondary action" visual weight against the
   primary quiz-option buttons below it. */
.quiz-hint-btn {
  display: inline-block;
  margin: 0 0 0.75rem;
}

.quiz-save-status {
  margin-top: 1rem;
  text-align: center;
  font-size: 0.9rem;
  color: var(--color-text-muted);
  min-height: 1.2em;
}

/* Per-week word breakdown on the Scores page (Build Task 6c) */

.score-details {
  margin-top: 1rem;
}

.score-details summary {
  cursor: pointer;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--color-accent);
}

.score-details summary:hover {
  text-decoration: underline;
}

.score-word-list {
  list-style: none;
  margin: 0.75rem 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr;
  gap: 0.5rem;
}

@media (min-width: 768px) {
  .score-word-list {
    /* The card housing this list is now full-width once open (see
       .history-item:has(.score-details[open]) above), so there's room for
       the 7-word breakdown to sit 2-3 across instead of one long column —
       auto-fit, not a fixed count, since 7 doesn't divide evenly. */
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  }
}

.score-word {
  padding: 0.65rem 0.85rem;
  border-radius: 8px;
  border: 1px solid var(--color-border);
}

.score-word--correct {
  background: var(--color-success-bg);
  border-color: var(--color-success);
}

.score-word--incorrect {
  background: var(--color-error-bg);
  border-color: var(--color-error-border);
}

.score-word-term {
  display: block;
  font-weight: 600;
}

.score-word-def {
  display: block;
  font-size: 0.9rem;
  color: var(--color-text);
}

/* Weighted score line (Build Task 20, 2026-08-29) — same "extra info,
   secondary to the headline number" treatment as .history-note, muted
   and italic so it doesn't compete with the raw score above it. */
.weighted-score {
  margin: 0.3rem 0 0;
  color: var(--color-text-muted);
  font-size: 0.9rem;
  font-style: italic;
}

/* Accuracy trend line on the Scores page (Build Task 22c, 2026-08-29) —
   plain inline SVG, no charting library, geometry precomputed server-side
   in score_service.py's build_score_trend() so the template just draws
   already-scaled coordinates. */
.score-trend {
  padding: 1.25rem 1.25rem 1rem;
  /* Bug List #15 fix (2026-08-30): .history-list (the week-card grid
     right below this) has margin: 0 — it relies entirely on being inside
     a CSS grid with its own `gap`, but this card sits outside that grid
     as a separate sibling, so nothing enforced any space between them.
     Andy's screenshot showed this card visually running into the tiles
     below it. */
  margin-bottom: 1.25rem;
}

.score-trend-axis-label {
  fill: var(--color-text-muted);
  font-size: 11px;
}

.score-trend-svg {
  width: 100%;
  height: auto;
  display: block;
  margin-top: 0.5rem;
}

.score-trend-gridline {
  stroke: var(--color-border);
  stroke-width: 1;
}

.score-trend-line {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.score-trend-line--raw {
  stroke: var(--color-accent);
}

.score-trend-line--weighted {
  stroke: var(--color-success);
  stroke-dasharray: 6 5;
}

.score-trend-dot--raw {
  fill: var(--color-accent);
}

.score-trend-dot--weighted {
  fill: var(--color-success);
}

.score-trend-legend {
  display: flex;
  gap: 1rem;
  margin: 0.6rem 0 0;
  font-size: 0.8rem;
  color: var(--color-text-muted);
}

.score-trend-legend-item--raw {
  color: var(--color-accent);
  font-weight: 600;
}

.score-trend-legend-item--weighted {
  color: var(--color-success);
  font-weight: 600;
}

.score-trend-range {
  margin: 0.25rem 0 0;
  font-size: 0.75rem;
  color: var(--color-text-muted);
}

/* Per-word reveal-count badge, in the Scores breakdown (Build Task 20) —
   same pill shape as .difficulty-badge/.streak-badge, reusing the streak
   badge's accent-tinted treatment since both are "extra context about
   this word," not a pass/fail signal of their own (score-word--correct/
   incorrect already owns that color language). */
.reveal-count-badge {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  margin-left: 0.4rem;
  vertical-align: middle;
  background: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
  color: var(--color-accent);
}

/* Hint-used badge (Build Task 28g, 2026-09-08) — same pill shape/sizing
   as .reveal-count-badge (both are "extra context about this word," not
   a pass/fail signal), reused verbatim on the per-word breakdown
   (scores.html, the quiz results popup) and standalone next to a
   friend's weighted-score line (friends.html/friend_scores.html), where
   there's no per-word breakdown to sit inside at all. */
.hint-used-badge {
  display: inline-block;
  font-size: 0.7rem;
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  margin-left: 0.4rem;
  vertical-align: middle;
  background: color-mix(in srgb, var(--color-accent) 16%, var(--color-surface));
  color: var(--color-accent);
}

/* Quiz completion results popup (Bug List #20, 2026-08-30) — first real
   modal this app has needed; no existing modal pattern to reuse, so this
   is a plain fixed-position backdrop + centered card, dismissible via
   the Close button or clicking the backdrop itself.

   Real bug caught by Andy 2026-08-30, same day this shipped: this rule's
   `display: flex` unconditionally overrode the `hidden` HTML attribute —
   author-stylesheet CSS beats the browser's own `[hidden] { display:
   none }` regardless of selector specificity, so the modal showed
   permanently (empty, since nothing had populated it) and "Close"
   (which only ever set `backdrop.hidden = true`) had no visible effect.
   Fixed by making `[hidden]` win explicitly with a higher-specificity
   rule below, rather than relying on the browser's UA default. */
.quiz-results-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1rem;
  z-index: 100;
}

.quiz-results-backdrop[hidden] {
  display: none;
}

.quiz-results-modal {
  max-width: 480px;
  width: 100%;
  max-height: 85vh;
  overflow-y: auto;
}

.quiz-results-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-top: 1.25rem;
}

/* Build Task 29j (2026-09-09) — MidWeek Checkin's card sits below the
   Sunday quiz's own content on the same /quiz page; a little breathing
   room between the two, same spacing instinct .friend-section already
   uses to separate stacked sections on one page. Everything inside reuses
   the existing .card/.quiz/.quiz-question/.quiz-option*/.quiz-results-*
   rules above as-is — no new component styles needed. */
.midweek-checkin {
  margin-top: 2rem;
}

/* Build Task 23a (2026-08-31) — username setup form. Same input-field
   visual language as .note-form textarea (border/bg/focus-ring tokens),
   just a single-line text input instead of a textarea. */
#username-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-top: 1rem;
}

#username-input {
  flex: 1 1 220px;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.95rem;
  box-sizing: border-box;
}

#username-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* Build Task 30b (2026-09-10) — broadened from #username-status alone so
   delete_account.html's own status paragraph (a different id, same
   error-state need) picks this up too, rather than duplicating the rule
   under a second id selector. */
.username-status--error {
  color: var(--color-error-text);
}

/* Build Task 30b (2026-09-10) — delete-account form. Same input-field
   visual language as #username-input/#username-form just above (border/bg/
   focus-ring tokens), own ids since it's a distinct form on a distinct
   page, not shared markup. */
#delete-account-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  align-items: flex-start;
  margin-top: 1rem;
}

#delete-confirm-input {
  width: 100%;
  max-width: 320px;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.95rem;
  box-sizing: border-box;
}

#delete-confirm-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

/* Build Task 23d (2026-08-31) — Friends page: add-a-friend form (same
   visual language as #username-form) and per-card action rows (reuses
   .note-actions' flex/gap layout from today.html's note buttons, which
   already does exactly "a row of small action buttons" this page needs
   too). */
#friend-request-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-top: 1rem;
}

#friend-username-input {
  flex: 1 1 220px;
  padding: 0.6rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 8px;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: inherit;
  font-size: 0.95rem;
  box-sizing: border-box;
}

#friend-username-input:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: 1px;
}

.friend-section {
  margin-top: 1.5rem;
}

.friend-section .history-item .note-actions {
  margin-top: 0.75rem;
  justify-content: flex-start;
}

.friend-section .history-item .note-actions > * {
  display: inline-block;
}

/* Build Task 24 (2026-09-02) — emoji reactions on a friend's quiz score.
   score-reactions/score-reaction: read-only display of reactions already
   sent (scores.html, friends.html, friend_scores.html all use this same
   pair). reaction-picker/reaction-btn: the small curated send row
   (friends.html/friend_scores.html only — you don't react to your own
   score, so scores.html never renders a picker). */
.score-reactions {
  margin-top: 0.5rem;
  font-size: 1.1rem;
}

.score-reaction {
  margin-right: 0.35rem;
}

.reaction-picker {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.75rem;
}

.reaction-btn {
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-surface);
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.35rem 0.55rem;
  cursor: pointer;
}

.reaction-btn:hover,
.reaction-btn:focus-visible {
  border-color: var(--color-accent);
}

.reaction-btn:disabled {
  opacity: 0.5;
  cursor: default;
}
