/* ==========================================================================
   Eleições — the stage and the parts every template shares

   One rule governs this whole package: **the stage is exactly 1920 x 1080 CSS
   pixels and nothing inside it is ever expressed in a relative unit.** Every
   `left`, `top` and `font-size` is the number measured off the approved PNG.
   Fitting to a viewport is a `scale()` on the outside, so the diff harness can
   put a reference frame over a live render and expect them to cancel.
   ========================================================================== */

.el-frame {
  position: relative;
  overflow: hidden;
  /* Sized by JS to stage x scale, so the page flows normally around it. */
}

.el-stage {
  position: relative;
  width: 1920px;
  height: 1080px;
  overflow: hidden;
  transform-origin: 0 0;
  font-family: var(--el-font);
  font-weight: 400;
  color: var(--el-on-ground);
  /* A broadcast frame is composited, not read on a phone. Kill the platform
     smoothing choices that would otherwise differ between this preview and
     the renderer. */
  -webkit-font-smoothing: antialiased;
  text-rendering: geometricPrecision;
}

/* Every positioned child. Children are placed, never flowed. */
.el-stage * {
  box-sizing: border-box;
}

/* --- the ground ------------------------------------------------------------
   THE BACKGROUND IS A LAYER, NOT A PROPERTY OF THE STAGE, and that is the
   whole reason it exists as a pseudo-element.

   Painted on `.el-stage` itself it could not be animated, so a graphic that
   had loaded but not played put a full-frame grey rectangle on air and held it
   there until someone hit play. Everything else was correctly hidden; the card
   it all stands on was not.

   As a `::before` it is the first thing in paint order, so it sits under every
   field, photo and line without a z-index, and it can arrive and leave with
   the rest of the frame.
   -------------------------------------------------------------------------- */

.el-stage::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--c-neutral);
}

/* A STAGE INSIDE A STAGE HAS NO GROUND OF ITS OWN. `<el-hemicycle>` is an
   ElectionElement too, so it builds its own `.el-stage` — which meant Balance
   of Power carried a second full-frame background, unanimated because only the
   outer stage is ever given `data-motion`. It went unnoticed for as long as
   the two were the same colour and the outer one was always painted. */
.el-stage .el-stage::before { content: none; }

/* --- text ------------------------------------------------------------------
   `top` is the CAP TOP and `--cap` is the cap height, both in the numbers a
   pixel scan of the approved frame gives you. `font-size` and the vertical
   trim are derived from the family's metrics, so a font swap moves nothing.
   Do not add padding to a text node.
   -------------------------------------------------------------------------- */

.el-t {
  position: absolute;
  margin: 0;
  font-family: var(--el-font);
  font-weight: 400;
  font-size: calc(var(--cap, 40) / var(--el-cap) * 1px);
  line-height: var(--lh, 1);
  white-space: pre;
  transform: translateY(calc(var(--el-cap-shift) * -1 + var(--el-cap-nudge)));
}

.el-t--c { text-align: center; }
.el-t--r { text-align: right; }

/* --- the 2024 ELECTION DATA chip ------------------------------------------
   Identical in all seven approved frames: 294 x 46 at (59, 32).
   -------------------------------------------------------------------------- */

.el-chip {
  position: absolute;
  left: 59px;
  top: 32px;
  width: 294px;
  height: 46px;
  background: var(--c-data);
}

.el-chip__label {
  position: absolute;
  left: 13px;
  top: 14px;
  font-size: calc(19 / var(--el-cap) * 1px);
  font-weight: 700;
  letter-spacing: 0.15px;
  color: var(--c-black);
  line-height: 1;
  transform: translateY(calc(var(--el-cap-shift) * -1 + var(--el-cap-nudge)));
}

/* --- rules ---------------------------------------------------------------- */

.el-rule {
  position: absolute;
  width: 96px;
  height: 3px;
  background: currentColor;
}

/* --- the check box --------------------------------------------------------
   Two flat triangles split by a frame diagonal, plus the tick. The sunk half
   is always the one the diagonal leaves ABOVE, and it reads as if it passes
   under the colour band.

   `data-split="anti"`  the diagonal runs bottom-left to top-right (Projected
                        Winner, whose frame diagonal does the same)
   `data-split="main"`  top-left to bottom-right (Two Candidate, Race - Map)

   The inset shadow deliberately follows the box, not the triangle: in the
   artwork the darkening sits along the top and left EDGES and the diagonal
   itself is clean.
   -------------------------------------------------------------------------- */

.el-check {
  position: absolute;
  width: var(--side);
  height: var(--side);
  overflow: hidden;
}

.el-check__half {
  position: absolute;
  inset: 0;
}

.el-check[data-split="anti"] .el-check__half--back  { clip-path: polygon(0 0, 100% 0, 0 100%); }
.el-check[data-split="anti"] .el-check__half--front { clip-path: polygon(100% 0, 100% 100%, 0 100%); }
.el-check[data-split="main"] .el-check__half--back  { clip-path: polygon(0 0, 100% 0, 100% 100%); }
.el-check[data-split="main"] .el-check__half--front { clip-path: polygon(0 0, 100% 100%, 0 100%); }

/* The paint sits on the inner fill so it can slide inside the clip. */
.el-check__fill { position: absolute; inset: 0; }

.el-check__fill--back {
  background: var(--back, var(--el-party-shade));
  box-shadow: inset
    calc(var(--side) * 0.019) calc(var(--side) * 0.016)
    calc(var(--side) * 0.171) rgba(0, 0, 0, 0.45);
}

.el-check__fill--front {
  background: var(--front, var(--el-party));
  box-shadow: inset 0 0 calc(var(--side) * 0.056) rgba(0, 0, 0, 0.15);
}

/* The tick.
   The shadow lives on a WRAPPER and the clip on the child, because a filter
   and a clip-path on the same element apply in that order: the drop-shadow is
   generated and then clipped straight back off. That one detail is the whole
   reason the first cut of these frames had no shadow under any tick.

   `--tick-*` place the tick inside its box as a fraction, so a template that
   scaled it differently (Two Candidate did) only overrides four numbers. */
.el-check__tick-wrap {
  position: absolute;
  inset: 0;
  filter: drop-shadow(
    calc(var(--side) * 0.05) calc(var(--side) * 0.05)
    calc(var(--side) * 0.12) rgba(0, 0, 0, 0.5)
  );
}

.el-check__tick {
  position: absolute;
  left: var(--tick-x, 8.07%);
  top: var(--tick-y, 18.94%);
  width: var(--tick-w, 79.51%);
  height: var(--tick-h, 63.05%);
  background: var(--tick, var(--c-white));
  clip-path: var(--tick-path);
}

/* The six points of the tick, normalised to its own box. */
:root {
  --tick-path: polygon(
    0% 56.65%, 13.67% 39.40%, 34.37% 65.51%,
    86.33% 0%, 100% 17.24%, 34.37% 100%
  );
}

/* --- photo slot -----------------------------------------------------------
   The approved frames put a duotone portrait under the party field, fading
   into it. The fade is a gradient in the slot itself, so dropping the real
   cutout in means setting one background-image and nothing else.
   -------------------------------------------------------------------------- */

.el-photo {
  position: absolute;
  background-color: var(--c-muted);
  background-image: var(--photo, none);
  background-size: cover;
  background-position: center;
}

.el-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to var(--fade, right),
    rgba(var(--el-party-rgb), 0) 0%,
    rgba(var(--el-party-rgb), 0.22) 45%,
    rgba(var(--el-party-rgb), 1) 93%,
    rgba(var(--el-party-rgb), 1) 100%
  );
}

/* The neutral side of a two-candidate frame fades into the ground, not into a
   party colour. */
.el-photo[data-fade-to="ground"]::after {
  background: linear-gradient(
    to var(--fade, right),
    rgba(210, 210, 210, 0) 0%,
    rgba(210, 210, 210, 0.22) 45%,
    rgba(210, 210, 210, 1) 93%,
    rgba(210, 210, 210, 1) 100%
  );
}

/* --- the emblem -----------------------------------------------------------
   Star inside a concentric ring, both in the party wash. It only ever appears
   inside a coloured triangle, so the triangle clips it.
   -------------------------------------------------------------------------- */

.el-emblem {
  position: absolute;
  left: calc(var(--cx) - var(--r));
  top: calc(var(--cy) - var(--r));
  width: calc(var(--r) * 2);
  height: calc(var(--r) * 2);
  pointer-events: none;
}

.el-emblem__ring {
  position: absolute;
  inset: 0;
  border: var(--ring-w) solid var(--el-party-wash);
  border-radius: 50%;
}

.el-emblem__star {
  position: absolute;
  left: 50%;
  top: 50%;
  width: calc(var(--r) * 1.232);
  height: calc(var(--r) * 1.172);
  transform: translate(-50%, -50%);
  background: var(--el-party-wash);
  clip-path: polygon(
    50% 0%, 61.8% 34.5%, 98.2% 34.5%, 68.8% 55.9%,
    80.1% 90.5%, 50% 69.1%, 19.9% 90.5%, 31.2% 55.9%,
    1.8% 34.5%, 38.2% 34.5%
  );
}

/* --- fields ---------------------------------------------------------------
   A "field" is a flat party-coloured region cut by a 45-degree frame diagonal.
   Templates give it a clip-path; this only carries the paint.
   -------------------------------------------------------------------------- */

.el-field {
  position: absolute;
  inset: 0;
}

/* --- slots we have not built yet ------------------------------------------ */

.el-slot {
  position: absolute;
  display: grid;
  place-items: center;
  font-family: var(--el-font);
  font-size: 28px;
  letter-spacing: 1px;
  color: rgba(0, 0, 0, 0.45);
  background: rgba(176, 176, 176, 0.30);
  outline: 2px dashed rgba(0, 0, 0, 0.18);
  outline-offset: -8px;
}

/* OFF MEANS GONE, not merely unlabelled. A slot is a preview affordance — it
   says what artwork is still missing — and a renderer must never see one: an
   unplayed graphic was putting a grey placeholder rectangle on air under the
   3D chamber, which is the one place it could not be mistaken for design.
   The OGraf adapter turns them off on load; the gallery leaves them on. */
.el-stage[data-slots="off"] .el-slot {
  outline: none;
  color: transparent;
  background: transparent;
}

/* --- the rolling number ----------------------------------------------------
   See `roll()` in core.js for why it is built this way. The two rules that
   matter here: the strip's lines are exactly 1em so the animation can travel
   in whole ems, and the window is clipped with `clip-path` rather than
   `overflow`, because `overflow: hidden` on an inline-block moves its baseline
   to the bottom margin edge and drags the rest of the line with it.
   -------------------------------------------------------------------------- */

.el-roll {
  position: relative;
  display: inline-block;
  /* TRIMMED TO THE NUMBER, NOT TO THE LINE BOX. A line box is 1em and the
     digits inside it only 0.686, so clipping at the box shows a third of an em
     of the neighbouring values above and below — the roll spills past the
     number it belongs to. These insets leave 0.08em to 0.83em, which clears
     the glyphs (0.10 to 0.81 including the overshoot on round digits) with
     about 0.02em to spare at each end and hides the rest of the strip. */
  clip-path: inset(0.08em 0 0.17em 0);
}

/* In flow, invisible: it sets the width and the baseline. */
.el-roll__ghost { visibility: hidden; font-style: inherit; }

.el-roll__strip {
  position: absolute;
  top: 0;
  right: 0;
  line-height: 1;
  text-align: right;
  /* AT REST THE STRIP IS ALREADY AT ITS DESTINATION. The settled graphic has
     to read the final number whether or not anything is animating — with the
     strip parked at its start instead, motion off showed a frame full of
     zeroes. The animation overrides this while it runs and its `both` fill
     hands the value straight back here. */
  translate: 0 var(--roll-to, 0em);
}

.el-roll__step { display: block; line-height: 1; white-space: nowrap; }
