/* ============================================================
   Profyl — design tokens, v3 (Direction A, Lock-in)

   The plain-CSS core. docs/brand-v3/tokens.json is the source of every
   value in the first half of this file; `npm run lint:contrast` (frontend)
   fails the build if one drifts. The Tailwind layer (utilities, type
   classes, keyframes) belongs to the web app, not here: the admin portal and
   the website read this file as plain CSS.

   Read by: frontend (copied in by scripts/sync-design-system.js), admin
   (imported), profyl-website (copied by its sync:css), the docs HTML, and
   the print path — a resume's PDF is this file and components.css.

   The file has four parts:
     1. THE CORE        palette, roles per theme, type, space, motion, layout
     2. COMPATIBILITY   every v2 name a consumer still uses, pointed at the
                        nearest v3 role (migration-plan.md, "Alias mapping").
                        Temporary: session 5 deletes them.
     3. DOCUMENTS       the resume and cover letter, frozen at v2 (below)
     4. GLOBAL BASE

   Architecture note, unchanged from v2: modes are token presets, not
   component variants. No component should ever branch on mode.
   ============================================================ */


/* ============================================================
   1. THE CORE
   ============================================================ */

:root {
  color-scheme: light;

  /* ---------- palette (brand-identity.md §4) ---------- */
  --color-white:#FFFFFF; --color-black:#000000;

  /* Primary. 600 is the brand cobalt. */
  --color-cobalt-50:#EEF2FF;  --color-cobalt-100:#DFE6FF; --color-cobalt-200:#C0CDFF;
  --color-cobalt-300:#96A9FF; --color-cobalt-400:#6680FF; --color-cobalt-500:#3D5AFE;
  --color-cobalt-600:#2541E8; --color-cobalt-700:#1B31BF; --color-cobalt-800:#172893;
  --color-cobalt-900:#121E6B; --color-cobalt-950:#0C1445;

  /* Accent. Fills and markers only, never text on light. No dark steps on
     purpose: below 500 it turns olive. */
  --color-volt-50:#FBFFE5;  --color-volt-100:#F4FFC2; --color-volt-200:#EAFF8F;
  --color-volt-300:#DDFB57; --color-volt-400:#CCF22E; --color-volt-500:#B2D914;

  /* Secondary. The dark brand surface: hero bands, splash, store art. */
  --color-ink-600:#2E3A73; --color-ink-700:#1F2856; --color-ink-800:#141B3D; --color-ink-900:#0B1026;

  /* Neutrals. */
  --color-steel-50:#F6F7F9;  --color-steel-100:#ECEEF2; --color-steel-200:#DCE0E7;
  --color-steel-300:#C2C8D2; --color-steel-400:#8B93A1; --color-steel-500:#5F6777;
  --color-steel-600:#4B5363; --color-steel-700:#373D4A; --color-steel-800:#252A33;
  --color-steel-850:#1E222B; --color-steel-900:#161A21; --color-steel-950:#0E1116;

  /* ---------- roles, light ----------
     What code uses. Each switches with the theme, below. Ratios are
     computed by `npm run lint:contrast`, not recorded here. */
  --pf-bg:#F6F7F9;
  --pf-surface:#FFFFFF;
  --pf-surface-raised:#FFFFFF;
  --pf-surface-sunken:#ECEEF2;
  --pf-fg:#161A21;
  --pf-fg-secondary:#4B5363;
  --pf-fg-muted:#5F6777;
  --pf-border:#DCE0E7;          /* decorative only */
  --pf-border-strong:#C2C8D2;   /* decorative only */
  --pf-border-input:#5F6777;    /* the 3:1 boundary */
  --pf-primary:#2541E8;
  --pf-primary-hover:#1B31BF;
  --pf-primary-pressed:#172893;
  --pf-on-primary:#FFFFFF;
  --pf-primary-text:#2541E8;
  --pf-primary-soft:#EEF2FF;
  --pf-on-primary-soft:#1B31BF;
  --pf-accent:#CCF22E;
  --pf-on-accent:#0B1026;
  --pf-accent-soft:#F4FFC2;
  --pf-link:#2541E8;
  --pf-focus:#2541E8;
  --pf-inverse-bg:#0B1026;
  --pf-inverse-fg:#FFFFFF;
  --pf-success:#0D7A55; --pf-success-bg:#E4F7EE; --pf-success-border:#7FD3AE;
  --pf-success-solid:#0D7A55; --pf-on-success-solid:#FFFFFF;
  --pf-warning:#8A5800; --pf-warning-bg:#FFF5D9; --pf-warning-border:#F2B230;
  --pf-warning-solid:#F2B230; --pf-on-warning-solid:#0B1026;
  --pf-error:#C8102E; --pf-error-bg:#FFEBEE; --pf-error-border:#F5A3B0;
  --pf-error-solid:#C8102E; --pf-on-error-solid:#FFFFFF;
  --pf-info:#1B31BF; --pf-info-bg:#EEF2FF; --pf-info-border:#C0CDFF;
  --pf-info-solid:#2541E8; --pf-on-info-solid:#FFFFFF;
  /* Gap analysis (§4): matched is Volt with an icon and a label, close is
     amber, to-address is a dashed neutral outline. Gaps are never red. */
  --pf-match:#CCF22E; --pf-on-match:#0B1026;
  --pf-partial:#8A5800; --pf-partial-bg:#FFF5D9;
  --pf-gap-border:#5F6777;
  --pf-logo-stem:#0B1026; --pf-logo-bowl:#2541E8;
  --pf-scan:#2541E8;
  --pf-scrim:rgba(11, 16, 38, 0.48);

  /* Cool-tinted in light. Cards rest on sm, hover one step up; menus md,
     modals lg, toasts xl. */
  --pf-shadow-xs:0 1px 2px rgba(14, 17, 22, 0.06);
  --pf-shadow-sm:0 1px 2px rgba(14, 17, 22, 0.06), 0 1px 1px rgba(14, 17, 22, 0.04);
  --pf-shadow-md:0 4px 12px -2px rgba(14, 17, 22, 0.10), 0 2px 4px rgba(14, 17, 22, 0.04);
  --pf-shadow-lg:0 16px 36px -10px rgba(14, 17, 22, 0.22), 0 4px 8px rgba(14, 17, 22, 0.06);
  --pf-shadow-xl:0 28px 64px -16px rgba(14, 17, 22, 0.28), 0 8px 16px rgba(14, 17, 22, 0.06);

  /* ---------- type (§5) ----------
     The family names lead with the ones @fontsource-variable registers
     ("… Variable"); the bare names follow for a page that self-hosts the
     faces another way (the website's fonts.css). Mono lists Archivo second:
     JetBrains Mono has no rupee sign. */
  --font-sans:"Archivo Variable","Archivo",ui-sans-serif,system-ui,sans-serif;
  --font-display:"Archivo Variable","Archivo",ui-sans-serif,system-ui,sans-serif;
  --font-mono:"JetBrains Mono Variable","JetBrains Mono","Archivo Variable","Archivo",ui-monospace,SFMono-Regular,Menlo,monospace;

  --font-weight-regular:400; --font-weight-medium:500; --font-weight-semibold:600;
  --font-weight-strong:650;  --font-weight-bold:700;   --font-weight-extrabold:800;

  /* Archivo's width axis, through font-stretch. Width goes up with size. */
  --font-width-normal:100%; --font-width-heading:104%; --font-width-overline:108%; --font-width-display:112%;

  /* The scale, as plain values: --type-<style>-size / -leading / -weight /
     -width / -tracking. Not --text-*: in v2 that prefix carried colours
     (--text, --text-body), and Tailwind owns it for sizes, so the web app's
     theme layer maps these onto --text-* itself. */
  --type-display-xl-size:clamp(2.75rem, 1.6rem + 4.6vw, 4.5rem); --type-display-xl-leading:1.05; --type-display-xl-weight:800; --type-display-xl-width:112%; --type-display-xl-tracking:-0.03em;
  --type-display-lg-size:clamp(2.25rem, 1.5rem + 3.2vw, 3.5rem); --type-display-lg-leading:1.07; --type-display-lg-weight:800; --type-display-lg-width:112%; --type-display-lg-tracking:-0.025em;
  --type-display-md-size:clamp(2rem, 1.55rem + 1.9vw, 2.75rem);  --type-display-md-leading:1.09; --type-display-md-weight:700; --type-display-md-width:108%; --type-display-md-tracking:-0.02em;
  --type-h1-size:clamp(1.75rem, 1.5rem + 1vw, 2.25rem);          --type-h1-leading:1.22; --type-h1-weight:700; --type-h1-width:104%; --type-h1-tracking:-0.02em;
  --type-h2-size:clamp(1.5rem, 1.4rem + 0.5vw, 1.75rem);         --type-h2-leading:1.29; --type-h2-weight:700; --type-h2-width:100%; --type-h2-tracking:-0.015em;
  --type-h3-size:clamp(1.25rem, 1.2rem + 0.25vw, 1.375rem);      --type-h3-leading:1.36; --type-h3-weight:650; --type-h3-width:100%; --type-h3-tracking:-0.01em;
  --type-h4-size:1.125rem;                                       --type-h4-leading:1.44; --type-h4-weight:650; --type-h4-width:100%; --type-h4-tracking:-0.005em;
  --type-body-lg-size:clamp(1.0625rem, 1rem + 0.25vw, 1.125rem); --type-body-lg-leading:1.56; --type-body-lg-weight:400; --type-body-lg-width:100%; --type-body-lg-tracking:0em;
  --type-body-size:1rem;                                         --type-body-leading:1.5;  --type-body-weight:400; --type-body-width:100%; --type-body-tracking:0em;
  --type-body-sm-size:0.875rem;                                  --type-body-sm-leading:1.43; --type-body-sm-weight:400; --type-body-sm-width:100%; --type-body-sm-tracking:0.005em;
  --type-label-lg-size:1rem;                                     --type-label-lg-leading:1.25; --type-label-lg-weight:600; --type-label-lg-width:100%; --type-label-lg-tracking:0em;
  --type-label-size:0.875rem;                                    --type-label-leading:1.43; --type-label-weight:600; --type-label-width:100%; --type-label-tracking:0.005em;
  --type-overline-size:0.75rem;                                  --type-overline-leading:1.33; --type-overline-weight:600; --type-overline-width:108%; --type-overline-tracking:0.08em;
  --type-caption-size:0.75rem;                                   --type-caption-leading:1.33; --type-caption-weight:400; --type-caption-width:100%; --type-caption-tracking:0.01em;
  /* Mono has no width axis. Set these with tabular-nums. */
  --type-data-lg-size:clamp(1.75rem, 1.5rem + 1vw, 2rem);        --type-data-lg-leading:1.125; --type-data-lg-weight:600; --type-data-lg-tracking:-0.01em;
  --type-data-size:0.875rem;                                     --type-data-leading:1.43; --type-data-weight:500; --type-data-tracking:0em;
  --type-code-size:0.8125rem;                                    --type-code-leading:1.54; --type-code-weight:400; --type-code-tracking:0em;

  /* ---------- space (4px base; the keys are Tailwind's spacing numbers) ---------- */
  --space-1:4px;   --space-2:8px;   --space-3:12px;  --space-4:16px;
  --space-5:20px;  --space-6:24px;  --space-8:32px;  --space-10:40px;
  --space-12:48px; --space-16:64px; --space-20:80px; --space-24:96px;

  /* ---------- radius (echoes the logo's 25% stem corner) ---------- */
  --radius-xs:2px;  --radius-sm:4px; --radius-md:8px; --radius-lg:12px;
  --radius-xl:16px; --radius-2xl:24px; --radius-full:9999px;

  /* ---------- focus: appears instantly, never animated ---------- */
  --focus-ring-width:2px;
  --focus-ring-offset:2px;

  /* ---------- motion (§7) ---------- */
  --duration-instant:0ms; --duration-xs:100ms; --duration-sm:160ms; --duration-md:240ms;
  --duration-lg:360ms;    --duration-xl:520ms; --duration-stagger:40ms;
  --ease-standard:cubic-bezier(0.2, 0, 0, 1);
  --ease-enter:cubic-bezier(0.05, 0.7, 0.1, 1);
  --ease-exit:cubic-bezier(0.3, 0, 0.8, 0.15);
  --ease-snap:cubic-bezier(0.34, 1.4, 0.64, 1);
  --motion-distance-sm:4px; --motion-distance-md:8px; --motion-distance-lg:12px; --motion-distance-logo:12px;

  /* ---------- layers, opacity, grid, containers, breakpoints ---------- */
  --z-base:0; --z-raised:10; --z-sticky:100; --z-overlay:1000; --z-modal:1100; --z-toast:1200;
  --opacity-disabled:0.45;
  --grid-mobile-columns:4;   --grid-mobile-margin:16px;  --grid-mobile-gutter:16px;
  --grid-tablet-columns:8;   --grid-tablet-margin:24px;  --grid-tablet-gutter:24px;
  --grid-desktop-columns:12; --grid-desktop-margin:32px; --grid-desktop-gutter:24px;
  --container-content:80rem; --container-prose:68ch;
  /* Reference only: a media query cannot read a custom property. */
  --breakpoint-sm:40rem; --breakpoint-md:48rem; --breakpoint-lg:64rem; --breakpoint-xl:80rem; --breakpoint-2xl:96rem;

  /* ---------- vertical rhythm, sizes, layout (kept from v2) ----------
     None of these is a brand decision; they are how the app is built, and
     the v3 package does not replace them.

     --density scales vertical rhythm only. A custom property is substituted
     where it is *declared*, not where it is read, so these four resolve
     against the --density on this element and inherit already-computed —
     which is why each mode preset below restates them. */
  --density:1;
  --stack-tight:calc(var(--space-2) * var(--density));
  --stack:calc(var(--space-4) * var(--density));
  --stack-loose:calc(var(--space-8) * var(--density));
  --section:calc(var(--space-16) * var(--density));

  --control-sm:32px; --control-md:40px; --control-lg:48px;
  --hit-min:44px;                 /* touch targets, §6 */
  --icon-sm:16px; --icon-md:20px; /* §8: 16 inline, 20 in buttons */
  /* Lucide's stroke (§8). Icons are drawn in JSX, never in CSS. */
  --icon-stroke:1.75;

  --hairline:1px;
  --rule:2px;
  --marker:3px;   /* status card left rule */

  /* How long a surface may stay silent before it admits it is loading
     (design-system.md §1.7). A threshold, not a transition. */
  --dur-reveal:400ms;

  /* A measure is a LINE LENGTH, not a page width: it belongs on the
     paragraph or the field column. Layout width is --shell-max. */
  --measure-prose:68ch;
  --measure-ui:60ch;

  --shell-max:1240px;   /* the hand-written marketing pages */
  --content-max:1080px; /* the app's content column */
  --col-form:640px;     /* one task, one column, left-aligned */
  --rail:240px;
  --topbar-h:56px;      /* the masthead below lg, where it is sticky */
  --masthead-h:64px;    /* the masthead from lg, where it scrolls away */
  --page-margin:56px;   /* the manuscript margin's inset (design-system.md §0.3) */
  --rail-top:calc(var(--topbar-h) + var(--space-6));

  /* The sticky chrome a jump link has to clear. The strip's `top`, every
     artifact's scroll-margin-top and the scroll-spy's rootMargin all read
     this, so it cannot drift between them. */
  --tabs-h:60px;
  --sticky-stack:calc(var(--topbar-h) + var(--tabs-h));
  --jump-top:calc(var(--sticky-stack) + var(--space-6));
}

/* Registered so it computes: the scroll-spy reads it from JS and needs a
   number, not `calc(var(--topbar-h) + var(--tabs-h))`. */
@property --sticky-stack{
  syntax:'<length>'; inherits:true; initial-value:108px;
}

/* Conditional on exactly the breakpoint that unmounts the top bar. */
@media (min-width:1024px){
  :root{ --sticky-stack:var(--tabs-h); }
}

/* ---------- roles, dark ----------
   No attribute follows the OS; data-theme="light" or "dark" on <html> is an
   explicit choice. The two blocks carry the same declarations: keep them in
   sync (lint:contrast checks both). Lift in dark comes from the next lighter
   surface plus a 1px top highlight built into the shadows. */
[data-theme="dark"] {
  color-scheme: dark;
  --pf-bg:#0E1116;
  --pf-surface:#161A21;
  --pf-surface-raised:#1E222B;
  --pf-surface-sunken:#0A0C10;
  --pf-fg:#ECEEF2;
  --pf-fg-secondary:#C2C8D2;
  --pf-fg-muted:#8B93A1;
  --pf-border:#252A33;
  --pf-border-strong:#373D4A;
  --pf-border-input:#8B93A1;
  --pf-primary:#3D5AFE;
  --pf-primary-hover:#2541E8;
  --pf-primary-pressed:#1B31BF;
  --pf-on-primary:#FFFFFF;
  --pf-primary-text:#96A9FF;
  --pf-primary-soft:#282E40;
  --pf-on-primary-soft:#96A9FF;
  --pf-accent:#CCF22E;
  --pf-on-accent:#0B1026;
  --pf-accent-soft:#1E222B;
  --pf-link:#96A9FF;
  --pf-focus:#96A9FF;
  --pf-inverse-bg:#ECEEF2;
  --pf-inverse-fg:#0B1026;
  --pf-success:#5BE0A6; --pf-success-bg:#203634; --pf-success-border:#2E5F50;
  --pf-success-solid:#5BE0A6; --pf-on-success-solid:#0B1026;
  --pf-warning:#FFC94D; --pf-warning-bg:#373327; --pf-warning-border:#685730;
  --pf-warning-solid:#FFC94D; --pf-on-warning-solid:#0B1026;
  --pf-error:#FF8597; --pf-error-bg:#372932; --pf-error-border:#683F4A;
  --pf-error-solid:#FF8597; --pf-on-error-solid:#0B1026;
  --pf-info:#96A9FF; --pf-info-bg:#282E40; --pf-info-border:#434C6F;
  --pf-info-solid:#96A9FF; --pf-on-info-solid:#0B1026;
  --pf-match:#CCF22E; --pf-on-match:#0B1026;
  --pf-partial:#FFC94D; --pf-partial-bg:#373327;
  --pf-gap-border:#8B93A1;
  --pf-logo-stem:#ECEEF2; --pf-logo-bowl:#6680FF;
  --pf-scan:#6680FF;
  --pf-scrim:rgba(0, 0, 0, 0.6);

  --pf-shadow-xs:inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --pf-shadow-sm:0 1px 2px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.04);
  --pf-shadow-md:0 8px 24px -8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  --pf-shadow-lg:0 18px 40px -12px rgba(0, 0, 0, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  --pf-shadow-xl:0 32px 72px -16px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.08);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --pf-bg:#0E1116;
    --pf-surface:#161A21;
    --pf-surface-raised:#1E222B;
    --pf-surface-sunken:#0A0C10;
    --pf-fg:#ECEEF2;
    --pf-fg-secondary:#C2C8D2;
    --pf-fg-muted:#8B93A1;
    --pf-border:#252A33;
    --pf-border-strong:#373D4A;
    --pf-border-input:#8B93A1;
    --pf-primary:#3D5AFE;
    --pf-primary-hover:#2541E8;
    --pf-primary-pressed:#1B31BF;
    --pf-on-primary:#FFFFFF;
    --pf-primary-text:#96A9FF;
    --pf-primary-soft:#282E40;
    --pf-on-primary-soft:#96A9FF;
    --pf-accent:#CCF22E;
    --pf-on-accent:#0B1026;
    --pf-accent-soft:#1E222B;
    --pf-link:#96A9FF;
    --pf-focus:#96A9FF;
    --pf-inverse-bg:#ECEEF2;
    --pf-inverse-fg:#0B1026;
    --pf-success:#5BE0A6; --pf-success-bg:#203634; --pf-success-border:#2E5F50;
    --pf-success-solid:#5BE0A6; --pf-on-success-solid:#0B1026;
    --pf-warning:#FFC94D; --pf-warning-bg:#373327; --pf-warning-border:#685730;
    --pf-warning-solid:#FFC94D; --pf-on-warning-solid:#0B1026;
    --pf-error:#FF8597; --pf-error-bg:#372932; --pf-error-border:#683F4A;
    --pf-error-solid:#FF8597; --pf-on-error-solid:#0B1026;
    --pf-info:#96A9FF; --pf-info-bg:#282E40; --pf-info-border:#434C6F;
    --pf-info-solid:#96A9FF; --pf-on-info-solid:#0B1026;
    --pf-match:#CCF22E; --pf-on-match:#0B1026;
    --pf-partial:#FFC94D; --pf-partial-bg:#373327;
    --pf-gap-border:#8B93A1;
    --pf-logo-stem:#ECEEF2; --pf-logo-bowl:#6680FF;
    --pf-scan:#6680FF;
    --pf-scrim:rgba(0, 0, 0, 0.6);

    --pf-shadow-xs:inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --pf-shadow-sm:0 1px 2px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.04);
    --pf-shadow-md:0 8px 24px -8px rgba(0, 0, 0, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.06);
    --pf-shadow-lg:0 18px 40px -12px rgba(0, 0, 0, 0.75), inset 0 1px 0 rgba(255, 255, 255, 0.08);
    --pf-shadow-xl:0 32px 72px -16px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.08);
  }
}

/* ---------- reduced motion ----------
   When the OS asks, or when the app sets data-motion="reduced" on <html>:
   movement distances go to zero, entries become short fades, loops stop.
   The --animate-* names are the web app's (its Tailwind layer); naming them
   here is what switches them off on every surface at once. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --motion-distance-sm:0px; --motion-distance-md:0px; --motion-distance-lg:0px; --motion-distance-logo:0px;
    --duration-md:120ms; --duration-lg:150ms; --duration-xl:150ms; --duration-stagger:0ms;
    --animate-scan:none; --animate-width-in:none; --animate-grow:none;
  }
}
[data-motion="reduced"] {
  --motion-distance-sm:0px; --motion-distance-md:0px; --motion-distance-lg:0px; --motion-distance-logo:0px;
  --duration-md:120ms; --duration-lg:150ms; --duration-xl:150ms; --duration-stagger:0ms;
  --animate-scan:none; --animate-width-in:none; --animate-grow:none;
}


/* ============================================================
   2. COMPATIBILITY ALIASES (v2 names)

   Every v2 name that profyl-com or profyl-website still reads, pointed at
   the nearest v3 role so the app re-skins without a component changing.
   The full mapping, with who uses each name, is in
   docs/brand-v3/migration-plan.md ("Alias mapping"). Consumers move to the
   v3 names in sessions 2a–4; session 5 deletes this block.

   They resolve on :root, so they follow the theme blocks above (same
   element). A descendant scope that wants a different value restates the v2
   name itself — the presence tint and the document scope below do.
   ============================================================ */

:root {
  /* ---------- palette ----------
     Constant in both themes, like the v2 ramps were. Pine was the brand, so
     it becomes Cobalt step for step; its two darkest steps were the dark
     brand panel, which is Ink now. The marker (a highlighter) becomes Volt,
     except its text step: Volt is never text, so that one is Ink. */
  --pine-50:var(--color-cobalt-50);   --pine-100:var(--color-cobalt-100);
  --pine-200:var(--color-cobalt-200); --pine-300:var(--color-cobalt-300);
  --pine-400:var(--color-cobalt-400); --pine-500:var(--color-cobalt-500);
  --pine-600:var(--color-cobalt-600); --pine-700:var(--color-cobalt-700);
  --pine-800:var(--color-ink-900);    --pine-900:var(--color-ink-900);
  --marker-50:var(--color-volt-50);   --marker-100:var(--color-volt-100);
  --marker-300:var(--color-volt-300); --marker-500:var(--color-volt-500);
  --marker-700:var(--color-ink-900);
  --stone-100:var(--color-steel-100); --stone-200:var(--color-steel-200);
  --stone-300:var(--color-steel-300); --stone-400:var(--color-steel-400);
  --stone-500:var(--color-steel-500); --stone-600:var(--color-steel-600);
  --stone-700:var(--color-steel-700); --stone-800:var(--color-steel-800);
  --stone-900:var(--color-steel-900);
  /* v3 has no red ramp, only the error role, which flips in dark. These stay
     constant: the destructive button is white on red in both themes. */
  --red-600:#C8102E;
  --red-700:color-mix(in srgb, #C8102E 82%, #000000);
  --red-50:#FFEBEE;
  --slate-600:var(--color-cobalt-700); --slate-500:var(--color-cobalt-600); --slate-50:var(--color-cobalt-50);

  /* v2's "palette" names that flipped with the theme. */
  --paper:var(--pf-bg);
  --surface:var(--pf-surface);

  /* ---------- grounds and text ----------
     v2 had three text tiers (900/700/600) and v3 has three (fg, secondary,
     muted); they map tier for tier. */
  --bg:var(--pf-bg);
  --bg-sunken:var(--pf-surface-sunken);
  --bg-raised:var(--pf-surface-raised);
  --text:var(--pf-fg);
  --text-body:var(--pf-fg-secondary);
  --text-muted:var(--pf-fg-muted);
  --text-disabled:var(--color-steel-400);   /* exempt; never the sole signal. Dark below. */
  --text-on-dark:var(--on-dark);

  --primary:var(--pf-primary);
  --primary-hover:var(--pf-primary-hover);
  --primary-active:var(--pf-primary-pressed);
  --on-primary:var(--pf-on-primary);
  --accent:var(--pf-accent);
  --accent-hover:var(--color-volt-500);

  --border-control:var(--pf-border-input);
  --border-control-hover:var(--pf-fg-secondary);
  --border-control-focus:var(--pf-focus);
  --border-divider:var(--pf-border);
  --border-subtle:var(--pf-border-strong);
  /* The tonal (secondary) button: one step, two steps, three steps off the
     ground. Light steps down from white; dark steps up. Dark below. */
  --btn-tonal-bg:var(--pf-surface-sunken);
  --btn-tonal-hover:var(--pf-border);
  --btn-tonal-active:var(--pf-border-strong);
  --btn-danger-hover:color-mix(in srgb, var(--pf-error) 5%, var(--pf-error-bg));

  /* ---------- status ----------
     A v2 gap was the highlighter. v3 gaps are "close" (amber) or "to
     address" (neutral), never red, and never Volt either: Volt means a
     requirement is matched. So the card and the highlight over a gap's
     title (.gapnote) take the close colours. */
  --gap-bg:var(--pf-partial-bg);
  --gap-rule:var(--pf-partial);
  --gap-text:var(--pf-partial);
  --gap-mark:var(--pf-partial-bg);

  --notice-bg:var(--info-bg);
  --notice-rule:var(--info-rule);
  --notice-text:var(--info-text);

  /* Rules are the solid colour: the v3 border step is decorative and does
     not reach 3:1 on its own background. */
  --success-bg:var(--pf-success-bg);
  --success-rule:var(--pf-success-solid);
  --success-text:var(--pf-success);
  --error-bg:var(--pf-error-bg);
  --error-rule:var(--pf-error-solid);
  --error-text:var(--pf-error);
  --info-bg:var(--pf-info-bg);
  --info-rule:var(--pf-info-solid);
  --info-text:var(--pf-info);

  /* A live microphone is active, not wrong and not a fit: the brand colour.
     Never red (red means the system failed). The text step, not the fill:
     dark's fill cobalt is 2.6:1 on the soft tint. */
  --recording-bg:var(--pf-primary-soft);
  --recording-rule:var(--pf-primary-text);
  --recording-text:var(--pf-on-primary-soft);
  --recording-dot:var(--pf-primary-text);

  --scrim:var(--pf-scrim);
  --margin-ink:var(--pf-border);

  /* ---------- module tint and navigation ----------
     v3 specifies no per-module tint, so Presence takes the same brand roles
     as Applications until the app's styling session decides otherwise. */
  --module-accent:var(--pf-primary-text);
  --module-ink:var(--pf-on-primary-soft);
  --module-soft:var(--pf-primary-soft);
  --presence-accent:var(--pf-primary-text);
  --presence-ink:var(--pf-on-primary-soft);
  --presence-soft:var(--pf-primary-soft);
  --nav-current-bg:var(--module-soft);
  --nav-current-text:var(--module-ink);
  --nav-marker:var(--module-accent);
  --wordmark-color:var(--pf-logo-stem);
  --sidenav-hover-bg:var(--pf-surface-sunken);   /* dark below */

  /* The sidebar, masthead and front door are a dark field in BOTH themes,
     so they are built from constants and take constant ink: a surface built
     from palette values takes its ink from palette values too (the bug class
     found 2026-09-22). The pine field becomes Ink, v3's dark brand surface. */
  --on-dark:var(--color-white);
  --navsurface-bg:var(--color-ink-900);
  --navsurface-border:var(--color-ink-800);
  --navsurface-text:var(--color-steel-300);
  --navsurface-muted:var(--color-steel-400);
  --navsurface-hover-bg:var(--color-ink-800);
  --navsurface-current-bg:var(--color-ink-700);
  --navsurface-current-text:var(--on-dark);
  --module-onnav:var(--color-cobalt-300);

  /* ---------- type ----------
     The brand serif is gone: what was serif is Archivo now. A resume's own
     typefaces live on --font-doc-* (part 3), never on these.

     Sizes with a v3 step map to it; the three without one (15px UI text,
     13px labels, the prose sizes) keep their v2 values until the app's
     styling session moves those components onto --type-*. */
  --font-serif:var(--font-display);
  --text-display:var(--type-display-lg-size);
  --text-h1:var(--type-h1-size);
  --text-h2:var(--type-h2-size);
  --text-h3:var(--type-h3-size);
  --text-prose-lg:1.25rem;
  --text-prose:1.125rem;
  --text-body-size:var(--type-body-size);
  --text-ui:0.9375rem;
  --text-sm:var(--type-body-sm-size);
  --text-label:0.8125rem;
  --text-caption:var(--type-caption-size);
  --lh-display:var(--type-display-lg-leading);
  --lh-heading:var(--type-h1-leading);
  --lh-prose:1.6;
  --lh-body:var(--type-body-leading);
  --lh-tight:var(--type-h3-leading);
  --tracking-display:var(--type-display-lg-tracking);
  --tracking-heading:var(--type-h2-tracking);
  --tracking-label:var(--type-caption-tracking);
  --tracking-overline:var(--type-overline-tracking);

  /* ---------- shape, elevation, focus, motion ---------- */
  --radius-control:var(--radius-md);   /* buttons, inputs */
  --radius-button:var(--radius-md);
  --radius-surface:var(--radius-lg);   /* cards */
  --radius-pill:var(--radius-full);
  --shadow-overlay:var(--pf-shadow-lg);

  /* v3's ring is a 2px focus-colour outline 2px off the element. Drawn as
     v2's two box-shadows (an inner ring of the page colour, then the focus
     colour) it looks the same and every component using --focus-ring keeps
     working. */
  --focus-inner:var(--pf-bg);
  --focus-outer:var(--pf-focus);
  --focus-ring:0 0 0 var(--focus-ring-offset) var(--focus-inner),
               0 0 0 calc(var(--focus-ring-offset) + var(--focus-ring-width)) var(--focus-outer);

  --dur-fast:var(--duration-sm);
  --dur:var(--duration-sm);
  --dur-slow:var(--duration-md);
  --ease:var(--ease-standard);
}

/* Dark. Two jobs. Five aliases map differently by theme rather than by role
   (the first block of lines). And every name v2's dark blocks declared is
   declared here again, at the same specificity v2 had, so the cascade is
   v2's: a consumer that overrides a v2 name on :root for its light theme —
   the admin portal's white ground, for one — is still overridden in dark,
   as it was. Without that, admin rendered white in a dark OS (2026-10-13).
   The values are the ones above; only where they are declared changes. */
[data-theme="dark"] {
  --text-disabled:var(--color-steel-600);
  --btn-tonal-bg:var(--pf-border);
  --btn-tonal-hover:var(--pf-border-strong);
  --btn-tonal-active:var(--color-steel-600);
  --sidenav-hover-bg:var(--pf-surface-raised);

  --bg:var(--pf-bg); --bg-sunken:var(--pf-surface-sunken); --bg-raised:var(--pf-surface-raised);
  --paper:var(--pf-bg); --surface:var(--pf-surface);
  --text:var(--pf-fg); --text-body:var(--pf-fg-secondary); --text-muted:var(--pf-fg-muted);
  --primary:var(--pf-primary); --primary-hover:var(--pf-primary-hover);
  --primary-active:var(--pf-primary-pressed); --on-primary:var(--pf-on-primary);
  --accent:var(--pf-accent);
  --border-control:var(--pf-border-input); --border-control-hover:var(--pf-fg-secondary);
  --border-control-focus:var(--pf-focus); --border-divider:var(--pf-border); --border-subtle:var(--pf-border-strong);
  --btn-danger-hover:color-mix(in srgb, var(--pf-error) 5%, var(--pf-error-bg));
  --gap-bg:var(--pf-partial-bg); --gap-rule:var(--pf-partial); --gap-text:var(--pf-partial); --gap-mark:var(--pf-partial-bg);
  --success-bg:var(--pf-success-bg); --success-rule:var(--pf-success-solid); --success-text:var(--pf-success);
  --error-bg:var(--pf-error-bg); --error-rule:var(--pf-error-solid); --error-text:var(--pf-error);
  --info-bg:var(--pf-info-bg); --info-rule:var(--pf-info-solid); --info-text:var(--pf-info);
  --recording-bg:var(--pf-primary-soft); --recording-rule:var(--pf-primary-text);
  --recording-text:var(--pf-on-primary-soft); --recording-dot:var(--pf-primary-text);
  --module-accent:var(--pf-primary-text); --module-ink:var(--pf-on-primary-soft); --module-soft:var(--pf-primary-soft);
  --presence-accent:var(--pf-primary-text); --presence-ink:var(--pf-on-primary-soft); --presence-soft:var(--pf-primary-soft);
  --nav-current-bg:var(--module-soft); --nav-current-text:var(--module-ink); --nav-marker:var(--module-accent);
  --wordmark-color:var(--pf-logo-stem);
  --navsurface-border:var(--color-ink-800);
  --margin-ink:var(--pf-border);
  --focus-inner:var(--pf-bg); --focus-outer:var(--pf-focus);
  --scrim:var(--pf-scrim);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --text-disabled:var(--color-steel-600);
    --btn-tonal-bg:var(--pf-border);
    --btn-tonal-hover:var(--pf-border-strong);
    --btn-tonal-active:var(--color-steel-600);
    --sidenav-hover-bg:var(--pf-surface-raised);

    --bg:var(--pf-bg); --bg-sunken:var(--pf-surface-sunken); --bg-raised:var(--pf-surface-raised);
    --paper:var(--pf-bg); --surface:var(--pf-surface);
    --text:var(--pf-fg); --text-body:var(--pf-fg-secondary); --text-muted:var(--pf-fg-muted);
    --primary:var(--pf-primary); --primary-hover:var(--pf-primary-hover);
    --primary-active:var(--pf-primary-pressed); --on-primary:var(--pf-on-primary);
    --accent:var(--pf-accent);
    --border-control:var(--pf-border-input); --border-control-hover:var(--pf-fg-secondary);
    --border-control-focus:var(--pf-focus); --border-divider:var(--pf-border); --border-subtle:var(--pf-border-strong);
    --btn-danger-hover:color-mix(in srgb, var(--pf-error) 5%, var(--pf-error-bg));
    --gap-bg:var(--pf-partial-bg); --gap-rule:var(--pf-partial); --gap-text:var(--pf-partial); --gap-mark:var(--pf-partial-bg);
    --success-bg:var(--pf-success-bg); --success-rule:var(--pf-success-solid); --success-text:var(--pf-success);
    --error-bg:var(--pf-error-bg); --error-rule:var(--pf-error-solid); --error-text:var(--pf-error);
    --info-bg:var(--pf-info-bg); --info-rule:var(--pf-info-solid); --info-text:var(--pf-info);
    --recording-bg:var(--pf-primary-soft); --recording-rule:var(--pf-primary-text);
    --recording-text:var(--pf-on-primary-soft); --recording-dot:var(--pf-primary-text);
    --module-accent:var(--pf-primary-text); --module-ink:var(--pf-on-primary-soft); --module-soft:var(--pf-primary-soft);
    --presence-accent:var(--pf-primary-text); --presence-ink:var(--pf-on-primary-soft); --presence-soft:var(--pf-primary-soft);
    --nav-current-bg:var(--module-soft); --nav-current-text:var(--module-ink); --nav-marker:var(--module-accent);
    --wordmark-color:var(--pf-logo-stem);
    --navsurface-border:var(--color-ink-800);
    --margin-ink:var(--pf-border);
    --focus-inner:var(--pf-bg); --focus-outer:var(--pf-focus);
    --scrim:var(--pf-scrim);
  }
}


/* ============================================================
   MODE PRESETS
   Applied on a container: <main data-mode="document">
   These override tokens only. Never palette, never typeface.
   ============================================================ */

[data-mode="document"] {
  --density:1;
  /* Restated so the calc resolves against this block's --density. */
  --stack-tight:calc(var(--space-2) * var(--density));
  --stack:calc(var(--space-4) * var(--density));
  --stack-loose:calc(var(--space-8) * var(--density));
  --section:calc(var(--space-16) * var(--density));
  --measure-prose:68ch;
  --prose-size:var(--text-prose);
  --page-edge:var(--hairline) solid var(--border-divider);
  --page-bg:var(--surface);
  --margin-rule:var(--margin-ink); /* decorative manuscript rule */
  --chrome-opacity:1;              /* toolbars always visible */
  --field-label-display:block;     /* every label persistent */
  /* The sheet's own edge separates stacked artifacts; the head draws none. */
  --artifact-rule:none;
  --artifact-rule-gap:0;
}

[data-mode="compose"] {
  --density:1.15;
  --stack-tight:calc(var(--space-2) * var(--density));
  --stack:calc(var(--space-4) * var(--density));
  --stack-loose:calc(var(--space-8) * var(--density));
  --section:calc(var(--space-16) * var(--density));
  --measure-prose:52ch;
  --prose-size:var(--text-prose-lg);
  --page-edge:none;
  --page-bg:transparent;
  --margin-rule:transparent;
  --chrome-opacity:0;              /* toolbars reveal on focus-within */
  --field-label-display:block;     /* NEVER 'none' — see §5.4 */
  /* No sheet here, so the head carries the hairline that separates artifacts. */
  --artifact-rule:var(--hairline) solid var(--border-divider);
  --artifact-rule-gap:var(--stack-loose);
}

/* ============================================================
   MODULE TINT
   Applied on the shell: <div class="shell" data-module="presence">.
   A separate axis from data-mode on purpose (the register of the canvas is
   not the half of the product you are in).
   ============================================================ */

[data-module="presence"]{
  --module-accent:var(--presence-accent);
  --module-ink:var(--presence-ink);
  --module-soft:var(--presence-soft);
  /* Restated: the aliases on :root cannot see the change above. */
  --nav-current-bg:var(--module-soft);
  --nav-current-text:var(--module-ink);
  --nav-marker:var(--module-accent);
  --module-onnav:var(--color-cobalt-200);
}


/* ============================================================
   3. DOCUMENTS — the resume and the cover letter

   What a user sends is theirs, not the brand's. A resume must look exactly
   as it did before the app re-skinned (migration-plan.md, session 1; proved
   by `node scripts/document-baseline.js` in frontend/), so everything a
   document reads is pinned here to its v2 value. The app around it moves
   on; the sheet does not.

   [data-typeface] marks the document: every part of a resume or cover
   letter in the preview (Artifact.jsx), the typeface picker's samples, and
   the <main> of the printed page (lib/documents). "house" is a typeface
   too, so the attribute is always present on a document.

   The typefaces (frontend/src/lib/profile/typefaces.js is the list, and the
   DOCX renderer reads the same names) set two roles: --font-doc-words for
   the user's own words (the name, the bullets, the letter) and
   --font-doc-labels for the labels (section heads, roles, dates, the
   contact line). Each Office font leads its stack and is followed by an
   open font drawn to the same widths, so the preview, the PDF and the DOCX
   break lines in the same places. They no longer depend on --font-serif,
   which is the brand's and is Archivo now.
   ============================================================ */

:root {
  /* House: what Profyl was set in until v3, and what a resume still is. */
  --font-doc-words:"Newsreader Variable","Newsreader",Georgia,"Times New Roman",serif;
  --font-doc-labels:"Public Sans Variable","Public Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;
  /* What a document's unstyled text inherited from the page in v2. Not a
     typeface's: it stays the house sans whichever one is chosen. */
  --font-doc-inherited:"Public Sans Variable","Public Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;

  /* The v2 ink, per theme. Never a v3 role: a role is allowed to move. */
  --doc-fg:#111915;
  --doc-fg-body:#36403A;
  --doc-fg-muted:#56615A;
  --doc-rule:#D6DCD4;
  --doc-edge:#C3CBC2;
  --doc-mark:#FFEE8A;
  --doc-gap-text:#3A3000;
  --doc-gap-rule:#957C00;
  --doc-ground:#EEF1EC;   /* the printed page's body, where the sheet does not reach */
}
[data-theme="dark"] {
  --doc-fg:#E8EDE9; --doc-fg-body:#CBD3CE; --doc-fg-muted:#9AA59E;
  --doc-rule:#26302A; --doc-edge:#1E2621; --doc-mark:#4A4312;
  --doc-gap-text:#F6EBB0; --doc-gap-rule:#E3C94F; --doc-ground:#0E1411;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --doc-fg:#E8EDE9; --doc-fg-body:#CBD3CE; --doc-fg-muted:#9AA59E;
    --doc-rule:#26302A; --doc-edge:#1E2621; --doc-mark:#4A4312;
    --doc-gap-text:#F6EBB0; --doc-gap-rule:#E3C94F; --doc-ground:#0E1411;
  }
}

[data-typeface="classic"]{
  --font-doc-words:Cambria,Caladea,Georgia,serif;
  --font-doc-labels:Calibri,Carlito,"Segoe UI",sans-serif;
}
[data-typeface="book"]{
  --font-doc-words:Georgia,Gelasio,serif;
  --font-doc-labels:Georgia,Gelasio,serif;
}
[data-typeface="plain"]{
  --font-doc-words:Arial,Arimo,Helvetica,sans-serif;
  --font-doc-labels:Arial,Arimo,Helvetica,sans-serif;
}

/* The v2 names the document's rules read (components.css: .artifact__*,
   .pf-prose, .pf-standfirst, .typeface__*), restated with v2 values. */
[data-typeface]{
  --font-serif:var(--font-doc-words);
  --font-sans:var(--font-doc-labels);

  --text:var(--doc-fg);
  --text-body:var(--doc-fg-body);
  --text-muted:var(--doc-fg-muted);
  --border-divider:var(--doc-rule);
  --border-subtle:var(--doc-edge);
  --gap-mark:var(--doc-mark);
  --gap-text:var(--doc-gap-text);
  --gap-rule:var(--doc-gap-rule);
  /* The v3 ink roles too, so a document stays put when its markup moves
     onto them. A utility that resolves on :root (the web app's v2 Tailwind
     bridge, `text-fg-muted`) cannot be reached from here at all: inside a
     document, read the variable on the element instead. */
  --pf-fg:var(--doc-fg);
  --pf-fg-secondary:var(--doc-fg-body);
  --pf-fg-muted:var(--doc-fg-muted);
  --pf-border:var(--doc-rule);

  --text-display:clamp(2.25rem,1.5rem + 3.6vw,4.5rem);
  --text-h1:clamp(2.25rem,1.8rem + 1.6vw,3.25rem);
  --text-h2:2rem;
  --text-h3:1.1875rem;
  --text-prose-lg:1.25rem;
  --text-prose:1.125rem;
  --text-body-size:1rem;
  --text-ui:0.9375rem;
  --text-sm:0.875rem;
  --text-label:0.8125rem;
  --text-caption:0.75rem;
  --lh-display:1.02; --lh-heading:1.12; --lh-prose:1.6; --lh-body:1.55; --lh-tight:1.35;
  --tracking-display:-0.03em; --tracking-heading:-0.015em; --tracking-label:0.01em; --tracking-overline:0.08em;
}

/* What the document's unstyled text inherits. Zero specificity, so every
   rule that sets these itself still wins. */
:where([data-typeface]){
  font-family:var(--font-doc-inherited);
  color:var(--text-body);
  line-height:var(--lh-body);
}

/* The printed page (lib/documents): its body is outside the sheet. */
.pf-print{
  --bg:var(--doc-ground);
  --text-body:var(--doc-fg-body);
  --font-sans:var(--font-doc-inherited);
  --lh-body:1.55;
}


/* ============================================================
   4. GLOBAL BASE
   ============================================================ */

*,*::before,*::after{box-sizing:border-box;}

/* Reserve the scrollbar's gutter on every page, scrolling or not, so the
   column does not shift between a short page and a long one. */
html{ -webkit-text-size-adjust:100%; scrollbar-gutter:stable; }
/* …and stop Radix's dialog scroll lock compensating for it a second time. */
html body[data-scroll-locked]{ margin-right:0 !important; }

body{
  margin:0;
  background:var(--bg);
  color:var(--text-body);
  font-family:var(--font-sans);
  font-size:var(--text-body-size);
  line-height:var(--lh-body);
  font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased;
}

/* Headings are Archivo, wider as they get bigger (§5). Base sizes, so a
   heading with no class is still a heading; any size class overrides. */
h1,h2,.pf-display{
  font-family:var(--font-display); font-weight:var(--type-h1-weight); color:var(--text);
  letter-spacing:var(--type-h1-tracking); text-wrap:balance;
}
h1{ font-size:var(--text-h1); line-height:var(--lh-heading); font-stretch:var(--type-h1-width); }
h2{ font-size:var(--text-h2); line-height:var(--lh-heading); letter-spacing:var(--type-h2-tracking); }
.pf-display{
  font-weight:var(--type-display-lg-weight); font-stretch:var(--type-display-lg-width);
  letter-spacing:var(--type-display-lg-tracking);
}
/* The smaller headings are Archivo too, at v3's heading weight (650). */
h3,h4,h5,h6{ font-family:var(--font-display); font-weight:var(--type-h3-weight); color:var(--text); }
p{ text-wrap:pretty; }

/* …except in a document, where the name is still set as it was in v2:
   the words face, regular weight. Size and leading come from the scoped
   tokens through the rules above and .artifact__title. */
:is(h1,h2,.pf-display):where([data-typeface], [data-typeface] *){
  font-family:var(--font-serif); font-weight:400; font-stretch:normal;
  letter-spacing:var(--tracking-heading); color:var(--text);
}
/* …and a document's smaller headings keep v2's labels face at 600. */
:is(h3,h4,h5,h6):where([data-typeface], [data-typeface] *){ font-family:var(--font-sans); font-weight:600; }

.pf-prose{               /* every byte generated from the user's profile */
  font-family:var(--font-serif);
  font-size:var(--prose-size,var(--text-prose));
  line-height:var(--lh-prose);
  max-width:var(--measure-prose);
  color:var(--text-body);
}

.pf-tabular{ font-variant-numeric:tabular-nums; }

/* Focus: never removed, only replaced. Never animated. */
:focus{ outline:none; }
:focus-visible{ outline:none; box-shadow:var(--focus-ring); border-radius:var(--radius-control); }

@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;
  }
}
[data-motion="reduced"] *,[data-motion="reduced"] *::before,[data-motion="reduced"] *::after{
  animation-duration:0.01ms !important; animation-iteration-count:1 !important;
  transition-duration:0.01ms !important; scroll-behavior:auto !important;
}

/* Never rely on fixed heights for text containers — WCAG 1.4.12 */
.pf-no-clip{ min-height:0; height:auto; overflow:visible; }
