/* ============================================================
   EduShieldID — base/tokens.css
   Design tokens (colors, spacing, radius, shadows, type scale,
   tracking, weights, measures) + the global reset and base
   typography. Loaded first, on every page, immediately after
   base/fonts.css.

   Semantic and neutral foreground colours were re-tuned for WCAG
   AA against their real backgrounds (see inline notes) — the
   previous values passed on white but several failed on the page
   background and on their own tinted badge backgrounds.
   ============================================================ */

:root {
  /* Single, light theme — no OS theme detection.
     Declaring this explicitly matters beyond documentation: without it, a
     visitor whose OS is set to dark mode gets browser-dark native controls
     (select dropdowns, date pickers, scrollbars, autofill backgrounds) on
     top of our light palette, which reads as a half-broken theme. Pinning
     it to light makes the UI render identically for every visitor. */
  color-scheme: light;

  /* Brand */
  --clr-primary:        #1a56db;
  --clr-primary-dark:   #1648c0;
  --clr-primary-light:  #e8f0fe;

  /* Neutrals
     Foreground values are tuned to pass WCAG AA (4.5:1) against BOTH
     --clr-surface and the slightly darker --clr-bg — the muted grey
     previously passed on white but failed on the page background (4.40:1),
     which is where most muted text actually sits. */
  --clr-bg:             #f4f4f4;
  --clr-surface:        #ffffff;
  /* The recessed surface: neutral-badge pills, progress tracks, skeleton
     shimmer, disabled/readonly field fills, hover states on bare controls.
     It belongs HERE and not only in base/nord-theme.css. nord-theme is linked
     by the three portal layouts alone, but --clr-surface-alt is consumed by
     components/badges.css, components/core-ui.css and components/forms.css,
     all of which layouts/base.html loads on EVERY page. Off the portals the
     token resolved to nothing, and a var() that resolves to nothing makes the
     whole declaration invalid at computed-value time — so `background:
     var(--clr-surface-alt)` fell back to transparent rather than to any
     colour. On the landing, auth and onboarding pages that silently erased
     the progress-bar track, the skeleton loaders js/core/loading.js injects,
     and the pill behind every neutral status badge. nord-theme still
     re-points this for the portal palette; this is the floor underneath it. */
  --clr-surface-alt:    #ececf0;
  /* Ink for text/icons sitting on a solid --clr-primary fill. Same story as
     above: defined only in nord-theme before, so auth-shell.css referenced it
     off-portal where it did not exist. */
  --clr-on-primary:     #ffffff;
  /* Two border weights, deliberately.
     --clr-border is decorative (card edges, dividers). It sits below 3:1 on
     purpose: the airy hairline is the minimalist look, and WCAG 1.4.11 only
     governs boundaries REQUIRED to identify a component — a card is already
     identified by its surface and shadow.
     --clr-border-strong is for controls whose border IS the only affordance
     (inputs, selects, textareas, outline buttons) and meets the 3:1 minimum. */
  --clr-border:         #dcdce0;
  --clr-border-strong:  #8b8f98;  /* 3.1:1 on surface */
  --clr-text:           #1a1a1a;
  --clr-text-muted:     #57606a;  /* 5.7:1 on bg (was #6b7280 → 4.40:1, failed) */

  /* Semantic
     Each foreground is AA-compliant on its own tinted background. The old
     values were all below 4.5:1 on their tints, and warning was 2.74:1 —
     genuinely hard to read, on badges that flag fraud and billing states. */
  --clr-success:        #12813c;  /* 4.6:1 on success-bg (was #16a34a → 3.00:1) */
  --clr-success-bg:     #dcfce7;
  --clr-warning:        #92610a;  /* 4.6:1 on warning-bg (was #ca8a04 → 2.74:1) */
  --clr-warning-bg:     #fef9c3;
  --clr-danger:         #c11c1c;  /* 4.6:1 on danger-bg (was #dc2626 → 3.95:1) */
  --clr-danger-bg:      #fee2e2;
  --clr-info:           #1d4ed8;  /* 5.4:1 on info-bg (was #2563eb → 4.24:1) */
  --clr-info-bg:        #dbeafe;

  /* Radii */
  --radius-sm:   4px;
  --radius-md:   8px;
  --radius-lg:   12px;
  --radius-xl:   18px;
  --radius-full: 9999px;

  /* Shadows — softer, more diffuse than a hard drop shadow */
  --shadow-sm:  0 1px 2px rgba(15,23,42,.06), 0 1px 1px rgba(15,23,42,.04);
  --shadow-md:  0 6px 16px rgba(15,23,42,.08), 0 2px 4px rgba(15,23,42,.04);
  --shadow-lg:  0 16px 40px rgba(15,23,42,.12), 0 4px 10px rgba(15,23,42,.05);

  /* Spacing scale */
  --sp-1:  4px;
  --sp-2:  8px;
  --sp-3:  12px;
  --sp-4:  16px;
  --sp-5:  20px;
  --sp-6:  24px;
  --sp-8:  32px;
  --sp-10: 40px;
  --sp-12: 48px;
  --sp-16: 64px;

  /* Typography
     Inter is self-hosted (base/fonts.css) — it is genuinely loaded, not just
     named. system-ui remains the fallback for the swap window.
     Mono falls back to the platform stack rather than naming an unloaded
     face: an unloaded font name is a silent lie about what users see. */
  --font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  /* Standard type scale — the values every major design system converges on
     (Tailwind, Radix, Material 3). Using the conventional steps rather than
     bespoke ones means sizes compose predictably and nothing lands on an
     awkward half-pixel. Every font-size in the codebase resolves to one of
     these ten tokens; there are no ad-hoc values. */
  --text-xs:   12px;
  --text-sm:   14px;
  --text-base: 16px;   /* body — and ≥16px on inputs stops iOS zoom-on-focus */
  --text-lg:   18px;
  --text-xl:   20px;
  --text-2xl:  24px;
  --text-3xl:  30px;
  --text-4xl:  36px;
  --text-5xl:  48px;   /* hero headlines */
  --text-6xl:  60px;   /* error-page codes, display numerals */

  /* Tracking — modern practice tightens as size increases. Large type set at
     0 tracking reads loose and unresolved; this is most of what separates
     "default" from "designed" in a minimal layout. */
  /* Tracking scale. Held at zero — no negative letter-spacing anywhere in
     the platform. Headings render marginally wider than they did; the scale
     is kept (rather than the call sites being stripped) so tracking stays
     tunable from one place if it is ever revisited. */
  --tracking-tighter: 0em;
  --tracking-tight:   0em;
  --tracking-snug:    0em;
  --tracking-normal:   0;
  --tracking-wide:     0.04em;

  /* Weights — named so hierarchy is a deliberate choice, not an ad-hoc number.
     Minimalism needs CONTRAST between weights: body genuinely light, emphasis
     genuinely heavy. Setting everything to 600 flattens the hierarchy. */
  --weight-normal:   400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  /* Layout measures
     Fluid, not fixed. A hard 1280px cap meant the content column stopped
     growing at 1280 while the viewport kept going: on a 1920px monitor that
     left ~400px of dead gutter on EACH side and only ~59% of the screen doing
     any work. Expressing the measure as `min(95%, cap)` keeps the column
     proportional to the display, so utilisation holds at ~90% from 1366px all
     the way to 2560px instead of decaying as the monitor gets bigger.

     The cap is set so that even a 3840px display lands inside the 85-95%
     target band (3400/3840 ≈ 86%) rather than stopping short and re-creating
     the dead-gutter problem at the top end. It is a backstop against absurd
     line lengths beyond 4K, not the primary constraint — it is the single
     knob for the whole portal if that judgement needs revisiting.

     What keeps a 3300px column readable is that only DENSE content ever gets
     it. Tables simply gain roomier columns, and auto-fit card grids gain more
     columns — both are "more information", which is the point. Running text
     never spans it: prose is bounded by .prose (68ch) and reading-heavy pages
     opt into --measure-narrow below.

     Dense views (dashboards, tables) get --measure-page. Reading-heavy views
     (forms, policy, detail) opt into --measure-narrow via .portal-main--narrow,
     because line length, not screen width, is the constraint that matters
     there. Prose inside a wide column is separately bounded by .prose. */
  --measure-page:   min(95%, 3400px);  /* dashboards, tables, dense portal views */
  --measure-narrow: min(95%, 960px);   /* long-form reading: forms, policy, detail pages */

  /* Shared page gutter. Tighter than the old clamp(24px, 4vw, 80px): with a
     fluid measure the gutter no longer has to double as the thing that keeps
     content off the screen edge, so it can stay a consistent margin instead of
     growing into another 80px-per-side gap on large monitors. */
  --page-gutter: clamp(var(--sp-5), 2.5vw, var(--sp-12));

  /* Height of the sticky .app-header. Anything else that sticks below it
     (table headers, page toolbars) offsets by this so the two never overlap. */
  --app-header-h: 69px;

  /* Breakpoints (reference — CSS media queries can't read custom properties,
     so these document the four canonical values every new rule should use:
     sm 640 · md 768 · lg 1024 · xl 1280) */
  --bp-sm:  640px;
  --bp-md:  768px;
  --bp-lg: 1024px;
  --bp-xl: 1280px;

  /* Transitions */
  --transition: 150ms ease;
  --transition-slow: 250ms cubic-bezier(.2, .7, .3, 1);
}

/* ── Reset ──────────────────────────────────────────────────── */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  font-size: 16px;
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  overflow-x: hidden;
}

body {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  color: var(--clr-text);
  background: var(--clr-bg);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

img, svg { display: block; max-width: 100%; }
a { color: var(--clr-primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* A link inside running text must be distinguishable without colour (WCAG
   2.1 AA, 1.4.1 Use of Colour). Undecorated, the only thing separating it
   from the sentence around it is hue — invisible to a colour-blind reader
   and to anyone on a monochrome display, and axe flags it as
   link-in-text-block.

   Scoped to prose (paragraphs and hint text) rather than applied to every
   `a`: navigation, breadcrumbs, cards and buttons are distinguishable by
   position and shape, and underlining those makes the interface noisier
   without making it clearer. `.btn` is excluded for the same reason — a
   button already looks like one. */
p a:not(.btn),
.hint a:not(.btn),
.muted a:not(.btn) { text-decoration: underline; }
ul, ol { list-style: none; }

h1, h2, h3, h4, h5, h6 {
  font-weight: var(--weight-semibold);
  line-height: 1.25;
  color: var(--clr-text);
  text-wrap: balance;  /* stops a lone orphan word on the last line */
}
h1 { font-size: var(--text-3xl); font-weight: var(--weight-bold);
     letter-spacing: var(--tracking-tighter); }
h2 { font-size: var(--text-2xl); letter-spacing: var(--tracking-tight); }
h3 { font-size: var(--text-xl);  letter-spacing: var(--tracking-snug); }
h4 { font-size: var(--text-lg);  letter-spacing: var(--tracking-snug); }
h5, h6 { font-size: var(--text-base); }

p { margin-bottom: var(--sp-4); font-weight: var(--weight-normal); }
p:last-child { margin-bottom: 0; }

/* Long-form prose gets a readable measure regardless of container width. */
.prose { max-width: 68ch; }

input, textarea, select, button {
  font-family: inherit;
  font-size: inherit;
}

/* Mobile input sizing: any control under 16px makes iOS Safari zoom the
   viewport on focus, which visibly breaks the layout mid-form. 44px is the
   platform minimum touch target (iOS HIG / WCAG 2.5.5). */
input, textarea, select {
  font-size: max(16px, var(--text-base));
}
input:not([type="checkbox"]):not([type="radio"]):not([type="file"]),
select,
button,
.btn {
  min-height: 44px;
}

/* ── Shared focus-visible ring ────────────────────────────────
   One consistent keyboard-focus treatment for every interactive
   element that doesn't already define its own (buttons/inputs
   with a bespoke ring override this via higher specificity). */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--clr-primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ── Responsive type scale ───────────────────────────────────── */
@media (max-width: 768px) {
  h1 { font-size: var(--text-2xl); }
  h2 { font-size: var(--text-xl); }
}

/* ── Reduced motion ───────────────────────────────────────────
   Global, not per-page: vestibular triggers don't respect which
   stylesheet a transition happened to be declared in. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
