/* ==========================================================================
   Eleições — motion

   Counted in FRAMES, not milliseconds, the same way the L-Bar counts it: one
   value takes the package from 25p to 50p. `--motion-frame` lives in tokens.css.

   Everything here is a CSS animation on purpose. This package is rendered
   non-real-time — the playout engine asks for the frame at a timestamp and
   `motion.js` answers by pausing every animation under the root and setting
   `currentTime`. A JS-driven tween is invisible to that; a CSS one is seekable
   for free, and the scrubber in diff.html drives it with the engine's own call.

   Nothing animates without `data-motion="on"` on the stage. The settled state
   is the geometry, untouched.

   Two independent transform properties are used rather than `transform`:
   `translate` and `scale`. `.el-t` already spends `transform` on the cap-height
   trim, and a keyframe writing `transform` would silently throw that away and
   move every line of type by a fraction of an em.
   ========================================================================== */

:root {
  --el-frame: var(--motion-frame, 40ms);

  /* The gestures, in frames, and when each starts. They overlap: a graphic
     that plays its parts one after another reads as a list, not as one move. */
  /* Everything starts at once and finishes at its own pace. Nothing here
     waits for anything else to end — that wait is what made an earlier cut
     read as two beats instead of one build.

       0-14   the two halves of the box close, each from its far end
       0-17   the colour arrives along its own side, at the diagonal's angle,
              landing on the line the box is already split by
       3-23   the type is uncovered by that same edge, left to right, each
              line also rising behind its own mask
       6-20   the tick opens from its own centre
      12-30   the names come out laterally from behind the box

     The type overlaps the wipe rather than following it because it lives
     INSIDE the travelling clip (see projected-winner.js). Timed after the
     wipe it could not: white on the party colour, its far corner is the last
     point the edge covers — the title clears at 97.7% of the travel, the
     subhead at 99.1%, both measured off the render. Every deceleration curve
     spends its closing frames in exactly that corner, so ordering the type
     after the colour forces the hitch no matter how the curve is shaped. */
  --pw-box-delay:  0ms;
  --pw-box-dur:    calc(var(--el-frame) * 14);
  --pw-wipe-delay: 0ms;
  --pw-wipe-dur:   calc(var(--el-frame) * 17);
  --pw-tick-delay: calc(var(--el-frame) * 6);
  --pw-tick-dur:   calc(var(--el-frame) * 14);
  --pw-head-delay: calc(var(--el-frame) * 3);
  --pw-head-dur:   calc(var(--el-frame) * 15);
  --pw-name-delay: calc(var(--el-frame) * 12);
  --pw-name-dur:   calc(var(--el-frame) * 15);

  /* Broadcast easing: leaves fast, arrives slow, never overshoots. A wipe that
     bounces reads as a mistake at this size. */
  --ease-out: cubic-bezier(0.16, 0.84, 0.30, 1);
}

/* ==========================================================================
   The ground

   The card the whole frame stands on. It leads everything by a few frames and,
   being the bottom layer, it is also the last thing to leave when the entrance
   is played backwards.

   A FADE rather than a wipe, deliberately. Every other gesture here is an edge
   travelling, and giving the ground one too would put a second moving edge in
   the opening frames competing with the colour's — which is the one the eye is
   meant to follow. The card appears; then the graphic is built on it.
   ========================================================================== */

@keyframes el-ground { from { opacity: 0; } to { opacity: 1; } }

.el-stage[data-motion="on"]::before {
  animation: el-ground calc(var(--el-frame) * 7) var(--ease-out) both;
}

/* ==========================================================================
   The colour wipe

   The party field arrives from its own side, travelling PERPENDICULAR to its
   own diagonal edge — so the edge stays parallel to itself the whole way and
   lands exactly where the geometry puts it, through the centre of the tick.

   That last part is not a coincidence to be arranged: on Projected Winner the
   check box centre (874, 789) already sits on the frame diagonal, because the
   box hangs off it by two corners. The wipe finishing "at the centre of the V"
   is the same statement as the wipe finishing in place.

   1663 is the diagonal's x-intercept, so shifting the field by half of it on
   each axis carries the edge out through the top-left corner.
   ========================================================================== */

@keyframes el-wipe-from-left {
  from { translate: -832px -832px; }
  to   { translate: 0 0; }
}

@keyframes el-wipe-from-right {
  from { translate: 832px 832px; }
  to   { translate: 0 0; }
}

/* The exact inverse, for content that must stay still inside a travelling
   clip. Same duration and same easing, so the two cancel at every sample and
   not merely at the ends. */
@keyframes el-wipe-hold-left  { from { translate:  832px  832px; } to { translate: 0 0; } }
@keyframes el-wipe-hold-right { from { translate: -832px -832px; } to { translate: 0 0; } }

/* ==========================================================================
   The check box

   Each half arrives horizontally from ITS far end and stops on the diagonal.
   The clip does not move — see the note in core.js — so what reads is two
   panels closing on the split, not a rectangle sliding past a window.

   THE FILL IS WIPED, NOT SLID, and that is not interchangeable here. These
   fills are not flat: they carry the box's inset shadows, and the Two
   Candidate plate carries its centre step as well. All of that shading is
   registered to the fill's OWN edges, so translating the fill drags a second,
   fully shaded box frame across the middle of the first — which is exactly
   what it looks like.

   Revealing with `inset()` instead leaves every pixel of shading where the
   settled frame puts it and moves only the edge that uncovers it. Same
   gesture, one box. It is the same rule the big diagonal wipe follows for the
   same reason: move the edge, not the paint.
   ========================================================================== */

@keyframes el-fill-from-left {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes el-fill-from-right {
  from { clip-path: inset(0 0 0 100%); }
  to   { clip-path: inset(0 0 0 0); }
}

/* The tick grows from its own centre — which is not the box centre, because
   the mark sits low and left inside its box. */
@keyframes el-tick-pop {
  from { scale: 0; }
  to   { scale: 1; }
}

/* ==========================================================================
   Type

   A wipe plus a short slide in the same direction. The slide is in `em` so it
   scales with the line: a fixed pixel offset reads as a lurch on a 159px cap
   and as nothing at all on a 23px one.

   The insets go slightly negative at rest so the mask never shaves a glyph's
   overhang once it has finished.
   ========================================================================== */

@keyframes el-type-up {
  from { clip-path: inset(-30% -10% 100% -10%); translate: 0 0.28em; }
  to   { clip-path: inset(-30% -10% -30% -10%); translate: 0 0; }
}

@keyframes el-type-in {
  from { clip-path: inset(-30% 100% -30% -10%); translate: -0.22em 0; }
  to   { clip-path: inset(-30% -10% -30% -10%); translate: 0 0; }
}

/* ==========================================================================
   Projected Winner
   ========================================================================== */

.pw[data-motion="on"] .pw__wipe {
  animation: el-wipe-from-left var(--pw-wipe-dur) var(--ease-out) var(--pw-wipe-delay) both;
}
.pw[data-motion="on"] .pw__photo,
.pw[data-motion="on"] .pw__head {
  animation: el-wipe-hold-left var(--pw-wipe-dur) var(--ease-out) var(--pw-wipe-delay) both;
}

.pw[data-motion="on"] .el-check__fill--back {
  animation: el-fill-from-left var(--pw-box-dur) var(--ease-out) var(--pw-box-delay) both;
}
.pw[data-motion="on"] .el-check__fill--front {
  animation: el-fill-from-right var(--pw-box-dur) var(--ease-out) var(--pw-box-delay) both;
}

.pw[data-motion="on"] .el-check__tick {
  transform-origin: 48% 50%;
  animation: el-tick-pop var(--pw-tick-dur) var(--ease-out) var(--pw-tick-delay) both;
}

/* The header sits ABOVE the box, so it rises. */
.pw[data-motion="on"] .pw__kicker,
.pw[data-motion="on"] .pw__title,
.pw[data-motion="on"] .pw__region,
.pw[data-motion="on"] .pw__subhead {
  animation: el-type-up var(--pw-head-dur) var(--ease-out) both;
}
.pw[data-motion="on"] .pw__kicker  { animation-delay: var(--pw-head-delay); }
.pw[data-motion="on"] .pw__title   { animation-delay: calc(var(--pw-head-delay) + var(--el-frame) * 2); }
.pw[data-motion="on"] .pw__region  { animation-delay: calc(var(--pw-head-delay) + var(--el-frame) * 3); }
.pw[data-motion="on"] .pw__subhead { animation-delay: calc(var(--pw-head-delay) + var(--el-frame) * 5); }

/* The rule is a bar, not type: it draws out from the edge it is anchored to. */
.pw[data-motion="on"] .pw__rule {
  transform-origin: left center;
  animation: el-rule-draw var(--pw-head-dur) var(--ease-out)
             calc(var(--pw-head-delay) + var(--el-frame) * 3) both;
}

@keyframes el-rule-draw {
  from { scale: 0 1; }
  to   { scale: 1 1; }
}

/* The names sit BESIDE the box, level with it, so they come in laterally —
   out from behind the tick rather than up from under it. */
.pw[data-motion="on"] .pw__given,
.pw[data-motion="on"] .pw__surname {
  animation: el-type-in var(--pw-name-dur) var(--ease-out) both;
}
.pw[data-motion="on"] .pw__given   { animation-delay: var(--pw-name-delay); }
.pw[data-motion="on"] .pw__surname { animation-delay: calc(var(--pw-name-delay) + var(--el-frame) * 3); }

.pw[data-motion="on"] .el-chip {
  animation: el-type-in var(--pw-head-dur) var(--ease-out) var(--pw-head-delay) both;
}

/* ==========================================================================
   Two Candidate

   Same grammar, one frame wider in scope: this composition has TWO halves that
   want to arrive, not one. The diagonal splits the whole picture — header and
   right-hand candidate above it, left-hand candidate and footnote below — so
   each half travels perpendicular to that line, from its own outer corner,
   and the two meet on it. Which is the centre of the tick, because the plate
   hangs off the diagonal by two corners.

   Only one of the halves is coloured, and the other still needs an entrance;
   giving both the same gesture is what keeps the frame from arriving in two
   unrelated pieces.

   760px on each axis clears both directions: the frame's extreme values of
   x - y are +1920 and -1080 against a diagonal at 406, so 2 x 760 covers the
   longer of the two with room to spare.
   ========================================================================== */

@keyframes tc-wipe-l { from { translate: -760px  760px; } to { translate: 0 0; } }
@keyframes tc-wipe-r { from { translate:  760px -760px; } to { translate: 0 0; } }
@keyframes tc-hold-l { from { translate:  760px -760px; } to { translate: 0 0; } }
@keyframes tc-hold-r { from { translate: -760px  760px; } to { translate: 0 0; } }

/* Type revealed from its RIGHT edge, for the block that sits left of the
   plate: both candidates come out from behind the plate, away from centre. */
@keyframes el-type-in-rtl {
  from { clip-path: inset(-30% -10% -30% 100%); translate: 0.22em 0; }
  to   { clip-path: inset(-30% -10% -30% -10%); translate: 0 0; }
}

:root {
  --tc-plate-dur:  calc(var(--el-frame) * 14);
  --tc-wipe-dur:   calc(var(--el-frame) * 17);
  --tc-tick-delay: calc(var(--el-frame) * 6);
  --tc-tick-dur:   calc(var(--el-frame) * 14);
  --tc-head-delay: calc(var(--el-frame) * 3);
  --tc-head-dur:   calc(var(--el-frame) * 15);
  --tc-name-delay: calc(var(--el-frame) * 6);
  --tc-name-dur:   calc(var(--el-frame) * 15);
  --tc-foot-delay: calc(var(--el-frame) * 12);
}

.tc[data-motion="on"] .tc__side--l { animation: tc-wipe-l var(--tc-wipe-dur) var(--ease-out) both; }
.tc[data-motion="on"] .tc__side--r { animation: tc-wipe-r var(--tc-wipe-dur) var(--ease-out) both; }
.tc[data-motion="on"] .tc__hold--l { animation: tc-hold-l var(--tc-wipe-dur) var(--ease-out) both; }
.tc[data-motion="on"] .tc__hold--r { animation: tc-hold-r var(--tc-wipe-dur) var(--ease-out) both; }

/* The plate's back half is the one ABOVE the diagonal, so its far end is the
   top-right corner; the front half's is the bottom-left. */
.tc[data-motion="on"] .tc__plate-fill--back {
  animation: el-fill-from-right var(--tc-plate-dur) var(--ease-out) both;
}
.tc[data-motion="on"] .tc__plate-fill--front {
  animation: el-fill-from-left var(--tc-plate-dur) var(--ease-out) both;
}

.tc[data-motion="on"] .tc__tick {
  transform-origin: 52% 50%;
  animation: el-tick-pop var(--tc-tick-dur) var(--ease-out) var(--tc-tick-delay) both;
}

/* The header stands above the plate, so it rises. */
.tc[data-motion="on"] .tc__kicker,
.tc[data-motion="on"] .tc__region,
.tc[data-motion="on"] .tc__subhead {
  animation: el-type-up var(--tc-head-dur) var(--ease-out) both;
}
.tc[data-motion="on"] .tc__kicker  { animation-delay: var(--tc-head-delay); }
.tc[data-motion="on"] .tc__region  { animation-delay: calc(var(--tc-head-delay) + var(--el-frame) * 3); }
.tc[data-motion="on"] .tc__subhead { animation-delay: calc(var(--tc-head-delay) + var(--el-frame) * 5); }

.tc[data-motion="on"] .tc__rule {
  transform-origin: center;
  animation: el-rule-draw var(--tc-head-dur) var(--ease-out)
             calc(var(--tc-head-delay) + var(--el-frame) * 2) both;
}

/* Each candidate comes out from behind the plate, away from the centre. */
.tc[data-motion="on"] .tc__pct-l,
.tc[data-motion="on"] .tc__votes-l,
.tc[data-motion="on"] .tc__name-l {
  animation: el-type-in-rtl var(--tc-name-dur) var(--ease-out) both;
}

/* The footnote belongs to neither candidate — it is centred under the plate —
   so it reads left to right like the sentence it is. */
.tc[data-motion="on"] .tc__foot {
  animation: el-type-in var(--tc-name-dur) var(--ease-out) both;
}
.tc[data-motion="on"] .tc__pct-r,
.tc[data-motion="on"] .tc__votes-r,
.tc[data-motion="on"] .tc__name-r {
  animation: el-type-in var(--tc-name-dur) var(--ease-out) both;
}

.tc[data-motion="on"] .tc__pct-l,
.tc[data-motion="on"] .tc__pct-r   { animation-delay: var(--tc-name-delay); }
.tc[data-motion="on"] .tc__votes-l,
.tc[data-motion="on"] .tc__votes-r { animation-delay: calc(var(--tc-name-delay) + var(--el-frame) * 3); }
.tc[data-motion="on"] .tc__name-l,
.tc[data-motion="on"] .tc__name-r  { animation-delay: calc(var(--tc-name-delay) + var(--el-frame) * 5); }
.tc[data-motion="on"] .tc__foot    { animation-delay: var(--tc-foot-delay); }

.tc[data-motion="on"] .el-chip {
  animation: el-type-in var(--tc-head-dur) var(--ease-out) var(--tc-head-delay) both;
}

/* ==========================================================================
   Senate Flips

   The most parts of any frame here, so the rule is the same one that fixed
   Projected Winner: nothing waits for anything else to finish.

      0-18   the two coloured sides arrive from their own side
      3-22   the Capitol resolves — out of blur, up into place
      5-21   the title is uncovered from below
      9-22   the rule grows out of the centre, both ways at once
     11-..   the pills rise into place, two frames apart down each column
     13-30   the net rolls off zero
     17-35   the seat totals roll off zero

   THE SIDES ARE WIPED AND THE PILLS ARE SLID, and the difference is not
   taste. Both fields and both bands are gradients under a clip, so moving the
   painted element drags the colour ramp with it — a second, differently-lit
   copy of the shape passing through the first. A pill has no clip: the whole
   object moves, shadow included, which is what an object arriving looks like.
   ========================================================================== */

@keyframes sf-wipe-l { from { translate: -860px 0; } to { translate: 0 0; } }
@keyframes sf-wipe-r { from { translate:  860px 0; } to { translate: 0 0; } }
@keyframes sf-hold-l { from { translate:  860px 0; } to { translate: 0 0; } }
@keyframes sf-hold-r { from { translate: -860px 0; } to { translate: 0 0; } }

/* Out of focus, transparent and a little low. Three cues for one object, and
   at this size any one of them alone reads as a mistake rather than a move. */
@keyframes sf-capitol {
  from { filter: blur(26px); opacity: 0; translate: 0 34px; }
  to   { filter: blur(0);    opacity: 1; translate: 0 0; }
}

/* Uncovered from below: the top inset falls away, so the type grows upward
   out of its own baseline. */
@keyframes sf-title {
  from { clip-path: inset(100% -10% -30% -10%); }
  to   { clip-path: inset(-30% -10% -30% -10%); }
}

@keyframes sf-rule { from { scale: 0 1; } to { scale: 1 1; } }

@keyframes sf-pill {
  from { translate: 0 46px; opacity: 0; }
  to   { translate: 0 0;    opacity: 1; }
}

/* The strip travels in whole ems because its lines are exactly 1em; `roll()`
   in core.js writes the two ends, and their order is what makes a loss fall
   and a gain climb. */
@keyframes el-roll {
  from { translate: 0 var(--roll-from, 0em); }
  to   { translate: 0 var(--roll-to, 0em); }
}

:root {
  --sf-side-dur:    calc(var(--el-frame) * 18);
  --sf-cap-delay:   calc(var(--el-frame) * 3);
  --sf-cap-dur:     calc(var(--el-frame) * 19);
  --sf-title-delay: calc(var(--el-frame) * 5);
  --sf-title-dur:   calc(var(--el-frame) * 16);
  --sf-rule-delay:  calc(var(--el-frame) * 9);
  --sf-rule-dur:    calc(var(--el-frame) * 13);
  --sf-pill-delay:  calc(var(--el-frame) * 11);
  --sf-pill-dur:    calc(var(--el-frame) * 13);
  --sf-net-delay:   calc(var(--el-frame) * 13);
  --sf-net-dur:     calc(var(--el-frame) * 17);
  --sf-tally-delay: calc(var(--el-frame) * 17);
  --sf-tally-dur:   calc(var(--el-frame) * 18);
}

.sf[data-motion="on"] .sf__wipe--l { animation: sf-wipe-l var(--sf-side-dur) var(--ease-out) both; }
.sf[data-motion="on"] .sf__wipe--r { animation: sf-wipe-r var(--sf-side-dur) var(--ease-out) both; }
.sf[data-motion="on"] .sf__wipe--l > * { animation: sf-hold-l var(--sf-side-dur) var(--ease-out) both; }
.sf[data-motion="on"] .sf__wipe--r > * { animation: sf-hold-r var(--sf-side-dur) var(--ease-out) both; }

.sf[data-motion="on"] .sf__photo {
  animation: sf-capitol var(--sf-cap-dur) var(--ease-out) var(--sf-cap-delay) both;
}

.sf[data-motion="on"] .sf__title {
  animation: sf-title var(--sf-title-dur) var(--ease-out) var(--sf-title-delay) both;
}

/* Both halves of the rule are already separate elements meeting at x=960, so
   growing them out of the centre is one transform-origin each. */
.sf[data-motion="on"] .sf__rule-l,
.sf[data-motion="on"] .sf__rule-r {
  animation: sf-rule var(--sf-rule-dur) var(--ease-out) var(--sf-rule-delay) both;
}
.sf[data-motion="on"] .sf__rule-l { transform-origin: right center; }
.sf[data-motion="on"] .sf__rule-r { transform-origin: left center; }

/* `--i` is the pill's position in its column, set in senate-flips.js. */
.sf[data-motion="on"] .sf__pill {
  animation: sf-pill var(--sf-pill-dur) var(--ease-out) both;
  animation-delay: calc(var(--sf-pill-delay) + var(--el-frame) * var(--i, 0) * 2);
}

/* The type settles into the pill just after the pill lands, so the block
   arrives first and the words fill it. */
.sf[data-motion="on"] .sf__pill-code,
.sf[data-motion="on"] .sf__pill-state {
  animation: el-type-in calc(var(--el-frame) * 10) var(--ease-out) both;
  animation-delay: calc(var(--sf-pill-delay) + var(--el-frame) * (var(--i, 0) * 2 + 3));
}
.sf[data-motion="on"] .sf__pill-state {
  animation-delay: calc(var(--sf-pill-delay) + var(--el-frame) * (var(--i, 0) * 2 + 5));
}

/* The number ARRIVES as it starts rolling. Parked at zero from frame nought it
   sits there for half a second looking like a value, and then moves — which
   reads as a graphic that was wrong and corrected itself. */
.sf[data-motion="on"] .sf__net-l,
.sf[data-motion="on"] .sf__net-r {
  animation: el-type-up calc(var(--el-frame) * 10) var(--ease-out) var(--sf-net-delay) both;
}

.sf[data-motion="on"] .sf__net-l .el-roll__strip,
.sf[data-motion="on"] .sf__net-r .el-roll__strip {
  animation: el-roll var(--sf-net-dur) var(--ease-out) var(--sf-net-delay) both;
}

.sf[data-motion="on"] .sf__tally-num {
  animation: el-type-in calc(var(--el-frame) * 10) var(--ease-out) var(--sf-tally-delay) both;
}

.sf[data-motion="on"] .sf__tally-num .el-roll__strip {
  animation: el-roll var(--sf-tally-dur) var(--ease-out) var(--sf-tally-delay) both;
}

.sf[data-motion="on"] .sf__tally-party {
  animation: el-type-in calc(var(--el-frame) * 12) var(--ease-out)
             calc(var(--sf-tally-delay) + var(--el-frame) * 4) both;
}

.sf[data-motion="on"] .el-chip {
  animation: el-type-in var(--sf-title-dur) var(--ease-out) var(--sf-title-delay) both;
}

/* ==========================================================================
   Balance of Power

   The same seven beats as Senate Flips, against a different hero: where that
   frame resolves a photograph, this one raises a chamber. The hemicycle keeps
   its own entrance — the seats rising from the middle of the arc outward over
   45 frames — because it is a real 3D object with a real clock, and it is the
   longest thing on the frame for the same reason the Capitol is the loudest.

      0-18   the two coloured sides arrive from their own side
      0-45   the chamber builds (hemicycle.js drives this one)
      5-21   the title is uncovered from below
      9-22   the rule grows out of its own centre
     11-24   the subhead, then the seats-up line
     13-26   the independent block rises, its number rolling after it
     15-33   each party's net, total and name
     20-34   the control band opens from the centre
   ========================================================================== */

/* 860, not 760: the FOOTER BANDS are the widest thing each side carries —
   they reach x=821 on the left and back to 1099 on the right — and at 760
   a sliver of each stayed in the corner of an armed graphic. */
@keyframes bp-wipe-l { from { translate: -880px 0; } to { translate: 0 0; } }
@keyframes bp-wipe-r { from { translate:  880px 0; } to { translate: 0 0; } }
@keyframes bp-hold-l { from { translate:  880px 0; } to { translate: 0 0; } }
@keyframes bp-hold-r { from { translate: -880px 0; } to { translate: 0 0; } }

/* The control band belongs to neither half — it is the one shape that
   straddles the divider — so it opens from its own centre. The inset is on the
   PAINT, inside the polygon clip, so the gradient never moves. */
@keyframes el-fill-from-centre {
  from { clip-path: inset(0 50% 0 50%); }
  to   { clip-path: inset(0 0 0 0); }
}

@keyframes bp-rise {
  from { translate: 0 40px; opacity: 0; }
  to   { translate: 0 0;    opacity: 1; }
}

:root {
  --bp-side-dur:    calc(var(--el-frame) * 18);
  --bp-title-delay: calc(var(--el-frame) * 5);
  --bp-title-dur:   calc(var(--el-frame) * 16);
  --bp-rule-delay:  calc(var(--el-frame) * 9);
  --bp-rule-dur:    calc(var(--el-frame) * 13);
  --bp-head-delay:  calc(var(--el-frame) * 11);
  --bp-head-dur:    calc(var(--el-frame) * 13);
  --bp-ind-delay:   calc(var(--el-frame) * 13);
  --bp-ind-dur:     calc(var(--el-frame) * 13);
  --bp-side-delay:  calc(var(--el-frame) * 15);
  --bp-num-delay:   calc(var(--el-frame) * 17);
  --bp-num-dur:     calc(var(--el-frame) * 16);
  --bp-ctrl-delay:  calc(var(--el-frame) * 20);
  --bp-ctrl-dur:    calc(var(--el-frame) * 14);
}

.bp[data-motion="on"] .bp__wipe--l { animation: bp-wipe-l var(--bp-side-dur) var(--ease-out) both; }
.bp[data-motion="on"] .bp__wipe--r { animation: bp-wipe-r var(--bp-side-dur) var(--ease-out) both; }
.bp[data-motion="on"] .bp__wipe--l > * { animation: bp-hold-l var(--bp-side-dur) var(--ease-out) both; }
.bp[data-motion="on"] .bp__wipe--r > * { animation: bp-hold-r var(--bp-side-dur) var(--ease-out) both; }

.bp[data-motion="on"] .bp__title {
  animation: sf-title var(--bp-title-dur) var(--ease-out) var(--bp-title-delay) both;
}

/* One element rather than two halves here, so a centre origin grows it both
   ways by itself. */
.bp[data-motion="on"] .bp__rule {
  transform-origin: center;
  animation: sf-rule var(--bp-rule-dur) var(--ease-out) var(--bp-rule-delay) both;
}

/* The divider drops out of the chamber it hangs from. */
.bp[data-motion="on"] .bp__divider {
  transform-origin: top center;
  animation: bp-divider var(--bp-rule-dur) var(--ease-out) var(--bp-rule-delay) both;
}
@keyframes bp-divider { from { scale: 1 0; } to { scale: 1 1; } }

.bp[data-motion="on"] .bp__subhead,
.bp[data-motion="on"] .bp__seats-cap {
  animation: el-type-up var(--bp-head-dur) var(--ease-out) var(--bp-head-delay) both;
}
.bp[data-motion="on"] .bp__seats-cap {
  animation-delay: calc(var(--bp-head-delay) + var(--el-frame) * 3);
}
.bp[data-motion="on"] .bp__seats {
  animation: el-type-up var(--bp-head-dur) var(--ease-out)
             calc(var(--bp-head-delay) + var(--el-frame) * 2) both;
}
.bp[data-motion="on"] .bp__seats .el-roll__strip {
  animation: el-roll var(--bp-num-dur) var(--ease-out)
             calc(var(--bp-head-delay) + var(--el-frame) * 2) both;
}

.bp[data-motion="on"] .bp__ind {
  animation: bp-rise var(--bp-ind-dur) var(--ease-out) var(--bp-ind-delay) both;
}
.bp[data-motion="on"] .bp__ind-num,
.bp[data-motion="on"] .bp__ind-cap {
  animation: el-type-up var(--bp-ind-dur) var(--ease-out)
             calc(var(--bp-ind-delay) + var(--el-frame) * 3) both;
}
.bp[data-motion="on"] .bp__ind-cap {
  animation-delay: calc(var(--bp-ind-delay) + var(--el-frame) * 5);
}
.bp[data-motion="on"] .bp__ind-num .el-roll__strip {
  animation: el-roll var(--bp-num-dur) var(--ease-out)
             calc(var(--bp-ind-delay) + var(--el-frame) * 3) both;
}

/* Each party block comes out of its own side, away from the centre. */
.bp[data-motion="on"] .bp__net-l,
.bp[data-motion="on"] .bp__num-l,
.bp[data-motion="on"] .bp__party-l {
  animation: el-type-in var(--bp-num-dur) var(--ease-out) var(--bp-side-delay) both;
}
.bp[data-motion="on"] .bp__net-r,
.bp[data-motion="on"] .bp__num-r,
.bp[data-motion="on"] .bp__party-r {
  animation: el-type-in-rtl var(--bp-num-dur) var(--ease-out) var(--bp-side-delay) both;
}
.bp[data-motion="on"] .bp__num-l,
.bp[data-motion="on"] .bp__num-r  { animation-delay: var(--bp-num-delay); }
.bp[data-motion="on"] .bp__party-l,
.bp[data-motion="on"] .bp__party-r {
  animation-delay: calc(var(--bp-num-delay) + var(--el-frame) * 3);
}

.bp[data-motion="on"] .bp__num-l .el-roll__strip,
.bp[data-motion="on"] .bp__num-r .el-roll__strip {
  animation: el-roll var(--bp-num-dur) var(--ease-out) var(--bp-num-delay) both;
}

.bp[data-motion="on"] .bp__wipe--c > * {
  animation: el-fill-from-centre var(--bp-ctrl-dur) var(--ease-out) var(--bp-ctrl-delay) both;
}
.bp[data-motion="on"] .bp__control-num,
.bp[data-motion="on"] .bp__control-cap {
  animation: el-type-up var(--bp-ctrl-dur) var(--ease-out)
             calc(var(--bp-ctrl-delay) + var(--el-frame) * 3) both;
}
.bp[data-motion="on"] .bp__control-cap {
  animation-delay: calc(var(--bp-ctrl-delay) + var(--el-frame) * 5);
}

.bp[data-motion="on"] .el-chip {
  animation: el-type-in var(--bp-title-dur) var(--ease-out) var(--bp-title-delay) both;
}

/* ==========================================================================
   Race · Map

   PROVISIONAL, and deliberately restrained. This is the one frame the approved
   set of 02/09 did not bring, so there is no artwork to measure an entrance
   against. What is here uses only gestures the other four already established
   — the sides arrive from their sides, the header rises, each candidate comes
   out from behind the centre — and invents nothing. When the art lands, this
   is the file to argue with.

      0-18   the two coloured sides arrive from their own side
      4-20   the state name rises, then the rule and the subhead
     10-24   the reporting percentage
     12-28   each candidate's block, out from the centre
     18-32   the lead
   ========================================================================== */

/* The corner fields reach x=820 at the frame bottom, so 700 left a wedge. */
@keyframes rm-wipe-l { from { translate: -880px 0; } to { translate: 0 0; } }
@keyframes rm-wipe-r { from { translate:  880px 0; } to { translate: 0 0; } }
@keyframes rm-hold-l { from { translate:  880px 0; } to { translate: 0 0; } }
@keyframes rm-hold-r { from { translate: -880px 0; } to { translate: 0 0; } }

:root {
  --rm-side-dur:   calc(var(--el-frame) * 18);
  --rm-head-delay: calc(var(--el-frame) * 4);
  --rm-head-dur:   calc(var(--el-frame) * 16);
  --rm-rep-delay:  calc(var(--el-frame) * 10);
  --rm-cand-delay: calc(var(--el-frame) * 12);
  --rm-cand-dur:   calc(var(--el-frame) * 16);
  --rm-lead-delay: calc(var(--el-frame) * 18);
  --rm-lead-dur:   calc(var(--el-frame) * 14);
}

/* The mask, the centre band and the two bars are the frame's own ground rather
   than content — and, with no approved art to give them a gesture, they arrive
   with the ground instead of separately. Without this they were simply always
   there, which is the flash this whole section exists to remove. */
.rm[data-motion="on"] .rm__mask,
.rm[data-motion="on"] .rm__band,
.rm[data-motion="on"] .rm__chip,
.rm[data-motion="on"] .rm__bar,
.rm[data-motion="on"] .rm__map,
.rm[data-motion="on"] .el-emblem,
.rm[data-motion="on"] .el-check {
  animation: el-ground calc(var(--el-frame) * 7) var(--ease-out) both;
}

.rm[data-motion="on"] .rm__wipe--l { animation: rm-wipe-l var(--rm-side-dur) var(--ease-out) both; }
.rm[data-motion="on"] .rm__wipe--r { animation: rm-wipe-r var(--rm-side-dur) var(--ease-out) both; }
.rm[data-motion="on"] .rm__wipe--l > * { animation: rm-hold-l var(--rm-side-dur) var(--ease-out) both; }
.rm[data-motion="on"] .rm__wipe--r > * { animation: rm-hold-r var(--rm-side-dur) var(--ease-out) both; }

.rm[data-motion="on"] .rm__header,
.rm[data-motion="on"] .rm__subhead {
  animation: el-type-up var(--rm-head-dur) var(--ease-out) var(--rm-head-delay) both;
}
.rm[data-motion="on"] .rm__subhead {
  animation-delay: calc(var(--rm-head-delay) + var(--el-frame) * 3);
}
.rm[data-motion="on"] .rm__rule {
  transform-origin: center;
  animation: sf-rule var(--rm-head-dur) var(--ease-out)
             calc(var(--rm-head-delay) + var(--el-frame) * 2) both;
}

.rm[data-motion="on"] .rm__reporting,
.rm[data-motion="on"] .rm__reporting-sym,
.rm[data-motion="on"] .rm__reporting-cap {
  animation: el-type-up var(--rm-head-dur) var(--ease-out) var(--rm-rep-delay) both;
}

/* Each candidate comes out of the centre, away from it. */
.rm[data-motion="on"] .rm__pct-l,
.rm[data-motion="on"] .rm__votes-l,
.rm[data-motion="on"] .rm__inc-l,
.rm[data-motion="on"] .rm__name-l {
  animation: el-type-in var(--rm-cand-dur) var(--ease-out) var(--rm-cand-delay) both;
}
.rm[data-motion="on"] .rm__pct-r,
.rm[data-motion="on"] .rm__votes-r,
.rm[data-motion="on"] .rm__inc-r,
.rm[data-motion="on"] .rm__name-r {
  animation: el-type-in-rtl var(--rm-cand-dur) var(--ease-out) var(--rm-cand-delay) both;
}
.rm[data-motion="on"] .rm__votes-l,
.rm[data-motion="on"] .rm__votes-r { animation-delay: calc(var(--rm-cand-delay) + var(--el-frame) * 2); }
.rm[data-motion="on"] .rm__inc-l,
.rm[data-motion="on"] .rm__inc-r,
.rm[data-motion="on"] .rm__name-l,
.rm[data-motion="on"] .rm__name-r { animation-delay: calc(var(--rm-cand-delay) + var(--el-frame) * 4); }

.rm[data-motion="on"] .rm__lead,
.rm[data-motion="on"] .rm__lead-cap {
  animation: el-type-up var(--rm-lead-dur) var(--ease-out) var(--rm-lead-delay) both;
}
.rm[data-motion="on"] .rm__lead-cap {
  animation-delay: calc(var(--rm-lead-delay) + var(--el-frame) * 2);
}

.rm[data-motion="on"] .el-chip {
  animation: el-type-in var(--rm-head-dur) var(--ease-out) var(--rm-head-delay) both;
}
