/* Hand-written, no build step — see docs/UI_DESIGN.md §0. */

@layer reset, tokens, base, components;

@layer reset {
  *, *::before, *::after { box-sizing: border-box; }
  body { margin: 0; }
  a { color: inherit; }
}

@layer tokens {
  :root {
    color-scheme: light;

    /* "Thrive by Design" nature palette — light green/white, applied
       app-wide (2026-07-23 direction change; supersedes the warm-toned
       palette docs/UI_DESIGN.md §2 originally specified — that doc's
       *principles*, one accent used sparingly, flat over shadowed,
       monospace for data, still apply, only the actual hue changed).
       Every hue below carries a touch of green (not the old warm
       yellow/red); lightness/contrast pairs are chosen to clear WCAG AA
       (4.5:1 body text, 3:1 large text/UI) against the surfaces they
       sit on, same discipline as before. */
    --color-bg: oklch(99.3% 0.003 140);
    --color-surface: oklch(100% 0 0);
    --color-surface-hover: oklch(96.5% 0.012 140);
    --color-border: oklch(90% 0.02 135);
    --color-text: oklch(27% 0.03 150);
    --color-text-muted: oklch(27% 0.03 150 / 0.62);

    --color-accent: oklch(62% 0.12 145);
    --color-accent-hover: oklch(56% 0.12 145);
    --color-accent-active: oklch(50% 0.12 145);
    --color-accent-content: oklch(99% 0.01 140);
    --color-focus: oklch(60% 0.16 145);

    --color-surface-active: oklch(91% 0.025 140);

    --color-success: oklch(55% 0.13 145);
    --color-warning: oklch(58% 0.15 70);
    --color-error: oklch(55% 0.17 25);
    /* --color-warning is tuned for small status text on a light surface
       (4.7:1) — a colored background with light text needs the opposite
       end of the lightness range to hit the same 4.5:1, hence a
       separate, darker token for the impersonation banner rather than
       reusing --color-warning as a background. */
    --color-warning-strong: oklch(38% 0.15 55);
    /* .card--warning's own, more muted pair — a saturated warm
       red/orange border (--color-warning) read as jarring against the
       green theme (2026-07-26), a near-complementary color clash. These
       stay low-chroma/neutral instead, so a "needs attention" card
       reads as a distinct-but-calm card, not an alarm — --color-warning/
       --color-warning-strong are left alone for what they're already
       used for elsewhere (.status--warning small text, the
       impersonation banner's deliberately loud background, see its own
       comment below). */
    --color-alert-border: oklch(78% 0.02 80);
    --color-alert-heading: oklch(42% 0.03 70);

    --radius-sm: 0.25rem;
    --radius-md: 0.375rem;
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.25rem;
    --space-6: 1.5rem;

    --breakpoint-mobile: 768px;

    --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, ui-sans-serif, system-ui, sans-serif;
    --font-mono: ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, monospace;
  }
}

@layer base {
  body {
    background: var(--color-bg);
    color: var(--color-text);
    font-family: var(--font-sans);
    font-size: 14px;
    line-height: 1.5;
  }
  h1, h2, h3 { margin: 0 0 var(--space-2); font-weight: 600; }
  h1 { font-size: 1.5rem; }
  h2 { font-size: 1.125rem; }
  label { display: block; font-size: 0.8125rem; color: var(--color-text-muted); margin-bottom: var(--space-1); }
  .form-hint { font-size: 0.8125rem; color: var(--color-text-muted); margin: var(--space-1) 0 0; }
  input, select, textarea {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    color: var(--color-text);
    font: inherit;
  }
  input:focus, select:focus, textarea:focus { outline: 1px solid var(--color-accent); border-color: var(--color-accent); }
  input:disabled, select:disabled, textarea:disabled {
    background: var(--color-surface-hover);
    color: var(--color-text-muted);
    cursor: not-allowed;
  }

  /* Accessibility: a visible focus ring on every interactive element,
     not just form fields above — the browser default outline is often
     suppressed by resets, so this is explicit rather than assumed. */
  a:focus-visible, button:focus-visible, input:focus-visible,
  select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
    outline: 2px solid var(--color-focus);
    outline-offset: 2px;
  }

  .skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    background: var(--color-accent);
    color: var(--color-accent-content);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    z-index: 200;
    text-decoration: none;
  }
  .skip-link:focus { left: var(--space-2); top: var(--space-2); }
}

@layer components {
  /* Deliberate exception to "one accent colour per screen" (UI_DESIGN.md
     §2) — this is a safety indicator for an active impersonation
     session, not themed page decoration, and must stay visually
     distinct from everything else on the page at all times. */
  .impersonation-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-4);
    padding: var(--space-2) var(--space-4);
    background: var(--color-warning-strong);
    color: var(--color-accent-content);
    font-size: 0.875rem;
    font-weight: 600;
    position: sticky;
    top: 0;
    z-index: 100;
  }
  .impersonation-banner form { margin: 0; }

  /* Shared app shell — one sidebar-based layout for every user type
     (school admin/staff, guardian, GW Sensory platform admin), built by
     internal/views/layouts/shell.templ. See docs/UI_DESIGN.md §4. */
  .app-shell { display: flex; min-height: 100vh; }

  .mobile-topbar {
    display: none;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    background: var(--color-surface);
    border-bottom: 1px solid var(--color-border);
    position: sticky;
    top: 0;
    z-index: 50;
  }
  .mobile-topbar .page-title { font-weight: 600; }

  .nav-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    padding: 0;
    background: transparent;
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    color: var(--color-text);
  }
  .nav-toggle svg { width: 1.25rem; height: 1.25rem; }
  .nav-toggle:hover { background: var(--color-surface-hover); }
  .nav-toggle:active { background: var(--color-surface-active); }

  .sidebar-backdrop {
    display: none;
    position: fixed;
    inset: 0;
    background: oklch(24% 0.014 50 / 0.4);
    z-index: 90;
  }

  .sidebar {
    width: 240px;
    flex-shrink: 0;
    background: var(--color-surface);
    border-right: 1px solid var(--color-border);
    padding: var(--space-4);
    display: flex;
    flex-direction: column;
  }
  .sidebar .product-label { font-weight: 600; }
  /* General-purpose small pill — the platform-admin sidebar badge and
     any other "this is a distinguishing tag, not a status" label (e.g.
     the "Auto-logged" marker on system-generated session events) share
     this base; a context selector only adds spacing overrides. */
  .badge {
    display: inline-block;
    padding: 0.125rem var(--space-2);
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-accent);
    background: color-mix(in oklch, var(--color-accent), transparent 88%);
    border-radius: var(--radius-sm);
    margin-left: var(--space-2);
  }
  .sidebar .product-label:last-of-type { margin-bottom: var(--space-5); }
  .sidebar .badge { display: block; width: fit-content; margin: var(--space-1) 0 var(--space-4); }
  .sidebar nav { display: flex; flex-direction: column; gap: var(--space-1); }
  /* Section headings above a group of related nav items (Care/
     Facilities/Admin) — see internal/views/layouts/shell.templ's
     NavItem.Group. Not a link, so no hover/focus states needed. */
  .nav-group-label {
    margin: var(--space-3) 0 var(--space-1);
    padding: 0 var(--space-3);
    font-size: 0.6875rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
  }
  .nav-group-label:first-child { margin-top: 0; }
  .sidebar nav a {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: var(--color-text-muted);
    border-left: 2px solid transparent;
  }
  .sidebar nav a svg { width: 1.125rem; height: 1.125rem; flex-shrink: 0; }
  .sidebar nav a:hover { background: var(--color-surface-hover); color: var(--color-text); }
  .sidebar nav a:active { background: var(--color-surface-active); color: var(--color-text); }
  .sidebar nav a.active { border-left-color: var(--color-accent); color: var(--color-text); background: var(--color-surface-hover); }

  .sidebar .user-info {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    margin-top: auto;
    padding-top: var(--space-4);
    border-top: 1px solid var(--color-border);
    font-size: 0.8125rem;
    color: var(--color-text-muted);
  }
  .sidebar .user-info .btn { align-self: flex-start; }
  /* "My account" reads as a proper menu item, not a bare inline link —
     same visual treatment as .sidebar nav a above (full-width, padded,
     hover/active state), just living below the divider in .user-info
     rather than in the main nav list, since it's account-level, not a
     section of the app. */
  .sidebar .user-info a {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: var(--color-text-muted);
  }
  .sidebar .user-info a:hover { background: var(--color-surface-hover); color: var(--color-text); }
  .sidebar .user-info a:active { background: var(--color-surface-active); color: var(--color-text); }
  .user-info-identity { display: flex; align-items: center; gap: var(--space-2); }
  .user-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    flex-shrink: 0;
    border-radius: 50%;
    background: color-mix(in oklch, var(--color-accent), transparent 82%);
    color: var(--color-accent);
    font-size: 0.6875rem;
    font-weight: 600;
  }

  .content { flex: 1; padding: var(--space-6); max-width: 1100px; }

  /* Shared by every unauthenticated, no-nav page (login, setup, 404) via
     layouts.PublicCentered — see internal/views/layouts/public.templ. */
  .public-centered {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: var(--space-4);
  }

  @media (max-width: 768px) {
    .mobile-topbar { display: flex; }

    .sidebar {
      position: fixed;
      inset: 0 auto 0 0;
      z-index: 100;
      transform: translateX(-100%);
      transition: transform 0.2s ease-in-out;
      box-shadow: 0 0 24px oklch(24% 0.014 50 / 0.2);
    }
    .nav-open .sidebar { transform: translateX(0); }
    .nav-open .sidebar-backdrop { display: block; }

    .content { padding: var(--space-4); }
  }

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

  .page-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--space-4); gap: var(--space-4); }
  .page-header p { color: var(--color-text-muted); margin: 0; }

  .card {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    background: var(--color-surface);
    padding: var(--space-4);
  }

  .card--warning { border-color: var(--color-alert-border); margin-bottom: var(--space-4); }
  .card--warning h2 { color: var(--color-alert-heading); }
  .alert-list { margin: var(--space-2) 0 0; padding-left: var(--space-4); }

  .stat-block { border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-4); background: var(--color-surface); }
  .stat-block .label { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--color-text-muted); }
  .stat-block .value { font-family: var(--font-mono); font-size: 1.5rem; margin-top: var(--space-1); }
  .stat-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-3); margin-bottom: var(--space-6); }
  /* admin.summaryTile (session_detail.templ) — a stat-block variant for
     an answer that's a vocabulary term (icon + short label text) rather
     than a bare number, so it needs its own non-mono, normal-size value
     row instead of .stat-block .value's big monospace digit styling. */
  .stat-block--icon .value--icon { display: flex; align-items: center; gap: var(--space-2); font-family: inherit; font-size: 1rem; font-weight: 600; }
  .stat-block--icon .value--icon svg { width: 1.25rem; height: 1.25rem; color: var(--color-accent); flex-shrink: 0; }

  /* Overview dashboard's two report cards (sessions-per-week, cost of
     provision) — side by side above ~640px combined width, stacked
     below, rather than one full-width and one arbitrarily narrower. */
  .dashboard-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--space-4); }

  table.data-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
  .data-table th { text-align: left; padding: var(--space-2) var(--space-3); color: var(--color-text-muted); font-weight: 500; border-bottom: 1px solid var(--color-border); }
  .data-table td { padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--color-border); }
  .data-table tbody tr:hover { background: var(--color-surface-hover); }
  .data-table .mono { font-family: var(--font-mono); color: var(--color-text-muted); }

  /* Sortable column headers: a link, styled through --link-* custom
     properties so :hover/:focus-visible can't lose to a variant the same
     way .btn's states are protected — see the comment above .btn below.
     The .sortable class (rather than a bare `th a`) keeps this selector's
     specificity from descending relative to .sidebar nav a's rules above. */
  .data-table th.sortable a {
    --link-color: var(--color-text-muted);
    --link-color-hover: var(--color-text);

    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--link-color);
    text-decoration: none;
    font-weight: 500;
  }
  .data-table th.sortable a:hover { --link-color: var(--link-color-hover); }
  .data-table th.sortable a:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; border-radius: var(--radius-sm); }

  /* Rich-table screens: a normal <table> on desktop, but a
     horizontally-scrolling table is unreadable on a phone (headers
     scroll out of view, cells truncate) — below the mobile breakpoint
     .data-table--cards turns every row into its own bordered card, with
     each <td>'s data-label attribute standing in for the column header
     that .data-table--cards hides. Opt-in per table (not every
     .data-table use is a paginated list screen with data-label set on
     every cell — e.g. the event-history table on a session/client
     detail page isn't converted) — see docs/UX_SPEC.md §5. */
  @media (max-width: 768px) {
    .data-table--cards thead { display: none; }
    .data-table--cards, .data-table--cards tbody { display: block; width: 100%; }
    .data-table--cards tr {
      display: block;
      border: 1px solid var(--color-border);
      border-radius: var(--radius-md);
      background: var(--color-surface);
      margin-bottom: var(--space-3);
      padding: 0 var(--space-3);
    }
    .data-table--cards tr:hover { background: var(--color-surface); }
    .data-table--cards td {
      display: flex;
      justify-content: space-between;
      align-items: center;
      gap: var(--space-3);
      padding: var(--space-2) 0;
      border-bottom: 1px dashed var(--color-border);
      text-align: right;
    }
    .data-table--cards td:last-child { border-bottom: none; }
    .data-table--cards td::before {
      content: attr(data-label);
      font-weight: 500;
      color: var(--color-text-muted);
      text-align: left;
      padding-right: var(--space-3);
    }
    /* A cell with no meaningful column label (a lone action button, a
       bulk-select checkbox) opts out via data-label="" — no label
       prefix, content right-aligned same as a labelled value would be. */
    .data-table--cards td[data-label=""]::before { display: none; }
    /* Vocabulary's inline rename form (text + number input + Save
       button) is sized for a wide desktop cell — let it wrap rather
       than overflow a narrow card row. */
    .data-table--cards td form { flex-wrap: wrap; }
  }

  .table-toolbar { display: flex; align-items: center; gap: var(--space-4); margin-bottom: var(--space-3); }
  .table-toolbar .table-search { max-width: 20rem; margin-bottom: 0; }
  .table-toolbar-toggle { display: flex; align-items: center; gap: var(--space-2); font-size: 0.8125rem; color: var(--color-text-muted); margin-bottom: 0; }
  .table-toolbar-toggle input[type="checkbox"] { width: auto; }
  .table-toolbar-pagesize { display: flex; align-items: center; gap: var(--space-2); font-size: 0.8125rem; color: var(--color-text-muted); margin-bottom: 0; margin-left: auto; }
  .table-toolbar-pagesize select { width: auto; padding: var(--space-1) var(--space-2); }

  .pagination { display: flex; align-items: center; gap: var(--space-3); margin-top: var(--space-3); }
  .pagination-indicator { font-size: 0.8125rem; color: var(--color-text-muted); }
  .pagination [aria-disabled="true"] { opacity: 0.5; pointer-events: none; }

  .bulk-actions { margin-top: var(--space-3); }

  /* Separates a table/list from an "Add X" form directly below it (e.g.
     Vocabulary's add-term card, Platform Admins' add-admin card, the
     add-outcome/provision/attendance cards on the client detail tabs).
     The form's own .card border alone read as just another list row
     rather than a new section starting — this marks that transition
     explicitly. */
  .section-divider { border: none; border-top: 1px solid var(--color-border); margin: var(--space-5) 0; }

  .status { display: inline-flex; align-items: center; gap: var(--space-2); font-size: 0.8125rem; }
  .status::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex-shrink: 0; }
  .status--success { color: var(--color-success); }
  .status--warning { color: var(--color-warning); }
  .status--muted { color: var(--color-text-muted); }

  /* Every variant sets --btn-* custom properties rather than the final
     background/color/border-color directly, and :hover/:active only ever
     read those same custom properties. This is deliberate: a selector
     like `.btn--primary { background: ... }` (specificity 0,1,0) can
     never out-rank `.btn:hover { background: ... }` (0,2,0) written
     earlier in the file, no matter what order the rules appear in — a
     variant declared after a state rule loses that property silently.
     That exact bug shipped once (a primary button hovered to a plain
     white background) and is why this file routes every state through
     custom properties instead: there's only one `background:`
     declaration site for the whole component, so there's no specificity
     race for a variant to lose. See docs/DESIGN_SYSTEM.md §4.
  */
  .btn {
    --btn-bg: var(--color-surface);
    --btn-bg-hover: var(--color-surface-hover);
    --btn-bg-active: var(--color-surface-active);
    --btn-color: var(--color-text);
    --btn-border: var(--color-border);

    display: inline-flex;
    align-items: center;
    flex-shrink: 0;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: 0.875rem;
    white-space: nowrap;
    border: 1px solid var(--btn-border);
    background: var(--btn-bg);
    color: var(--btn-color);
    cursor: pointer;
    text-decoration: none;
  }
  .btn:hover { background: var(--btn-bg-hover); }
  .btn:active { background: var(--btn-bg-active); }
  .btn:disabled {
    cursor: not-allowed;
    opacity: 0.5;
  }
  .btn--primary {
    --btn-bg: var(--color-accent);
    --btn-bg-hover: var(--color-accent-hover);
    --btn-bg-active: var(--color-accent-active);
    --btn-color: var(--color-accent-content);
    --btn-border: var(--color-accent);
  }
  .btn--sm { padding: var(--space-1) var(--space-2); font-size: 0.8125rem; }
  .btn--danger {
    --btn-color: var(--color-error);
    --btn-border: var(--color-error);
  }

  /* auto-fit/minmax rather than a fixed 2-column split so a .form-grid
     inside a wide, full-width card (e.g. RoomForm) reflows into more
     columns instead of stretching each field absurdly wide, while a
     narrower card (or mobile) still collapses it down to one column —
     same responsive-card pattern as .equipment-picker-grid. */
  .form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-3); }
  .form-group { margin-bottom: var(--space-3); }
  .form-actions { display: flex; gap: var(--space-3); margin-top: var(--space-4); }

  /* Conditional fields revealed by data-toggle-fields (layouts/scripts.templ)
     — progressive disclosure for a form whose extra fields only apply to
     one option of a select, e.g. restrictive-intervention detail on the
     session event form. See docs/UX_SPEC.md §3.7. */
  .conditional-fields { border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: var(--space-3); margin-bottom: var(--space-3); }
  .conditional-fields legend { font-weight: 600; font-size: 0.875rem; padding: 0 var(--space-1); }
  .field-hint { color: var(--color-text-muted); font-size: 0.8125rem; margin: 0 0 var(--space-3); }
  .checkbox-label { display: flex; align-items: center; gap: var(--space-2); }
  .checkbox-label input[type="checkbox"] { width: auto; }

  /* ARIA tabs pattern (internal/views/layouts/tabs.templ) — used by
     Vocabulary (one tab per term type), Client/Staff detail pages (one
     tab per resource area) instead of stacking every section's cards/
     forms one after another on a single long page. */
  .tab-list {
    display: flex;
    gap: var(--space-1);
    border-bottom: 1px solid var(--color-border);
    margin-bottom: var(--space-4);
    overflow-x: auto;
  }
  .tab {
    padding: var(--space-2) var(--space-3);
    border: none;
    border-bottom: 2px solid transparent;
    background: transparent;
    color: var(--color-text-muted);
    font: inherit;
    font-size: 0.875rem;
    cursor: pointer;
    white-space: nowrap;
  }
  .tab:hover { color: var(--color-text); }
  .tab.active { color: var(--color-accent); border-bottom-color: var(--color-accent); font-weight: 600; }
  .tab-panels [data-tab-panel][hidden] { display: none; }

  /* Sidebar tab variant (layouts.SidebarTabs) — Vocabulary's one-tab-
     per-type list got long enough that the horizontal .tab-list above
     needed overflow-x scroll to fit; a nested sidebar column reads
     better than a scrolling strip once there are this many. Same
     tablist/JS wiring as .tab-list, just laid out as a column and given
     its own vertical divider instead of an underline per tab. */
  .tabs--sidebar { display: flex; gap: var(--space-5); align-items: flex-start; }
  .tab-list--sidebar {
    flex: 0 0 190px;
    flex-direction: column;
    gap: var(--space-1);
    border-bottom: none;
    border-right: 1px solid var(--color-border);
    margin-bottom: 0;
    padding-right: var(--space-4);
    overflow-x: visible;
  }
  .tab-list--sidebar .tab {
    text-align: left;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border-bottom: none;
    border-left: 2px solid transparent;
  }
  .tab-list--sidebar .tab:hover { background: var(--color-surface-hover); }
  .tab-list--sidebar .tab.active {
    border-bottom-color: transparent;
    border-left-color: var(--color-accent);
    background: color-mix(in oklch, var(--color-accent), transparent 92%);
  }
  .tabs--sidebar .tab-panels { flex: 1; min-width: 0; }

  @media (max-width: 768px) {
    /* No room for a permanent second sidebar at phone width — falls
       back to the same horizontal-scroll strip .tab-list already uses
       elsewhere, rather than eating most of a narrow screen's width. */
    .tabs--sidebar { flex-direction: column; gap: var(--space-3); }
    .tab-list--sidebar {
      flex-direction: row;
      overflow-x: auto;
      border-right: none;
      border-bottom: 1px solid var(--color-border);
      padding-right: 0;
      padding-bottom: var(--space-2);
    }
    .tab-list--sidebar .tab { border-left: none; border-bottom: 2px solid transparent; }
    .tab-list--sidebar .tab.active { border-left-color: transparent; border-bottom-color: var(--color-accent); background: transparent; }
  }

  .empty-state { color: var(--color-text-muted); padding: var(--space-4) 0; }
  .error-banner { color: var(--color-error); background: color-mix(in oklch, var(--color-error), transparent 90%); border: 1px solid var(--color-error); border-radius: var(--radius-sm); padding: var(--space-2) var(--space-3); margin-bottom: var(--space-3); font-size: 0.875rem; }

  /* One-time success/error confirmation after a redirect — see
     docs/UX_SPEC.md §3.2. Calmer than .error-banner (which is for an
     in-form validation failure the user must fix before resubmitting,
     not a "this already happened" acknowledgement). */
  .flash {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    margin-bottom: var(--space-3);
    font-size: 0.875rem;
  }
  .flash--success { color: var(--color-success); background: color-mix(in oklch, var(--color-success), transparent 92%); border-color: var(--color-success); }
  .flash--error { color: var(--color-error); background: color-mix(in oklch, var(--color-error), transparent 92%); border-color: var(--color-error); }
  .flash button {
    background: none;
    border: none;
    color: inherit;
    font-size: 1rem;
    line-height: 1;
    cursor: pointer;
    padding: 0;
  }

  /* Confirm dialog for destructive actions — see docs/UX_SPEC.md §3.4.
     Native <dialog> already gets correct focus-trapping/Escape-to-close
     from the browser; this is just visual styling on top. */
  dialog.confirm-dialog {
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--space-4);
    max-width: 400px;
    background: var(--color-surface);
    color: var(--color-text);
  }
  dialog.confirm-dialog::backdrop { background: oklch(24% 0.014 50 / 0.4); }
  dialog.confirm-dialog p { margin-top: 0; }
  dialog.confirm-dialog .confirm-dialog-actions { display: flex; gap: var(--space-3); justify-content: flex-end; margin-top: var(--space-4); }

  /* Vocabulary icon-picker modal (internal/views/pages/admin/
     vocabulary.templ) — a grid of every built-in icon, each its own
     one-click submit button, rather than an inline <select> a staff
     member can't preview before choosing. Reuses dialog.confirm-dialog's
     open/close wiring ([data-confirm]/[data-dialog-cancel] in
     scripts.templ already handles any <dialog>, not just confirm-style
     ones), just a wider dialog and its own grid layout. */
  .icon-picker-trigger { color: var(--color-accent); }
  .icon-picker-trigger svg { width: 1.25rem; height: 1.25rem; }
  /* width, not just max-width — a <dialog> with no explicit width
     shrink-wraps to its content's natural size, and an empty
     auto-fill/auto-fit grid has no natural size to wrap to, so without
     this the whole modal (search box included) rendered as a narrow
     sliver instead of using the space max-width allows. */
  dialog.icon-picker-dialog { width: 90vw; max-width: 560px; }
  .icon-picker-search {
    width: 100%;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 0.875rem;
  }
  .icon-picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    gap: var(--space-2);
    max-height: 320px;
    overflow-y: auto;
    margin: var(--space-3) 0;
  }
  .icon-picker-group-heading {
    grid-column: 1 / -1;
    margin: var(--space-2) 0 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
  }
  .icon-picker-group-heading:first-child { margin-top: 0; }
  .icon-picker-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-2);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text-muted);
    font-size: 0.6875rem;
    cursor: pointer;
  }
  .icon-picker-option svg { width: 1.25rem; height: 1.25rem; }
  .icon-picker-option:hover { background: var(--color-surface-hover); color: var(--color-text); }
  /* .icon-picker-option--selected (server-rendered, from a term's
     already-saved icon — vocabIconCell) and .picker-chip--selected
     (client-toggled by the shared [data-picker] JS — the "Add a term"
     icon field, vocabNewTermIconPicker, which has no saved icon to
     render server-side yet) both mark the same visual state on this
     element; kept as one rule so the two pickers can't visually drift. */
  .icon-picker-option--selected, .icon-picker-option.picker-chip--selected { border-color: var(--color-accent); background: color-mix(in oklch, var(--color-accent), white 88%); color: var(--color-text); }
  .icon-picker-option[hidden], .icon-picker-group-heading[hidden] { display: none; }
  .icon-picker-trigger { display: inline-flex; align-items: center; gap: var(--space-2); }

  /* admin.RoomEquipmentQuantityControl — the +/- stepper on a room's
     Equipment table (internal/views/pages/admin/rooms.templ). Reuses
     .icon-picker-search/[data-icon-search] wholesale for the search box
     above the table (same generic label-filtering JS, not exclusive to
     the icon picker despite the name) — this just needs its filtered
     rows (<tr data-icon-option>) to actually disappear, since a plain
     [hidden] table row isn't reliably hidden across browsers' table
     layout algorithms without an explicit rule. */
  tr[data-icon-option][hidden] { display: none; }
  .room-equipment-qty { display: flex; align-items: center; gap: var(--space-2); }
  .room-equipment-qty-value { min-width: 1.5em; text-align: center; font-variant-numeric: tabular-nums; }

  /* Equipment picker (admin.EquipmentPicker on the room edit form, and
     the read-only equivalent on RoomDetail) — a category heading
     followed by that category's items, all flat children of one grid so
     [data-icon-search]'s heading-visibility walk (scripts.templ) keeps
     working unmodified; the grid just wraps items onto as many rows as
     needed instead of a single long list. */
  .equipment-picker-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: var(--space-2) var(--space-3); }
  .equipment-picker-heading {
    grid-column: 1 / -1;
    margin: var(--space-3) 0 0;
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--color-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.02em;
  }
  .equipment-picker-heading:first-child { margin-top: 0; }
  .equipment-picker-heading[hidden] { display: none; }
  .equipment-picker-item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
  }
  .equipment-picker-item[hidden] { display: none; }
  .equipment-picker-icon { flex-shrink: 0; display: flex; color: var(--color-text-muted); }
  .equipment-picker-icon svg { width: 1.25rem; height: 1.25rem; }
  .equipment-picker-name {
    flex: 1;
    min-width: 0;
    font-size: 0.875rem;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: normal;
    overflow-wrap: break-word;
    line-height: 1.25;
  }
  .equipment-picker-item--readonly { background: transparent; }
  .equipment-picker-qty { flex-shrink: 0; font-size: 0.875rem; font-variant-numeric: tabular-nums; color: var(--color-text-muted); }

  /* Mobile QR check-in (docs/QR_CHECKIN.md) — scoped under .qr-page so
     it never affects the desktop admin screens' deliberately tighter
     density (docs/UI_DESIGN.md §2). Every interactive element in here
     targets a >=44px touch target per standard mobile guidance, wider
     than .btn's desktop sizing. More generous spacing than the desktop
     scale throughout, deliberately — a shared tablet is read at arm's
     length, glanced at quickly, and every chip here is a safeguarding-
     relevant write, so legible breathing room matters more than fitting
     everything above the fold. */
  .qr-page { max-width: 480px; margin: 0 auto; padding: var(--space-4); }
  .qr-page .card { margin-bottom: var(--space-6); padding: var(--space-4) var(--space-4) var(--space-5); }
  .qr-checkin-jump { display: flex; width: 100%; margin-bottom: var(--space-6); }
  .qr-session-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
  .qr-session-info { margin-bottom: var(--space-4); }
  .qr-session-meta { color: var(--color-text-muted); font-size: 0.8125rem; margin: 0; }
  .qr-last-logged { color: var(--color-text-muted); font-size: 0.8125rem; margin: var(--space-1) 0 0; }
  .qr-severity-scale-labels { display: flex; justify-content: space-between; font-size: 0.75rem; color: var(--color-text-muted); margin-top: var(--space-1); }
  .qr-chip-section { margin-bottom: var(--space-4); }
  .qr-chip-section:last-of-type { margin-bottom: var(--space-3); }
  .qr-chip-label {
    display: block;
    font-size: 0.75rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--color-text-muted);
    margin-bottom: var(--space-2);
  }
  /* Checking out is a distinct, session-ending action, not just one more
     logging section under Mood/Event — a heavier top border plus its
     own generous padding (rather than the qr-chip-section spacing used
     above it) is deliberate, so it doesn't read as "just another chip
     row" the way a thin border and matching spacing did before. The
     h2 inside gets the same visual weight as "Check in a child"'s own
     h2, for the same reason: this is that action's mirror image, not a
     minor sub-section. */
  .qr-checkout-form {
    border-top: 2px solid var(--color-border);
    padding-top: var(--space-6);
    margin-top: var(--space-6);
  }
  .qr-checkout-submit { width: 100%; margin-top: var(--space-4); }

  .chip-form { display: contents; }
  .chip-grid { display: flex; flex-wrap: wrap; gap: var(--space-3); }
  .chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-height: 44px;
    min-width: 44px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 0.9375rem;
    cursor: pointer;
  }
  .chip:hover { background: var(--color-surface-hover); }
  .chip:active { background: var(--color-surface-active); }
  .chip--sm { min-height: 40px; min-width: 40px; padding: var(--space-1) var(--space-2); font-size: 0.875rem; }

  /* Horizontal-scrolling large-button picker — replaces a plain <select>
     for choosing a child or staff member on the QR check-in page
     (docs/QR_CHECKIN.md): a shared classroom tablet's roster can be long
     enough that a native dropdown becomes a scroll-hunt, and a dropdown
     itself is poor on touch generally (the same reasoning §3.5/UX_SPEC.md
     already gives for mood/event chips over a <select>). A search box
     narrows a long roster; the scroll strip itself needs no scrollbar
     arrows on a touch device, just a swipe. See layouts.SharedScripts'
     [data-picker] wiring. */
  .picker-search { margin-bottom: var(--space-3); }
  .picker-scroll {
    display: flex;
    gap: var(--space-3);
    overflow-x: auto;
    padding-bottom: var(--space-2);
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
  }
  /* [hidden] alone loses to .picker-chip's own `display` below — equal
     specificity (0,1,0 vs 0,1,0), and the author rule is declared after
     the browser's default UA [hidden] rule, so it wins the cascade and
     the element stays visible despite the `hidden` attribute. Same class
     of bug as the .btn hover/variant issue documented above; fixed the
     same way other [hidden]-driven components in this file are (see
     .tab-panels [data-tab-panel][hidden]) — an explicit, equally-specific
     override that actually wins. */
  .picker-chip[hidden] { display: none; }
  /* Same --btn-*-style custom-property routing as §7 describes for
     .btn, and for the same reason: .picker-chip:hover (0,2,0) has
     higher specificity than .picker-chip--selected (0,1,0), so a plain
     `.picker-chip--selected { background: ... }` gets silently
     overridden by :hover the instant a selected chip is hovered —
     background reverts to the unselected hover gray while `color`
     stays --color-accent-content (light text), making the label
     unreadable. Routing every state through one set of custom
     properties means there's only one `background:`/`color:`
     declaration site, so a variant can never lose that race. */
  .picker-chip {
    --picker-bg: var(--color-surface);
    --picker-bg-hover: var(--color-surface-hover);
    --picker-bg-active: var(--color-surface-active);
    --picker-color: var(--color-text);
    --picker-border: var(--color-border);

    flex: 0 0 auto;
    scroll-snap-align: start;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-height: 56px;
    min-width: 88px;
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-md);
    border: 1px solid var(--picker-border);
    background: var(--picker-bg);
    color: var(--picker-color);
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
    white-space: nowrap;
  }
  .picker-chip:hover { background: var(--picker-bg-hover); }
  .picker-chip:active { background: var(--picker-bg-active); }
  .picker-chip--selected {
    --picker-bg: var(--color-accent);
    --picker-bg-hover: var(--color-accent-hover);
    --picker-bg-active: var(--color-accent-active);
    --picker-color: var(--color-accent-content);
    --picker-border: var(--color-accent);
  }
  .picker-empty { color: var(--color-text-muted); font-size: 0.875rem; padding: var(--space-2) 0; }
  /* Same tinted-box treatment as .error-banner (an in-form validation
     failure the user must fix, not a passive hint) — plain small red
     text was too easy to miss on a kiosk tablet, especially once a
     search filter pushed it further down the screen. */
  .picker-error {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--color-error);
    background: color-mix(in oklch, var(--color-error), transparent 90%);
    border: 1px solid var(--color-error);
    border-radius: var(--radius-sm);
    padding: var(--space-2) var(--space-3);
    margin: var(--space-2) 0 0;
    font-size: 0.875rem;
    font-weight: 600;
  }
  /* Without this, [hidden] loses to the `display: flex` above — same
     (0,1,0) specificity, and this rule comes later — so the box stayed
     visible even with the hidden attribute set. See the identical
     .picker-tile[hidden] fix above. */
  .picker-error[hidden] { display: none; }
  .picker-error::before { content: "⚠"; line-height: 1; }

  /* Icon+name tile grid — the redesigned staff/room/child pickers
     (checkinpages.picker), same visual family as the equipment picker's
     own icon+name cards (admin.EquipmentPicker, rooms.templ) instead of
     the old plain-text .picker-chip scroll strip above (still used
     as-is by roomSwitcher's own hand-rolled room links). Wraps onto as
     many rows as needed rather than scrolling horizontally — a roster
     search narrows it down long before scrolling would matter. Reuses
     .picker-chip--selected as its own selected-state class (unchanged
     JS, see layouts.SharedScripts' [data-picker] wiring) rather than
     inventing a second one — a .picker-tile can never actually pick up
     that class's styling on its own, since these two base classes are
     never on the same element as .picker-chip's, but sharing the class
     name means nothing in that shared JS needed to change for this. */
  .picker-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: var(--space-3);
    max-height: 340px;
    overflow-y: auto;
    padding: 2px;
    border-radius: var(--radius-md);
    outline: 2px solid transparent;
    outline-offset: 4px;
    transition: outline-color 0.15s ease;
  }
  /* Toggled by SharedScripts alongside .picker-error, so a required
     field failing validation is obvious at the grid itself — not just
     in the small message below it, which a search filter can push well
     out of view on a long roster. */
  .picker-grid--invalid { outline-color: var(--color-error); }
  .picker-tile[hidden] { display: none; }
  .picker-tile {
    --picker-bg: var(--color-surface);
    --picker-bg-hover: var(--color-surface-hover);
    --picker-bg-active: var(--color-surface-active);
    --picker-color: var(--color-text);
    --picker-border: var(--color-border);

    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 92px;
    padding: var(--space-3) var(--space-2);
    border-radius: var(--radius-md);
    border: 1px solid var(--picker-border);
    background: var(--picker-bg);
    color: var(--picker-color);
    font-size: 0.9375rem;
    font-weight: 500;
    text-align: center;
    cursor: pointer;
  }
  .picker-tile:hover { background: var(--picker-bg-hover); }
  .picker-tile:active { background: var(--picker-bg-active); }
  .picker-tile.picker-chip--selected {
    --picker-bg: var(--color-accent);
    --picker-bg-hover: var(--color-accent-hover);
    --picker-bg-active: var(--color-accent-active);
    --picker-color: var(--color-accent-content);
    --picker-border: var(--color-accent);
  }
  .picker-tile-icon { display: flex; }
  .picker-tile-icon svg { width: 1.75rem; height: 1.75rem; }
  .picker-tile-label { overflow-wrap: anywhere; }

  /* Checkbox/radio-styled chips — a visually hidden native input plus a
     styled sibling label is the accessible way to build a toggle-button
     group: real form-control semantics/keyboard support for free, no
     custom JS state. Checkbox: final-mood picker on checkout (several at
     once). Radio: severity picker inside an event chip's confirm dialog
     (one of 1-5, or none). */
  .chip-checkbox-wrap, .chip-radio-wrap { position: relative; display: inline-flex; }
  .chip-checkbox, .chip-radio {
    position: absolute;
    opacity: 0;
    width: 44px;
    height: 44px;
    margin: 0;
    cursor: pointer;
  }
  .chip-checkbox + .chip, .chip-radio + .chip { pointer-events: none; }
  .chip-checkbox:checked + .chip, .chip-radio:checked + .chip { background: var(--color-accent); color: var(--color-accent-content); border-color: var(--color-accent); }
  .chip-checkbox:focus-visible + .chip, .chip-radio:focus-visible + .chip { outline: 2px solid var(--color-focus); outline-offset: 2px; }

  .pin-input {
    width: 100%;
    min-height: 44px;
    padding: var(--space-2) var(--space-3);
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    font-size: 1.25rem;
    letter-spacing: 0.5em;
    text-align: center;
  }

  /* layouts.PinInput is type="text", not type="password" (see that
     component's own comment for why — sidesteps password-manager
     autofill/save-prompt heuristics entirely rather than fighting them),
     so masking digits from a shared-screen glance is CSS's job instead
     of the browser's. -webkit-text-security is non-standard but the
     de-facto way to do this — supported in Chromium/WebKit (this app's
     kiosk tablets), no equivalent in Firefox, where this silently falls
     back to plain visible digits rather than breaking anything. */
  [data-pin-mask] { -webkit-text-security: disc; }

  .qr-page .btn, .qr-page select, .qr-page input { min-height: 44px; }

  /* Overview dashboard's sessions-per-week trend — hand-rolled CSS bars,
     not a JS charting library (no build step — docs/UI_DESIGN.md §0).
     Bar height is set inline per-value (style="height: N%") since it's
     genuinely data-driven; everything else here is static. */
  .week-chart {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    height: 120px;
  }
  .week-chart-bar {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
  }
  .week-chart-count { font-size: 0.6875rem; color: var(--color-text-muted); margin-bottom: var(--space-1); }
  .week-chart-fill {
    width: 100%;
    min-height: 2px;
    background: color-mix(in oklch, var(--color-accent), transparent 25%);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  }
  .week-chart-label { font-size: 0.6875rem; color: var(--color-text-muted); margin-top: var(--space-2); white-space: nowrap; }
}

@layer base {
  /* .thrive no longer carries its own color tokens — the nature palette
     moved to :root (2026-07-23, applies app-wide) — it just sets the
     kiosk flow's own background/sizing on <body>. */
  .thrive {
    background: var(--color-bg);
    color: var(--color-text);
    min-height: 100vh;
    /* Mobile WebKit/Chromium's default tap-highlight rectangle (a
       translucent blue-grey box on any tap) is a separate mechanism from
       the :focus-visible outline removed elsewhere in this file — it
       fires on every tap regardless of focus state, which on this
       touchscreen-kiosk flow made every tile tap (equipment, mood,
       impact, ...) flash a "blue border" no CSS focus rule was
       producing. Disabled for the whole kiosk shell, not just the
       option tiles, since it'd look just as out of place on any other
       tappable element here (buttons, picker chips). */
    -webkit-tap-highlight-color: transparent;
  }
}

@layer components {
  /* Thrive layout shell — header (logo + status), hero (icon + heading),
     and the shared "big tappable tile" pattern used on both the landing
     screen (Scan QR Code / Select Child) and every wizard step's option
     grid. Deliberately generous touch targets throughout — same
     reasoning as .qr-page (docs/QR_CHECKIN.md): read at arm's length on
     a shared tablet, tapped quickly, safeguarding-relevant data. */
  .thrive-page { max-width: 640px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-6); }
  .thrive-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-6); }
  .thrive-brand { display: flex; align-items: center; gap: var(--space-2); }
  .thrive-brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    border-radius: 999px;
    background: color-mix(in oklch, var(--color-accent), transparent 82%);
    color: var(--color-accent);
  }
  .thrive-brand-mark svg { width: 1.25rem; height: 1.25rem; }
  .thrive-brand-name { font-weight: 700; line-height: 1.1; color: var(--color-text); }
  .thrive-brand-name small { display: block; font-weight: 500; font-size: 0.75rem; color: var(--color-text-muted); }
  .thrive-status { display: flex; align-items: center; gap: var(--space-2); font-size: 0.8125rem; color: var(--color-text-muted); }
  .thrive-status svg { width: 1rem; height: 1rem; }

  .thrive-hero { text-align: center; margin-bottom: var(--space-6); }
  .thrive-hero-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 4rem;
    height: 4rem;
    border-radius: 999px;
    background: color-mix(in oklch, var(--color-accent), transparent 85%);
    color: var(--color-accent);
    margin-bottom: var(--space-4);
  }
  .thrive-hero-icon svg { width: 2rem; height: 2rem; }
  .thrive-hero h1 { font-size: 1.75rem; margin: 0; color: var(--color-text); }
  .thrive-hero p { color: var(--color-text-muted); margin: var(--space-2) 0 0; }

  /* The landing screen's two big entry tiles — "Scan QR Code" (filled,
     primary) and "Select Child" (soft/secondary), same visual weight as
     each other since either is a complete, valid way in. */
  .thrive-entry-grid { display: flex; align-items: center; gap: var(--space-4); margin-bottom: var(--space-6); }
  .thrive-entry-or { color: var(--color-text-muted); font-size: 0.875rem; flex: 0 0 auto; }
  .thrive-tile {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    min-height: 160px;
    padding: var(--space-5) var(--space-3);
    border-radius: var(--radius-md);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    font-weight: 600;
    font-size: 1.0625rem;
    cursor: pointer;
    text-align: center;
    text-decoration: none;
  }
  .thrive-tile svg { width: 2.5rem; height: 2.5rem; }
  .thrive-tile--primary { background: var(--color-accent); border-color: var(--color-accent); color: var(--color-accent-content); }
  .thrive-tile--primary:hover { background: var(--color-accent-hover); }
  .thrive-tile--secondary { background: color-mix(in oklch, var(--color-accent), white 82%); border-color: color-mix(in oklch, var(--color-accent), transparent 60%); }
  .thrive-tile--secondary:hover { background: color-mix(in oklch, var(--color-accent), white 75%); }
  .thrive-tile:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 2px; }

  .thrive-stat-row { display: flex; border-top: 1px solid var(--color-border); padding-top: var(--space-5); }
  .thrive-stat { flex: 1; text-align: center; }
  .thrive-stat + .thrive-stat { border-left: 1px solid var(--color-border); }
  .thrive-stat-icon { color: var(--color-accent); margin-bottom: var(--space-2); }
  .thrive-stat-icon svg { width: 1.25rem; height: 1.25rem; }
  .thrive-stat-label { font-size: 0.8125rem; color: var(--color-text-muted); margin-bottom: var(--space-1); }
  .thrive-stat-value { font-size: 1.375rem; font-weight: 700; color: var(--color-text); }

  /* Wizard chrome — a progress bar (steps filled so far) plus the
     back/next footer shared by every question screen. */
  .thrive-wizard-header { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-5); }
  .thrive-wizard-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 999px;
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    flex: 0 0 auto;
  }
  .thrive-wizard-back svg { width: 1.375rem; height: 1.375rem; }
  .thrive-wizard-progress { flex: 1; display: flex; gap: var(--space-1); }
  .thrive-wizard-progress-step { flex: 1; height: 6px; border-radius: 999px; background: var(--color-border); }
  .thrive-wizard-progress-step--done { background: var(--color-accent); }
  .thrive-wizard-count { flex: 0 0 auto; font-size: 0.8125rem; color: var(--color-text-muted); white-space: nowrap; }

  .thrive-question { font-size: 1.375rem; font-weight: 700; margin: 0 0 var(--space-5); color: var(--color-text); }

  /* Big icon-tile options — the primary interaction this whole flow is
     built around (docs/PRODUCT_SPEC.md's MVP redesign brief): a large
     tappable card per choice, selected state shown with a filled accent
     border/background rather than a checkbox, so the current answer is
     legible at a glance on a shared tablet. Real <input type=radio>/
     checkbox underneath for accessible keyboard/AT support, same
     hidden-input-plus-styled-label mechanism .chip-checkbox-wrap already
     uses — see thrive.js below for the "tap tile -> Next enabled" wiring. */
  .thrive-option-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: var(--space-3); margin-bottom: var(--space-6); }
  /* .thrive-option-wrap, not <label>, wraps each input+tile pair — this
     codebase already hit (and fixed, see .picker-chip[hidden] above) the
     bug where a global `label { display: block }` author rule beats the
     browser's own `[hidden]` UA rule, so a hidden <label> stays visible.
     A plain <span> wrapper (same mechanism .chip-checkbox-wrap already
     uses) has no such global override, and gets an explicit [hidden]
     rule below anyway since it's relied on by the "greatest impact"
     step's dynamic equipment filtering (see layouts/scripts.templ's
     [data-wizard] behavior). */
  .thrive-option-wrap { position: relative; display: block; }
  .thrive-option-wrap[hidden] { display: none; }
  .thrive-option-input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; z-index: 1; }
  .thrive-option-input + .thrive-option { pointer-events: none; width: 100%; }
  .thrive-option {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 108px;
    padding: var(--space-4) var(--space-2);
    border-radius: var(--radius-md);
    border: 2px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    font-weight: 600;
    font-size: 0.9375rem;
    text-align: center;
    cursor: pointer;
    /* --chip-accent (equipment_categories.color/equipment_catalog.color,
       set inline per chip — see equipmentChipAccent in wizard.templ) is
       deliberately a thin left-edge stripe, not a full accent swap —
       AGENTS.md §3 item 2 keeps one accent color (green) per screen, so
       this only ever category-codes a chip at a glance without
       competing with the selected-state green below. Falls back to the
       ordinary border color when unset (a custom item with no
       category), so nothing changes for the common case. */
    border-left-width: 3px;
    border-left-color: var(--chip-accent, var(--color-border));
  }
  .thrive-option svg { width: 1.75rem; height: 1.75rem; color: var(--color-accent); }
  .thrive-option:hover { background: var(--color-surface-hover); }
  .thrive-option-input:checked + .thrive-option {
    border-color: var(--color-accent);
    background: color-mix(in oklch, var(--color-accent), white 85%);
  }
  /* No focus-visible outline here (unlike most other interactive
     elements, see the shared a/button/input :focus-visible rule near the
     top of this file) — Chrome and Safari both treat any tap on a
     checkbox/radio as "focus-visible" regardless of input modality,
     which turned every touch/click on this kiosk's own on-screen tiles
     into a stray blue box around the tile. The :checked style below is
     already the tile's real "this is selected" indicator. */

  .thrive-other-field { margin: calc(var(--space-3) * -1) 0 var(--space-6); }

  /* Touch-friendly end-time stepper (wizard.templ's [data-timepicker]) —
     date is fixed (sessions can't run longer than 8 hours), so only
     hour/minute need adjusting, via large tap targets rather than the
     native datetime-local widget's fiddly tiny spinners. */
  .thrive-timepicker { display: flex; align-items: center; justify-content: center; gap: var(--space-2); margin: var(--space-2) 0 var(--space-6); }
  .thrive-timepicker-unit { display: flex; flex-direction: column; align-items: center; gap: var(--space-2); }
  .thrive-timepicker-btn {
    width: 48px;
    height: 48px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--color-border);
    background: var(--color-surface);
    color: var(--color-text);
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
  }
  .thrive-timepicker-btn:hover { background: var(--color-surface-hover); }
  .thrive-timepicker-btn:active { background: color-mix(in oklch, var(--color-accent), white 85%); }
  .thrive-timepicker-display { min-width: 2.5ch; font-size: 1.5rem; font-weight: 600; font-variant-numeric: tabular-nums; text-align: center; }
  .thrive-timepicker-colon { font-size: 1.5rem; font-weight: 600; color: var(--color-text-muted); align-self: center; }

  .thrive-wizard-footer { display: flex; gap: var(--space-3); }
  .thrive-wizard-footer .btn { flex: 1; min-height: 52px; font-size: 1rem; }

  /* Docked bottom bar on mobile/tablet — Prev/Next stay reachable with
     zero scrolling no matter how many options a step has (some questions
     have well over a screenful, e.g. visit reason / equipment), rather
     than sitting inline after the last option where a long list pushes
     them off-screen. Left at the inline layout above on desktop/wide
     tablet, where a step's content plus footer already fits without
     scrolling being the bottleneck. Only the visible step's footer is
     ever fixed — the other six are inside a sibling <section hidden>,
     which display:none already removes from layout entirely. */
  @media (max-width: 768px) {
    .thrive-wizard-footer {
      position: fixed;
      bottom: 0;
      left: 0;
      right: 0;
      max-width: 640px;
      margin: 0 auto;
      background: var(--color-surface);
      border-top: 1px solid var(--color-border);
      padding: var(--space-3) var(--space-4);
      padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom));
      box-shadow: 0 -2px 8px oklch(24% 0.014 50 / 0.1);
      z-index: 20;
    }
    /* Clearance so the docked bar never covers a step's last field/option
       — generous enough to also cover env(safe-area-inset-bottom) on
       notch/home-indicator devices, where the bar itself grows taller. */
    [data-wizard-step] { padding-bottom: 112px; }
  }

  .thrive-complete { text-align: center; padding: var(--space-6) 0; }
  .thrive-complete-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 5rem;
    height: 5rem;
    border-radius: 999px;
    background: color-mix(in oklch, var(--color-success), white 80%);
    color: var(--color-success);
    margin-bottom: var(--space-5);
  }
  .thrive-complete-icon svg { width: 2.5rem; height: 2.5rem; }
  .thrive-complete h1 { margin: 0 0 var(--space-2); }
  .thrive-complete p { color: var(--color-text-muted); margin: 0 0 var(--space-6); }

  /* Paired kiosk device's "change room" escape hatch (checkin.templ's
     roomSwitcher) — a floating button so it never competes with the
     actual check-in form for attention, since switching room is a rare,
     secondary action on a device that's otherwise fixed to one room. */
  .thrive-switch-room-btn {
    position: fixed;
    right: var(--space-5);
    bottom: var(--space-5);
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: 0 2px 8px oklch(24% 0.014 50 / 0.15);
    font-size: 0.875rem;
    cursor: pointer;
    z-index: 10;
  }
  .thrive-switch-room-btn svg { width: 1.125rem; height: 1.125rem; color: var(--color-accent); }
  .thrive-switch-room-btn:hover { background: var(--color-surface-hover); }

  /* End-of-session wizard's floating "exit to room" shortcut
     (checkin.templ's floatingBackToRoom) — icon-only (the label is
     aria-label, not visible text) and bottom-LEFT specifically so it
     never collides with roomSwitcher's bottom-right "change room" button
     above, even though the two don't currently appear on the same
     screen. Same fixed/shadow treatment as that button, just circular
     since there's no label text to give it a pill shape. */
  .thrive-back-to-room-btn {
    position: fixed;
    left: var(--space-5);
    bottom: var(--space-5);
    display: flex;
    align-items: center;
    justify-content: center;
    width: 48px;
    height: 48px;
    border: 1px solid var(--color-border);
    border-radius: 999px;
    background: var(--color-surface);
    color: var(--color-text);
    box-shadow: 0 2px 8px oklch(24% 0.014 50 / 0.15);
    z-index: 10;
  }
  .thrive-back-to-room-btn svg { width: 1.25rem; height: 1.25rem; color: var(--color-accent); }
  .thrive-back-to-room-btn:hover { background: var(--color-surface-hover); }

  /* "Active now" list on the select-child screen — a full-card link per
     session rather than a single-line button-list, so name/status/
     elapsed time each get their own visual weight instead of being
     squeezed into one line of button text. */
  .thrive-active-list { margin-bottom: var(--space-5); }
  .thrive-active-heading { font-size: 1rem; margin: 0 0 var(--space-3); color: var(--color-text); }
  .thrive-active-card {
    display: block;
    padding: var(--space-4);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-sm);
    background: var(--color-surface);
    color: var(--color-text);
    text-decoration: none;
    margin-bottom: var(--space-3);
  }
  .thrive-active-card:hover { background: var(--color-surface-hover); border-color: var(--color-accent); }
  .thrive-active-card:last-child { margin-bottom: 0; }
  .thrive-active-card-main { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); margin-bottom: var(--space-1); }
  .thrive-active-card-main strong { font-size: 1.0625rem; }
  .thrive-active-card-meta { font-size: 0.8125rem; color: var(--color-text-muted); }

  /* Scan QR Code / Select Room stay side by side down to phone widths —
     a stacked column here (the old behavior below 480px) turns a
     glanceable two-option choice into something that needs scrolling,
     which is exactly what this flow is trying to avoid on a shared
     kiosk. Just tightened up (smaller icon/padding/min-height) instead,
     so two tiles plus the "or" label still fit comfortably at ~375px. */
  @media (max-width: 768px) {
    .thrive-tile { min-height: 128px; padding: var(--space-4) var(--space-2); font-size: 0.9375rem; gap: var(--space-2); }
    .thrive-tile svg { width: 2rem; height: 2rem; }
    .thrive-entry-grid { gap: var(--space-3); }
  }
}
