/* architecture.css — pairs with the SVG embedded in platform.html
   (canonical asset: assets/arch/architecture.svg, generated by
   tools/generate-architecture.py). Plain CSS on purpose: utilities and
   inline styles cannot reach inside an inlined SVG.

   TWO GATES, deliberately separate:
     [data-seen="1"]  one-shot. Fires the entrance sequence the first time the
                      diagram scrolls into view, then never resets. Without
                      this the entrance plays while the diagram is still below
                      the fold and the user scrolls down to a finished picture.
     [data-run="1"]   two-way. Ambient pulses and rail sequencing pause when
                      the diagram leaves the viewport (stroke-dashoffset
                      repaints on the main thread — do not waste the frames).

   Hardcoded colours below are deliberate: keyframes cannot read custom
   properties for fill transitions in all engines. They mirror the generator's
   palette in tools/generate-architecture.py — change them together:
     #00C18B = --color-pints-mint · #1E1E1E = --color-pints-ink
     #5F6762 = --color-muted-text-light · #A9B2AD = --color-muted-text
*/

.archWrap{position:relative;
  --ink:var(--bg-base);
  --font-mono:ui-monospace,"SF Mono",Menlo,monospace}
.archScroll{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:thin}
.archWrap svg{width:100%;height:auto;display:block;overflow:visible;
  min-width:1240px;aspect-ratio:1500/1100}

/* edge fade so horizontal scroll reads as intentional. The container is
   min(1500px, 100vw) − 2×64px page padding, so scrolling stops at a
   1368px viewport — the fade hides exactly there, not at an arbitrary width */
.archWrap::after{content:'';position:absolute;inset:0 0 0 auto;width:56px;
  background:linear-gradient(90deg,transparent,var(--ink,#1E1E1E));pointer-events:none}
@media (min-width:1368px){.archWrap::after{display:none}}

/* ── resting state: everything staged, nothing shown ───────────── */
.archWrap svg .plane{opacity:0;transform:translateY(-22px)}
.archWrap svg .chip,
.archWrap svg .pill,
.archWrap svg .zlabel,
.archWrap svg .tag,
.archWrap svg .tlabel{opacity:0}
.archWrap svg .callout{opacity:0;transform:translateX(-12px)}
.archWrap svg .pulse{stroke-dashoffset:1000;opacity:0}

/* ── the reveal, bottom of the stack upward ────────────────────── */
.archWrap[data-seen="1"] svg .plane{
  animation:archSettle 880ms cubic-bezier(.2,.6,.2,1) forwards}
@keyframes archSettle{to{opacity:1;transform:none}}

.archWrap[data-seen="1"] svg .zlabel{animation:archFade 560ms ease .84s forwards}
.archWrap[data-seen="1"] svg .chip  {animation:archFade 560ms ease .74s forwards}
.archWrap[data-seen="1"] svg .tag   {animation:archFade 540ms ease .98s forwards}
.archWrap[data-seen="1"] svg .pill  {animation:archFade 560ms ease .92s forwards}
.archWrap[data-seen="1"] svg .tlabel{animation:archFade 560ms ease 1.06s forwards}
.archWrap[data-seen="1"] svg .callout{
  animation:archSlide 700ms cubic-bezier(.2,.6,.2,1) 1.2s forwards}
@keyframes archFade{to{opacity:1}}
@keyframes archSlide{to{opacity:1;transform:none}}

/* ── ambient life, pausable ────────────────────────────────────── */
.archWrap[data-run="1"] svg .pulse{animation:archRise 4.6s linear infinite}
@keyframes archRise{
  0%{stroke-dashoffset:1000;opacity:0}
  10%{opacity:.95} 82%{opacity:.95}
  100%{stroke-dashoffset:0;opacity:0}
}
/* animation-delay must ride INSIDE these rules: the shorthand resets delay
   to 0s, and a lower-specificity `animation-delay:inherit` loses to it — the
   bug that had all six rail lights flashing in unison instead of running
   bottom-up (INTAKE first, delays inherited from each g.step's inline style) */
.archWrap[data-run="1"] svg .step rect{animation:archBox 5.1s linear infinite;animation-delay:inherit}
.archWrap[data-run="1"] svg .step text{animation:archTxt 5.1s linear infinite;animation-delay:inherit}
@keyframes archBox{0%,13%{fill:#1E1E1E;stroke:#5F6762}5%{fill:#00C18B;stroke:#00C18B}}
@keyframes archTxt{0%,13%{fill:#A9B2AD}5%{fill:#00C18B}}

/* ── hover ─────────────────────────────────────────────────────── */
.archWrap svg .chip rect{transition:stroke 180ms ease}
.archWrap svg .chip:hover rect{stroke:#00C18B}

/* ── the hero bleed ────────────────────────────────────────────────
   The diagram rises into the hero's lower-right so the two read as one
   scene; the pull stays inside the scene's whitespace so no drawn text
   ever sits behind the hero copy. Below 768px the bleed is off and the
   diagram stacks normally (it scrolls inside .archScroll there). */
.archBleed{margin-top:calc(var(--gap-band) * -0.5)}
@media (max-width:767px){.archBleed{margin-top:0}}
/* On phones the diagram comes out entirely (review, 20 Aug 2026): a
   1500-unit-wide technical drawing has no honest rendering at 375px, and
   the stage tabs above it already carry the four stages' copy. display:none
   also parks the entrance/pulse observers, so the hidden drawing costs
   nothing. Every width above the site's phone breakpoint is untouched. */
@media (max-width:640px){.archWrap{display:none}}

/* ── stage highlighting ────────────────────────────────────────────
   data-stage mirrors the sticky tab row: the live zone holds, the other
   three recede; the live zone's plate takes the accent. Transitions sit
   inside the motion-token cap (240ms). */
.archWrap svg .zone{transition:opacity 240ms var(--ease-out)}
.archWrap[data-stage] svg .zone{opacity:.4}
.archWrap[data-stage="01"] svg .zone[data-zone="01"],
.archWrap[data-stage="02"] svg .zone[data-zone="02"],
.archWrap[data-stage="03"] svg .zone[data-zone="03"],
.archWrap[data-stage="04"] svg .zone[data-zone="04"]{opacity:1}
.archWrap svg .zlabel rect{transition:stroke 240ms var(--ease-out)}
.archWrap[data-stage="01"] svg .zlabel[data-zone="01"] rect,
.archWrap[data-stage="02"] svg .zlabel[data-zone="02"] rect,
.archWrap[data-stage="03"] svg .zlabel[data-zone="03"] rect,
.archWrap[data-stage="04"] svg .zlabel[data-zone="04"] rect{stroke:#00C18B}

/* ── the hero lockup ───────────────────────────────────────────────
   Letters type on once at load — a stepped clip reveal, one step per
   glyph — and the mark glows in the accent, breathing gently while
   motion is live. Both rest fully drawn. The glow colour is
   --color-pints-mint with alpha; keyframed filters cannot read custom
   properties reliably, same rule as the greens above. */
/* the type-on: the letters are inline SVG paths, and each glyph fades in
   with a small rise, staggered 55ms left to right — sequential like a
   typewriter, smooth because nothing ever cuts through a letterform. The
   file stores its paths right-to-left, so the delays invert: nth-child(10)
   is the first letter. Base state is fully drawn; only data-motion="live"
   stages the glyphs, so reduced motion and the PAUSE chip never see a
   missing letter. */
.at-lockup .at-letters svg{display:block;width:100%;height:auto}
:root[data-motion="live"] .at-lockup .at-letters path{
  opacity:0;transform:translateY(6px);
  animation:atLetter 200ms cubic-bezier(0.16, 1, 0.3, 1) both}
@keyframes atLetter{to{opacity:1;transform:none}}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(10){animation-delay:150ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(9){animation-delay:205ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(8){animation-delay:260ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(7){animation-delay:315ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(6){animation-delay:370ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(5){animation-delay:425ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(4){animation-delay:480ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(3){animation-delay:535ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(2){animation-delay:590ms}
:root[data-motion="live"] .at-lockup .at-letters path:nth-child(1){animation-delay:645ms}
:root[data-motion="still"] .at-lockup .at-letters path{animation:none!important;opacity:1!important;transform:none!important}
.at-lockup .at-arrow{filter:drop-shadow(0 0 10px rgba(0,193,139,.5))}
:root[data-motion="live"] .at-lockup .at-arrow{
  animation:atGlow 3.6s ease-in-out 1.1s infinite}
@keyframes atGlow{
  0%,100%{filter:drop-shadow(0 0 10px rgba(0,193,139,.5))}
  50%{filter:drop-shadow(0 0 20px rgba(0,193,139,.8))}
}
:root[data-motion="still"] .at-lockup .at-letters,
:root[data-motion="still"] .at-lockup .at-arrow{animation:none!important;clip-path:none!important}

/* Stilled by the site's own PAUSE MOTION chip (html[data-motion="still"],
   see the head script on every page), which is also where the OS preference
   lands once the head script has resolved it. The @media twins of these
   rules were removed 21 Aug 2026: CSS cannot see the JS bypass, so they
   froze the diagram — flows and all — for every visitor with Reduce Motion
   on, while the page believed motion was running. Add motion rules HERE,
   keyed to the attribute; never to a raw media query. */
:root[data-motion="still"] .archWrap svg *{animation:none!important;transform:none!important;opacity:1!important}
:root[data-motion="still"] .archWrap svg .pulse{display:none}
