/* #159, the reward beat (#307 split); #2995, the finale.

   The card was a "Poster slam" (#1927: a skewed green band with streaks racing
   through it, the word slammed over it, two sparks). The owner, 2026-10-05:
   "it definitely feels flat and unnuanced currently and bad. So remove it" --
   and put "the one we see at the end of the burger video comp" in its place,
   "But no audio for now." This file is that ending, ported from
   docs/demos/2026-10-02-linkedin-promo/v3-order-up.html (its `frame(t)` and
   common.js's easings), in the same order:

     t=0 ms      the scene LANDS            promo `drop`: falls in, squashes, settles (FALL = .42s)
     t=370 ms    six gold sparkles bloom    promo: seg(t, T_DONE - .05, T_DONE + .9) = 950ms
     t=970 ms    the ribbon slides in       promo: T_RIB = T_DONE + .55, 550ms, `E.back`

   where T_DONE (the last piece landing) is this card's t=420. The promo's
   level-5 bloom and "Order up!" line are not here, and why is in reward.js.
   Before and after, at both phone sizes, both themes: docs/demos/2026-10-05-reward-finale/.

   What this file still owes the rest of the app, unchanged:

   - The element is still `<p class="beat-text beat-reward hooray">`. `hooray`
     is study.css's celebration class and the finished-sitting card wears it
     too (#431), so this card keeps the class and overrides the parts of it
     the finale does not want (the gradient wave and the wobble) at (0,2,0)
     rather than dropping it: tests/js/test_study_offramp.mjs pins that this
     beat's text opts in.
   - Its text is HEBREW (#131's memo), so it needs the Hebrew face.
   - Continue stays `.advance` + `.beat-dismiss` -- the unified shape every
     beat's forward control wears (#706), the JS hook `wire()` queries, and the
     seam `shared/advance.js` listens on. It is usable from the first frame:
     the finale adds no gate, no delay and no disabled state to it.

   ## The colours are fixed, on purpose

   The ribbon, the ink and the paper are the same in dark and light, like the
   trophy card's (shared/trophy_hero.css: "its palette is fixed and identical
   in light and dark"). Nothing below reads `--ground`, `--ink` or `--ok` for a
   FILL, which is also what makes the contrast arithmetic theme-proof:

       the word      #F6F4EE paper over its 4px #16181C outline         15.20:1
                     (the outline is what the letters sit on: the green never
                     touches a glyph)
       "Well done!"  white over its 3px ink outline                     16.9:1
       Continue      #16181C ink on #F0EDE6 paper                        15.20:1
       the ribbon    green over dark ground 9.86:1 / 6.46:1 (teal end); over
                     light ground 1.78:1 / 2.72:1, where its 4px ink edge
                     carries it at 16.16:1
       focus ring    `--ink` against the ground it sits on, the theme's own
                     pair: 16.52:1 dark / 16.16:1 light

   The green (#2ED47A -> #22A699) is never text and never carries one. */

/* `--sans-he` because the serif stack has no Hebrew and falls through to a
   synthesised face, which at this size reads as a different word rather than
   a bigger one. Restated, not inherited, because `.hooray` sets its own weight
   and a change to the shared celebration must not be able to take the Hebrew
   face away from it. */
.beat-reward { font-family:var(--sans-he); }

/* The card is clickable end to end (beats/reward.js: the card, the page around
   it, and the labelled control all continue), so the cursor should say so on a
   laptop. */
@media (hover: hover) {
  .beat:has(.beat-reward) { cursor:pointer; }
}

/* ---- the finale ------------------------------------------------------------

   `.beat`'s own 18px column gap is taken to 0 for this card: the stage and the
   button are one composition, and the button's gap to the stage is the button's
   own top margin. `:has()` rather than a class on `.beat` because `.beat` is
   painted by beat_between_cues.js, which this file does not own.

   The stage is full-bleed (`.stage` pads its children 24px each side, and a
   ribbon that stopped 24px short of the edges would read as a card, not a
   banner), and `overflow:hidden` clips the skewed band's corners and the
   ribbon's run-up from off-screen left. Its height is the ribbon (top) plus the
   scene (below it), measured at 390x844 and 375x667.

   The 34px above the ribbon is INSIDE the stage, not a `margin-top` on it: this
   stage is `.beat`'s first child, and #1300 measured what a positive top margin
   on a first child does -- it pushes the whole card off the anchor every other
   card's first line sits on. */
.beat:has(.reward-finale) { gap:0; }

.reward-finale {
  --h1:#2ED47A; --h2:#22A699; --ink-fixed:#16181C; --paper:#F6F4EE;
  position:relative; flex:0 0 auto; overflow:hidden;
  width:calc(100% + 48px); height:420px; margin:0 -24px;
}

/* ---- the scene (the promo's `drop`) -----------------------------------------
   `MilimIsoArt`'s own `.path-iso` is `position:absolute; left:50%; bottom:0;
   translateX(-50%)` (study.css, for a node on the path); here it fills this box
   instead, at (0,2,0) over that rule. Its ink rules (`.path-iso .t ...`) apply
   anywhere and are not restated. The box is the art's own 176x200 viewBox
   at 1.28x, so the plinth stands ~140px wide. */
.rf-scene {
  position:absolute; left:calc(50% - 113px); top:160px; width:226px; height:257px;
  animation:rf-land 420ms both;
}
.rf-scene .path-iso { left:0; bottom:auto; top:0; width:100%; height:100%; transform:none; }
/* A soft ground shadow under the plinth: the promo's blurred ellipse, as one gradient. */
.rf-scene::before {
  content:""; position:absolute; left:-20px; right:-20px; bottom:2px; height:34px;
  background:radial-gradient(closest-side, rgba(0,0,0,.26), rgba(0,0,0,0));
}
/* The promo: lift = (1 - drop(u)) * 34 world units of a 200-unit box = 17%; it
   is solid by u = .33; it lands at u = .6, sinks ~1 unit (.5%) and settles. */
@keyframes rf-land {
  0%   { transform:translateY(-17%); opacity:0; animation-timing-function:cubic-bezier(.5,0,.9,.6); }
  33%  { opacity:1; }
  60%  { transform:translateY(0); animation-timing-function:ease-out; }
  80%  { transform:translateY(.6%); animation-timing-function:ease-in-out; }
  100% { transform:translateY(0); opacity:1; }
}

/* ---- the sparkles -----------------------------------------------------------
   Six gold stars at the promo's own offsets round the scene's middle, each
   drifting outward as it blooms (promo: radius .7 -> 1.2 of its offset) and
   scaling by sin(pi * u), so it is born and dies at nothing. `--dx/--dy/--s`
   come from reward.js, already scaled to a phone. Sampled at u = 0, .25, .5,
   .75, 1: sin = 0, .707, 1, .707, 0 and radius - 1 = -.3, -.175, -.05, .075, .2. */
.rf-spark {
  position:absolute; left:50%; top:calc(100% - 50px); width:20px; height:20px;
  margin:calc(var(--dy) - 10px) 0 0 calc(var(--dx) - 10px);
  pointer-events:none; opacity:0;
  animation:rf-spark 950ms linear 370ms both;
}
@keyframes rf-spark {
  0%   { opacity:0;    transform:translate(calc(var(--dx) * -.3),   calc(var(--dy) * -.3))   scale(0); }
  25%  { opacity:.707; transform:translate(calc(var(--dx) * -.175), calc(var(--dy) * -.175)) scale(calc(var(--s) * .707)); }
  50%  { opacity:1;    transform:translate(calc(var(--dx) * -.05),  calc(var(--dy) * -.05))  scale(var(--s)); }
  75%  { opacity:.707; transform:translate(calc(var(--dx) * .075),  calc(var(--dy) * .075))  scale(calc(var(--s) * .707)); }
  100% { opacity:0;    transform:translate(calc(var(--dx) * .2),    calc(var(--dy) * .2))    scale(0); }
}

/* ---- the ribbon (the promo's `.ribbon`) -------------------------------------
   A skewed green band, the heavy Hebrew word, and "Well done!" under it, both
   skewed with the band. It runs 40px past each edge so the skew never shows a
   corner, and slides in from off-screen left on the promo's overshoot curve
   (`E.back`, c1 = 1.9 -- the app's own --ease-spring family). Until its delay
   has run it is parked off-screen, which is what `both` is for. */
.rf-ribbon {
  position:absolute; left:-40px; right:-40px; top:34px; height:128px;
  animation:rf-ribbon-in 550ms cubic-bezier(.2,1.5,.4,1) 970ms both;
}
@keyframes rf-ribbon-in {
  from { transform:translateX(-125%); }
  to   { transform:translateX(0); }
}
.rf-band {
  position:absolute; inset:0; transform:skewY(-6deg);
  background:linear-gradient(100deg, var(--h1), var(--h2));
  border-block:4px solid var(--ink-fixed);
}
/* the promo's one light streak across the band's upper left */
.rf-band::after {
  content:""; position:absolute; left:18%; top:16px; width:26%; height:7px; border-radius:4px;
  background:rgba(255,255,255,.35);
}
.rf-text {
  position:absolute; inset:0; transform:skewY(-6deg);
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  padding:0 48px; box-sizing:border-box; pointer-events:none;
}

/* The word. `.hooray` (study.css) is overridden here at (0,2,0): its gradient
   wave runs `background-clip:text` with a transparent fill, and its wobble is a
   second animation on `transform` -- the finale wants neither, and a leftover
   `-webkit-text-fill-color:transparent` would paint the word invisible over the
   band. So the fill, the clip, the background and the animation are all
   STATED, not left to whatever `.hooray` leaves.

   `-webkit-text-stroke` with `paint-order:stroke fill` draws the outline UNDER
   the fill, so the letters keep their full width. The hard shadow is the
   promo's `text-shadow: 7px 7px 0` at this size. Size is `.hooray`'s own scale
   (the text-size setting still works here, #886). */
.beat-reward.hooray {
  position:relative; display:block; margin:0; text-align:center;
  font-size:calc(clamp(48px, 14.5vw, 60px) * var(--milim-text-scale));
  font-weight:900; line-height:1; letter-spacing:0; text-wrap:balance;
  color:var(--paper); -webkit-text-fill-color:var(--paper);
  background:none; -webkit-background-clip:border-box; background-clip:border-box;
  -webkit-text-stroke:4px var(--ink-fixed); paint-order:stroke fill;
  text-shadow:4px 4px 0 var(--ink-fixed);
  transform:none; animation:none;
}
/* The English line (#2995): the ribbon says it in English until the phrase is
   known. It is a Latin word set in `.hooray`'s own weight, so it gets the
   Hebrew line's treatment (outline, shadow) but a smaller size -- the longest
   gloss ("Wonderful!") must fit the ~300px band on a 360px phone without a
   break inside a word, and a two-word one ("Very good!") may wrap on the space.
   `.rf-sub`, the old second line, is gone with the card's second language. */
.beat-reward.hooray.rf-en {
  font-size:calc(clamp(38px, 11vw, 48px) * var(--milim-text-scale));
  overflow-wrap:normal;
}

/* ---- Continue: the poster's button -----------------------------------------

   Still `.advance` (and `.beat-dismiss`): this rule re-dresses it, it does not
   replace it, so the unified shape's behaviour -- the 44px floor, the press,
   the sheen, the `[hidden]` contract, shared/advance.js -- is inherited and not
   re-implemented. It loads after beat_card.css, so at equal specificity this
   wins on order -- `.rp-go` over `.advance`, and the `:active` and
   `:focus-visible` rules below over `.advance:active` / `.advance:focus-visible`
   (each pair the same weight).

   The skew leans the OTHER way from the trophy card's English button, because
   this card's word is Hebrew (the trophy card's own `.is-flipped` Hebrew face
   does the same: `skewX(10deg)`, shadow to the left). The label sits in a
   `<span>` that un-skews it.

   `z-index:1`: nothing paints over it now (the fixed check mark that needed
   this lift is gone), but the stage's skewed band can overhang by the hard
   shadow's reach and the button must stay above it. */
.rp-go {
  position:relative; z-index:1; width:min(100%, 340px); min-height:56px;
  margin:6px auto 0; padding:0 22px; cursor:pointer;
  color:#16181C; background:#F0EDE6;
  border:3px solid #16181C; border-radius:4px;
  box-shadow:-6px 6px 0 #2FD37A, -6px 6px 0 3px #16181C;
  transform:skewX(10deg);
  font:900 28px/1 var(--sans-he); letter-spacing:0; text-transform:none;
}
.rp-go span { display:inline-block; transform:skewX(-10deg); }
.rp-go:active {
  translate:none; transform:skewX(10deg) translate(-4px, 4px);
  box-shadow:-2px 2px 0 #2FD37A, -2px 2px 0 3px #16181C;
}
.rp-go:focus-visible { outline:3px solid var(--ink); outline-offset:4px; }
/* The release sheen (`.advance::after`) is toned from `--line`, which is the
   page's neutral and reads as a grey smear on this paper-white face. Tinted
   with the poster's own green instead, theme-proof like the rest of the card. */
.rp-go::after {
  background:linear-gradient(105deg, transparent 35%,
             color-mix(in srgb, #2FD37A 55%, transparent) 50%, transparent 65%);
}

/* ---- Reduced motion: the still ----------------------------------------------

   study.css's blanket `* { animation-duration:.01ms !important }` would leave
   each one-shot parked at its end frame (which is right for the scene and the
   ribbon) but would flash the sparkles for one frame. So this states the still
   instead of relying on it: no animation on anything, the sparkles left out
   (they exist only in motion, ending at nothing), and the scene and ribbon at
   rest where their entrances end -- the final state appears at once. */
@media (prefers-reduced-motion: reduce) {
  .rf-scene, .rf-ribbon, .rf-spark { animation:none; }
  .rf-spark { display:none; }
  .rp-go, .rp-go:active { transition:none; }
}
