/* ADDITIVE — Objectives Collect / Reroll VFX.
   Lifted from public/POCs/lobby-redesign/v2/css/objectives-vfx.css.

   Never edits objectives.css card chrome. Everything here keys off
   [data-collect-vfx] / [data-reroll-vfx] on .module--objectives, which the live
   binder pins to `strike` / `ticker`. The other four looks (Punch Seal, Bank
   Line, Slot Flip, Hex Reforge) are kept: they are the same signed-off sheet,
   they cost nothing while their attribute is never set, and losing them would
   mean re-deriving them if the owner ever wants to compare again.

   Speeds are BAKED, not picked. Strike Out 1.85x and Ticker 2.50x are the
   locked numbers (lock 22); the lab's sliders do not exist on production, and
   the :root values below are the only place they are written.

   Perf contract: transform / opacity / background on the card and its existing
   pseudo-elements. No blur on the rail, no per-sparkle WAAPI node, no rAF —
   the wallet magnet canvas stays the only fullscreen surface.

   Reserved hooks on .objective (unused by objectives.css):
     ::before  seal shock ring
     ::after   settled left rail (strike / seal)
   Reused: .objective__sweep (was completion sweep only). */

:root {
  --vfx-ease-spring: cubic-bezier(0.16, 1, 0.3, 1);
  --vfx-ease-hard: cubic-bezier(0.2, 0.9, 0.15, 1);
  --vfx-ease-punch: cubic-bezier(0.3, 1.5, 0.4, 1);
  /* Per-look movie speed. 1 = authored. Magnet + die shake ignore these. */
  --vfx-speed-strike: 1.85;
  --vfx-speed-seal: 1;
  --vfx-speed-bank: 1;
  --vfx-speed-ticker: 2.5;
  --vfx-speed-flip: 1;
  --vfx-speed-reforge: 1;
  --vfx-speed-collect: 1.85;
  --vfx-speed-reroll: 2.5;
}

/* ─────────────────────────────────────────────────────────────
   SHARED
   ───────────────────────────────────────────────────────────── */

/* The corpse is banned. Every look owns its own settled rest. */
.module--objectives[data-collect-vfx] .objective.is-claimed {
  opacity: 1;
}

/* While a movie is mid-flight the row keeps its claimable slot: order is
   released at the seat beat so the sink is a FLIP, not a jump. */
.module--objectives .objective.is-vfx-hold {
  order: 0;
}

/* The hold is a freeze-frame of the pre-claim row: the CTA stays put so the
   magnet still visibly leaves the button it was fired from. */
.module--objectives .objective.is-vfx-hold .objective__reward-slot .collect-button {
  opacity: 1;
  visibility: visible;
  transform: translate(2px, -1px) scale(1);
}

.module--objectives .objective.is-vfx-hold .objective__progress-row {
  opacity: 1;
}

/* Rest states only bind once the hold is released. */
.module--objectives .objective.is-vfx-resolving {
  overflow: hidden;
}

/* Anticipation on the button the magnet leaves from — sheared slabs,
   never a radial bloom. */
.objective .collect-button.is-vfx-fired {
  animation: vfx-collect-recoil 260ms var(--vfx-ease-hard) both;
}

@keyframes vfx-collect-recoil {
  0% { transform: translateY(0) scaleX(1); filter: brightness(1); }
  22% { transform: translateY(1px) scaleX(0.92); filter: brightness(1.55); }
  100% { transform: translateY(0) scaleX(1); filter: brightness(1); }
}

/* Group head "that board is done" tick. Shared by all three Collect looks. */
.objective-group__head.is-vfx-complete {
  animation: vfx-group-seal calc(620ms * var(--vfx-speed-collect, 1)) var(--vfx-ease-hard) both;
}

.objective-group__head.is-vfx-complete .objective-group__stat {
  animation: vfx-group-stat calc(620ms * var(--vfx-speed-collect, 1)) var(--vfx-ease-punch) both;
}

@keyframes vfx-group-seal {
  0% { border-left-color: rgba(var(--green-rgb), 0.55); background: rgba(var(--green-rgb), 0.04); }
  26% { border-left-color: rgba(var(--green-rgb), 1); background: rgba(var(--green-rgb), 0.18); box-shadow: -3px 0 12px rgba(var(--green-rgb), 0.4); }
  100% { border-left-color: rgba(var(--green-rgb), 0.55); background: rgba(var(--green-rgb), 0.04); box-shadow: none; }
}

@keyframes vfx-group-stat {
  0% { transform: scale(1); color: #87929d; }
  30% { transform: scale(1.16); color: var(--green); }
  100% { transform: scale(1); color: var(--green); }
}

/* A settled row never loops. The title marquee belongs to live contracts —
   on a claimed row it is noise, and it violates the "nothing animates at rest"
   half of the perf contract. Ellipsis takes over. */
.module--objectives[data-collect-vfx] .objective.is-claimed .objective__title.is-marquee .objective__title-track {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  gap: 0;
  text-overflow: ellipsis;
  animation: none;
}

.module--objectives[data-collect-vfx] .objective.is-claimed .objective__title.is-marquee .objective__title-copy ~ .objective__title-copy {
  display: none;
}

.module--objectives[data-collect-vfx] .objective.is-claimed .objective__title.is-marquee {
  mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 12px), transparent 100%);
}

/* Settled chrome injected once per card by objectives-vfx.js.
   Hidden unless the selected look asks for it. */
.objective__seal,
.objective__ledger {
  display: none;
}

/* Shards. Fixed, capped at 8 per fire, self-removing on animationend.
   z-index sits below the wallet canvas (2400) so the magnet still owns the air. */
.obj-shard {
  position: fixed;
  z-index: 90;
  width: 7px;
  height: 3px;
  background: var(--shard-fill);
  box-shadow: 0 0 6px var(--shard-glow);
  clip-path: polygon(3px 0, 100% 0, calc(100% - 3px) 100%, 0 100%);
  opacity: 0;
  pointer-events: none;
  animation: vfx-shard-throw var(--shard-life, 520ms) var(--vfx-ease-hard) forwards;
}

.obj-shard--green {
  --shard-fill: linear-gradient(90deg, rgba(var(--green-rgb), 0.25), var(--green));
  --shard-glow: rgba(var(--green-rgb), 0.55);
}

.obj-shard--cyan {
  --shard-fill: linear-gradient(90deg, rgba(var(--cyan-rgb), 0.25), var(--cyan));
  --shard-glow: rgba(var(--cyan-rgb), 0.55);
}

.obj-shard--gold {
  --shard-fill: linear-gradient(90deg, rgba(var(--gold-rgb), 0.25), var(--gold));
  --shard-glow: rgba(var(--gold-rgb), 0.6);
}

@keyframes vfx-shard-throw {
  0% { opacity: 0; transform: translate3d(0, 0, 0) rotate(var(--shard-rot, 0deg)) scaleX(0.4); }
  16% { opacity: 1; transform: translate3d(calc(var(--shard-x) * 0.22), calc(var(--shard-y) * 0.22), 0) rotate(var(--shard-rot, 0deg)) scaleX(1.15); }
  100% { opacity: 0; transform: translate3d(var(--shard-x), var(--shard-y), 0) rotate(var(--shard-rot, 0deg)) scaleX(0.5); }
}

/* ═════════════════════════════════════════════════════════════
   COLLECT · STRIKE OUT
   The paid row is crossed off. It folds to a hairline, the list
   tightens, the next live contract rises into the gap.
   ═════════════════════════════════════════════════════════════ */

/* --- movie --- */
.module--objectives[data-collect-vfx="strike"] .objective.is-vfx-collect {
  animation: vfx-strike-flash calc(300ms * var(--vfx-speed-strike, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-vfx-collect .objective__sweep {
  animation: vfx-strike-wipe calc(320ms * var(--vfx-speed-strike, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-vfx-collect .objective__status-node {
  animation: vfx-node-punch calc(340ms * var(--vfx-speed-strike, 1)) var(--vfx-ease-punch) both;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-vfx-collect .progress__fill {
  transform-origin: 100% 50%;
  animation: vfx-strike-bar calc(280ms * var(--vfx-speed-strike, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-strike-flash {
  0% { background-color: rgba(var(--green-rgb), 0.02); }
  30% { background-color: rgba(var(--green-rgb), 0.16); box-shadow: inset 0 0 0 1px rgba(var(--green-rgb), 0.42); }
  100% { background-color: rgba(var(--green-rgb), 0.03); box-shadow: none; }
}

@keyframes vfx-strike-wipe {
  0% { opacity: 0; transform: translateX(-100%) skewX(-14deg); }
  30% { opacity: 1; }
  100% { opacity: 0; transform: translateX(100%) skewX(-14deg); }
}

@keyframes vfx-strike-bar {
  0% { transform: scaleX(1); filter: brightness(1); }
  34% { transform: scaleX(1); filter: brightness(2.2); }
  100% { transform: scaleX(0); filter: brightness(1); }
}

@keyframes vfx-node-punch {
  0% { transform: scale(1); }
  34% { transform: scale(1.85); box-shadow: 0 0 14px rgba(var(--green-rgb), 0.62); border-color: var(--green); background: rgba(var(--green-rgb), 0.5); }
  100% { transform: scale(1); }
}

/* --- settled rest: the hairline --- */
.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) {
  min-height: 0;
  padding: 5px 10px 5px 10px;
  background: linear-gradient(90deg, rgba(var(--green-rgb), 0.05), rgba(var(--green-rgb), 0.012) 46%, transparent);
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold)::after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  background: linear-gradient(180deg, rgba(var(--green-rgb), 0.85), rgba(var(--green-rgb), 0.28));
  clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
  content: "";
  pointer-events: none;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__progress-row,
.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__meta-row,
.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__error {
  display: none;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__top {
  grid-template-columns: 14px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__reward-slot {
  display: none;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__status-node {
  width: 12px;
  height: 12px;
  margin-top: 0;
  border-color: rgba(var(--green-rgb), 0.7);
  background: rgba(var(--green-rgb), 0.18);
  color: var(--green);
  font-size: 7px;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__title {
  color: #6f8079;
  font-size: 11px;
}

/* The rule has to sit on the track: .objective__title-track is an inline-block,
   which is an atomic inline — decoration on the parent never paints through it. */
.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__title-track {
  text-decoration: line-through;
  text-decoration-color: rgba(var(--green-rgb), 0.62);
  text-decoration-thickness: 1px;
}

.module--objectives[data-collect-vfx="strike"] .objective.is-claimed:not(.is-vfx-hold) .objective__reward {
  color: rgba(var(--green-rgb), 0.86);
  font-size: 9px;
}

/* Neighbours rise into the freed gap. */
.objective.is-vfx-rise {
  animation: vfx-rise calc(320ms * var(--vfx-speed-strike, 1)) var(--vfx-ease-spring) both;
}

@keyframes vfx-rise {
  0% { background-color: rgba(var(--green-rgb), 0.055); }
  100% { background-color: rgba(255, 255, 255, 0.012); }
}

/* ═════════════════════════════════════════════════════════════
   COLLECT · PUNCH SEAL
   The row stays and earns its rest. A mechanical plate lands on
   the CTA slot, the bar re-cuts to a paid hatch, the card reads
   authorised instead of dimmed.
   ═════════════════════════════════════════════════════════════ */

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-collect {
  animation: vfx-seal-charge calc(320ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-collect .objective__sweep {
  animation: vfx-seal-wash calc(340ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-seal-charge {
  0% { background-color: rgba(var(--green-rgb), 0.02); }
  46% { background-color: rgba(var(--green-rgb), 0.11); }
  100% { background-color: rgba(var(--green-rgb), 0.05); }
}

@keyframes vfx-seal-wash {
  0% { opacity: 0; transform: translateX(-100%) skewX(-16deg); }
  40% { opacity: 0.9; }
  100% { opacity: 0; transform: translateX(100%) skewX(-16deg); }
}

/* impact frame — ink punch + shock ring on the reserved ::before */
.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch {
  animation: vfx-seal-impact calc(420ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch::before {
  position: absolute;
  z-index: 3;
  top: 50%;
  right: 4px;
  width: 34px;
  height: 34px;
  margin-top: -17px;
  border: 1px solid rgba(var(--green-rgb), 0.9);
  clip-path: polygon(9px 0, 100% 0, calc(100% - 9px) 100%, 0 100%);
  content: "";
  pointer-events: none;
  animation: vfx-seal-shock calc(460ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch .objective__seal {
  animation: vfx-seal-stamp calc(420ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-punch) both;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch .objective__status-node {
  animation: vfx-node-punch calc(380ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-punch) both;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch .progress__fill {
  animation: vfx-seal-bar calc(520ms * var(--vfx-speed-seal, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-seal-impact {
  0% { background-color: rgba(var(--green-rgb), 0.05); }
  8% { background-color: rgba(var(--green-rgb), 0.3); box-shadow: inset 0 0 0 1px rgba(var(--green-rgb), 0.7); }
  100% { background-color: rgba(var(--green-rgb), 0.07); box-shadow: none; }
}

@keyframes vfx-seal-shock {
  0% { opacity: 0; transform: scale(0.5); }
  10% { opacity: 0.95; transform: scale(0.72); }
  100% { opacity: 0; transform: scale(2.1); }
}

@keyframes vfx-seal-stamp {
  0% { opacity: 0; transform: rotate(-15deg) scale(2.4); }
  40% { opacity: 1; }
  58% { transform: rotate(-3deg) scale(0.93); }
  78% { transform: rotate(-3deg) scale(1.05); }
  100% { opacity: 1; transform: rotate(-3deg) scale(1); }
}

@keyframes vfx-seal-bar {
  0% { filter: brightness(1); }
  18% { filter: brightness(2.2); }
  100% { filter: brightness(1); }
}

/* --- settled rest: authorised --- */
.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) {
  background: linear-gradient(100deg, rgba(var(--green-rgb), 0.085), rgba(var(--green-rgb), 0.02) 62%, transparent);
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold)::after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 3px;
  background: linear-gradient(180deg, var(--green), rgba(var(--green-rgb), 0.35));
  box-shadow: 0 0 8px rgba(var(--green-rgb), 0.32);
  clip-path: polygon(1px 0, 100% 0, calc(100% - 1px) 100%, 0 100%);
  content: "";
  pointer-events: none;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .objective__progress-row {
  opacity: 1;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .progress {
  background: rgba(var(--green-rgb), 0.09);
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .progress__fill {
  background:
    repeating-linear-gradient(115deg, rgba(var(--green-rgb), 0.78) 0 3px, rgba(var(--green-rgb), 0.28) 3px 7px);
  box-shadow: none;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .objective__title {
  color: #dbe6e0;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .objective__count {
  color: rgba(var(--green-rgb), 0.9);
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .objective__status-node {
  border-color: var(--green);
  background: rgba(var(--green-rgb), 0.2);
  color: var(--green);
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .collect-button {
  display: none;
}

.module--objectives[data-collect-vfx="seal"] .objective.is-claimed:not(.is-vfx-hold) .objective__seal {
  display: inline-flex;
  /* .objective__reward-slot rides at margin-right: -4px; the plate is rotated,
     so its corner needs that back or the card's overflow clips the slant. */
  margin: 1px 5px 0 0;
}

.objective__seal {
  align-items: center;
  gap: 3px;
  min-height: 18px;
  padding: 0 7px 0 6px;
  border: 1px solid rgba(var(--green-rgb), 0.62);
  background: linear-gradient(180deg, rgba(var(--green-rgb), 0.24), rgba(var(--green-rgb), 0.08));
  box-shadow:
    inset 0 1px rgba(255, 255, 255, 0.14),
    0 0 10px rgba(var(--green-rgb), 0.16);
  clip-path: polygon(5px 0, 100% 0, calc(100% - 5px) 100%, 0 100%);
  color: #b6f2d4;
  font-family: Rajdhani, sans-serif;
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 0.13em;
  line-height: 1;
  text-transform: uppercase;
  transform: rotate(-3deg);
  transform-origin: 50% 50%;
  white-space: nowrap;
}

.objective__seal i {
  font-size: 7px;
  color: var(--green);
}

/* ═════════════════════════════════════════════════════════════
   COLLECT · BANK LINE
   The contract is spent: the row compresses into its status node,
   throws its shards down the magnet vector, and re-forms as a
   ledger strip that stacks with every other secured line.
   ═════════════════════════════════════════════════════════════ */

.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect .objective__copy,
.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect .objective__reward-slot {
  animation: vfx-bank-suck calc(260ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect .objective__progress-row,
.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect .objective__meta-row {
  animation: vfx-bank-suck calc(260ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-hard) calc(40ms * var(--vfx-speed-bank, 1)) both;
}

.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect .objective__status-node {
  animation: vfx-bank-flare calc(420ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-punch) both;
}

.module--objectives[data-collect-vfx="bank"] .objective.is-vfx-collect {
  animation: vfx-bank-drain calc(320ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-bank-suck {
  0% { opacity: 1; transform: translateX(0) skewX(0deg) scaleX(1); }
  100% { opacity: 0; transform: translateX(-14px) skewX(-12deg) scaleX(0.82); }
}

@keyframes vfx-bank-flare {
  0% { transform: scale(1) rotate(0deg); }
  28% {
    transform: scale(1.9) rotate(45deg);
    border-color: var(--cyan);
    background: var(--cyan);
    box-shadow: 0 0 20px rgba(var(--cyan-rgb), 0.85);
    color: transparent;
  }
  62% { transform: scale(1.2) rotate(45deg); background: rgba(var(--green-rgb), 0.7); box-shadow: 0 0 12px rgba(var(--green-rgb), 0.6); }
  100% { transform: scale(1) rotate(0deg); }
}

@keyframes vfx-bank-drain {
  0% { background-color: rgba(var(--green-rgb), 0.02); }
  24% { background-color: rgba(var(--cyan-rgb), 0.12); }
  100% { background-color: rgba(255, 255, 255, 0.012); }
}

/* --- settled rest: the ledger strip --- */
.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) {
  min-height: 0;
  padding: 0;
  border-top: 1px solid rgba(var(--green-rgb), 0.16);
  border-bottom: 1px solid rgba(0, 0, 0, 0.5);
  background: linear-gradient(180deg, #101821, #0a0f14);
}

.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) .objective__top,
.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) .objective__progress-row,
.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) .objective__meta-row,
.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) .objective__error {
  display: none;
}

.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) .objective__ledger {
  display: flex;
}

/* Consecutive secured lines fuse into one ledger block. */
.module--objectives[data-collect-vfx="bank"] .objective.is-claimed:not(.is-vfx-hold) + .objective.is-claimed:not(.is-vfx-hold) {
  margin-top: -2px;
  border-top-color: rgba(var(--green-rgb), 0.08);
}

.objective__ledger {
  position: relative;
  align-items: center;
  gap: 7px;
  height: 26px;
  padding: 0 9px 0 10px;
  overflow: hidden;
  color: #7f8d97;
  font-family: Rajdhani, sans-serif;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.13em;
  line-height: 1;
  text-transform: uppercase;
}

.objective__ledger::before {
  flex: 0 0 auto;
  width: 3px;
  height: 12px;
  background: var(--green);
  box-shadow: 0 0 7px rgba(var(--green-rgb), 0.55);
  clip-path: polygon(1px 0, 100% 0, calc(100% - 1px) 100%, 0 100%);
  content: "";
}

.objective__ledger i {
  flex: 0 0 auto;
  color: rgba(var(--green-rgb), 0.85);
  font-size: 8px;
}

.objective__ledger em {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  color: #93a0aa;
  font-style: normal;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.objective__ledger b {
  flex: 0 0 auto;
  color: var(--cyan);
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
}

/* One-shot scan on seat. Nothing loops at rest. */
.objective.is-vfx-bank-seat .objective__ledger {
  animation: vfx-ledger-seat calc(300ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-spring) both;
}

.objective.is-vfx-bank-seat .objective__ledger::after {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 34%;
  background: linear-gradient(100deg, transparent, rgba(var(--cyan-rgb), 0.3), transparent);
  content: "";
  pointer-events: none;
  animation: vfx-ledger-scan calc(760ms * var(--vfx-speed-bank, 1)) var(--vfx-ease-hard) calc(80ms * var(--vfx-speed-bank, 1)) both;
}

@keyframes vfx-ledger-seat {
  0% { opacity: 0; transform: translateY(-7px); }
  100% { opacity: 1; transform: translateY(0); }
}

@keyframes vfx-ledger-scan {
  0% { opacity: 0; transform: translateX(-120%) skewX(-18deg); }
  22% { opacity: 1; }
  100% { opacity: 0; transform: translateX(360%) skewX(-18deg); }
}

/* ═════════════════════════════════════════════════════════════
   REROLL · shared die shake. Always first, always physical.
   ═════════════════════════════════════════════════════════════ */

.reroll-button.is-vfx-shake {
  color: var(--gold);
  animation: vfx-die-shake 400ms cubic-bezier(0.3, 0.05, 0.35, 1) both;
}

@keyframes vfx-die-shake {
  0% { transform: rotate(0deg) translate(0, 0) scale(1); }
  10% { transform: rotate(-19deg) translate(-1.5px, 1px) scale(1.12); }
  23% { transform: rotate(17deg) translate(1.5px, -1px) scale(1.14); }
  36% { transform: rotate(-15deg) translate(-1.5px, 1px) scale(1.1); }
  49% { transform: rotate(13deg) translate(1px, -0.5px) scale(1.08); }
  62% { transform: rotate(-9deg) translate(-1px, 0.5px) scale(1.05); }
  75% { transform: rotate(6deg) translate(0.5px, 0) scale(1.03); }
  88% { transform: rotate(-3deg) translate(0, 0) scale(1.01); }
  100% { transform: rotate(0deg) translate(0, 0) scale(1); }
}

/* ═════════════════════════════════════════════════════════════
   REROLL · TICKER — split-flap on the title line only.
   ═════════════════════════════════════════════════════════════ */

.objective__title-track.is-vfx-flap {
  animation: vfx-ticker-flap calc(92ms * var(--vfx-speed-ticker, 1)) linear both;
}

.objective__title-track.is-vfx-land {
  animation: vfx-ticker-land calc(260ms * var(--vfx-speed-ticker, 1)) var(--vfx-ease-punch) both;
}

@keyframes vfx-ticker-flap {
  0% { opacity: 0.2; transform: translateY(-88%) scaleY(0.7); }
  60% { opacity: 1; }
  100% { opacity: 0.95; transform: translateY(0) scaleY(1); }
}

@keyframes vfx-ticker-land {
  0% { transform: translateY(-24%); }
  46% { transform: translateY(2.5%); }
  100% { transform: translateY(0); }
}

.objective__count.is-vfx-tick {
  animation: vfx-count-tick calc(380ms * var(--vfx-speed-reroll, 1)) var(--vfx-ease-punch) both;
}

@keyframes vfx-count-tick {
  0% { opacity: 0; transform: translateY(-7px); }
  40% { opacity: 1; transform: translateY(1px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* ═════════════════════════════════════════════════════════════
   REROLL · SLOT FLIP — the card turns over, a new face seats.
   ═════════════════════════════════════════════════════════════ */

.module--objectives[data-reroll-vfx="flip"] .objective.is-vfx-flip-out {
  animation: vfx-flip-out calc(180ms * var(--vfx-speed-flip, 1)) cubic-bezier(0.5, 0, 0.85, 0.4) both;
}

.module--objectives[data-reroll-vfx="flip"] .objective.is-vfx-flip-in {
  animation: vfx-flip-in calc(300ms * var(--vfx-speed-flip, 1)) var(--vfx-ease-spring) both;
}

@keyframes vfx-flip-out {
  0% { transform: perspective(620px) rotateX(0deg); background-color: rgba(255, 255, 255, 0.012); }
  100% { transform: perspective(620px) rotateX(-82deg); background-color: rgba(var(--gold-rgb), 0.16); }
}

@keyframes vfx-flip-in {
  0% { transform: perspective(620px) rotateX(74deg); background-color: rgba(var(--gold-rgb), 0.16); }
  58% { transform: perspective(620px) rotateX(-7deg); background-color: rgba(var(--gold-rgb), 0.05); }
  82% { transform: perspective(620px) rotateX(2.5deg); }
  100% { transform: perspective(620px) rotateX(0deg); background-color: rgba(255, 255, 255, 0.012); }
}

/* Hard edge light at the fold so the turn reads as a physical panel. */
.module--objectives[data-reroll-vfx="flip"] .objective.is-vfx-flip-out .objective__sweep,
.module--objectives[data-reroll-vfx="flip"] .objective.is-vfx-flip-in .objective__sweep {
  background: linear-gradient(180deg, transparent 40%, rgba(var(--gold-rgb), 0.55) 50%, transparent 60%);
  opacity: 1;
  transform: none;
  animation: vfx-flip-edge calc(300ms * var(--vfx-speed-flip, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-flip-edge {
  0% { opacity: 0.85; }
  100% { opacity: 0; }
}

/* ═════════════════════════════════════════════════════════════
   REROLL · HEX REFORGE — die bursts, copy scrambles, contract reforges.
   ═════════════════════════════════════════════════════════════ */

.module--objectives[data-reroll-vfx="reforge"] .objective.is-vfx-reroll {
  animation: vfx-reforge-heat calc(620ms * var(--vfx-speed-reforge, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-reroll-vfx="reforge"] .objective.is-vfx-reroll .objective__sweep {
  background: linear-gradient(100deg, transparent 30%, rgba(var(--gold-rgb), 0.3) 50%, transparent 70%);
  animation: vfx-reforge-wash calc(500ms * var(--vfx-speed-reforge, 1)) var(--vfx-ease-hard) calc(90ms * var(--vfx-speed-reforge, 1)) both;
}

.module--objectives[data-reroll-vfx="reforge"] .objective.is-vfx-reroll .progress__fill {
  animation: vfx-reforge-drain calc(460ms * var(--vfx-speed-reforge, 1)) var(--vfx-ease-hard) both;
}

.module--objectives[data-reroll-vfx="reforge"] .objective.is-vfx-scramble .objective__title {
  color: var(--gold);
  text-shadow: 0 0 9px rgba(var(--gold-rgb), 0.55);
}

.module--objectives[data-reroll-vfx="reforge"] .objective.is-vfx-reroll .objective__status-node {
  animation: vfx-reforge-node calc(620ms * var(--vfx-speed-reforge, 1)) var(--vfx-ease-hard) both;
}

@keyframes vfx-reforge-heat {
  0% { background-color: rgba(255, 255, 255, 0.012); }
  22% { background-color: rgba(var(--gold-rgb), 0.09); box-shadow: inset 0 0 0 1px rgba(var(--gold-rgb), 0.4); }
  100% { background-color: rgba(255, 255, 255, 0.012); box-shadow: none; }
}

@keyframes vfx-reforge-wash {
  0% { opacity: 0; transform: translateX(-100%) skewX(-16deg); }
  34% { opacity: 1; }
  100% { opacity: 0; transform: translateX(100%) skewX(-16deg); }
}

@keyframes vfx-reforge-drain {
  0% { filter: brightness(2.2) hue-rotate(-40deg); }
  100% { filter: brightness(1) hue-rotate(0deg); }
}

@keyframes vfx-reforge-node {
  0% { transform: rotate(0deg); }
  30% { transform: rotate(45deg) scale(1.35); border-color: var(--gold); background: rgba(var(--gold-rgb), 0.32); }
  100% { transform: rotate(0deg) scale(1); }
}

/* ═════════════════════════════════════════════════════════════
   REDUCED MOTION — snap to the rest state, keep every look's chrome.
   ═════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
  .objective.is-vfx-collect,
  .objective.is-vfx-collect *,
  .objective.is-vfx-punch,
  .objective.is-vfx-punch::before,
  .objective.is-vfx-punch *,
  .objective.is-vfx-reroll,
  .objective.is-vfx-reroll *,
  .objective.is-vfx-flip-out,
  .objective.is-vfx-flip-in,
  .objective.is-vfx-bank-seat .objective__ledger,
  .objective.is-vfx-bank-seat .objective__ledger::after,
  .objective.is-vfx-rise,
  .objective-group__head.is-vfx-complete,
  .objective-group__head.is-vfx-complete .objective-group__stat,
  .reroll-button.is-vfx-shake,
  .collect-button.is-vfx-fired,
  .obj-shard {
    animation: none !important;
    transform: none !important;
  }

  .module--objectives[data-collect-vfx="seal"] .objective.is-vfx-punch::before {
    display: none;
  }
}
