/* =============================================================================
   proses — design system
   Extracted from Proses.html (v7) and rebuilt as a real stylesheet.
   Version 1.0 · 2026-09-02

   Source of the palette, radii and type scale: the v7 bundle, measured by
   occurrence count rather than eyeballed. Every value below appears in v7 or is
   a deliberate, noted change.

   Deliberate changes from v7:
     1. v7 styled every element inline. This is a stylesheet with custom
        properties. Nothing here is set on an element attribute.
     2. Motion is calmed. Removed: the rotating hero word, the scroll-scrubbed
        word-by-word opacity fill on "the turn", the auto-rotating belt.
        Kept, slower: scroll reveals, the staged chat reveal, the accordion.
     3. v7 had NO `prefers-reduced-motion` CSS rule — it handled reduced motion
        in JS only (`matchMedia(...)` inside boot()). There is a real media
        query here, so motion is off before a single line of JS runs.
     4. Fonts are self-hosted. v7 preconnected to fonts.googleapis.com and
        fonts.gstatic.com; those are removed. Zero external requests.
     5. The three woff2 files are unicode-range SUBSETS of one variable face
        (wght axis), not three weights. They are declared as a weight range so
        400/500/600/700 all render as real weights with no synthesis.
   ============================================================================= */

/* --- fonts ---------------------------------------------------------------- */

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url('fonts/archivo-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url('fonts/archivo-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-stretch: 100%;
  font-display: swap;
  src: url('fonts/archivo-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1,
    U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329,
    U+1EA0-1EF9, U+20AB;
}

/* --- tokens --------------------------------------------------------------- */

:root {
  /* The design commits to a light palette. color-scheme is declared so a
     dark-mode browser renders it as authored instead of auto-inverting form
     controls and scrollbars. Every surface below sets an explicit background —
     nothing inherits the host's ground. */
  color-scheme: light;

  /* surfaces */
  --page:        #EDEDEB;
  --page-alt:    #EFEFEC;
  --card:        #FFFFFF;
  --rule:        #DCDCD7;

  /* ink */
  --ink:         #0A0A0A;
  --ink-2:       #2A2A2A;
  --ink-3:       #3A3A3A;
  --muted:       #6B6B6B;
  /* --muted-2 is NOT a text colour: #8A8A8A reads 2.94:1 on --page and fails
     WCAG AA. Lighthouse caught it on .eyebrow after verify.py's own check
     had excused it as decorative. It was doing five text jobs, all now on
     --muted. Kept as a token for hairlines and disabled states only. */
  --muted-2:     #8A8A8A;
  --muted-3:     #B4B4B4;

  /* accent */
  --accent:      #2036C7;
  --accent-2:    #4A6BFF;
  /* v7's secondary blue is kept above, but it is a FILL/decorative token:
     #4A6BFF reads 3.70:1 on --page and 4.34:1 on --card, so as a link HOVER
     colour it made links less readable on hover than at rest. Lighthouse and
     the self-scoping contrast check both flag it. --accent-hover is the same
     move — brighter than the resting blue — at a readable weight. */
  --accent-hover:#3A54E0;
  --accent-wash: #DDE2FD;
  --accent-line: #C9D0FB;

  /* signal — two reds, because red is doing two different jobs.
     v7's #E15241 is the brand red and it comes back, but it fails WCAG AA as
     TEXT on a light ground (3.27:1 on --page, 3.83:1 on --card). It does not
     fail as a FILL: at 18px+ with #0A0A0A on top it clears 4.5:1 comfortably,
     which is what v7 actually did on the "Not mentioned" pill.
     So the rule is by role, not by taste:
       --alert      fills, borders, rules, the zero-cell background.  NEVER text on light.
       --alert-ink  a red WORD on a light surface. Same hue, darkened to pass.
     verify.py check 13 asserts both roles. Do not collapse them back into one. */
  --alert:       #E15241;
  --alert-ink:   #BE3227;
  --alert-wash:  #FBE9E7;

  /* shape — v7's measured values: 999px ×14, 28px ×7, 32px ×2 */
  --r-pill:      999px;
  --r-card:      28px;
  --r-card-lg:   32px;
  --r-chat:      22px 22px 8px 22px;
  --r-chat-alt:  22px 22px 22px 8px;
  --r-xs:        4px;

  /* type scale */
  --t-display:   clamp(38px, 6.4vw, 78px);
  --t-h1:        clamp(32px, 5vw, 60px);
  --t-h2:        clamp(26px, 3.4vw, 42px);
  --t-h3:        clamp(20px, 2.2vw, 27px);
  --t-lead:      clamp(17px, 1.7vw, 22px);
  --t-body:      clamp(15px, 1.15vw, 17px);
  --t-small:     clamp(13px, 1vw, 14px);
  --t-micro:     clamp(11px, 0.85vw, 12px);

  /* rhythm */
  --pad-x:       clamp(20px, 4vw, 44px);
  --pad-y:       clamp(72px, 9vw, 128px);
  --gap-s:       clamp(8px, 1vw, 12px);
  --gap-m:       clamp(16px, 2vw, 24px);
  --gap-l:       clamp(28px, 3.5vw, 48px);
  --maxw:        1240px;
  --maxw-read:   68ch;

  /* motion — calmed. v7 used .8s cubic-bezier reveals; these are ~500ms. */
  --ease:        cubic-bezier(.22, .9, .24, 1);
  --dur-reveal:  500ms;
  --dur-ui:      200ms;
  --rise:        8px;
}

/* --- base ----------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: var(--t-body);
  font-weight: 400;
  line-height: 1.55;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1, h2, h3, h4 {
  margin: 0;
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: -0.022em;
  text-wrap: balance;
}

h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); letter-spacing: -0.012em; line-height: 1.18; }

p { margin: 0 0 var(--gap-m); text-wrap: pretty; }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--accent-hover); }

img { max-width: 100%; height: auto; display: block; }

/* Focus is visible and never removed. WCAG 2.4.7. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--r-xs);
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  left: var(--pad-x); top: -100px;
  z-index: 100;
  padding: 12px 20px;
  background: var(--ink); color: var(--page);
  border-radius: var(--r-pill);
  text-decoration: none;
  transition: top var(--dur-ui) var(--ease);
}
.skip-link:focus { top: 12px; color: var(--page); }

/* --- layout --------------------------------------------------------------- */

.section {
  padding: var(--pad-y) var(--pad-x);
  background: var(--page);
}
.section--card  { background: var(--card); }
.section--alt   { background: var(--page-alt); }
.section--tight { padding-block: clamp(48px, 6vw, 80px); }

.wrap {
  max-width: var(--maxw);
  margin-inline: auto;
  width: 100%;
}
.wrap--read { max-width: var(--maxw-read); }

.stack       { display: flex; flex-direction: column; gap: var(--gap-m); }
.stack--l    { gap: var(--gap-l); }
.stack--s    { gap: var(--gap-s); }

/* --- masthead ------------------------------------------------------------- */

.masthead {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-m);
  padding: 18px var(--pad-x);
  background: color-mix(in srgb, var(--page) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid transparent;
}
.masthead[data-scrolled='true'] { border-bottom-color: var(--rule); }

.wordmark {
  font-weight: 700;
  font-size: var(--t-lead);
  letter-spacing: -0.03em;
  color: var(--ink);
  text-decoration: none;
}

/* --- eyebrow / labels ----------------------------------------------------- */

.eyebrow {
  margin: 0;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--muted);
}

.lead {
  font-size: var(--t-lead);
  line-height: 1.45;
  color: var(--ink-3);
  max-width: 58ch;
}

.fine {
  font-size: var(--t-small);
  line-height: 1.5;
  color: var(--muted);
}

/* --- buttons and pills ---------------------------------------------------- */

.btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 14px 26px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font: inherit;
  font-size: var(--t-body);
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-ui) var(--ease),
              color var(--dur-ui) var(--ease),
              border-color var(--dur-ui) var(--ease);
}
.btn--primary  { background: var(--ink); color: var(--page); }
.btn--primary:hover  { background: var(--accent); color: #FFFFFF; }
.btn--ghost    { background: transparent; color: var(--ink); border-color: var(--rule); }
.btn--ghost:hover    { border-color: var(--ink); color: var(--ink); }

.pill {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 7px 15px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--rule);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}
.pill--alert  { background: var(--alert-wash); border-color: var(--alert); color: var(--alert-ink); }
.pill--accent { background: var(--accent-wash); border-color: var(--accent-line); color: var(--accent); }

/* --- cards ---------------------------------------------------------------- */

.card {
  background: var(--card);
  border-radius: var(--r-card);
  padding: clamp(24px, 3vw, 40px);
  /* No shadows anywhere in this system. Separation is by ground and radius. */
}
.card--lg    { border-radius: var(--r-card-lg); }
.card--ruled { border: 1px solid var(--rule); }
.card--flat  { background: var(--page-alt); }

/* --- the chat mock -------------------------------------------------------- */
/* v7's best element. On the marketing site it is an illustration and says so.
   On a Gap Read page it holds a real, dated, verbatim capture. Same component,
   two uses — the honesty label is what differs, and it is not optional. */

.chat {
  background: var(--card);
  border-radius: var(--r-card-lg);
  padding: clamp(20px, 2.6vw, 32px);
  display: flex; flex-direction: column; gap: var(--gap-m);
  max-width: 720px;
}

.chat__ask {
  align-self: flex-end;
  max-width: 88%;
  padding: 14px 20px;
  border-radius: var(--r-chat);
  background: var(--ink);
  color: var(--page);
  font-size: var(--t-body);
}

.chat__answer {
  align-self: flex-start;
  max-width: 96%;
  padding: 4px 2px;
  color: var(--ink-2);
}

.chat__list { margin: var(--gap-s) 0; padding-left: 1.2em; }
.chat__list li { margin-bottom: 6px; color: var(--ink-2); }
.chat__list b { font-weight: 700; color: var(--ink); }

/* The absence card — the single most important element in the system. */
.chat__absence {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--gap-m); flex-wrap: wrap;
  margin-top: var(--gap-s);
  padding: 18px 22px;
  border: 1px solid var(--alert);
  border-radius: var(--r-card);
  background: var(--alert-wash);
}
.chat__absence-name { font-weight: 700; font-size: var(--t-h3); color: var(--ink); }
/* "Not mentioned" — v7's treatment restored: the brand red as a FILL with ink
   on top, which is both the stronger design and the accessible one. It is the
   single most important phrase on a pitch page and it should read as a stamp,
   not as coloured text. */
.chat__absence-verdict {
  font-weight: 600; font-size: var(--t-body);
  letter-spacing: 0.01em;
  padding: 6px 14px;
  border-radius: var(--r-pill);
  background: var(--alert);
  color: var(--ink);
}

.chat__note {
  font-size: var(--t-micro);
  color: var(--muted);
  letter-spacing: 0.04em;
}

/* --- the number ----------------------------------------------------------- */

.figure {
  display: flex; flex-direction: column; gap: 6px;
}
.figure__value {
  font-size: var(--t-display);
  font-weight: 700;
  line-height: 0.98;
  letter-spacing: -0.035em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.figure__value em { font-style: normal; color: var(--accent); }
.figure__label { font-size: var(--t-body); color: var(--muted); max-width: 46ch; }

.figure-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--gap-l);
}

/* --- evidence table ------------------------------------------------------- */

.table-scroll { overflow-x: auto; }

.evidence {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
  font-variant-numeric: tabular-nums;
}
.evidence th,
.evidence td {
  padding: 12px 14px;
  text-align: left;
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}
.evidence th {
  font-weight: 500;
  font-size: var(--t-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}
.evidence td:first-child { font-weight: 500; white-space: normal; }
.evidence .is-zero { color: var(--alert-ink); font-weight: 700; }

/* --- the screenshot ------------------------------------------------------- */
/* The screenshot is CORROBORATION. The quote itself lives above it as real
   text, because an engine cannot read a picture of text and a page whose
   evidence exists only in pixels fails the exact product this sells.
   So the capture is framed like a captured artifact, not a page element:
   never wider than it was taken, and never a 2,000px wall of scroll. */

.shot {
  /* Hug the capture rather than span the column: a 560px image floating in a
     1,250px frame reads as a broken layout, not as a captured artifact. */
  max-width: 620px;
  margin: 0 auto;
  background: var(--card);
  border: 1px solid var(--rule);
  border-radius: var(--r-card);
  overflow: hidden;
}
.shot__viewport {
  position: relative;
  background: var(--page-alt);
  border-bottom: 1px solid var(--rule);
}
.shot__frame {
  max-height: 420px;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  justify-content: center;
  /* Without this the flex default (align-items: stretch) stretches the capture
     vertically to the frame height — a 640x1434 screenshot rendered 560x400,
     squashed 3.5x and unreadable, with no error anywhere. Caught by measuring
     the rendered box in a real browser, which is the only way to catch it. */
  align-items: flex-start;
  padding: 20px 20px 0;
}
.shot img {
  display: block;
  width: auto;
  /* 560px cap, and never above native width — the captures are 640 and 724
     wide, so this always renders at or below 1:1. */
  max-width: min(100%, 560px);
  height: auto;
  border-radius: 10px;
  border: 1px solid var(--rule);
}
/* The fade sits on the viewport, not inside the scroller, so it stays put. */
.shot__viewport::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  height: 72px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(239, 239, 236, 0), var(--page-alt));
}
.shot.is-open .shot__frame { max-height: none; overflow: visible; padding-bottom: 20px; }
.shot.is-open .shot__viewport::after { display: none; }

.shot__toggle {
  display: block;
  width: 100%;
  padding: 12px 20px;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: var(--card);
  font: inherit;
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--accent);
  cursor: pointer;
  text-align: left;
}
.shot__toggle:hover { background: var(--accent-wash); }
.shot__toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.shot figcaption {
  padding: 14px 20px;
  border-top: 1px solid var(--rule);
  font-size: var(--t-micro);
  color: var(--muted);
  letter-spacing: 0.03em;
}

/* --- method note ---------------------------------------------------------- */
/* Small, and never moved below the fold or shortened. It is the reason the
   number above it is worth anything. */

.method {
  padding: clamp(18px, 2vw, 26px);
  border: 1px solid var(--rule);
  border-radius: var(--r-card);
  background: var(--page-alt);
  font-size: var(--t-small);
  color: var(--muted);
  line-height: 1.6;
}
.method dt {
  font-weight: 500; color: var(--ink-3);
  font-size: var(--t-micro); letter-spacing: 0.07em; text-transform: uppercase;
}
.method dd { margin: 2px 0 12px; }
.method dd:last-child { margin-bottom: 0; }

/* --- accordion ------------------------------------------------------------ */

.acc { border-top: 1px solid var(--rule); }
.acc__item { border-bottom: 1px solid var(--rule); }
.acc__btn {
  width: 100%;
  display: flex; align-items: baseline; gap: var(--gap-m);
  padding: clamp(20px, 2.2vw, 28px) 0;
  background: none; border: 0;
  font: inherit; color: var(--ink); text-align: left; cursor: pointer;
}
.acc__num { font-size: var(--t-small); color: var(--muted); font-variant-numeric: tabular-nums; }
.acc__title { flex: 1; font-size: var(--t-h3); font-weight: 700; letter-spacing: -0.012em; }
.acc__sign { font-size: var(--t-h3); color: var(--muted); line-height: 1; }
.acc__panel {
  overflow: hidden;
  max-height: 0;
  transition: max-height var(--dur-reveal) var(--ease);
}
.acc__item[data-open='true'] .acc__panel { max-height: 40rem; }
.acc__panel-inner { padding-bottom: clamp(20px, 2.2vw, 28px); max-width: 62ch; color: var(--ink-3); }

/* --- forms ---------------------------------------------------------------- */

.field { display: flex; flex-direction: column; gap: 7px; }
.field label { font-size: var(--t-small); font-weight: 500; color: var(--ink-3); }
.field input,
.field textarea {
  width: 100%;
  padding: 14px 18px;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--card);
  font: inherit; font-size: var(--t-body); color: var(--ink);
  transition: border-color var(--dur-ui) var(--ease);
}
.field textarea { border-radius: var(--r-card); min-height: 120px; resize: vertical; }
.field input:focus,
.field textarea:focus { border-color: var(--accent); }
.field input::placeholder,
.field textarea::placeholder { color: var(--muted-3); }
.field__error { font-size: var(--t-small); color: var(--alert-ink); }

/* Honeypot field for the contact form (functions/api/contact.js checks it
   server-side). Different from .visually-hidden on purpose: that class is
   readable by assistive tech by design, which is exactly wrong for a trap
   field — a screen reader user must never be asked to fill it in. This is
   hidden from layout AND pulled out of the accessibility tree, and taken out
   of tab order, so no real visitor — sighted, keyboard, or AT — ever meets
   it. Only an automated filler that ignores aria-hidden reaches it. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

/* --- footer --------------------------------------------------------------- */

.footer {
  padding: clamp(36px, 4vw, 56px) var(--pad-x);
  border-top: 1px solid var(--rule);
  background: var(--page);
  font-size: var(--t-small);
  color: var(--muted);
}
.footer__row {
  max-width: var(--maxw); margin-inline: auto;
  display: flex; flex-wrap: wrap; gap: var(--gap-m);
  align-items: center; justify-content: space-between;
}

/* Every page carries a visible author and last-updated date. */
.byline {
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  font-size: var(--t-small); color: var(--muted);
}
.byline time { font-variant-numeric: tabular-nums; }

/* --- direct-answer block -------------------------------------------------- */
/* 40–60 words at the top of every article and on /gap-read. Sits first in the
   DOM after the h1 so an answer engine reaches it before anything else. */

.answer-block {
  padding: clamp(20px, 2.4vw, 30px);
  border-left: 3px solid var(--accent);
  border-radius: 0 var(--r-card) var(--r-card) 0;
  background: var(--card);
  font-size: var(--t-lead);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 62ch;
}

/* --- motion --------------------------------------------------------------- */
/* Scroll reveal: fade + 8px rise, 500ms. Applied by adding [data-reveal];
   JS sets data-shown="true". If JS never runs, content is visible — the
   default state below is the visible one for anything without JS support. */

@media (prefers-reduced-motion: no-preference) {
  [data-reveal] {
    opacity: 0;
    transform: translateY(var(--rise));
    transition: opacity var(--dur-reveal) var(--ease),
                transform var(--dur-reveal) var(--ease);
  }
  [data-reveal][data-shown='true'] {
    opacity: 1;
    transform: none;
  }
  /* Staged chat reveal — each message follows the one before it. */
  .chat > [data-reveal]:nth-child(2) { transition-delay: 120ms; }
  .chat > [data-reveal]:nth-child(3) { transition-delay: 240ms; }
  .chat > [data-reveal]:nth-child(4) { transition-delay: 360ms; }
  .chat > [data-reveal]:nth-child(5) { transition-delay: 480ms; }
}

/* Belt-and-braces: even if a token above is overridden, motion stops here. */
@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-reveal] { opacity: 1 !important; transform: none !important; }
  .acc__panel { transition: none !important; }
}

/* --- print ---------------------------------------------------------------- */
/* A Gap Read page gets forwarded and printed. It should survive that. */

@media print {
  .masthead, .skip-link, .btn { display: none; }
  body { background: #FFFFFF; font-size: 11pt; }
  .section { padding: 0 0 18pt; }
  .card, .chat, .shot, .method { border: 1pt solid #999; break-inside: avoid; }
  /* Print the whole capture. A scroll frame is a screen affordance and on
     paper it would silently crop the evidence. */
  .shot__toggle { display: none; }
  .shot__frame { max-height: none; overflow: visible; }
  .shot__viewport::after { display: none; }
  a[href^='http']::after { content: ' (' attr(href) ')'; font-size: 9pt; color: #555; }
}
