/* =============================================================================
   PRISM II — FOUNDATION. Tokens, both themes, the type scale, the button family,
   the page primitives, and the prism mark.

   Everything else in site_* inherits from this file, so nothing here is allowed
   to be approximately right.

   WHERE THIS SITS IN THE CASCADE. The layout makes exactly one stylesheet call,
   `stylesheet_link_tag :app`, and Propshaft answers it with every CSS file
   under app/assets, sorted by logical path, one link each, on every page:

       builds/tailwind.css   (compiled from app/assets/tailwind/application.css)
       labs_00 … labs_09
       site_00 … site_06     <- this file and its siblings, LAST

   Loading last is deliberate: the :root below has to be the final word on the
   tokens, because /labs and /admin adopt them in phase 5. The cost is that a
   generic class here would beat a labs rule of equal specificity, so every class
   name in site_* was grepped against all ten labs files and application.css
   before it was used. Two came back and both are handled without a rename:

     .eyebrow  application.css defines it globally as a full mono label, and
               /labs and /admin both render it. Our rule is `.mono.eyebrow`, two
               classes, which only matches the new markup — the mockup writes
               `class="mono eyebrow"` on every eyebrow it has.
     .wrap     shared with /admin, which overrides its width by redeclaring
               `--maxw` on `.admin-topbar, .admin-main` — the area follows the
               screen where the marketing site holds a fixed measure. So .wrap
               keeps the custom-property indirection rather than hard-coding
               1140px, and that override still works. NOTE THE DIRECTION: /admin
               overrides the TOKEN on an ancestor, never this rule, and it must
               never move the declaration below, which the whole public site
               reads.

   `.stack`, `.hero`, `.lab` and `.lede` looked like collisions and are not: every
   existing use is a descendant selector, which outranks a bare class here.
   ============================================================================= */

/* -----------------------------------------------------------------------------
   THE SPECTRUM. Warm end = a person is present. Cool end = a machine is.

   Each light hue has a text-weight partner, because the light one cannot carry
   type. That rule is not a preference: the previous mock stated it and then broke
   it in its two most visible places — the emphasised word in every H1 (measured
   2.33:1 and 2.47:1) and every lab stage label (2.92:1) — and a measurement pass
   returned 150 contrast failures from a single ink token. A light hue is a wash
   or a stroke. It is never a `color`.
   -------------------------------------------------------------------------- */
:root {
  --ember:  #FF4C00;  --clay: #A33500;   /* a person        · graphic / text */
  --peri:   #8B8ADD;  --plum: #4B3B8F;   /* the translation · graphic / text */
  --cobalt: #3545A3;  --deep: #1F2A6B;   /* the machine     · graphic / text */

  --apricot: #FEC180;  /* warm light */
  --lilac:   #BFC6FA;  /* translated light */
  --ice:     #D0DFF1;  /* blueprint light */
  --rose:    #E76F70;  /* the revenue reading — deliberately rare */
  --sand:    #F6E4CE;
  --blueprint: #EEF3FB;

  /* Ground and ink. --ink3 is #635D52 rather than the #8E887D it started as:
     one token, and 150 of the measurement run's contrast failures with it. */
  --bg: #FFFFFF;  --bg2: #F5F1E9;  --bg3: #EFE9DE;
  --ink: #17140F; --ink2: #5E594F; --ink3: #635D52;
  --line: rgba(28,23,15,.13); --line2: rgba(28,23,15,.24); --warm-line: #E4D9C8;

  /* Fills. Both are the TEXT partner of their position, not the light hue, so
     the label clears 4.5:1 — white on #FF4C00 measured 3.34:1 and white on
     #8B8ADD measured 3.10:1. */
  --fill-primary: #3545A3; --fill-primary-hi: #2B3A8D;
  --fill-human:   #C43C00; --fill-human-hi:   #A82F00;
  --on-fill: #FFFFFF;
  /* --draw-warm is --draw's warm sibling, for a drawing on a warm plate (home
     figure 1.3). It exists because a STROKE may not be multiplied by --hero-k
     the way a wash is: figure 1.3 mixed clay at `55% * --hero-k` against --bg,
     which is 15.4% of a hue against near-black in dark mode, and the whole
     drawing vanished but for its ember highlight. The light value is exactly
     what that mix computed to, so light mode is unchanged; the dark one is
     tuned to --draw's own 5.3:1 against --bg rather than to clay's lightness. */
  --draw: #8794C4; --draw-warm: #CC9273; --grid: rgba(53,69,163,.09);

  /* Status is a separate axis from identity and always ships with a word. */
  --pass: #12722F; --part: #8A5A08; --miss: #B03024;
  --pass-line: rgba(18,114,47,.42);
  --part-line: rgba(138,90,8,.4);
  --miss-line: rgba(176,48,36,.4);

  /* Three type voices. The families changed; the encoding did not. Serif is the
     human voice, mono is the data voice, sans narrates. Newsreader appears in
     exactly three places on the whole site — the triad's human reading, every
     quotation, and About's opening line — and that scoping is the point: the
     moment-of-truth triad asserts that one conversation is three things at once,
     and it demonstrates that claim typographically or it is only asserting it. */
  --display: "Inter","SF Pro Display",-apple-system,system-ui,sans-serif;
  --sans:    "Inter","SF Pro Display",-apple-system,system-ui,sans-serif;
  --human:   "Newsreader",Georgia,"Times New Roman",serif;
  --mono:    "IBM Plex Mono",ui-monospace,Menlo,monospace;

  /* Page metrics. --maxw and --gutter keep the names application.css already
     used, so /admin's `.wrap.is-wide` width override survives phase 3's deletion
     of the old marketing block. The gutter was a clamp(20px,5vw,64px); it is now
     the mockup's flat 32, stepping to 24 below 920. */
  --maxw: 1140px;
  --gutter: 32px;
}

/* Dark is DESIGNED, not inverted. The previous mock's entire dark mode was one
   rule setting the background to white.

   Declared twice on purpose: once behind the system preference, guarded so an
   explicit light choice wins, and once behind an explicit dark choice so the
   toggle wins in both directions.

   Only the TEXT partners brighten. The light hues themselves — ember, peri,
   cobalt, apricot, lilac, ice, rose, sand — stay exactly where they are, because
   they are washes mixed against --bg, and mixing them against a dark ground is
   what produces the dark band. Redefining them would double-correct. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #14120F;  --bg2: #1C1915;  --bg3: #26221C;
    --ink: #F2EFE9; --ink2: #B8B2A6; --ink3: #ADA79B;
    --line: rgba(242,239,233,.14);
    --line2: rgba(242,239,233,.26);
    --warm-line: rgba(242,239,233,.16);
    --clay: #FF8A55; --plum: #A9A6F0; --deep: #8FA2E8;
    --fill-primary: #8FA2E8; --fill-primary-hi: #A9B6EE;
    --fill-human:   #FF8A45; --fill-human-hi:   #FFA06A;
    --on-fill: #12100D;
    --draw: #7C86AE; --draw-warm: #B0805F; --grid: rgba(169,166,240,.10);
    --pass: #7FD8A0; --part: #E0B458; --miss: #F08A7E;
    --pass-line: rgba(127,216,160,.42);
    --part-line: rgba(224,180,88,.42);
    --miss-line: rgba(240,138,126,.42);
  }
}
:root[data-theme="dark"] {
  --bg: #14120F;  --bg2: #1C1915;  --bg3: #26221C;
  --ink: #F2EFE9; --ink2: #B8B2A6; --ink3: #ADA79B;
  --line: rgba(242,239,233,.14);
  --line2: rgba(242,239,233,.26);
  --warm-line: rgba(242,239,233,.16);
  --clay: #FF8A55; --plum: #A9A6F0; --deep: #8FA2E8;
  --fill-primary: #8FA2E8; --fill-primary-hi: #A9B6EE;
  --fill-human:   #FF8A45; --fill-human-hi:   #FFA06A;
  --on-fill: #12100D;
  --draw: #7C86AE; --draw-warm: #B0805F; --grid: rgba(169,166,240,.10);
  --pass: #7FD8A0; --part: #E0B458; --miss: #F08A7E;
  --pass-line: rgba(127,216,160,.42);
  --part-line: rgba(224,180,88,.42);
  --miss-line: rgba(240,138,126,.42);
}

/* -----------------------------------------------------------------------------
   BASE
   -------------------------------------------------------------------------- */
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* .essay (site_02) bleeds past .wrap with 50vw arithmetic; clip, not hidden,
     so the page never gains a sideways scroll and sticky chrome keeps working. */
  overflow-x: clip;
}
h1, h2, h3 { margin: 0; font-weight: 400; }
p { max-width: 64ch; margin: 0 0 14px; }
:focus-visible {
  outline: 2px solid var(--fill-primary);
  outline-offset: 2px;
  border-radius: 2px;
}

/* THE SURFACES NOBODY DRAWS. A selection highlight ships in the browser's own
   blue on a site that owns three hues, and it is the one piece of chrome a
   reader produces themselves, every time they quote a price or a figure to a
   colleague. Painted from the palette: the wash carries it, the ink stays the
   page's own, so the rule that a light hue is never type still holds. */
::selection {
  background: color-mix(in srgb, var(--ice) 78%, var(--bg));
  color: var(--ink);
}

.wrap {
  max-width: var(--maxw);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
}

/* -----------------------------------------------------------------------------
   THE TYPE SCALE, and it is a scale. The previous mock rendered 32 distinct sizes
   across two families and set 360 of its ~1,000 text runs below 14px. These are
   the only sizes any element may take, and 12px is the floor.
   -------------------------------------------------------------------------- */
.d {
  font-family: var(--display);
  font-weight: 500;
  letter-spacing: -.022em;
  font-optical-sizing: auto;
}

.t-96 { font-size: clamp(42px, 6.6vw, 96px); line-height: .96;  letter-spacing: -.03em;  }
.t-56 { font-size: clamp(30px, 4.2vw, 56px); line-height: 1.05; letter-spacing: -.024em; }
.t-38 { font-size: clamp(25px, 2.9vw, 38px); line-height: 1.1;  letter-spacing: -.02em;  }
.t-28 { font-size: 28px; line-height: 1.16; letter-spacing: -.018em; }
.t-21 { font-size: 21px; line-height: 1.3;  letter-spacing: -.014em; }

.lede  { font-size: 19px; line-height: 1.55; color: var(--ink2); font-weight: 300; max-width: 58ch; }
.body  { font-size: 17px; color: var(--ink2); font-weight: 400; }
.small { font-size: 15px; color: var(--ink2); font-weight: 400; }

/* The label voice. Uppercase is reserved for labels short enough to be labels;
   anything longer stays sentence case, which is what .mono.sentence is for. */
.mono {
  font-family: var(--mono);
  font-size: 12px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink3);
  font-weight: 400;
}
.mono.sentence { text-transform: none; letter-spacing: .02em; font-size: 13px; }

/* Two classes, not one. application.css already defines .eyebrow globally and
   /labs and /admin both render it; matching on both classes leaves those alone.
   See the collision note at the top of this file. */
.mono.eyebrow { margin-bottom: 12px; }

/* The page accent, inline. Both fall back to a hue that is already a text
   partner, so an element that somehow escapes its page scope is still legible.
   em.it is the emphasised H1 word: not italic, not bolder, just the accent — the
   headline's payload, and previously the least legible thing on the page. */
.o { color: var(--accent, var(--cobalt)); }
em.it { font-style: normal; font-weight: inherit; color: var(--accent, var(--plum)); }

/* -----------------------------------------------------------------------------
   THE TYPEWRITER. Moved here from application.css when the old marketing block
   was deleted, because it belongs beside the controller that drives it and
   because every page's hero uses it now, not just the home page.

   The reveal does NOT empty the heading. It used to, which meant the h1 collapsed
   and its height had to be reserved first, and the reserved space showed as a
   ~288px empty rectangle in the fold for the whole animation — a fix for one flaw
   that produced another. The remaining text is drawn faintly instead, so the
   headline is a complete shape from the first frame and the typing sharpens it
   left to right: no void, and nothing below it can move.

   That is also what lets the accent word survive. The controller splits every
   text node into a typed and a pending span in place, so the em around the
   payload word is never removed — .tw-pending's opacity applies inside it and the
   accent colour is inherited rather than reapplied.
   -------------------------------------------------------------------------- */
/* THE CARET AND THE .typewriter-text HOOK ARE BOTH GONE (2026-09-05), and the
   caret is the half worth explaining, because it looks like something that was
   working and got deleted.

   Until Prism II the headline was <h1><span class="typewriter-text"
   data-controller="typewriter">, that span was hidden until the controller
   announced itself, and a blinking caret hung off the typed run. Prism II put the
   controller on the h1 itself so it could type through child nodes and keep the
   <em class="it"> accent word alive, and the wrapper span went with it — so
   nothing has carried .typewriter-text since, and every rule keyed to it has been
   dead for days.

   Reviving the caret by rescoping it to the controller attribute was tried first
   and MEASURED, which is what settled it: an inline-block of 0.85em against a
   96px headline grows the line box, taking /software-development's h1 from 266px
   to 289px and its hero band from 673px to 695px FOR THE WHOLE ANIMATION. That is
   precisely the failure the reveal was rebuilt to end — "the headline holding one
   height is what keeps everything under it still" — so the caret cannot come back
   in this form. It belonged to the empty-and-retype mechanism, which no longer
   exists.

   The visibility pair went with it and was purely vestigial: the remainder is held
   faint now (.tw-pending below), never removed, so there is nothing to hide and no
   <noscript> override to need. */
/* The not-yet-typed remainder, held as a ghost of the finished line.

   FAINT, NOT INVISIBLE, and the difference is the whole point. The reveal used to
   empty the h1 and reserve its height, which stopped the page below from sliding
   about but left a ~288px blank rectangle in the fold for the length of the
   animation — the promise arriving as a hole. Setting this to `opacity: 0` fixes
   the layout half and keeps the hole, because transparent text still draws
   nothing. At a tenth of full ink the headline is present as a complete shape
   from the first frame and the typing sharpens it left to right, so there is no
   void at any moment and nothing below can move either.

   Not `visibility: hidden` and not a pseudo-element: this text stays in the
   accessibility tree and in textContent, because it is the headline whether it
   has been drawn at full strength yet or not. */
.tw-pending { opacity: 0.12; }
/* No narrow measure: this runs the full width of .wrap, the same call .labs-lede makes and
   for the same reason — the hero reads as one column, and at 800px this sentence broke to a
   third line carrying two words. The h1 above it keeps its own 24ch measure, so the two are
   deliberately different widths rather than accidentally the same one. */

/* -----------------------------------------------------------------------------
   BUTTONS. Rounded is reserved for actions — the pill is what makes a thing
   pressable here, and nothing else on the site carries a radius. Every variant
   clears 44px.
   -------------------------------------------------------------------------- */
.btns { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; }
.btns > * { min-width: 0; }

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  min-height: 44px;
  padding: 11px 22px;
  border-radius: 999px;
  font-family: var(--sans);
  font-size: 16px;
  font-weight: 500;
  border: 1px solid var(--line2);
  color: var(--ink);
  background: color-mix(in srgb, var(--bg) 55%, transparent);
  cursor: pointer;
  transition: background .18s, border-color .18s;
}
.btn:hover { background: var(--bg); }

/* The ask takes the colour of what it opens: a check a machine runs is the
   machine's blue, a conversation with a person is the person's ember. */
.btn.primary { background: var(--fill-primary); border-color: var(--fill-primary); color: var(--on-fill); }
.btn.primary:hover { background: var(--fill-primary-hi); border-color: var(--fill-primary-hi); }
.btn.human { background: var(--fill-human); border-color: var(--fill-human); color: var(--on-fill); }
.btn.human:hover { background: var(--fill-human-hi); border-color: var(--fill-human-hi); }

/* THE SOFT PILL — A FILL'S WEIGHT WITHOUT ITS RANK (2026-09-04).

   The header's "Free audit" was .btn.primary, which is the same fill the contact
   band gives the same words at the foot of five pages. That band is a page's one
   full-weight ask; the header is sticky, so its copy of it sat above the fold at
   every scroll position and the smaller ask permanently outranked the page it was
   drawn on. The hierarchy the header's own comment claims — a conversation is the
   larger ask, the audit is the smaller one — was contradicted by the styling.

   So: still a pill, because the radius is what makes a thing pressable here, and
   still cobalt, because the audit is a check a machine runs. One weight below the
   fill rather than level with it.

   Both values are color-mix against --bg rather than fixed colours, which is what
   makes this one rule serve both themes: on the dark ground the wash mixes dark
   and --deep has already brightened to #8FA2E8. A literal tint would have needed a
   second declaration in each of the two dark blocks above.

   The hairline is not decoration. At 12% ground the pill is close enough to the
   page that without a 42% border it reads as disabled — the one failure mode this
   variant has. */
.btn.soft {
  background: color-mix(in srgb, var(--cobalt) 12%, var(--bg));
  border-color: color-mix(in srgb, var(--cobalt) 42%, var(--bg));
  color: var(--deep);
}
.btn.soft:hover {
  background: color-mix(in srgb, var(--cobalt) 20%, var(--bg));
  border-color: var(--cobalt);
}

.btn.plain { border-color: transparent; background: none; padding-left: 0; padding-right: 12px; }
.btn.plain:hover { background: none; color: var(--accent, var(--cobalt)); }
.btn.sm { min-height: 44px; padding: 10px 16px; font-size: 15px; }

/* THE CLIPPED-TEXT UTILITY, and it is the site's first (2026-09-04). Admin had
   .gtm-visually-hidden in application.css; the marketing pages had nothing, so
   the only way to say something to a screen reader and not to a screen was to
   not say it. Clip-path rather than a negative text-indent or width:0 — both of
   those either drag the caret off-canvas or get dropped from the accessibility
   tree by at least one engine. It must never take display:none. */
.vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The external glyph is a NOTE, not an arrow: it sits a size down from svg.ico
   and inherits the label's colour at reduced weight, so it reads as a footnote
   on the button rather than as a second action beside it. */
svg.ico.ico-ext {
  width: 14px;
  height: 14px;
  opacity: .72;
}

svg.ico {
  width: 18px;
  height: 18px;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  flex: none;
}

/* -----------------------------------------------------------------------------
   SECTIONS

   THE SECTION HEADER IS ONE COLUMN. It used to be a 96px rail holding the number
   and a content column beside it, which put the ordinal in its own dead column
   and pushed every heading, lede and section body 128px in from the page's own
   left edge. The number belongs to the eyebrow, on the eyebrow's line, in the
   same face and colour: "01 The second audience" reads as one label, not as an
   ordinal next to a label.
   -------------------------------------------------------------------------- */
.sec { padding: 84px 0; border-bottom: 1px solid var(--line); }
.sec:last-child { border-bottom: 0; }

.shead { margin-bottom: 40px; }
.shead > div { min-width: 0; }
.shead h2 { max-width: 22ch; text-wrap: balance; }
.shead .lede { margin-top: 18px; }
/* A LINK IN A LEDE HAS TO LOOK LIKE ONE (2026-09-06). /ai-visibility's section 03
   names the rebuild path in prose and links it, and with no rule here the anchor
   inherited the lede's own ink and carried no underline: a link nobody can see,
   which is the failure mode that renders perfectly. It takes the page accent,
   the same treatment .ar-back a already uses on /audit. --accent is always a
   text partner, never a light hue, so this cannot drop below the contrast floor
   whichever page it lands on.

   IT IS UNDERLINED IN BOTH THEMES, AND COLOUR ALONE IS NOT ENOUGH — MEASURED.
   Against the page ground the accent is comfortable either way (13.11:1 light,
   7.57:1 dark). Against the SURROUNDING LEDE TEXT, which is what actually tells
   a reader this word is a link, it is 1.88:1 in light and 1.17:1 in dark: in
   dark the two inks are nearly the same lightness and differ only in hue, so the
   link disappears for anyone who does not separate blue from warm grey. The
   underline is the non-colour cue that carries it, and the hover thickens rather
   than introduces it. */
.shead .lede a {
  color: var(--accent, var(--deep));
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}
.shead .lede a:hover { text-decoration-thickness: 2px; }

.secnum { margin-right: 1.1em; font-variant-numeric: tabular-nums; }
/* As a block, the ordinal opens a numbered essay instead of a section head, so it
   takes a line of its own and drops the inline gap. */
p.secnum { display: block; margin: 0 0 10px; margin-right: 0; }

/* -----------------------------------------------------------------------------
   THE PRISM MARK.

   Reproduced from prism_blueprint() in lib/studio/auditor/report_renderer.py to
   the unit rather than redrawn: a beam of 72 entering on a measurement tick, a
   triangle 40 deep and 60 tall whose TIP is the crossing point, and three rays 76
   long to +30, 0 and -30, each landing on its own tick.

   The reason it is copied rather than approximated is that a prospect meets this
   drawing on the free audit report before they ever meet the site. If the two
   disagree, the site is the one that is wrong.

   Dotted throughout; only the ticks are solid, because they are instruments
   rather than light. The symbol itself lives in shared/_icon_sprite.
   -------------------------------------------------------------------------- */
.prism svg {
  width: 100%;
  height: auto;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.prism .pz-beam path,
.prism .pz-body path,
.prism .pz-fan path {
  stroke-width: 1.4;
  stroke-dasharray: 0 4.5;
  fill: none;
}
.prism .pz-beam path,
.prism .pz-body path { stroke: color-mix(in srgb, var(--deep) 62%, transparent); }
.prism .pz-tick path {
  stroke: color-mix(in srgb, var(--deep) 50%, transparent);
  stroke-width: .9;
  stroke-dasharray: none;
}
/* The three rays ARE the palette, in order: a person, the translation, the
   machine. One customer conversation goes in and comes out as three readings. */
.prism .r1 { stroke: var(--ember); }
.prism .r2 { stroke: var(--peri); }
.prism .r3 { stroke: var(--cobalt); }

/* -----------------------------------------------------------------------------
   RESPONSIVE
   -------------------------------------------------------------------------- */
@media (max-width: 920px) {
  :root { --gutter: 24px; }
  .sec { padding: 60px 0; }
}

/* The quotes move; nothing else on this site does. Under reduced motion even
   they stop — see site_05_voices, which replaces the loop with a native snap
   scroller rather than simply freezing it. */
@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }

  /* The one carve-out: a full-screen or floating panel that opens/closes still
     gets a quick opacity cross-fade rather than the blanket cut above — reduced
     motion means a gentler equivalent, not none. The lift/scale each already
     drops under this preference (see site_01_chrome.css and
     labs_00_listening_post.css); only the fade survives. Higher specificity than
     `*` above, so this wins the cascade without needing its own !important on
     every property — but transition itself still needs it, since `*` already
     claimed that property at the same importance. */
  .sheet, .lp-menu-panel { transition: opacity 100ms ease !important; }
}
