/* MIRÓ 1 landing.
   Palette values are taken directly from the plugin's own theme in
   software/miro-plugin/source/ui/MiroLookAndFeel.h so the page and the
   instrument share one identity. */

:root {
  --shell:      #0b0b0c;  /* theme::shell    */
  --recess:     #070708;  /* theme::recess   */
  --panel-top:  #151517;  /* theme::panelTop */
  --engraved:   #1e1e21;  /* theme::engraved */
  --surface-hi: #1d1d20;  /* theme::surfaceHi*/
  --accent:     #ff4f1e;  /* theme::accent — the neon itself */
  --hot:        #ff8a55;  /* theme::hot — specular core */
  --warm:       #e03a12;  /* theme::warm     */
  --ember:      #8f2a10;  /* theme::ember    */
  --text:       #ff6a38;  /* theme::text     */
  --faint:      #5c2c1c;  /* theme::textFaint*/

  /* ember (2.43:1), faint (1.78:1) and ash (1.46:1) are STRUCTURAL against
     #050506 — hairlines, rules, borders, unlit marks. They never carry text.
     Text uses the three tokens below; ratios computed with WCAG 2.1 relative
     luminance against #050506, not judged by eye. */
  --label: #c25c34;       /* 4.73:1 — overlines and small uppercase labels */
  --body:  #9a9490;       /* 6.80:1 — long-form copy, warm neutral */
  --body-dim: #857f7a;    /* 5.16:1 — secondary copy, indices, metadata */

  --column: 1180px;       /* one shared grid width, so every section's first
                             character starts on the same vertical line */
}

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

/* The h1: read by assistive tech, never painted. Screen one's title is the
   PNG mark itself, which is artwork and is never redrawn as text. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

html {
  background: #050506;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: #050506;
  color: var(--body);
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Brushed anodised aluminium: very fine vertical grain, near invisible. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 1;
  background: repeating-linear-gradient(
    90deg,
    rgba(255, 255, 255, 0.016) 0px,
    rgba(255, 255, 255, 0.016) 1px,
    rgba(0, 0, 0, 0.020)       1px,
    rgba(0, 0, 0, 0.020)       3px
  );
}

/* Cinematic film grain, self-contained SVG noise. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 2;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

main { position: relative; z-index: 0; }

/* ================================================== INTRO — screen one ==
   The mark alone on black. Separating it from the render is what stops the
   two identical wordmarks from competing inside one view. */

.intro {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 4vh, 40px) clamp(16px, 4vw, 56px) clamp(56px, 8vh, 76px);
  background: #050506;
}

/* The official PNG is used as-is: no font, no SVG redraw, no cropping.
   Measured from its alpha channel, the letterforms occupy only 34.4%–59.3%
   of the file's height; the rest is transparent padding wrapped in a faint
   halo. Negative margins cancel that padding so the spacing declared here is
   the real optical spacing, and the reveal maps to the ink only. */
.mark {
  --mark-w: clamp(260px, 46vw, 720px);

  position: relative;
  width: var(--mark-w);
  flex-shrink: 0;
  /* box height = width / 3 (2172x724), so padding above = w/3 x 0.344 */
  margin-top: calc(var(--mark-w) * -0.1147);
  margin-bottom: calc(var(--mark-w) * -0.1357);
  filter: drop-shadow(0 0 40px rgba(255, 79, 30, 0.16));
}

.mark__img {
  display: block;
  width: 100%;
  height: auto;
  /* Revealed top-down: the multi-line letterforms come in band by band. */
  clip-path: inset(0 0 65.6% 0);
  animation: mark-build 1.75s cubic-bezier(0.38, 0.05, 0.32, 1) 0.15s forwards;
}

/* The lit edge that draws the mark, riding the reveal line. */
.mark::after {
  content: "";
  position: absolute;
  left: -3%;
  top: 0;
  width: 106%;
  height: 1px;
  background: linear-gradient(90deg,
    transparent, rgba(255, 138, 85, 0.15) 12%,
    var(--hot) 50%,
    rgba(255, 138, 85, 0.15) 88%, transparent);
  box-shadow: 0 0 12px rgba(255, 138, 85, 0.55);
  opacity: 0;
  animation: mark-scan 1.75s cubic-bezier(0.38, 0.05, 0.32, 1) 0.15s forwards;
}

/* Both keyframes run between the ink boundaries, not 0-100%. Animating the
   full box would spend 34% of the time before the first letter appeared and
   41% after the last one - the build would be over in a blink. */
@keyframes mark-build {
  from { clip-path: inset(0 0 65.6% 0); }   /* just above the first band */
  to   { clip-path: inset(0 0 40.7% 0); }   /* just below the last band  */
}

@keyframes mark-scan {
  0%   { top: 34.4%; opacity: 0; }
  10%  {              opacity: 1; }
  86%  {              opacity: 1; }
  100% { top: 59.3%; opacity: 0; }
}

/* Says what the thing is, so the mark is never an orphan. */
.tagline {
  margin: clamp(26px, 4vh, 42px) 0 0;
  max-width: 42em;
  font-size: clamp(11px, 0.95vw, 13px);
  letter-spacing: 0.24em;
  text-indent: 0.24em;
  text-transform: uppercase;
  line-height: 1.9;
  text-align: center;
  color: var(--body);          /* was #7d6a60 — 3.98:1, under AA at 13px */
  opacity: 0;
  animation: fade-up 0.7s ease-out 1.6s forwards;
}

@keyframes fade-up {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: none; }
}

.intro__scroll {
  position: absolute;
  left: 50%;
  bottom: clamp(24px, 3.6vh, 38px);
  transform: translateX(-50%);
  display: block;
  padding: 10px;
  color: var(--label);   /* was --ember — 2.43:1, under 1.4.11's 3:1 */
  opacity: 0;
  animation: fade-up 0.7s ease-out 2s forwards;
  transition: color 0.4s ease;
}

.intro__scroll:hover,
.intro__scroll:focus-visible { color: var(--accent); }

/* ================================================ PRODUCT — screen two ==
   The instrument, and the only download control on the page. */

.product {
  position: relative;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: clamp(40px, 7vh, 80px) clamp(16px, 4vw, 56px);
  background:
    radial-gradient(120% 74% at 50% 44%, #131316 0%, #0a0a0b 48%, #050506 100%);
}

/* <picture> is only a source switch, so it disappears from the box tree and
   the img stays the flex item it has always been — identical layout. */
.product__picture { display: contents; }

/* Never cropped, never distorted: width and height stay auto and the two
   max-* limits keep the whole panel inside the viewport at any size. */
.product__render {
  width: auto;
  height: auto;
  max-width: min(1180px, 92vw);
  max-height: 62svh;
  margin-bottom: clamp(34px, 5.4vh, 56px);
  filter:
    drop-shadow(0 32px 56px rgba(0, 0, 0, 0.9))
    drop-shadow(0 0 70px rgba(255, 79, 30, 0.075));
}

/* ------------------------------------------------------------ download -- */
.download {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* Deliberately plain: one hairline rule, square corners, no fill, no rings.
   The only thing that moves on hover is brightness. */
.download__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  padding: clamp(16px, 2.1vh, 20px) clamp(30px, 5vw, 46px);
  text-decoration: none;
  border: 1px solid rgba(255, 79, 30, 0.55);
  border-radius: 0;
  background: transparent;
  -webkit-appearance: none;
  appearance: none;
  font-family: inherit;
  font-size: clamp(12px, 1.35vw, 15px);
  font-weight: 500;
  letter-spacing: 0.34em;
  text-indent: 0.34em;   /* balance the trailing letter-spacing */
  text-transform: uppercase;
  color: var(--text);
  transition: border-color 0.4s ease, color 0.4s ease, background-color 0.4s ease;
}

.download__button:hover:not(:disabled),
.download__button:focus-visible {
  border-color: var(--accent);
  background-color: rgba(255, 79, 30, 0.06);
  color: var(--hot);
}

/* Unlit, and it says so. Not a lit control that silently does nothing. */
.download__button:disabled {
  border-color: rgba(255, 79, 30, 0.2);
  color: var(--body-dim);
  cursor: not-allowed;
}

.download__button:focus-visible {
  outline: 1px solid var(--hot);
  outline-offset: 6px;
}

/* Metadata, not a second line of the button: pushed away and set smaller. */
.download__platform {
  margin: 20px 0 0;
  font-size: 10px;
  letter-spacing: 0.22em;
  text-indent: 0.22em;
  text-transform: uppercase;
  color: var(--label);   /* was #6b3120 — 2.02:1 at 10px */
}

/* Why the button is unlit. Hidden by the script once a release URL exists. */
.download__status {
  margin: 10px 0 0;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  text-transform: uppercase;
  color: var(--body-dim);
}

/* ============================================================= SECTIONS == */

.section {
  position: relative;
  max-width: var(--column);
  margin: 0 auto;
  padding: clamp(72px, 11vh, 132px) clamp(20px, 5vw, 40px);
}

/* Divider drawn as a centred pseudo-element rather than a border, so every
   separator keeps the same length even though the sections differ in width. */
.section::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(1100px, 92vw);
  height: 1px;
  background: var(--engraved);
}

/* Sections share one width; reading measure is constrained on the text
   itself (.lede, .body) rather than on the container. */

.overline {
  margin: 0 0 clamp(20px, 3vh, 30px);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  color: var(--label);
}

.lede {
  margin: 0 0 clamp(28px, 4vh, 40px);
  max-width: 24em;
  font-size: clamp(21px, 2.6vw, 30px);
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: -0.005em;
  color: #d8d2ce;
}

.lede em {
  font-style: normal;
  color: var(--accent);
}

.body {
  margin: 0 0 1.15em;
  max-width: 46em;
  font-size: clamp(14px, 1.15vw, 15.5px);
  line-height: 1.72;
  color: var(--body);
}

.body:last-child { margin-bottom: 0; }

.body em {
  font-style: normal;
  color: var(--text);
}

.note {
  margin: clamp(28px, 4vh, 40px) 0 0;
  font-size: 12px;
  letter-spacing: 0.08em;
  color: var(--body-dim);   /* was --faint — 1.78:1, structural token */
}

/* -------------------------------------------------------- signal chain -- */
.chain {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 0;
  margin: 0 0 clamp(28px, 4vh, 40px);
  padding: 0;
  list-style: none;
}

.chain__node {
  font-size: clamp(11px, 1.1vw, 13px);
  font-weight: 500;
  letter-spacing: 0.2em;
  text-indent: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
  white-space: nowrap;
}

.chain__node--io { color: var(--label); }   /* was --faint — 1.78:1 */

.chain__node + .chain__node::before {
  content: "→";
  display: inline-block;
  margin: 0 0.85em 0 0.35em;
  text-indent: 0;
  letter-spacing: 0;
  color: var(--label);   /* was --ember — 2.43:1, under 1.4.11's 3:1 */
}

/* ------------------------------------------------------------- engines --
   Three columns divided by hairlines. Deliberately not cards: no boxes,
   no fills, no rounded corners. */
.engines {
  display: grid;
  grid-template-columns: 1fr;
  gap: clamp(40px, 6vh, 56px);
}

.engine { min-width: 0; }

.engine__index {
  margin: 0 0 14px;
  font-size: 11px;
  letter-spacing: 0.2em;
  color: var(--body-dim);   /* was --faint — 1.78:1 */
}

.engine__name {
  margin: 0 0 6px;
  font-size: clamp(17px, 1.9vw, 21px);
  font-weight: 500;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--accent);
  text-shadow: 0 0 18px rgba(255, 79, 30, 0.25);
}

.engine__role {
  margin: 0 0 clamp(16px, 2.2vh, 22px);
  font-size: 12px;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  text-transform: uppercase;
  color: var(--label);
}

.example {
  margin: clamp(16px, 2.2vh, 22px) 0 0;
  padding-left: 16px;
  border-left: 1px solid var(--ember);
  font-size: clamp(13px, 1.05vw, 14.5px);
  line-height: 1.7;
  color: var(--body-dim);
}

.example em {
  font-style: normal;
  color: var(--text);
}

.example__tag {
  display: block;
  margin-bottom: 6px;
  font-size: 9.5px;
  font-weight: 500;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  color: var(--accent);
}

/* ------------------------------------------------------------ hardware -- */
.status {
  margin: 0;
  display: grid;
  gap: clamp(24px, 3.4vh, 34px);
}

.status__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 10px;
}

.status__state {
  margin: 0;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.26em;
  text-indent: 0.26em;
  text-transform: uppercase;
}

.status__state--on   { color: var(--accent); }
.status__state--wip  { color: var(--label); }
.status__state--next { color: var(--body-dim); }   /* was --faint — 1.78:1 */

.status__row dd { margin: 0; }

/* ============================================================== LAYOUT ==
   Three columns for the engines and a label column for the status list
   only once there is genuinely room for them. */
@media (min-width: 900px) {
  .engines {
    grid-template-columns: repeat(3, 1fr);
    gap: 0;
  }

  .engine + .engine {
    margin-left: clamp(24px, 3vw, 34px);
    padding-left: clamp(24px, 3vw, 34px);
    border-left: 1px solid var(--engraved);
  }

  .status__row {
    grid-template-columns: 120px 1fr;
    gap: 0 28px;
    align-items: baseline;
  }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* Everything arrives already built. No reveal, no scan line, no fade. */
  .mark__img {
    clip-path: none;
    animation: none;
  }

  .mark::after { display: none; }

  .tagline,
  .intro__scroll {
    opacity: 1;
    transform: translateX(0);
    animation: none;
  }

  .intro__scroll { transform: translateX(-50%); }

  .product__render {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .download__button,
  .intro__scroll { transition: none; }
}

/* ========================================================= ENGINE DIAGRAMS ==
   One drawing per engine, each making one claim that survives the next commit
   to the DSP, and one animated element to carry it. Everything is namespaced
   `dgm-` so it cannot collide with the rest of this sheet.

   Colour comes from the tokens above: one ink, distinguished by brightness. */

.dgm {
  display: block;
  width: 68%;
  min-width: 200px;
  height: auto;
  margin: 4px 0 10px;
}

.dgm text {
  font-family: inherit;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  fill: var(--label);
}

.dgm-ink        { stroke: var(--accent); fill: none; stroke-width: 1; }
.dgm-ink--sub   { opacity: 0.30; }
.dgm-ink--mid   { opacity: 0.55; }

.dgm__cap {
  margin: 0 0 clamp(16px, 2.2vh, 22px);
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--body-dim);
}

@media (max-width: 899px) { .dgm { width: 82%; } }

/* FRACTURE — the buffer is written continuously, and CAPTURE stops it.
   One tick is one second and the strip travels one second per second, so the
   drawing runs at the speed of the thing it stands for. The freeze is the same
   element's second state, not a second element: the strip advances for five
   seconds, then holds, which is what "retained" means. */
.dgm-fr { animation: dgm-fr-roll 9s linear infinite; }
@keyframes dgm-fr-roll {
  0%    { transform: translateX(0); }
  55.6% { transform: translateX(-42px); }   /* 5 s escritos, a tiempo real */
  100%  { transform: translateX(-42px); }   /* capturado: el historial se detiene */
}

/* BLOOM — the claim that survives a configurable grain count and SCATTER:
   discrete overlapping windows add up to something continuous. Drawn in the
   shipped state (scatter at zero), where the sum is not merely smooth but
   exactly flat — which is the identity the level compensation is built on. */
.dgm-bl { animation: dgm-bl-roll 9s linear infinite; }
@keyframes dgm-bl-roll { to { transform: translateX(-240px); } }

/* TIDE — one impulse circulating: it crosses on every hop and loses brightness
   on the way. A polyline is straight between vertices, so one keyframe per
   vertex is the same path `offset-path` drew, as a plain transform the
   compositor can take. Keyframe times follow segment length: constant speed. */
.dgm-td { animation: dgm-td-travel 3.4s linear infinite; will-change: transform; }
@keyframes dgm-td-travel {
    0.00% { transform: translate(26px, 26px); opacity: 1.000; }
   22.97% { transform: translate(84px, 74px); opacity: 0.793; }
   44.57% { transform: translate(136px, 26px); opacity: 0.599; }
   64.43% { transform: translate(180px, 74px); opacity: 0.420; }
   82.73% { transform: translate(216px, 26px); opacity: 0.255; }
  100.00% { transform: translate(246px, 74px); opacity: 0.100; }
}

/* Paused outright when the section is off screen, not merely throttled. */
.dgm-paused .dgm-fr,
.dgm-paused .dgm-bl,
.dgm-paused .dgm-td { animation-play-state: paused; }

@media (prefers-reduced-motion: reduce) {
  /* Everything already built, not a degraded version: the strips hold a full
     frame of their own pattern and the impulse parks at the input. */
  .dgm-fr, .dgm-bl, .dgm-td { animation: none; }
  .dgm-td { transform: translate(26px, 26px); opacity: 1; }
}

/* ================================================================= MANUAL ==
   Used by manual.html. Same ink, same square corners, brightness as the only
   state channel — no new tokens. */

/* An inline link. The rule under it is structure and is always there; only the
   brightness changes on hover, which is the system's single state channel. */
.link {
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid var(--ember);
  transition: color 0.35s ease, border-color 0.35s ease;
}

.link:hover,
.link:focus-visible { color: var(--hot); border-color: var(--accent); }

.link:focus-visible { outline: 1px solid var(--hot); outline-offset: 3px; }

/* Numbered steps. The number is a mark, not a bullet: it sits in the label
   rung and is set in tabular figures so the column stays straight. */
.steps {
  margin: clamp(18px, 2.4vh, 24px) 0;
  padding: 0;
  list-style: none;
  counter-reset: step;
}

.steps > li {
  position: relative;
  margin: 0 0 clamp(14px, 2vh, 20px);
  padding-left: 34px;
  max-width: 46em;
}

.steps > li:last-child { margin-bottom: 0; }

.steps > li::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.15em;
  font-size: 11px;
  letter-spacing: 0.2em;
  font-variant-numeric: tabular-nums;
  color: var(--label);
}

.steps > li b { color: var(--text); font-weight: 500; }

/* Inline code, in the manual's paths and file names. Engraved, not lit. */
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.92em;
  letter-spacing: 0.01em;
  color: var(--body);
}

.note strong { color: var(--label); font-weight: 500; }

/* The manual sits one click from the only button on the page. */
.download__more {
  margin: 14px 0 0;
  font-size: 11px;
  letter-spacing: 0.14em;
  text-indent: 0.14em;
  text-transform: uppercase;
}

/* A paragraph following a definition list needs the same air the list has
   between its own rows. Only occurs in the manual — `#hardware` follows its
   status list with a `.note`, which brings its own top margin. */
.status + .body { margin-top: clamp(20px, 3vh, 28px); }

/* ============================================================= LIVE PANEL ==
   The render carries the chrome; a canvas over it carries the two parts that
   move. Same split as the plugin: the chrome is rasterised once, only the live
   parts are repainted.

   The wrapper shrink-wraps the image, so the canvas inherits exactly the box
   the render occupies at any size and every overlay coordinate can be a
   fraction of it rather than a pixel. */

.panel {
  position: relative;
  display: inline-flex;
  line-height: 0;                 /* no descender space under the image */
  margin-bottom: clamp(34px, 5.4vh, 56px);

  /* The reveal lives here, on the wrapper, and not on the image. The overlay
     canvas is inside this box, so the two can never drift apart: an animated
     transform on the image alone would move the artwork out from under a canvas
     that had already been positioned against it. */
  opacity: 0;
  transform: translateY(10px);
  transition: opacity 0.9s ease-out, transform 0.9s ease-out;
}

.panel.is-visible {
  opacity: 1;
  transform: none;
}

.panel .product__render { margin-bottom: 0; }

.panel__live {
  /* Placed from the image's own box in JS, not from this wrapper: a wrapper is
     one layout quirk away from a few pixels of offset, and at this size a few
     pixels of offset is the whole illusion. */
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
}

/* The demo control. One word, tracked out, and brightness as the only state —
   the same rung the scroll cue sits on. Audio never starts without it. */
.demo {
  display: flex;
  align-items: baseline;
  gap: 14px;
  margin: 0 0 clamp(26px, 4vh, 40px);
}

.demo[hidden] { display: none; }

.demo__button {
  padding: 8px 14px;
  border: 1px solid rgba(255, 79, 30, 0.28);
  background: transparent;
  font: inherit;
  font-size: 10px;
  letter-spacing: 0.28em;
  text-indent: 0.28em;
  text-transform: uppercase;
  color: var(--label);
  cursor: pointer;
  transition: color 0.35s ease, border-color 0.35s ease;
}

.demo__button:hover,
.demo__button:focus-visible { color: var(--text); border-color: rgba(255, 79, 30, 0.55); }

.demo__note {
  font-size: 10px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--body-dim);
}

/* ========================================================== SIGNAL FLOW ==
   The diagram is inlined so its nodes can be animated. Its own <style> block
   was scoped under `.flow` before inlining — unscoped it would have set
   `font-size` on the page's `.note` paragraphs and on every other SVG's text.

   Three layers, staggered, and all three are true:
     1. the schematic draws itself, in the language the mark uses on screen one
     2. the signal then travels the chain — one signal, three engines
     3. the two feedback loops crawl in their own slower cycle

   Nothing else moves. The boxes, the labels, the 30 s ring and the Hann
   windows are structure, not movement. */

.flow-wrap {
  position: relative;
  margin: 0 0 clamp(28px, 4vh, 40px);
}

.flow {
  display: block;
  width: 100%;
  height: auto;
  max-width: 1000px;
}

/* The full diagram needs 900 px of content for its 10 px labels to land at
   9 px. With this section's padding that is a 980 px viewport; below it the
   compact diagram takes over rather than shrinking past legibility. */
#flow-compact { display: none; }

@media (max-width: 979px) {
  #flow-full { display: none; }
  #flow-compact { display: block; max-width: 560px; margin: 0 auto; }
}

/* The geometry of the signal column, used only for the two animations. It is
   the same route the diagram already draws. */
.flow .spine { fill: none; opacity: 0; }

/* ---- 1. the schematic draws itself ---- */
.flow-wrap.is-drawing .flow {
  clip-path: inset(0 0 100% 0);
  animation: flow-build 1.8s cubic-bezier(0.38, 0.05, 0.32, 1) 0.1s forwards;
}

@keyframes flow-build { to { clip-path: inset(0 0 0 0); } }

/* the lit edge riding the reveal line, as on the mark */
.flow-wrap::after {
  content: "";
  position: absolute;
  left: -1%;
  top: 0;
  width: 102%;
  height: 1px;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent, rgba(255, 138, 85, 0.15) 12%,
    var(--hot) 50%,
    rgba(255, 138, 85, 0.15) 88%, transparent);
  box-shadow: 0 0 12px rgba(255, 138, 85, 0.55);
  opacity: 0;
}

.flow-wrap.is-drawing::after {
  animation: flow-scan 1.8s cubic-bezier(0.38, 0.05, 0.32, 1) 0.1s forwards;
}

@keyframes flow-scan {
  0%   { top: 0;    opacity: 0; }
  8%   {            opacity: 1; }
  88%  {            opacity: 1; }
  100% { top: 100%; opacity: 0; }
}

/* the labels arrive after the boxes are traced */
.flow-wrap.is-drawing .flow text {
  opacity: 0;
  animation: flow-in 0.7s ease-out 1.45s forwards;
}

@keyframes flow-in { to { opacity: 1; } }

/* and last, the signal path connects, then hands over to the drawing beneath */
.flow-wrap.is-drawing .flow .spine {
  stroke: var(--hot);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-dasharray: var(--flow-len) var(--flow-len);
  stroke-dashoffset: var(--flow-len);
  animation: flow-connect 1.1s ease-out 2.1s forwards,
             flow-out 0.5s ease-out 3.2s forwards;
}

@keyframes flow-connect { from { opacity: 0.95; } to { opacity: 0.95; stroke-dashoffset: 0; } }
@keyframes flow-out { to { opacity: 0; } }

/* ---- 2 and 3. the signal, and the two feedback loops ---- */
/* Both live on a canvas over the diagram, not in it. Measured, because the
   obvious way was wrong: `stroke-dashoffset` is a paint property, and animating
   one inside a 1000x1322 inline SVG repaints the whole thing every frame. The
   two feedback crawls alone cost +14.4 points of a core over the static page,
   and a transform-driven dot inside the SVG still cost +3.7. On a canvas — one
   composited layer, three dots, 30 fps — all of it comes back under a point. */

.flow__live {
  position: absolute;
  left: 0;
  top: 0;
  pointer-events: none;
}

/* Nothing runs while the section is off screen. */
/* The canvas loop stops itself; see the script. */

@media (prefers-reduced-motion: reduce) {
  /* The diagram arrives finished: no build, no scan, no pulses. It is the same
     drawing, not a lesser one — which is why the default state is the complete
     one and the animations are added by script. */
  .flow-wrap::after { display: none; }
  .flow .spine { display: none; }
}
