/* The engine's four steps (engine.js), on the home page and on engine.html. One recording:
   the engine listens and marks what it finds, repairs it one step at a time, labels its
   guesses, and folds the frequencies back into one beam. The pictures are the engine's
   real output (scripts/engine_story.py), drawn so that height and colour mean pitch and
   brightness means level, the spectrum used only as light. Everything that moves is
   transform or opacity on those still pictures. Without engine.js, or with less motion,
   the four frames simply stand one under the other.

   Every time in the story is multiplied by --story-pace, and engine.js reads the same
   value for how long each step stays. 0.6667 plays it 1.5 times as fast as it was made
   on September 28, 2026 (the owner's ask, September 29). The rules are held inside
   .engine-story because the older animation (story.css, on the sign-in page since
   September 30) also has a .story-stage and a .story-pause. */
.engine-story { --story-pace:.6667; }

/* The story band. The steps are quiet text tabs; the one on show is lit, with a track
   under it that a thread of the light fills as its time runs out. */
.engine-story { padding:8px 0 128px; }
/* On the home page the story is a section like the others, with their room (landing.css).
   Since September 30 it follows Prism's reading band, so it starts on the plain black;
   the fade from the first screen's grain moved to Prism (prism.css). */
.section.engine-story { padding:136px 0; }
/* Under the steps, the way to the page that shows the rest of the example. */
.engine-story .story-more { margin-top:32px; }
.step-nav { margin-bottom:24px; }
.step-nav ol { display:flex; flex-wrap:wrap; gap:8px; margin:0; padding:0; list-style:none; }
.step-nav a { position:relative; display:inline-flex; align-items:center; justify-content:center; gap:8px; min-width:44px; min-height:44px; padding:0 12px; color:var(--snow-3); font-size:15px; font-weight:500; transition:color .25s ease-out; }
.step-nav a:hover,.step-nav a[aria-current="step"] { color:var(--snow); }
.step-number { font-weight:600; }
.step-nav a::before,.step-nav a::after { content:''; position:absolute; left:12px; right:12px; bottom:6px; height:2px; border-radius:1px; opacity:0; }
.step-nav a::before { background:#ffffff66; }
.step-nav a::after { background:linear-gradient(90deg,var(--spectrum)); transform:scaleX(0); transform-origin:left; }
.step-nav a[aria-current="step"]::before { opacity:1; }
.story-live .step-nav a[aria-current="step"]::after { opacity:1; animation:step-time var(--frame-time,4s) linear both; }
.story-paused .step-nav a[aria-current="step"]::after { animation-play-state:paused; }
@keyframes step-time { to { transform:scaleX(1); } }

/* The pause button is the home page's (landing.css .light-pause), set in the narrow column
   left of the picture, at the top, so it never covers the picture. */
.engine-story .story-stage { position:relative; }
.engine-story .story-stage .story-pause { top:12px; right:auto; left:12px; z-index:4; }

/* Without the script: the four frames one under the other, each a still. */
.frames { display:grid; gap:88px; margin:0; padding:0; list-style:none; }
.frame { scroll-margin-top:96px; }
.frame-text { max-width:46em; margin-top:32px; }
.frame-count { margin:0 0 12px; color:var(--snow-3); font-size:13px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; }
.site .frame-text h3 { margin:0; font-family:var(--serif); font-size:clamp(28px,3vw,38px); line-height:1.12; font-weight:500; font-variation-settings:'opsz' 48; letter-spacing:-.014em; color:var(--snow); }
.frame-caption { margin:12px 0 0; color:var(--snow-2); font-size:18px; line-height:1.5; text-wrap:pretty; }

/* Every list has one shape: a short tag on the left (with the mark's numbered key, if it
   has one), then a name and one sentence. */
.frame-list { display:grid; gap:12px; margin:24px 0 0; padding:0; list-style:none; color:var(--snow-2); font-size:16px; line-height:1.5; }
.frame-list li { display:grid; grid-template-columns:128px minmax(0,1fr); column-gap:16px; align-items:start; text-wrap:pretty; }
.row-tag { display:flex; align-items:center; gap:8px; min-height:24px; color:var(--snow-3); font-size:13px; font-weight:600; letter-spacing:.12em; text-transform:uppercase; white-space:nowrap; }
.row-tag.is-time { letter-spacing:.02em; text-transform:none; }
.key-chip { display:grid; place-items:center; flex:none; width:20px; height:20px; border-radius:50%; box-shadow:inset 0 0 0 1px var(--snow); color:var(--snow); font-size:12px; font-weight:600; line-height:1; letter-spacing:0; }
.row-body { display:block; }
.row-name { color:var(--snow); font-weight:600; }
.is-skipped .row-name { color:var(--snow-2); }
.repair-thumb { grid-column:1 / -1; display:block; width:100%; height:auto; margin:0 0 12px; border-radius:12px; }
/* Without the script each repair is a picture over its row, with room between them. */
.engine-story:not(.story-live) .repair-list { gap:32px; }

/* On a wide screen the list sits beside the words, its top on the headline's top line
   (the 20 pixel step count and its 12 pixel margin), so no frame is much taller than
   another. */
@media (min-width:1000px) {
  .frame-text { display:grid; grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr); grid-template-rows:auto auto 1fr; gap:0 56px; max-width:none; }
  .frame-text > :not(.frame-list) { grid-column:1; }
  .frame-text .frame-list { grid-column:2; grid-row:1 / span 3; margin-top:32px; }
}

/* The scene: a narrow column on the left for the pause button, and the field of the
   picture beside it. The prism mark stood in that column until September 29, 2026. */
.scene { position:relative; aspect-ratio:3/1; overflow:hidden; border-radius:20px; background:#07080a; }
/* The hairline edge, drawn over the picture so it frames the whole scene. */
.scene::after { content:''; position:absolute; z-index:3; inset:0; border-radius:inherit; box-shadow:inset 0 0 0 1px var(--hair-dark); pointer-events:none; }
.field { position:absolute; top:0; bottom:0; left:68px; right:0; }
.field .picture { position:absolute; inset:0; width:100%; height:100%; object-fit:fill; }
@media (min-width:1000px) { .scene { aspect-ratio:16/5; } }
/* The beam: the recording's outline as white light, all its frequencies together, drawn
   across the field the picture folds into. */
.beam { position:absolute; top:20%; left:0; width:100%; height:60%; overflow:visible; filter:drop-shadow(0 0 10px #fffdf740); }
.beam path { fill:#fffdf7; opacity:.85; }
/* Step 4 draws the damaged recording's beam, faintly, under the one handed back. */
.beam-ghost { filter:none; }
.beam-ghost path { opacity:.2; }
/* What the pictures show and how to read them, said once, close under the first so it
   reads as its key. */
.picture-key { margin:12px 0 0; color:var(--snow-3); font-size:15px; line-height:1.5; text-wrap:pretty; }

/* The drawings over a picture: lines where the damage is, and a numbered key for each
   that matches its row in the list. Found damage is drawn solid, guesses dotted. */
.overlay { position:absolute; inset:0; width:100%; height:100%; overflow:visible; pointer-events:none; }
.mark-line,.guess-line { stroke:#fffdf7; stroke-width:1.5; }
/* Found damage at half strength, so the voice stays the brightest thing in the picture
   and the gap under a dropout's line still shows; the numbered keys stay full. */
.mark-line { stroke-opacity:.5; }
.guess-line { stroke-dasharray:2 4; stroke-linecap:round; }
.mark-band { fill:#fffdf70d; }
.guess-band { fill:none; }
.key circle { fill:#07080acc; stroke:#fffdf7; stroke-width:1; }
.key text { fill:#fffdf7; font:600 12px var(--sans); text-anchor:middle; dominant-baseline:central; }

/* Step 4: the frequencies fold back into one line, and the repaired beam leaves it. */
.scene-return .field .picture { transform:scaleY(.02); opacity:0; }

/* With the script: one frame at a time, in one place, so the page never jumps. The frames
   share three rows: the picture, the key (only step 1 has one, but every step keeps its
   room) and the words, so every step's words start on the same line. Each step's words
   keep their own height, not the tallest step's, so their rows stay packed. The pictures
   cross-fade. The words fade through instead: the old ones are gone before the new ones
   come, so two steps' words never show one over the other. */
.story-live .frames { display:grid; grid-template-rows:auto auto 1fr; gap:0; }
.story-live .frame { grid-area:1 / 1 / 4 / 2; display:grid; grid-template-rows:subgrid; visibility:hidden; transition:visibility 0s linear calc(.5s * var(--story-pace)); }
.story-live .frame > .frame-text { grid-row:3; align-self:start; }
.story-live .frame.is-active { visibility:visible; transition:none; }
.story-live .frame > * { opacity:0; transition:opacity calc(.2s * var(--story-pace)) ease; }
.story-live .frame.is-active > * { opacity:1; transition:opacity calc(.3s * var(--story-pace)) ease calc(.2s * var(--story-pace)); }
.story-live .frame > .scene,.story-live .frame.is-active > .scene { transition:opacity calc(.5s * var(--story-pace)) ease; }
.story-starting .frame,.story-starting .frame > * { transition:none !important; }
.story-live .repair-thumb { display:none; }
.engine-story:not(.story-live) .scene-repairs { display:none; }
.story-paused .frame.is-active,.story-paused .frame.is-active * { animation-play-state:paused !important; }
/* A step chosen while paused, or paused before it began, shows as its finished still. */
.story-live .frame.is-still,.story-live .frame.is-still * { animation:none !important; }

/* Steps 1 and 3: each mark appears and its row lights with it, one after another. A row
   waits dimmed, never hidden, so a step held part way (off screen) still reads whole. */
.story-live .is-active .overlay > g { animation:fade-in calc(.7s * var(--story-pace)) ease both; }
.story-live .is-active :is(.found-list,.guess-list) li { animation:row-light calc(.7s * var(--story-pace)) ease both; }
.story-live .is-active .overlay > g:nth-child(1),.story-live .is-active :is(.found-list,.guess-list) li:nth-child(1) { animation-delay:calc(.3s * var(--story-pace)); }
.story-live .is-active .overlay > g:nth-child(2),.story-live .is-active :is(.found-list,.guess-list) li:nth-child(2) { animation-delay:calc(.9s * var(--story-pace)); }
.story-live .is-active .overlay > g:nth-child(3),.story-live .is-active :is(.found-list,.guess-list) li:nth-child(3) { animation-delay:calc(1.6s * var(--story-pace)); }
.story-live .is-active .overlay > g:nth-child(4),.story-live .is-active :is(.found-list,.guess-list) li:nth-child(4) { animation-delay:calc(2.3s * var(--story-pace)); }
.story-live .is-active .overlay > g:nth-child(n+5),.story-live .is-active :is(.found-list,.guess-list) li:nth-child(n+5) { animation-delay:calc(3s * var(--story-pace)); }
/* Step 2: the picture after each step fades in over the one before, 2.4 seconds apart at
   full length, and its row in the list lights at the same moment (engine.js times the
   step to match). */
.story-live .is-active .stack .picture:not(:first-child),.story-live .is-active .repair-step { animation:fade-in calc(.9s * var(--story-pace)) ease both; }
.story-live .is-active .repair-step { animation-name:row-light; }
.story-live .is-active .stack .picture:nth-child(2),.story-live .is-active .repair-step:nth-child(1) { animation-delay:calc(1.2s * var(--story-pace)); }
.story-live .is-active .stack .picture:nth-child(3),.story-live .is-active .repair-step:nth-child(2) { animation-delay:calc(3.6s * var(--story-pace)); }
.story-live .is-active .stack .picture:nth-child(4),.story-live .is-active .repair-step:nth-child(3) { animation-delay:calc(6s * var(--story-pace)); }
.story-live .is-active .stack .picture:nth-child(5),.story-live .is-active .repair-step:nth-child(4) { animation-delay:calc(8.4s * var(--story-pace)); }
.story-live .is-active .stack .picture:nth-child(6),.story-live .is-active .repair-step:nth-child(5) { animation-delay:calc(10.8s * var(--story-pace)); }
.story-live .is-active .scene-return .picture { animation:fold-picture calc(1.8s * var(--story-pace)) cubic-bezier(.6,0,.3,1) calc(.6s * var(--story-pace)) both; }
.story-live .is-active .scene-return .beam { animation:fold-beam calc(1.8s * var(--story-pace)) cubic-bezier(.6,0,.3,1) calc(1.1s * var(--story-pace)) both; }

@keyframes fade-in { from { opacity:0; } to { opacity:1; } }
@keyframes row-light { from { opacity:.35; } to { opacity:1; } }
@keyframes fold-picture { from { transform:none; opacity:1; } 80% { opacity:.6; } to { transform:scaleY(.02); opacity:0; } }
@keyframes fold-beam { from { transform:scaleY(.02); opacity:0; } to { transform:none; opacity:1; } }

/* A phone: the scene is taller than on a laptop, so the pictures stay readable, and the
   field starts 52 pixels in, so the 44 pixel pause button at 4 pixels never covers the
   picture. The four step names fit, so they show, and their numbers are for screen
   readers. All four frames keep one height, the tallest's, so nothing below moves as the
   story plays. */
@media (max-width:700px) {
  .engine-story { padding-bottom:96px; }
  .section.engine-story { padding:84px 0; }
  .step-nav ol { justify-content:space-between; gap:4px; }
  .step-nav a { padding:0 6px; }
  .step-number { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
  .step-nav a::before,.step-nav a::after { left:6px; right:6px; }
  .frames { gap:64px; }
  .frame-caption { font-size:17px; }
  .scene { aspect-ratio:16/10; border-radius:16px; }
  .field { left:52px; }
  .engine-story .story-stage .story-pause { left:4px; }
  .mark-line,.guess-line { stroke-width:1; }
}
/* A narrow phone: a row's tag sits over its name. */
@media (max-width:560px) {
  .frame-list li { grid-template-columns:minmax(0,1fr); row-gap:4px; }
}
