/* ══════════════════════════════════════════════════════════
   aw-shared.css — THE SHARED CHROME (Phase 3.2 of LAUNCH_TASKLIST.md, 2026-08-09)

   The decision, locked by Nathan: "shared but partial." This sheet holds ONLY what is
   byte-identical across every page that carries it — the colour tokens, the type stack,
   the nav, the wordmark pieces, the AW corner mark, the /command opening, the emblem
   container, the closing /connect ask, and the sheen. EVERYTHING page-specific stays in
   that page's own <style>: its mark + builder, its content styles, and every locked
   per-page number (--esize, --eop, --eblur, --espd, --hlnudge, trace curves, dash-scale).

   ⚠ THE LINK TO THIS SHEET MUST COME BEFORE THE PAGE'S OWN <style> — page rules are
   meant to win on equal specificity. Reordering that silently changes who wins.
   ⚠ ONE DECLARATION, ONE PLACE. Precedent: a duplicated --esize masked by a specificity
   crutch rendered a mark 220px small. If a value here needs a page-specific override,
   the override lives in the page WITH A COMMENT saying it overrides the shared sheet.
   ⚠ @keyframes are deliberately NOT here — they stay beside the rules that consume them
   (an undefined animation-name fails silently; keeping them adjacent is the guard).
   ⚠ Dark mode (Phase 3.3) lands in this file as a prefers-color-scheme block redefining
   the tokens — that is the whole reason the tokens moved here.
   ══════════════════════════════════════════════════════════ */

/* ══ GROUND — ANGL silver, LOCKED 2026-08-08 (Nathan). The six colour tokens every page
   reads, plus the chrome offsets and opening timings composed everywhere:
   --edge  — wordmark "more left" placement, capped so a narrow gutter never clips it
   --inMark / --fadeDur — the opening's two beats; retune here and every page moves.
   ⚠ A palette re-pick is queued (Phase 2.1, f.lux OFF, both modes) — when it lands,
   these values change HERE and only here. */
/* ✅ --bg RELOCKED #e7e2d0 by Nathan, 2026-08-09 (palette session, f.lux OFF): "thats
   really close to what it was thru my filter." Warm cream replaces the cool ANGL silver
   #eef1f5 (retired to prototype/README.md's ground history).
   ✅ --hair #cbc9bd, same session: bg and hair were first locked EQUAL, which erased the
   /connect field rules (the /send and /connect commands never used --hair, so only the
   form went lineless — Nathan spotted it on the preview). This value re-applies the OLD
   pair's per-channel bg→hair distance to the new ground, so the lines whisper at the
   strength they always did. Retune here if the ground ever moves again.
   ✅ ink / dim / gear / gear2 CONFIRMED UNCHANGED by Nathan, 2026-08-09 — light mode is
   fully locked. Dark mode still pending its own session. */
body{--bg:#e7e2d0;--ink:#16202f;--dim:#57647a;--gear:#99a2b3;--gear2:#22456f;--hair:#cbc9bd;
     --edge:max(-6rem,calc(1rem - clamp(1.5rem,7vw,9rem)));
     --inMark:500ms;--fadeDur:900ms}

/* ══ DARK MODE — LOCKED by Nathan 2026-08-09, same palette session: "dark mode is
   beautiful as is. no changes to any of the hex colors." Judged live in the palette
   picker across pages. Follows the VISITOR'S system setting — no manual toggle in v1
   (that is a v2 item; do not build it twice).
   The six values descend from the retired "void" ground (prototype/README.md), the only
   dark palette this project ever built — judged and confirmed unchanged.
   ⚠ THE THREE LIGHT-LOCKED HOLDOUTS (headline drop-shadow, emblem presence/blur, the
   /connect sheen) ship UNCHANGED in dark — deliberately. Nathan's ruling was on exactly
   what the picker rendered, which includes the navy shadow reading as effectively
   invisible on the dark ground and the sheen pushing against near-black. That look IS
   the approved look. If a dark shadow is ever wanted, the void-ground precedent was
   BLACK and heavier: rgba(0,0,0,.6) — do not reuse the navy.
   color-scheme makes native UI (scrollbars, form widgets) follow the mode. */
:root{color-scheme:light dark}

/* ⚠ THE VIEWPORT MUST NEVER SCROLL SIDEWAYS — spar finding, 2026-08-09. Body-level
   overflow does not reliably clamp the viewport (programmatic scrollX moved on /portfolio
   at 390px, and iOS Safari famously pans anyway). The html-level guard is the one that
   holds. The 24px culprit was the piece rows' overhanging arrow — also handled at its
   source in portfolio.html.

   ⚠⚠ AND IT IS THE **ONLY** ONE — 2026-09-04. Every page ALSO carried
   body{overflow-x:hidden}, and that second copy was doing real damage. Setting overflow on
   ONE axis forces the other axis from `visible` to `auto` (CSS spec: visible + non-visible
   is an illegal pair). So html got overflow-y:auto — which is what we want, it becomes the
   page scroller — but body got overflow-y:auto too, and a body that is its own scroll
   container also establishes a block formatting context, so its children's margins stop
   collapsing out through it. That trapped ~28px inside body, which is exactly enough to
   make body overflow ITSELF. Result, on every scrolling page:

     · TWO vertical scrollbars, side by side (html's real one, body's phantom 28px one).
     · The wheel latched onto the INNER scroller first, so the opening flicks of a gesture
       moved the page ~5px instead of ~600px. It read as "the page is locked until the
       intro animation finishes" — it was never locked; the wheel was being eaten. The
       animations and the scroll were already decoupled, and now they behave that way.

   So: the guard lives HERE, at html, and nowhere else. Do not re-add an overflow of any
   kind to body on a scrolling page — it cannot help (see above) and it costs a scrollbar.
   /connect is the one exception and it is not an exception to this rule: it sets
   body{overflow:hidden} on BOTH axes, which is a deliberate no-scroll page, not a clamp. */
html{overflow-x:hidden}

/* ⚠ THE OPENING'S SCROLL HOLD — 2026-09-04. The four deep pages stop the page while the
   slash command types itself, and hand the scroll back the instant the command fades out
   (the emblem and the headline then arrive over a page the reader can already move).
   Both classes are set from each page's runSequence(); index and /connect never set them,
   so this sheet is inert there. See any deep page's holdScroll() for the full reasoning —
   in particular why the gutter is reserved separately and never released. */
html.cmd-gutter{scrollbar-gutter:stable}
html.cmd-hold{overflow-y:hidden}

/* ⚠ NO PIPES ON A WRAPPED NAV — spar finding, 2026-08-09. On phone widths the nav wraps
   to two lines and the :not(:first-child) separator logic breaks its own "between only"
   contract: the second line opens with a stray "| " that reads as a typesetting error.
   Below 600px the pipes go entirely — the lowercase words hold the rhythm on their own.
   (body prefix = one element of extra specificity, so this beats the page-local pipe
   rules despite this sheet loading first.) */
@media (max-width:600px){body .nav nav a:not(:first-child)::before{content:none}}
@media (prefers-color-scheme: dark){
  body{--bg:#111a26;--ink:#eef1f5;--dim:#7c8a9c;--gear:#4a719e;--gear2:#99a2b3;--hair:#243447}
}

/* ⚠ FIVE NAV-STATE RULES ARE DELIBERATELY NOT IN THIS SHEET — learned the hard way
   during extraction (the home-page diff lit up with 44 property changes):
     .nav nav a::before · .nav nav a:not([aria-current]) · body.is-open .nav__brand,
     body.is-open .nav nav a · body.is-open .nav nav a:not(:first-child)::before ·
     body.is-open .nav nav a:not([aria-current])
   The five deep pages carry them; the HOME page has matching elements (it sets is-open,
   its nav has no aria-current) but deliberately no you-are-here dimming or pipe base —
   sharing them restyled the home nav. The shareability test is not "identical wherever
   it appears": it is "matches NOTHING on the pages that lack it." */


/* ══════════════════════════════════════════════════════════════════════════
   SITE FURNITURE — inherited unchanged from the locked home page.
   Ground, type stack, nav, wordmark + readout at --edge. Do not re-decide any
   of this here; it is settled in prototype/README.md.
   ══════════════════════════════════════════════════════════════════════════ */
:root{--font-display:'Source Serif 4',Georgia,serif;--font-mono:'JetBrains Mono',ui-monospace,monospace;
      --font-wordmark:'Cormorant Garamond',Georgia,serif}

.nav{position:fixed;top:0;left:0;right:0;z-index:5;display:flex;justify-content:space-between;align-items:center;
  padding:1.3rem clamp(1.5rem,7vw,9rem)}

/* ✅ LOCKED 2026-08-08, SITE-WIDE — the nav links and the closing /connect command are
   Quicksand on every page. ⚠ THE WORDMARK IS NOT: it stays Cormorant Garamond. Nathan was
   explicit, and it is right — the wordmark is a logotype, not chrome, and a logotype that
   changes face when the nav does has stopped being a logotype. */
/* ⚠⚠ `.nav nav a`, NOT `.nav nav a` — THE WORDMARK IS AN <a> INSIDE .nav. The broad selector
   swept it up and put the logotype in Quicksand, which is the exact thing this lock says
   must not happen. Only the links inside the <nav> element are chrome.
   ⚠⚠ `.ask a.ask__go` for specificity, not `.ask__go`. The locked closing-command rule
   sets font-family from --cmdfam and is declared LATER in the file, so the plain class
   selector lost the cascade silently and /connect stayed on Plex Sans. */
.nav nav a,.ask a.ask__go{font-family:'Quicksand',system-ui,sans-serif}

.nav nav a:hover{transform:translateY(-4px);color:var(--gear2)}

/* ⚠ RESTORED 2026-08-09 — Fable audit finding 1, Nathan: "easy fix." The hover tint and
   the deep pages' you-are-here dim rule are EQUAL specificity (0,2,2), so source order
   used to decide; the extraction moved the hover rule into this earlier-loading sheet
   and silently handed the win to the page-local dim — links raised on hover but stayed
   faded. This higher-specificity rule settles it independent of order. (The snapshot
   verification could not catch it: computed-style dumps cannot capture :hover.) */
.nav nav a:not([aria-current]):hover{color:var(--gear2)}

/* ⚠ THE PIPE HOLDS STILL WHILE THE WORD RAISES — the separator belongs to the ROW, not
   the item; this +4px cancels the link's locked -4px raise exactly. RESTORED 2026-08-09
   (Nathan: "only the words should raise, not the | mark"): the extraction moved this
   counter-rule into this earlier-loading sheet, and the page-local pipe rules — EQUAL
   specificity (0,2,3), later in order — silently beat it. Fourth member of the
   order-race family. The body prefix settles it by structure: (0,2,4).
   ⚠ If the raise distance ever changes, this number changes with it. */
body .nav nav a:hover::before{transform:translate(-50%,-50%) translateY(4px)}

.nav nav a[aria-current]{color:var(--gear2)}

.nav__brand b{font-weight:600}

.nav__brand span{font-weight:500;color:var(--gear)}

/* ⚠ THE OPENING SEQUENCER'S NAV RULES STAY IN EACH PAGE — body.is-landing
   .nav nav a[aria-current], the --navin transition rule, and their reasoning comment are
   one coherent group beside each page's boot machinery. Splitting the group across two
   files to save three duplicate lines would cost the reader the context. */

/* ⚠⚠ EVERY RULE BELOW IS SCOPED `.nav nav a`, NOT `.nav a`, AND THAT IS LOAD-BEARING.
   The wordmark is itself an <a> inside .nav, so the broad selector matched it too — which
   silently gave the logotype the nav's colour, its 1.7rem left margin, its opening
   opacity, and its raise-on-hover. That last one is what Nathan caught: the wordmark
   lifting on hover like a menu item, when it is a logotype, not a link in a row.
   This is the THIRD bug from the same root (the Quicksand chrome lock and the /connect
   font were the first two). The wordmark is chrome-adjacent but it is not a nav link.
   RULE: anything meant for the four links on the right goes under `.nav nav a`. Anything
   for the wordmark goes under `.nav__brand`. `.nav a` should never appear again. */
/* ══ THE CORNER MARK — IDENTICAL ON EVERY PAGE ═══════════════════════════════════
   Standing convention (Nathan, 2026-08-08): the wordmark, the nav and this mark arrive
   the same way on every page. Only WHICH nav item is current varies.
   Locked: "between" (half the outward pull, 2.6rem up), 80% presence, small, drawing 2.1s
   after a 3s dwell. The dwell and fade are composed FROM --inMark and --fadeDur rather
   than hard-coded, so retuning the opening carries every page with it at once. */
#awCorner{position:fixed;right:clamp(1.5rem,7vw,9rem);bottom:2.6rem;z-index:5;
  transform:translateX(calc(-1 * var(--edge) * .5));
  width:2.4rem;height:2.4rem;color:var(--gear);
  opacity:0;display:block;transition:opacity var(--fadeDur) ease var(--inMark),color .28s ease}

#awCorner svg{display:block;width:100%;height:100%;overflow:visible}

#awCorner path{stroke-dasharray:var(--awcLen,260);stroke-dashoffset:var(--awcLen,260)}

#awCorner:hover{color:var(--gear2);opacity:1}

body.is-open #awCorner{opacity:.8;transition:opacity .28s ease,color .28s ease}

body.is-open #awCorner path{animation:awcDraw 2100ms cubic-bezier(.35,0,.25,1) forwards
  calc(var(--inMark) + var(--fadeDur) + 3s)}

/* ⚠ width:fit-content and a CENTRE transform-origin are both load-bearing for the boot.
   As a full-width flex box, centring the BOX does not centre the TEXT — the text is
   left-aligned inside it, so it lands ~229px right of the viewport centre. And with
   transform-origin:left, the 1.55 scale pushes the box further right still. Shrink the
   box to its own text and scale about its middle, and the measurement means what it says. */
.cmd{font-family:var(--cmdfam);font-size:var(--cmdsize);letter-spacing:var(--cmdtrack);color:var(--gear2);
  margin:0 0 1.4rem;min-height:1.3em;display:flex;align-items:center;width:fit-content}

.cmd.is-flash .cmd__txt{background:var(--gear2);color:var(--bg)}

.cmd.is-done .cmd__caret{display:none}

body.is-booting .cmd,body.is-loading .cmd,body.is-open .cmd{opacity:1}

/* the command is gone once it has handed off — removed from flow so the headline sits
   where it belongs rather than under an invisible placeholder */
body.cmd-gone .cmd{display:none}

#emblem{position:fixed;inset:0;z-index:0;display:grid;place-items:center;pointer-events:none;
  filter:blur(0px);opacity:0;visibility:hidden;
  transition:filter var(--esettle) cubic-bezier(.4,0,.2,1),opacity var(--esettle) cubic-bezier(.4,0,.2,1)}

body.is-loading #emblem{opacity:1;visibility:visible}

/* the settle: added by JS once the last stroke has landed */
body.is-loading.settled #emblem{filter:blur(var(--eblur));opacity:var(--eop)}

#emblem [data-trace]{fill:none;stroke:var(--gear);stroke-width:1.5;vector-effect:non-scaling-stroke;
  stroke-linecap:round;stroke-linejoin:round}

/* ── THE CLOSING ASK — a door, not a form ────────────────────────────────────────
   ✅ 2026-08-08: the email field, heading, sub-line and note are all gone; one centred
   button routes to /connect. Asking for an address on a page whose job is to be looked
   at was the wrong ask in the wrong room — /connect exists to take the enquiry, and a
   second capture here would have split it.
   ⚠ max-width is dropped, not widened: the section is now a centring track for a single
   element, so any max-width would centre the button inside a narrow box that is itself
   left-aligned in the column — centred-looking on a wide screen, visibly off on a
   narrow one. Full width plus text-align is the only version that holds. ── */
.ask{padding:5.5rem 0 8rem;text-align:center}

/* ✅ THE CLOSING ASK — LOCKED 2026-08-08 as `/connect`, typed, with the blinking caret.
   Nathan's read, and it is the right one: "the blink invites the user to at LEAST hover
   over it and see what's up." That inverts the objection I had raised — that a command
   is the least obviously-clickable of the four. The caret is not decoration, it is the
   affordance; a blinking cursor is the one thing on a page that reads as *waiting for
   you*, and curiosity does the work a button shape would have done more loudly.

   Why it fits where a button did not: a filled navy rectangle was the ONLY solid, heavy,
   closed shape anywhere on this page — everything else is open, hairline, quiet. And the
   page OPENS by typing /portfolio, so closing by offering /connect makes the ask the same
   gesture the visitor already watched work. The site rhymes with itself.
   Retired: solid plate, open frame, ruled line.
   ⚠ The label IS the command — it cannot say "Start a project". Same face, size and
   tracking as the real opening command (--cmdfam/--cmdsize/--cmdtrack), not a lookalike:
   if the opening's type ever changes, this must follow it automatically. */
.ask__label{display:none}

/* ⚠ THE CARET IS OUT OF THE FLOW, AND THAT IS THE POINT. In the flow its width and left
   margin sit inside the link's box, so the hover plate extended a caret-and-a-half past
   the "t" of /connect — and worse, it did so while the caret itself was hidden, leaving a
   visible gap of empty highlight with nothing in it.
   Absolutely positioning it against .ask__cmd means the box measures the WORD and nothing
   else, at every moment, hovered or not. Shrinking the caret on hover instead would have
   worked too and would have made the plate jump width as the pointer arrived.
   left:calc(100% + .16em) preserves the old margin; top/translateY centres it on the line
   rather than hanging it off the baseline, which is what vertical-align was approximating. */
.ask__cmd{position:relative;display:inline-block}

.ask__go{display:inline-block;text-decoration:none;border:0;border-radius:2px;
  transition:background .22s ease,color .22s ease}

.ask__caret{position:absolute;left:calc(100% + .16em);top:50%;transform:translateY(-50%);
  width:.55em;height:1.05em;background:var(--gear2);animation:askBlink 1.05s step-end infinite}

.ask__go:hover{background:color-mix(in srgb,var(--gear2) 8%,transparent)}

/* ⚠ THE CARET WITHDRAWS ON HOVER (Nathan, 2026-08-08 — reversed from holding it solid).
   A blinking cursor is a prompt waiting for input. The moment someone puts a pointer on
   it, the waiting is over — so it steps out of the way rather than sitting there lit.
   Holding it ON, which is what this did first, made it read as the command still asking
   for something after the reader had already answered.
   `animation:none` and not `animation-play-state:paused`: pausing freezes it on whatever
   frame it happened to reach, so the caret would vanish or persist at random. Killing the
   animation and setting opacity explicitly makes the result the same every time. */
.ask__go:hover .ask__caret{animation:none;opacity:0}

/* ✅ LOCKED 2026-08-08 — opacity 0.029, blend NORMAL. The slider is gone. Soft-light,
   overlay and colour are retired: on a near-white ground soft-light has almost nothing to
   push against, and at this opacity a flat film is both simpler and the only one that
   behaves identically over the copy and the emblem. */
#sheen{position:fixed;inset:0;z-index:60;pointer-events:none;opacity:.029;
  mix-blend-mode:normal;
  background:linear-gradient(115deg,#ff5f6d,#ffc371,#7ee8fa,#80ff72,#f6d365,#c3a0f7,#86a8e7,#ff9a9e,#ff5f6d);
  background-size:260% 260%;
  animation:sheenDrift 90s linear infinite}
/* ✅ SHEEN ON EVERY PAGE — Nathan, 2026-08-09 (Fable audit finding 2): "put sheen
   everywhere. locked." Was /about + /connect only. The keyframes moved here FROM those
   pages to sit beside the one rule that consumes them (an undefined animation-name fails
   silently — the four newly-sheened pages would have shipped a frozen film). */
@keyframes sheenDrift{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
@media (prefers-reduced-motion: reduce){#sheen{animation:none}}
