/* ==========================================================================
   TELL — outro.css · "THE FILE CLOSES"
   ==========================================================================
   The skin for js/outro.js, the ~48s closing sequence that plays INSIDE the
   epilogue roll (index.html runEpilogueRoll, after the vignettes and before
   the told note). It is not a credits roll — a one-person project has nothing
   to credit — it is the game closing the file on the run the player just made.

   Everything here obeys the same three laws css/ui-paper.css states:
   1. Every plate is BLANK. Every word on this screen is runtime type.
   2. ONE saturated element per FRAME: --pp-red. The thread, the stamp, the
      ending's name and the ribbon are all that red, and no two of them are ever
      on screen together — the beats below are laid out so that they take turns.
      (The close beat withdraws the ribbon under the folder as the TELL stamp
      lands, which is the only place the sequence could have broken the law.)
   3. Paper only: cork, card, manila, ledger cream, ink. No rounded web-app
      cards, no gradients standing in for surfaces, no techy chrome. The one
      radial gradient in here is the lamp pool, which is LIGHT, not UI.

   SECOND PASS (2026-09-02) — SCALE. The reference is a full-frame noir title
   sequence: single-hue colour blocks that FILL the screen, giant condensed type
   bleeding off the edges and split across a diagonal panel seam, hard cuts on
   the beat, small tracked labels in the corners. The first cut had the beats
   right and the scale wrong — small prints on a big empty wall, a name list on
   one static folder, modest type on a strip, a small receipt alone in the dark.
   The translation into paper is now:
      colour blocks  → DUOTONE PHOTOSTATS (.o-duo): the crop desaturated and
                       printed in ONE material — card cream, manila, or ink on
                       --hn-oxblood — so a print is a flat hue, not a snapshot
      katakana       → the cue codes (TORSO-017) in tracked Courier, big enough
                       to read from the sofa
      slash wipes    → torn-paper wipes, and the red thread between pins
      starring cards → one FULL-FRAME manila card per body, name in the hand
      the big words  → chapter titles at ~13vw straddling a diagonal seam, half
                       the word in ink and half in cream (the BRI|DGE move)
      progress line  → one dotted tear line, which the numbers beat lays a torn
                       strip of receipt paper underneath

   REDUCED MOTION IS THE PLAYER'S SETTING, NOT THE OS QUERY. js/outro.js reads
   settings.reduceMotion() and puts `.rm` on the root; the `.rm` blocks at the
   bottom remove TRAVEL and keep the crossfade, at the same durations, so the
   sequence is the same length either way — and js/outro.js's RM_MIN_STEP holds
   every beat to at most one change per 1.2s, so the setting removes the
   FLASHING as well as the flying. The OS-level @media block in theme.css still
   applies on top of that and is harmless here: nothing in this sheet gates
   state on an animation, and js/outro.js drives every step off setTimeout.
   ========================================================================== */

/* --------------------------------------------------------------------------
   The surface. Inserted into #act-end after #ae-hint, so it paints over the
   curtain's own steps; the hint is hidden for the duration because the roll
   is not waiting for a CONTINUE here — it is playing.
   -------------------------------------------------------------------------- */
#act-end.outro-live #ae-hint { display: none; }

#outro {
  position: absolute;
  inset: 0;
  z-index: 5;
  overflow: hidden;
  background-color: #05090c;        /* the desk with the lamp off */
  cursor: pointer;
  --o-beat: 970ms;                  /* the bed's beat, ~62 bpm — js/outro.js BEAT */
  --o-cream: var(--pp-ledger, #e8dfc9);
}

/* ONE LAMP POOL, exactly the act card's (index.html #ae-step-card): honey over
   the centre, the room falling away below it. It fades UP over the first beat
   rather than being there from the cut, because the first thing the sequence
   does is turn the desk lamp on. */
#outro .o-pool {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0;
  transition: opacity 1200ms ease;
  background-image:
    radial-gradient(46% 38% at 50% 46%,
      rgba(240, 226, 190, .155), rgba(240, 226, 190, .055) 46%, transparent 78%),
    radial-gradient(92% 60% at 50% 104%, rgba(0, 0, 0, .58), transparent 72%);
}
#outro .o-pool.on { opacity: 1; }

/* Each beat is a full-bleed step that crossfades, the .ae-step idiom. visibility
   joins the fade on a delay so a faded-out beat is out of the hit-testing tree
   but is still painted while it is leaving. */
#outro .o-beat {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 420ms ease, visibility 0s linear 420ms;
}
#outro .o-beat.on {
  opacity: 1;
  visibility: visible;
  transition: opacity 420ms ease, visibility 0s linear;
}

/* --------------------------------------------------------------------------
   THE DUOTONE PHOTOSTAT — the reference's colour block, made out of a picture
   this run actually looked at.

   The crop is desaturated, then two full-bleed overlays print it in ONE
   material: --o-hi MULTIPLIES (so white becomes that paper) and --o-lo LIGHTENS
   (so black becomes the ink it was printed in). Between them every pixel lands
   on the ramp from the paper to the ink and nothing else survives, which is why
   a whole print reads as a single flat hue from across the room. `isolation`
   is load-bearing: without it the blend modes would reach past the print and
   composite against the cork wall behind it.
   -------------------------------------------------------------------------- */
#outro .o-duo {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background-color: var(--o-lo, #171d28);
}
#outro .o-duo > i {
  position: absolute;
  inset: 0;
  display: block;
  pointer-events: none;
}
#outro .o-duo-img {
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  /* LIFTED, THEN FLATTENED. A story panel in this game is painted at night:
     desaturated as-is it sits in the bottom quarter of the ramp, every pixel
     lands on --o-lo, and the print comes out a solid dark rectangle — which is
     what the first v2 cut put on the wall twice. Brightness pushes the picture
     into the middle of the ramp where BOTH inks are visible, and the low
     contrast keeps it there, so the material is what you read first and the
     picture second. --o-filter lets one beat override it (the record beat's
     covers are darker again). */
  filter: var(--o-filter, grayscale(1) brightness(1.62) contrast(.82));
}
#outro .o-duo-hi {
  background-color: var(--o-hi, #ede6d6);
  mix-blend-mode: multiply;
}
#outro .o-duo-lo {
  background-color: var(--o-lo, #171d28);
  mix-blend-mode: lighten;
}

/* --------------------------------------------------------------------------
   THE TORN WIPE — the reference's diagonal slash, in paper.
   A full-bleed sheet of one of the four papers, cut on a diagonal with a torn
   edge (the polygon's zigzag), sweeping across the frame between beats. Under
   `.rm` js/outro.js never builds one; the crossfade alone carries the cut.
   -------------------------------------------------------------------------- */
#outro .o-wipe {
  position: absolute;
  top: -12%;
  bottom: -12%;
  left: 0;
  width: 150%;
  z-index: 8;
  pointer-events: none;
  background-color: var(--o-wipe-paper, var(--pp-card, #ede6d6));
  transform: translateX(-155%);
  transition: transform 700ms cubic-bezier(.42, 0, .18, 1);
  clip-path: polygon(
    18% 0%, 100% 0%, 82% 100%, 0% 100%,
    2.1% 88%, 0.4% 76%, 2.6% 64%, 0.9% 52%, 3.1% 40%, 1.2% 28%, 3.4% 14%);
}
#outro .o-wipe.go { transform: translateX(120%); }

/* --------------------------------------------------------------------------
   BEAT 0 — FILED UNDER. An index card slides onto the lit desk and the
   ending's name is STAMPED onto it. The stamp ring is a real rubber-stamp
   plate on cream ground, so it is composited with multiply: the ground sinks
   into the card underneath and only the ink is left, which is what a stamp on
   paper actually looks like.
   -------------------------------------------------------------------------- */
#outro .o-b0 {
  display: flex;
  align-items: center;
  justify-content: center;
}
#outro .o-card {
  position: relative;
  width: min(62vw, 700px);
  aspect-ratio: 800 / 560;
  background-image: var(--plate-index-card);
  background-repeat: no-repeat;
  /* The card's own bbox in the plate (x 108-708, y 67-464 of 800x560), a hair
     loose: the card is photographed tilted, so a crop tight to the bbox would
     put the plate's dark ground into the corners. What ground still shows
     reads as the board it is pinned to. */
  background-size: 131% 139%;
  background-position: 53.5% 41.5%;
  background-color: var(--pp-card, #ede6d6);
  box-shadow: var(--pp-shadow-contact, 0 14px 26px -8px rgba(0, 0, 0, .78));
  opacity: 0;
  transform: translateY(-12%) rotate(-3.4deg);
  transition: opacity 520ms var(--pp-ease-paper, ease),
              transform 520ms var(--pp-ease-paper, ease);
}
#outro .o-card.in { opacity: 1; transform: translateY(0) rotate(-1.4deg); }

#outro .o-card-label {
  position: absolute;
  left: 0;
  right: 0;
  top: 21%;
  margin: 0;
  text-align: center;
  font-family: var(--font-type);
  font-size: clamp(.56rem, 1.15vw, .78rem);
  font-weight: 700;
  letter-spacing: .34em;
  text-indent: .34em;
  color: var(--pp-type-dim, rgba(34, 36, 43, .62));
}
#outro .o-stampbox {
  position: absolute;
  left: 50%;
  top: 52%;
  width: 68%;
  aspect-ratio: 500 / 220;
  transform: translate(-50%, -50%) rotate(-4.2deg) scale(1.55);
  transform-origin: 50% 50%;
  opacity: 0;
  background-image: var(--plate-stamp-ring);
  background-repeat: no-repeat;
  /* Cropped to the RED FRAME'S OWN BOX (measured on the plate: x 27-475,
     y 13-205 of 500x220), not to the canvas — the plate's outer cream margin
     is what made the stamp read as a pasted rectangle rather than as ink
     pressed into the paper under it. */
  background-size: 111.61% 114.58%;
  background-position: 51.90% 46.44%;
  mix-blend-mode: multiply;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: opacity var(--pp-t-stamp, 220ms) var(--pp-ease-stamp, ease),
              transform var(--pp-t-stamp, 220ms) var(--pp-ease-stamp, ease);
}
#outro .o-stampbox.in {
  opacity: 1;
  transform: translate(-50%, -50%) rotate(-2.6deg) scale(1);
}
/* Sized so the LONGEST ending name (CARRIED INTEREST, sixteen glyphs) still
   sits inside the ring's clear field — Courier is 0.6em per glyph and the
   tracking adds 0.2em, so the name is 16 x 0.8em wide and the field is 78% of
   the box. A name that still overruns wraps rather than breaking the ring. */
#outro .o-stamp-word {
  font-family: var(--font-type);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .2em;
  text-indent: .2em;
  font-size: clamp(.74rem, 2.4vw, 1.5rem);
  line-height: 1.16;
  color: var(--pp-red, #8e2f2a);
  padding: 0 9%;
  text-align: center;
}

/* --------------------------------------------------------------------------
   BEAT 1 — THE WALL. The cues the player READ, pinned up as PRINTS: a crop of
   the cue's own panel at the cue's own region box, printed as a duotone
   photostat on one of the three materials, matted, pinned with a brass pin,
   its cue code typed on the mat. The red thread joins them in ledger order.

   V2 SCALE: a print is HALF THE DESK TALL. js/outro.js lays sixteen of them
   over a 4x2 field of cells that are smaller than the prints are, in two
   passes, so they overlap the way a wall somebody has actually been working at
   overlaps, and the newest is always on top. Four to eight of them cover most
   of the frame at any moment after the third group.

   URL LAW: --plate-cork-wall is declared in css/ui-paper.css as
   '../assets/ui/cork-wall.webp'. A url() carried in a custom property resolves
   against the sheet that USES it, and this sheet sits in css/ beside that one,
   so the relative path is the same and consuming the token here is safe. (The
   act card in index.html cannot do this — a <style> element resolves against
   the document — which is why it writes its plate path out longhand.)
   -------------------------------------------------------------------------- */
#outro .o-wall {
  position: absolute;
  inset: 0;
  background-image: var(--plate-cork-wall);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  background-color: var(--pp-cork, #9a7743);
}
/* The pool is painted on the surface, not on the wall, so the wall takes its
   own shading here — the lamp reaches the middle of the board and not the
   corners, which is the whole reason a corkboard reads as a place. */
#outro .o-wall::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    radial-gradient(76% 68% at 50% 46%, transparent 36%, rgba(0, 0, 0, .40) 100%);
}

#outro .o-thread {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 40;                      /* over every print — a thread is strung ON TOP */
  pointer-events: none;
  overflow: visible;
}
/* DRAWN HARD, ON THE BEAT: 3px of thread, strung in 150ms as its far pin goes
   in. The first cut drew a 1.9px line over 600ms and it read as a hairline
   fading up rather than as a length of cotton being pulled tight. */
#outro .o-thread line {
  stroke: var(--pp-red, #8e2f2a);
  stroke-width: 2.8;
  stroke-linecap: round;
  fill: none;
  opacity: .86;
  transition: stroke-dashoffset 150ms cubic-bezier(.2, .8, .3, 1),
              opacity 120ms linear;
}
#outro .o-thread line.pending { opacity: 0; }

/* A PRINT. The mat is the element's own padding on paper stock; the photostat
   is the child. Wide bottom margin, like every other print in this game
   (.ae-vig-img, #ae-panel-img) — that band is where the cue code is typed.
   The mat sizes here are the ones js/outro.js measures with (matS / matB): if
   one changes, both change. */
#outro .o-print {
  position: absolute;
  box-sizing: border-box;
  padding: clamp(4px, .6vw, 9px);
  padding-bottom: clamp(24px, 3vw, 44px);
  background-color: var(--o-mat, var(--pp-card, #ede6d6));
  box-shadow: 0 10px 30px -8px rgba(0, 0, 0, .78),
              var(--pp-shadow-pinned, 0 2px 5px rgba(0, 0, 0, .5));
  opacity: 0;
  /* PRINTS LAND. 90ms of settle from 1.06, on the beat — not a 440ms glide in
     from off-cell, which is what made the first cut's wall read as a slideshow
     of arrivals instead of a hand pinning things up. */
  transform: rotate(var(--o-rot, 0deg)) scale(1.06);
  transition: opacity 70ms linear,
              transform 90ms var(--pp-ease-stamp, cubic-bezier(.2, .9, .3, 1));
}
#outro .o-print.in {
  opacity: 1;
  transform: rotate(var(--o-rot, 0deg)) scale(1);
}
#outro .o-shot {
  display: block;
  width: 100%;
}
/* READABLE FROM THE SOFA. The corner label is the reference's tracked caption,
   and at 0.5rem the first cut's was decoration rather than information. */
#outro .o-code {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(5px, .8vw, 11px);
  text-align: center;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(.8rem, 1.15vw, 1rem);
  letter-spacing: .18em;
  text-indent: .18em;
  color: var(--pp-type, #22242b);
}
/* The wide mat band, and the code on it, moved to the top of the print — see
   js/outro.js: the lower row runs off the bottom of the desk and takes its own
   caption with it. The two paddings swap; the print's total height does not
   change, which is what keeps the measured layout honest. */
#outro .o-print.code-top {
  padding-top: clamp(24px, 3vw, 44px);
  padding-bottom: clamp(4px, .6vw, 9px);
}
#outro .o-print.code-top .o-code {
  top: clamp(5px, .8vw, 11px);
  bottom: auto;
}

/* The pin. pin-sprites is a 5x2 grid of cut-outs (css/ui-paper.css): row 1 is
   five brass pins, and --o-pin picks the column so the wall is not sixteen
   copies of one pin. Brass, never red — the red on this beat is the thread. */
#outro .o-pin {
  position: absolute;
  left: 50%;
  top: clamp(-15px, -2vw, -9px);
  width: clamp(18px, 2.5vw, 32px);
  aspect-ratio: 1 / 1;
  transform: translateX(-50%);
  pointer-events: none;
  background-image: var(--plate-pin-sprites);
  background-repeat: no-repeat;
  background-size: 500% 200%;
  background-position: calc(var(--o-pin, 0) * 25%) 0%;
  filter: drop-shadow(0 2px 3px rgba(0, 0, 0, .55));
}

/* --------------------------------------------------------------------------
   BEAT 2 — THE PEOPLE. The reference's STARRING cards, as the case file would
   file them: one FULL-FRAME manila card per body, the name huge in the hand,
   the role typed under it in tracked caps, a duotone photostat of that body
   beside it. The card slides in hard from the right and pushes the one before
   it off to the left. One per beat.

   V2: the first cut stacked nine small tabs on ONE folder and held the list for
   ten seconds, which is a credits list — the thing this sequence exists not to
   be. A body gets a frame or it does not get a beat.

   The manila is the folder plate's own paint, zoomed past its printed edges so
   only the flat field of the cover is sampled (folder.png's manila field runs
   x 0-748 / y 36-555 of 900x640, measured; 430%/470% at 50%/52% lands inside
   it). --pp-manila is the ground under it, so a failed image is still manila.
   -------------------------------------------------------------------------- */
#outro .o-b2 { background-color: #05090c; }
#outro .o-pcard {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: stretch;
  background-color: var(--pp-manila, #c9b489);
  background-image: var(--plate-folder);
  background-repeat: no-repeat;
  background-size: 430% 470%;
  background-position: 50% 52%;
  box-shadow: -18px 0 34px -10px rgba(0, 0, 0, .7);
  opacity: 0;
  /* EXACTLY 100% EACH WAY, and both on the same curve. The two cards are one
     strip: the arriving card's left edge and the leaving card's right edge are
     the same line, so nothing of the desk behind shows between them. At 102 in
     and 104 out the first v2 cut left a 2-4% band of black down the leading
     edge for the whole slide, which reads as a seam in the card. */
  transform: translateX(100%);
  transition: transform 240ms cubic-bezier(.26, .86, .28, 1),
              opacity 0s linear;
}
#outro .o-pcard.in { opacity: 1; transform: translateX(0); }
/* The one that was on screen is PUSHED OFF, not faded out: two cards are on
   the desk for a quarter of a second and one of them is leaving. */
#outro .o-pcard.out { opacity: 1; transform: translateX(-100%); }

#outro .o-pcard-pic {
  order: 2;                          /* DOM-first, screen-right */
  flex: 0 0 40%;
  align-self: stretch;
  box-shadow: inset 14px 0 26px -12px rgba(0, 0, 0, .6);
}
#outro .o-pcard-txt {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(.5rem, 2vh, 1.2rem);
  padding: 0 clamp(.9rem, 3.1vw, 3.1rem);
}
#outro .o-pcard.nopic .o-pcard-txt { padding-right: clamp(2rem, 7vw, 7rem); }
#outro .o-pname {
  margin: 0;
  font-family: var(--font-hand);
  line-height: .92;
  color: var(--pp-graphite, #3a3f45);
  /* Caveat overhangs its advance width on the right — every hand in this game
     pays the same .35em to stop the last letter being clipped. */
  padding-right: .35em;
}
#outro .o-prole {
  margin: 0;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(.78rem, 1.5vw, 1.2rem);
  letter-spacing: .34em;
  text-indent: .34em;
  color: var(--pp-type-dim, rgba(34, 36, 43, .62));
}

/* --------------------------------------------------------------------------
   BEAT 3 — THE RECORD. One diagonal seam splits the frame: the chapter's cover
   duotoned on one side, flat ink on the other. The title is drawn TWICE,
   identically placed, once inside each half — so the seam CUTS THE WORD and
   the two pieces of it are in opposite inks. That is the reference's BRI|DGE
   move and it is the reason a chapter title is 13vw tall here. The seam swaps
   direction every beat; "No. NN" sits small in the corner.

   Only the LAST EIGHT reached chapters play (js/outro.js MAX_TITLES) — the
   cap is on the COUNT, never on the dwell, because a finished run reaches 28.
   -------------------------------------------------------------------------- */
#outro .o-b3 {
  background-color: var(--pp-ink, #0b1220);
  --o-sa: 63%;                      /* where the seam meets the top edge     */
  --o-sb: 33%;                      /* …and the bottom                       */
}
#outro .o-b3.flip { --o-sa: 37%; --o-sb: 67%; }
#outro .o-half { position: absolute; inset: 0; }
#outro .o-half-a { clip-path: polygon(0 0, var(--o-sa) 0, var(--o-sb) 100%, 0 100%); }
#outro .o-half-b {
  clip-path: polygon(var(--o-sa) 0, 100% 0, 100% 100%, var(--o-sb) 100%);
  background-color: var(--pp-ink, #0b1220);
}
#outro .o-rec-pic {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 90ms linear;
}
#outro .o-b3.cut .o-rec-pic { opacity: 1; }
/* THE WORD BLEEDS ONLY WHEN IT HAS TO. A title that is genuinely too big for
   the frame is flush to the edge and loses the outer fifth of its outermost
   glyph — that reads as the frame being too small for the word, which is the
   intention. A title that FITS must not be cut at all: the first v2 cut bled
   1.8% a side unconditionally and took a third of the leading O off "OPEN
   FLOOR PLAN" and the T off "THE SENTENCE" with 600px of empty frame beside
   them, which reads as a clipping bug. js/outro.js fits the word first and
   adds .bleed only when the longest word still needs more than the safe box. */
#outro .o-rec-word {
  position: absolute;
  left: 1.4%;
  right: 1.4%;
  top: 50%;
  margin: 0;
  transform: translateY(-50%);
  font-family: var(--font-type);
  font-weight: 700;
  font-size: var(--o-fs, 9vw);
  line-height: 1.02;
  letter-spacing: -.005em;
  text-transform: uppercase;
  text-align: left;
  opacity: 0;
  transition: opacity 90ms linear;
}
#outro .o-b3.bleed .o-rec-word { left: -1.8%; right: -1.8%; }
#outro .o-b3.rt .o-rec-word { text-align: right; }
#outro .o-half-a .o-rec-word { color: var(--pp-ink, #0b1220); }
#outro .o-half-b .o-rec-word {
  color: var(--o-cream);
  text-shadow: 0 3px 16px rgba(0, 0, 0, .6);
}
#outro .o-b3.cut .o-rec-word { opacity: 1; }
/* THE CORNER LABEL SITS ON THE INK, IN THE INK'S OWN CREAM. It used to sit
   bottom-LEFT in cream on mix-blend-mode: difference, "so it reads over either
   side" — but difference cancels at mid-tone and mid-tone is exactly what the
   photostat's brightness(1.62) contrast(.82) manufactures, so on the picture
   half it returned a 5/255 delta and vanished. .o-half-b (flat ink, cream type)
   is always the RIGHT of the seam whichever way the diagonal leans, and the
   bottom corner of it is clear of the word block at every size the fitter
   returns — so the label lives there, unblended, and reads on every frame. */
#outro .o-rec-no {
  position: absolute;
  right: 3.2vw;
  bottom: 5.4vh;
  z-index: 3;
  margin: 0;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(.7rem, 1.05vw, .92rem);
  letter-spacing: .42em;
  text-indent: .42em;
  color: rgba(233, 222, 196, .72);
}

/* --------------------------------------------------------------------------
   BEAT 4 — THE NUMBERS. The four buckets the case wall already prints
   (js/chapter-map.js reckon(): READ / NOTICED / OUT OF REACH / NEVER SEEN),
   summed over the chapters this run reached, typed STRAIGHT ONTO THE INK at
   ~16vw — one bucket per beat, the label small and tracked above it — and then
   the ending's name, which is the one red on the frame.

   V2 / DEFECT 5: the first cut rolled the tally down a receipt object whose
   lower third came out empty, because a 470px strip of paper with six 0.7rem
   rows on it is mostly paper. The receipt survives as the thing it was always
   for — a torn strip along the bottom, under the dotted tear line.
   -------------------------------------------------------------------------- */
#outro .o-b4 { background-color: var(--pp-ink, #0b1220); }
#outro .o-numwrap {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  /* 12vh, not 15: the count is fitted much larger now (js/outro.js raised the
     ceiling to 42vw and lets the height pass do the limiting), and the block
     has to re-centre in the ink rather than ride high above the torn strip. */
  bottom: 12vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: clamp(.35rem, 1.2vh, .9rem);
  opacity: 0;
  transition: opacity 90ms linear;
}
#outro .o-b4.cut .o-numwrap { opacity: 1; }
#outro .o-numlab {
  margin: 0;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(.9rem, 1.45vw, 1.4rem);
  letter-spacing: .44em;
  text-indent: .44em;
  color: rgba(233, 222, 196, .58);
}
#outro .o-num {
  margin: 0;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: 24vw;                  /* js/outro.js fits a long name down */
  line-height: .92;
  letter-spacing: -.012em;
  text-align: center;
  color: var(--o-cream);
  text-shadow: 0 6px 26px rgba(0, 0, 0, .7);
}
#outro .o-num.name {
  color: var(--pp-red, #8e2f2a);
  letter-spacing: .06em;
  text-indent: .06em;
}
#outro .o-numstrip {
  position: absolute;
  left: -3%;
  right: -3%;
  bottom: -1.5vh;
  height: clamp(90px, 17vh, 152px);
  background-image: var(--plate-caption-strip);
  background-repeat: no-repeat;
  background-position: center 18%;
  background-size: 106% auto;
  background-color: var(--pp-card, #ede6d6);
  transform: rotate(-.55deg);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .55);
}
#outro .o-numstrip-lab {
  position: absolute;
  left: 7%;
  bottom: 26%;
  margin: 0;
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(.6rem, .95vw, .8rem);
  letter-spacing: .34em;
  text-indent: .34em;
  color: var(--pp-type-dim, rgba(34, 36, 43, .62));
}

/* --------------------------------------------------------------------------
   BEAT 5 — THE CLOSE. The folder closes over the wall: the plate GROWS (its
   width transitions, so the paint re-rasterises and stays crisp — a transform
   scale of 3.6x would be a blur) until its manila face is the whole frame, and
   it does it in ONE BEAT. The ribbon drops down the right edge; then, as the
   TELL stamp lands with a real thud (1.15 to 1 in 120ms), the ribbon is
   withdrawn under the closing folder, because the red stamp and the red ribbon
   may not share a frame.
   -------------------------------------------------------------------------- */
#outro .o-b5 { background-color: #05090c; }
#outro .o-folder {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 30vw;
  aspect-ratio: 900 / 640;
  transform: translate(-50%, -50%);
  background-image: var(--plate-folder);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  box-shadow: var(--pp-shadow-contact, 0 14px 26px -8px rgba(0, 0, 0, .78));
  transition: width 950ms cubic-bezier(.34, .02, .18, 1);
}
#outro .o-folder.in { width: 128vw; }

#outro .o-ribbon {
  position: absolute;
  right: clamp(1.5rem, 9vw, 9rem);
  /* CLEAR OF THE FRAME BEFORE IT DROPS: at -46% the ribbon's 58% of height put
     its frayed tip 87px inside the desk a beat before its cue, and the tip was
     visible under the closing folder for a second and a half. -64% + 58% ends
     at -6%. */
  top: -64%;
  height: 58%;
  aspect-ratio: 80 / 388;
  z-index: 2;
  background-image: var(--plate-ribbon-bookmark);
  background-repeat: no-repeat;
  background-size: 300% 108.2%;
  background-position: 49.4% 43.7%;
  filter: drop-shadow(0 6px 10px rgba(0, 0, 0, .6));
  transition: top 900ms cubic-bezier(.24, .7, .3, 1), opacity 320ms ease;
}
#outro .o-ribbon.in { top: -2%; }
#outro .o-ribbon.out { opacity: 0; top: -60%; }

#outro .o-mark {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 3;
  width: min(52vw, 540px);
  aspect-ratio: 500 / 220;
  transform: translate(-50%, -50%) rotate(-3.6deg) scale(1.15);
  opacity: 0;
  background-image: var(--plate-stamp-ring);
  background-repeat: no-repeat;
  background-size: 111.61% 114.58%;   /* the ring's own box — see .o-stampbox */
  background-position: 51.90% 46.44%;
  mix-blend-mode: multiply;
  display: flex;
  align-items: center;
  justify-content: center;
  /* THE THUD. 120ms, not 220 — a stamp is an impact, and the extra hundred
     milliseconds were the difference between a press and a fade-in. */
  transition: opacity 120ms var(--pp-ease-stamp, ease),
              transform 120ms var(--pp-ease-stamp, ease);
}
#outro .o-mark.in {
  opacity: 1;
  transform: translate(-50%, -50%) rotate(-2.2deg) scale(1);
}
#outro .o-mark-word {
  font-family: var(--font-type);
  font-weight: 700;
  font-size: clamp(1.8rem, 7.2vw, 4.6rem);
  letter-spacing: .34em;
  text-indent: .34em;
  line-height: 1;
  color: var(--pp-red, #8e2f2a);
}

/* --------------------------------------------------------------------------
   THE TEAR LINE. The reference's dotted progress line, as the perforation on
   a receipt: a row of dashes across the bottom of the desk that is TORN across
   as the sequence runs. No bar, no fill, no percentage — the line is simply
   longer than it was. It is ink, never red.

   On beat 4 the numbers lay a torn strip of receipt paper underneath it, so
   `.on-paper` switches the SAME line to ink on cream rather than drawing a
   second dotted line on top of the first.
   -------------------------------------------------------------------------- */
#outro .o-tear {
  position: absolute;
  left: 4vw;
  right: 4vw;
  bottom: 2.4vh;
  height: 1px;
  z-index: 6;
  pointer-events: none;
  border-top: 1px dashed rgba(233, 222, 196, .30);
  clip-path: inset(0 100% 0 0);
  transition: clip-path 46000ms linear, border-color 300ms ease;
  opacity: 0;
}
#outro .o-tear.on { opacity: 1; }
#outro .o-tear.run { clip-path: inset(0 0 0 0); }
#outro .o-tear.on-paper { border-top-color: rgba(34, 36, 43, .46); }

/* --------------------------------------------------------------------------
   REDUCED MOTION — the player's setting (settings.reduceMotion()), applied as
   `.rm` by js/outro.js. Travel goes; the crossfade stays; every duration is
   unchanged, so the sequence is exactly as long and lands on exactly the same
   frames. Nothing here is a display:none — a beat that vanished would be a
   picture nobody saw.

   AND THE FLASHING GOES TOO. js/outro.js holds every beat to at most one
   change per RM_MIN_STEP (1.2s) — beat 3 keeps ONE cover for its whole length
   and never swaps the seam — and the crossfades below are lengthened to 200ms
   so a change is a dissolve rather than a cut.
   -------------------------------------------------------------------------- */
/* A TRANSFORM IS NOT ALWAYS TRAVEL, and this block learned that the hard way:
   the first cut wrote `transform: none` across every animated object, which
   also deleted the tab stack's OWN POSITION — the objects landed on top of one
   another and the beat played as one thing replacing another. Each rule below
   therefore restates the object's RESTING transform on both states, so what is
   removed is the difference between them — the travel — and nothing else. */
#outro.rm .o-print,
#outro.rm .o-print.in {
  transform: rotate(var(--o-rot, 0deg)) !important;   /* the scatter stays; the flight goes */
  transition: opacity 260ms ease !important;
}
#outro.rm .o-card,
#outro.rm .o-card.in {
  transform: rotate(-1.4deg) !important;
  transition: opacity 520ms ease !important;
}
#outro.rm .o-pcard,
#outro.rm .o-pcard.in,
#outro.rm .o-pcard.out {
  transform: none !important;
  transition: opacity 200ms ease !important;
}
#outro.rm .o-pcard.out { opacity: 0 !important; }

#outro.rm .o-rec-word {
  transform: translateY(-50%) !important;   /* this one is the vertical centring */
}
#outro.rm .o-rec-word,
#outro.rm .o-rec-pic,
#outro.rm .o-numwrap { transition: opacity 200ms ease !important; }

/* The thread is still strung, but at the pace of everything else. */
#outro.rm .o-thread line { transition: opacity 260ms ease !important; }

/* The two stamps keep their centring AND their crooked press — a stamp landing
   straight is a different object — and lose only the scale-down punch. */
#outro.rm .o-stampbox,
#outro.rm .o-stampbox.in {
  transform: translate(-50%, -50%) rotate(-2.6deg) !important;
  transition: opacity 300ms ease !important;
}
#outro.rm .o-mark,
#outro.rm .o-mark.in {
  transform: translate(-50%, -50%) rotate(-2.2deg) !important;
  transition: opacity 300ms ease !important;
}

#outro.rm .o-folder {
  width: 128vw !important;
  opacity: 0;
  transition: opacity 700ms ease !important;
}
#outro.rm .o-folder.in { opacity: 1; }

#outro.rm .o-ribbon {
  top: -2% !important;
  opacity: 0;
  transition: opacity 420ms ease !important;
}
#outro.rm .o-ribbon.in { opacity: 1; }
#outro.rm .o-ribbon.out { opacity: 0; }

#outro.rm .o-tear { transition: opacity 400ms ease !important; clip-path: none; }

/* --------------------------------------------------------------------------
   THE SNAP. A skip is a CUT to the held last frame, not a fast replay of it —
   so js/outro.js adds this for one frame (60ms, on a timer, the .book-reset
   precedent in index.html) while it writes the end state, then takes it off.
   A reverse or fast-forward transition is a promise an unfocused tab does not
   keep; a snap needs nothing to run at all.
   -------------------------------------------------------------------------- */
#outro.o-snap,
#outro.o-snap *,
#outro.o-snap *::before,
#outro.o-snap *::after { transition: none !important; }

/* A very narrow desk: the people card's two columns stop being two columns,
   and the index card takes the width it needs. Nothing is hidden — the type
   simply stops shrinking. */
@media (max-width: 640px) {
  #outro .o-card { width: 84vw; }
  #outro .o-pcard-pic { flex-basis: 38%; }
  #outro .o-pcard-txt { padding: 0 clamp(.8rem, 4vw, 2rem); }
  #outro .o-numstrip { height: clamp(70px, 14vh, 110px); }
}
/* Foreground loss holds authored animation together with score and beat timers. */
body.playback-paused #outro,
body.playback-paused #outro * { animation-play-state: paused !important; }
