/* rip/reveal-panel.css — THE REVEAL'S STYLES, for every universe and Pokémon.
 * ─────────────────────────────────────────────────────────────────────────────
 * Extracted from fun-pack.css on 2026-09-26, when Pokémon moved onto the same
 * reveal as every universe (Aaron: "Pokémon is just another universe, like the
 * rest"). packs.htm loads THIS sheet and not fun-pack.css (which also styles the
 * pack pages' own furniture); every page that loads fun-pack.css loads this
 * right after it. Rules moved verbatim, in their original order.
 * Pairs with rip/reveal-panel.js, which builds the panel these rules style.
 */
/* fun-pack.css — the shared rip experience for every fun pack.
 * ===========================================================================
 * Aaron, 2026-09-04: "all of the best bits of pack dojo in terms of features,
 * UI and UX must be applied when the user is ripping the marvel cards and any
 * other sets we add."
 *
 * ⚠ IT REUSES `body.sim-pulling`, IT DOES NOT INVENT A SECOND CLASS.
 * rip-header.css already fades and disables the header, the tab bar and the
 * sticky open bar under that exact class, and rip/admin/cli/tabbar-check.js
 * already sweeps every `position:fixed; bottom:0` element in the repo against
 * its offset block. A parallel `fp-pulling` would be a second list nobody
 * remembers to extend — the failure this repo has paid for twice in one day
 * (the trades SEND button, then the brand link killing a pull). One class,
 * inherited by everything, is the rule.
 *
 * What this file adds is only what is specific to a fun pack: the overlay that
 * has to opt back INTO pointer events while the page is inert, the pack and
 * card choreography, and the burst video's own layer.
 */

/* ⚠ THE PAGE IS INERT DURING A PULL AND THE REVEAL IS THE ONE EXCEPTION.
   body.sim-pulling{pointer-events:none} is inherited by everything — that is
   the point, and it is why the brand link can no longer kill a pull. The
   overlay opts back in the same way #card-overlay does on the pack page. */
/* ⚠ ONLY THE RESULT PANEL OPTS BACK IN. During the card-only phase the page is
   still inert and the card is a moment to watch, not a control — making it
   tappable there would let a stray touch dismiss the reveal mid-explosion. */
body.sim-pulling .fp-reveal.is-on { pointer-events: auto; }

/* ── the reveal ───────────────────────────────────────────────────────────
   ⚠ IT IS AN OVERLAY, NOT A BLOCK IN THE PAGE, AND THE FIRST VERSION GOT THIS
   WRONG. Aaron: "animation loads but card doesn't reveal properly, overlay
   doesn't show." He was exactly right — the reveal was an inline element in
   normal flow, so it appeared 237px down a scrolled page, behind a full-screen
   clip, and then JUMPED to 521px when the pack came back underneath it.
   Measured on the real page before the fix: revealTop 237 -> 521.

   The pack page's reveal has always been a centred modal (#card-overlay) and
   that is the thing being copied. Fixed, full-screen, above the clip at 9998,
   with its own scrim so the card reads against the page behind it. */
/* ⚠ TWO PHASES, AND THE FIRST ONE HAS NO SCRIM. Aaron: "the card is supposed
   to appear from the explosion and then the overlay appears. it's not doing
   that." It was not: the full overlay opened AT the explosion, and a 97.5%
   scrim over the clip means you never see the card emerge from anything.

   `.is-card` — the card alone, transparent background, in front of the clip at
   the explosion frame. This is the pack page's own behaviour: the slab reveals
   ON the bursts, in front of the video.
   `.is-on`   — the result panel: the scrim fades in and the details arrive,
   after the clip has finished. */
/* ⚠ THE PANEL IS `art | identity`, WITH THE ACTIONS FULL WIDTH BENEATH BOTH —
   AT EVERY WIDTH, THE PHONE INCLUDED. Aaron, 2026-09-04: "doesn't look anywhere
   near as good as the Pokémon reveal." Measured side by side at 390px, that was
   not taste: the Pokémon overlay is a two-column grid with a hero line, a
   bordered collection box and a primary/secondary button pair, and this was a
   centred stack — 78vw of card art with a 19px name, a tiny chip and two grey
   chips under it. The card filled the screen and nothing else had any weight.

   The proportions here are #card-overlay's own, and every one of them is a
   measurement that page already paid for, so they are copied rather than
   re-derived: 46% to the art on a phone (40% "finished 135px short of the
   window with the card at 156x220 — small art and empty space"), and
   min(280px,25vh) above 560 because a card is 5:7 and a 280px column is 392px
   tall, which overflowed an 808x787 window.

   ⚠ GRID, NOT FLEX-WRAP. That page fixed the same bug three times: flex clamps
   an item by max-width BEFORE deciding where the line breaks, so the actions
   row sat BESIDE the card at 1920 and under it at 1280. Two columns and an
   explicit full-width row behave identically at every width.

   ⚠ `safe center`. Plain centring pushes a panel taller than the window ABOVE
   the scroll origin, i.e. unreachable even with overflow-y:auto — measured on
   that page at -110px on a landscape phone. */
.fp-reveal {
  position: fixed; inset: 0; z-index: 9999;
  display: none;
  grid-template-columns: minmax(0, 46%) minmax(0, 1fr);
  gap: 13px 15px;
  justify-content: safe center; align-content: safe center; align-items: start;
  text-align: left; padding: 18px 14px;
  transition: background .38s ease;
  /* ⚠ NEARLY OPAQUE, NOT MERELY DARK. At .88 the page behind read straight
     through it — the red OPEN button and the header sat visibly across the
     card in a 390px screenshot. A reveal is a modal moment; the page behind it
     is not part of the composition, and backdrop-filter is not supported
     everywhere so the scrim cannot rely on the blur to do this job. */
  background: transparent;
  overflow-y: auto; overscroll-behavior: contain;
}
.fp-reveal.is-on { display: grid; }
.fp-reveal.is-on {
  background: rgba(4, 6, 10, .975);
  -webkit-backdrop-filter: blur(8px); backdrop-filter: blur(8px);
}
/* ⚠ PHASE 1 IS A CENTRED FLEX BOX, NOT THE GRID WITH ITS TEXT HIDDEN. The card
   comes out of the explosion ALONE, so it has to be centred in the viewport —
   and `opacity:0` siblings still occupy their tracks, which is what used to
   push it off centre before the panel had even opened. They are display:none
   here and the layout mode itself changes at .is-on. */
/* ⚠ `:not(.is-on)` ON EVERY PHASE-1 RULE, INCLUDING `display`. Both classes are
   on the element at once — revealCard() adds .is-card and show() adds .is-on to
   the same node — so a bare `.fp-reveal.is-card` is an equal-specificity tie
   that source order decides, and it silently kept the panel in the phase-1 flex
   box: measured, the grid never applied and the identity column overlapped the
   card. The phase is "card, and not yet the panel", so say that. */
.fp-reveal.is-card:not(.is-on) { display: flex; align-items: center; justify-content: center; }
/* ⚠ THE CARD IS `.fp-cardwrap` NOW, NOT `.fp-art` — this selector names the
   REVEAL'S DIRECT CHILD, and wrapping the image moved that. Left as
   `:not(.fp-art)` it hid the card in the one phase the card is the only thing
   on screen: measured, the art came back 0×0 at top 0. */
.fp-reveal.is-card:not(.is-on) > *:not(.fp-cardwrap) { display: none; }

/* ⚠ `align-self` IS SCOPED TO THE PANEL, BECAUSE AN ITEM'S OWN ALIGNMENT BEATS
   ITS CONTAINER'S. This is the second half of "card loads at top", and the one
   that was actually on screen: the grid rule pinning the art to the top of its
   column applied in PHASE 1 as well, where the container is a centred flex box
   — and `align-self:start` on the item overrides `align-items:center` on the
   parent every time. Measured before the fix: the card sat 202px above centre,
   i.e. hard against the top padding, with the page heading showing behind it.
   The container's rule was right and the item silently opted out of it. */
.fp-cardwrap        { grid-column: 1; position: relative; display: block; width: 100%; }
.fp-reveal.is-on .fp-cardwrap { align-self: start; }
/* ⚠ `overflow-wrap` HERE, BECAUSE IT INHERITS. Clamping the BOXES is only half
   the job: a single word longer than the column still paints straight out of a
   correctly-sized box, and `getBoundingClientRect()` on that element reports no
   overflow at all — only the container's scrollWidth sees it. That is what
   "Counterintelligence" did after the pill was fixed. One declaration on the
   column covers the name, the meta line and anything added here later. */
.fp-info   { grid-column: 2; align-self: center; min-width: 0;
             display: flex; flex-direction: column; align-items: flex-start; gap: 7px;
             overflow-wrap: break-word; }
/* ⚠ `.fp-tray`, NOT `.fp-foot` — that name is already the pack back's foot
   credit, twenty lines up. Reusing it would have given a one-line <em> inside
   the pack a full-width grid-column flex column. Grep before naming. */
.fp-tray   { grid-column: 1 / -1; width: 100%; max-width: 380px;
             justify-self: center; display: flex; flex-direction: column; gap: 9px; }

/* ⚠ THE CARD IS THE HERO AND IT GETS A REAL EDGE. A bare rounded rectangle with
   a drop shadow is what "not anywhere near as good" looked like: the pack page
   frames its card in a slab that glows the tier's own colour. This is the same
   idea at a fun pack's weight — a hairline, the pack's accent bloomed behind
   the art, and the depth shadow the overlay already had. */
.fp-art {
  display: block; width: 100%; height: auto; border-radius: 11px;
  background: var(--rip-panel-2);
  box-shadow: 0 16px 42px rgba(0,0,0,.62),
              0 0 0 1px rgba(255,255,255,.10),
              0 0 38px -8px var(--fp-accent, var(--rip-act));
}
/* ⚠⚠ THE REVEALED CARD IS A CONTROL ONCE THE PANEL IS OPEN, AND IT HAS TO LOOK
   LIKE ONE. Aaron: "I want to be able to open and explore the card more." A
   picture that does something with no affordance is a feature nobody finds, so
   it takes the zoom cursor on a pointer and the shared focus ring on a keyboard
   — and ONLY while `.is-on`, because during the explosion and the teases the
   page is pointer-events:none and the card is not tappable yet. */
.fp-reveal.is-on .fp-art[data-cardopen] { cursor: zoom-in; }
.fp-reveal.is-on .fp-art[data-cardopen]:focus-visible {
  outline: 3px solid var(--fp-accent, var(--rip-act));
  outline-offset: 3px;
}

/* Phase 1: big and centred, before the panel takes a column off it. */
.fp-reveal.is-card:not(.is-on) .fp-cardwrap { width: min(320px, 74vw); }

/* ⚠ THE WHITE FLASH IS THE ONE REVEAL EFFECT THAT FIRES ON EVERY PATH ON THE
   PACK PAGE (`.reveal-flash`, packs.htm:742) and it was missing here entirely.
   It is what sells the card as having been struck out of the burst rather than
   faded in — half a second, over the art, gone. */
@keyframes fpCardFlash {
  0%   { opacity: 0; }
  30%  { opacity: 1; }
  100% { opacity: 0; }
}
.fp-cardflash {
  position: absolute; inset: 0; border-radius: 11px; background: #fff;
  opacity: 0; pointer-events: none; z-index: 2;
}
.fp-reveal.is-pop .fp-cardflash { animation: fpCardFlash .5s ease-out forwards; }

/* ⚠ THE STATIC SHEEN — packs.htm's `.holo-sheen`, always on, never animated.
   A flat scan on a black scrim reads as a picture of a card; one diagonal
   highlight reads as a card under a light. `mix-blend-mode: overlay` so it
   brightens the art rather than greying it, and it must not eat the tap that
   dismisses the panel. */
.fp-sheen {
  position: absolute; inset: 0; border-radius: 11px; pointer-events: none;
  z-index: 1; mix-blend-mode: overlay;
  background: linear-gradient(135deg,
              transparent 28%, rgba(255,255,255,.30) 44%,
              rgba(255,255,255,.06) 54%, transparent 66%);
}

/* ── the identity column ───────────────────────────────────────────────── */
/* ⚠⚠ A FLEX ITEM'S `min-width` IS `auto`, WHICH MEANS MIN-CONTENT — AND THAT IS
   WHY THE PANEL SCROLLED SIDEWAYS (Aaron, 2026-09-07: "fix overflow", with a
   screenshot of the reveal scrolled right and the card half off the left edge).
   The pill is `white-space:nowrap`, so `.fp-idrow`'s min-content width is the
   WHOLE set name; as a flex item of `.fp-info` it was free to grow to it.
   Measured at 390px with the real font: .fp-info 180.5 (correct) but .fp-idrow
   and .fp-pill both 244.6 — the overlay's scrollWidth 440 against a 390 client,
   i.e. 50px of horizontal scroll.

   ⚠ AND IT MADE THE PILL'S OWN `max-width:100%` MEANINGLESS — the 100% was of a
   parent that had already grown to fit it, so the ellipsis it asks for could
   never fire. A clamp measured against a box the clamped thing is sizing is not
   a clamp. Whenever `max-width:100%` "does nothing", check what the 100% is OF.

   ⚠ `> *`, NOT A LIST OF THE ROWS THAT EXIST TODAY. A block added to this
   column next month inherits the clamp instead of being remembered. */
.fp-info > *      { max-width: 100%; min-width: 0; }
/* ⚠ DESCENDANTS, NOT `> *`. `.fp-flag` is `display:contents`, so the chip that
   actually becomes a flex item of this row is its CHILD — a `> *` rule lands on
   the box that was taken out of the layout and reaches nothing. */
.fp-idrow *       { min-width: 0; }
.fp-idrow { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; max-width: 100%; }
.fp-pill {
  font-family: 'Press Start 2P', var(--rip-display), sans-serif; font-size: 9px;
  letter-spacing: .04em; padding: 6px 10px; border-radius: 999px;
  /* ⚠ THE ACCENT IS A FILL COLOUR, NOT A TEXT COLOUR, ON A NEAR-BLACK SCRIM.
     Marvel red straight onto rgba(4,6,10,.975) measured 3.1:1 — the pill read
     as dark-on-dark in the 390px screenshot. Every accent this product will
     ever take is a saturated brand colour, so the label is mixed toward white
     rather than each page being asked to supply a second, lighter one. */
  color: #fff; border: 0;
  background: var(--fp-accent, var(--rip-act));
  box-shadow: 0 0 20px -4px var(--fp-accent, var(--rip-act));
  /* ⚠ IT WRAPS, IT DOES NOT ELLIPSIS — THE SET NAME IS DATA, NOT COPY.
     "The Galaxy's Most Wanted" is 245px of Press Start 2P at 9px against a
     180px column on a 390px phone, and no font size that fits it on one line is
     readable (it would need ~6.6px). Truncating is the wrong trade here because
     nothing else on the reveal says which set the card came from, and the next
     universe's names are not ours to shorten either. Two short lines show the
     whole name; `break-word` only splits a word that cannot fit on its own, so
     it breaks at the spaces. */
  max-width: 100%; min-width: 0;
  white-space: normal; overflow-wrap: break-word; line-height: 1.5;
}
/* ⚠ THE CARD'S NAME IS THE HERO LINE, BECAUSE A FUN PACK HAS NO PRICE TO PRINT.
   The Pokémon panel's weight comes from one huge gradient-clipped number; strip
   the value out of that layout and the hole is exactly what this page looked
   like. A fun pack's payoff is WHICH card it is, so the name takes that slot.
   ⚠ padding + box-decoration-break:clone are NOT optional on a
   background-clip:text run (Aaron, 2026-07-12: italic glyph overhang was being
   cut off on his phone). Padding alone does not fix a wrapped span — clone is
   what gives every line fragment its own padding. */
.fp-hero {
  font-family: var(--rip-display); font-weight: 800; margin: 0;
  font-size: clamp(23px, 6.4vw, 30px); line-height: 1.1; letter-spacing: -.02em;
  /* ⚠ HYPHENATE THE HERO LINE, DON'T JUST SNAP IT. Card names are data and some
     are one long word — "Counterintelligence" is 19 characters against a 180px
     column on a 390px phone, so it cannot fit at any size this clamp reaches.
     `break-word` alone gives "Counterintellige / nce"; with hyphens the break
     lands at a syllable and reads as typography rather than damage. It is only
     ever a hint — a browser with no dictionary for the page's lang falls back
     to the inherited break-word, which is why both are set and neither is
     load-bearing on its own. NOT on .fp-sub or .fp-pill: a hyphen in a metadata
     run or a pixel-font eyebrow reads as a glyph, not a break. */
  -webkit-hyphens: auto; hyphens: auto;
  padding: 0 .14em; margin-left: -.14em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  /* ⚠ THE TAIL OF THE GRADIENT IS A LIGHTENED ACCENT, NOT THE ACCENT. Ending on
     the raw brand colour put the last word of the name at Marvel red on a
     near-black scrim — the exact "cramped and dark" reading this rebuild is
     fixing. Mixed 55% toward white it still says which pack this is and stays
     legible on any accent the product takes later. */
  background-image: linear-gradient(135deg, #ffffff 0%, #ffffff 42%,
                    color-mix(in srgb, var(--fp-accent, var(--rip-act)) 55%, #ffffff) 100%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
  text-wrap: balance;
}
.fp-sub {
  font-family: var(--rip-data); font-weight: 600; font-size: 12.5px;
  color: var(--rip-fg-3); margin: 0; line-height: 1.4;
  font-variant-numeric: tabular-nums;
}

@media (min-width: 560px) {
  /* 280 + 15 + 380 = 675 — one readable column pair however wide the screen
     gets, capped on the TRACK because the overlay itself must stay full-screen. */
  .fp-reveal { grid-template-columns: min(280px, 25vh) minmax(0, 380px); gap: 15px 16px; }
  .fp-hero   { font-size: 34px; }
  .fp-info   { gap: 9px; }
}

.fp-hint { font-size: 11.5px; color: var(--rip-fg-3); opacity: .75; margin: 2px 0 0;
           text-align: center; }

/* ⚠ THE CARD EXPLODES OUT OF NOTHING, AND IT IS THE **ART** THAT DOES IT, NOT
   THE OVERLAY. `card-explode-out` (packs.htm:1706) starts at scale 0 at five
   times brightness and overshoots to 1.18 before settling — 900ms of it. The
   old fpPop started at scale .55 and took 520ms, which reads as a panel opening
   rather than a card being thrown out of an explosion, and animating the
   OVERLAY meant the scrim and the buttons scaled with it. */
@keyframes fpExplode {
  0%   { transform: scale(0)    rotate(-8deg); opacity: 0; filter: brightness(5); }
  55%  { transform: scale(1.18) rotate(2deg);  opacity: 1; filter: brightness(1.8); }
  78%  { transform: scale(.95)  rotate(-1deg);             filter: brightness(1.1); }
  100% { transform: scale(1)    rotate(0deg);              filter: brightness(1); }
}
/* ⚠ THE WRAPPER, SO THE FLASH AND THE SHEEN TRAVEL WITH THE CARD. Animating
   the <img> alone left both layers sitting still at full size while the art
   scaled up through them. */
.fp-reveal.is-pop .fp-cardwrap { animation: fpExplode .9s cubic-bezier(.175,.885,.32,1.275) forwards; }

/* ⚠⚠ THE TWO TEASES — packs.htm's `card-tease-1` / `card-tease-2`, keyframe for
   keyframe. The card is thrown part-way out of the clip's earlier explosions and
   sucked back, so a chase reads as three beats rather than one late arrival.
   They are the pack page's exact curves on purpose: the same two clips play on
   both sides, and a hand-tuned variant here would drift from the file it is
   supposed to be synchronised to.
   ⚠ BOTH END AT scale(0) AND ARE `forwards` — the card must be invisible again
   before the next beat, and revealCard() strips both classes before the real
   explosion or the fill would hold it there. */
@keyframes fpTease1 {
  0%   { transform: scale(0)   rotate(8deg);  opacity: 0;  filter: brightness(5); }
  40%  { transform: scale(.65) rotate(2deg);  opacity: .9; filter: brightness(3); }
  100% { transform: scale(0)   rotate(-3deg); opacity: 0;  filter: brightness(1); }
}
@keyframes fpTease2 {
  0%   { transform: scale(0)  rotate(-5deg); opacity: 0; filter: brightness(4); }
  40%  { transform: scale(.6) rotate(1deg);  opacity: 1; filter: brightness(2.5); }
  100% { transform: scale(0)  rotate(3deg);  opacity: 0; filter: brightness(1); }
}
.fp-reveal.is-tease1 .fp-cardwrap { animation: fpTease1 .6s  ease-out forwards; }
.fp-reveal.is-tease2 .fp-cardwrap { animation: fpTease2 .75s ease-out forwards; }
@media (prefers-reduced-motion: reduce) {
  .fp-pack.is-beat, .fp-pack.is-charge, .fp-pack.is-suck, .fp-pack.is-awaiting::after,
  .fp-reveal.is-pop .fp-cardwrap, .fp-reveal.is-pop .fp-cardflash,
  /* ⚠ THE TEASES TOO, AND NOT ONLY BECAUSE THEY MOVE: both keyframes END at
     scale(0)/opacity(0) with a `forwards` fill, so leaving them live here
     would hide the card outright from anybody who asked for less motion. */
  .fp-reveal.is-tease1 .fp-cardwrap, .fp-reveal.is-tease2 .fp-cardwrap { animation: none; }
  .fp-vig, .fp-cardflash { display: none; }
  /* `both` fill means removing the animation must not leave these at opacity 0. */
  .fp-reveal.is-on .fp-info, .fp-reveal.is-on .fp-tray { animation: none; }
}

/* ── the post-pull strip ──────────────────────────────────────────────── */
/* ⚠ NEW SITS BESIDE THE PACK PILL, NOT ON A LINE OF ITS OWN. That is the pack
   page's `.ov-idrow` decision, and the reason is width: on a ~200px identity
   column a chip with a whole row to itself buys nothing and costs ~26px of the
   panel's height. */
.fp-flag { margin: 0; display: contents; }
.fp-new {
  font-family: 'Press Start 2P', var(--rip-display), sans-serif; font-size: 9px;
  color: #06240f; background: var(--rip-up); border-radius: 999px;
  padding: 6px 10px; display: inline-block; letter-spacing: .04em;
  box-shadow: 0 0 18px -4px var(--rip-up);
}
.fp-dupe {
  font-family: var(--rip-data); font-weight: 700; font-size: 10px;
  letter-spacing: .04em; text-transform: uppercase; padding: 6px 10px;
  border-radius: 999px; display: inline-block;
  color: var(--rip-fg-3); border: 1px solid var(--rip-line);
}

/* ⚠ THE COLLECTION FIGURE IS A BORDERED BOX, NOT A LOOSE LINE. On the pack
   page it is a panel with the count, the bar and the way to go and look at it
   — one control. Loose on a scrim it read as a caption nobody had placed. */
.fp-box {
  display: flex; align-items: center; gap: 11px; width: 100%; box-sizing: border-box;
  background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.12);
  border-radius: 12px; padding: 9px 11px 9px 13px;
}
.fp-colbar { flex: 1 1 auto; min-width: 0; font-family: var(--rip-data);
  font-weight: 800; font-size: 12.5px; color: var(--rip-fg);
  font-variant-numeric: tabular-nums; }
/* ⚠ The count must not wrap. "1 / 1779" broke across two lines beside the bar
   because the <b> was allowed to shrink — a four-digit total is the normal
   case here, not the edge one.
   ⚠⚠ AND IT MUST STATE ITS WEIGHT. A bare <b> carries the UA's
   `font-weight: bolder`, which against this bar's own 800 computes to 900 —
   outside Bricolage Grotesque's 200..800 axis, so the browser SYNTHESISES a
   fake bold. Measured at 390px: every universe page rendered "0 / 1,779" at
   weight 900 and packs.htm rendered none, i.e. the one figure a member reads
   most on those pages was the one word on them in a different typeface.
   Chrome desktop hides it by smearing the real 800; Android and Safari do not,
   and Aaron reviews on a phone. This repo's own rule, earned twice before:
   audit the COMPUTED weight, never the declaration. */
.fp-colbar b { white-space: nowrap; font-weight: 800; }
.fp-colbar u { display: block; text-decoration: none; color: var(--rip-fg-3);
  font-weight: 700; font-size: 11px; }
/* ⚠ rgba on white, NOT --rip-panel-2. The track was panel-2 on a .975 black
   scrim, i.e. invisible — measured in a 390px screenshot, the bar read as a gap
   between two words. A progress bar nobody can see is not a progress bar. */
.fp-colbar span { display: block; margin-top: 6px; height: 8px; border-radius: 999px;
  background: rgba(255,255,255,.09); overflow: hidden; }
.fp-colbar i { display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--fp-accent, var(--rip-act)) 55%, #ffffff),
              var(--fp-accent, var(--rip-act)));
  transition: width .6s ease; }

/* ⚠ ONE PRIMARY, ONE SECONDARY — NOT TWO GREY CHIPS. The pack page pairs a
   filled OPEN ANOTHER at flex:1.6 with an outlined SHARE at flex:1, because a
   straight 50/50 split demotes the primary action to "one of two buttons".
   ⚠ (0,2,0)+ on every anchor: rip-ui.css repaints anchors at (0,2,1), which is
   how a CTA ends up cyan on its own accent fill. */
.fp-acts { display: flex; gap: 8px; }
.fp-acts button, .fp-acts a {
  flex: 1 1 0; min-width: 0; min-height: 46px;
  font-family: var(--rip-display); font-weight: 800; font-size: 14px;
  border-radius: 11px; padding: 12px 10px; cursor: pointer;
  display: flex; align-items: center; justify-content: center; gap: 7px;
  border: 1px solid rgba(255,255,255,.28); background: rgba(255,255,255,.10);
  color: var(--rip-fg); text-decoration: none;
}
.fp-acts button.is-primary, .fp-reveal a.fp-primary:not(.rip-btn) {
  flex: 1.6 1 0; border: none; color: #fff;
  background: linear-gradient(135deg,
              color-mix(in srgb, var(--fp-accent, var(--rip-act)) 78%, #ffffff),
              var(--fp-accent, var(--rip-act)) 62%,
              color-mix(in srgb, var(--fp-accent, var(--rip-act)) 72%, #000000));
  box-shadow: 0 6px 18px -4px var(--fp-accent, var(--rip-act)),
              inset 0 1px 0 rgba(255,255,255,.18);
}
.fp-acts button[disabled] { opacity: .5; cursor: default; }

/* ⚠ THE PANEL ARRIVES, IT DOES NOT APPEAR. Two beats, 90ms apart — identity
   first, then the controls — so the eye lands on the card and the name before
   the buttons. A single opacity flip is what made this read as a modal rather
   than a reveal. */
@keyframes fpIn {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: translateY(0); }
}
.fp-reveal.is-on .fp-info { animation: fpIn .34s ease-out both; }
.fp-reveal.is-on .fp-tray { animation: fpIn .34s ease-out .09s both; }

/* The card's own aspect, as a hairline down the pocket. Painted only when the
   pack HAS such an idea — `--pk-c` is unset otherwise and nothing shows, rather
   than a default colour inventing a fact about the card. */
/* ⚠ SUPERSEDED BY THE SLEEVE. `--pk-c` is now the sleeve's border, exactly as
   `--tc` is on the Pokémon pocket, so a second stripe down the art box would be
   the pack's colour drawn twice in one pocket. */

/* ⚠ THE UNIQUE STAR SITS WITH THE NEW BADGE, NOT NEAR THE NAME — and it is
   gold-on-dark rather than a fill, so it reads as a mark printed on the card
   rather than as a grade or a score. Marvel Champions has no rarity; this is
   the ★ the card itself carries. */
.fp-uniq {
  font-family: var(--rip-data); font-weight: 800; font-size: 9px;
  letter-spacing: .04em; padding: 6px 9px; border-radius: 999px;
  color: #ffd45e; border: 1px solid rgba(255,212,94,.45);
  background: rgba(255,212,94,.10); white-space: nowrap;
}

/* ⚠⚠ THE RARITY CHIP. Added 2026-09-09 — Aaron: "on the power rangers and
   disney page, the user has no idea if they're pulling a common card,
   uncommon, rare etc." It sits in .fp-idrow beside the set pill, which already
   wraps, so it costs no horizontal budget: that row is `flex-wrap:wrap` with
   `min-width:0` on its descendants precisely because the pill overflowed once.

   ⚠ THE LADDER IS WEIGHT AND BRIGHTNESS, NOT A NEW HUE, AND THAT IS FORCED.
   rip/CLAUDE.md's colour language already spends every signal colour this site
   has: cyan means "do this", amber means money, gold means prize, and the pack
   accent means "which universe". A rarity scale in gold would read as a prize
   on the one screen where a real prize can appear. So a common card is quiet
   grey, and rarer grades get border, glow and letter-spacing — the same
   information, carried by emphasis rather than by a colour that already means
   something else here. */
/* ⚠⚠ THE RARITY LADDER IS EIGHT TREATMENTS, AND EACH ONE SWITCHES ON A
   MECHANISM THE STEP BELOW DOES NOT HAVE (2026-09-09, third attempt).

   Attempt one was three classes: on Lorcana's eight grades four pills were
   byte-identical. Attempt two interpolated every property linearly off the
   rank — every rung then had its own numbers, and Aaron said, correctly,
   "there's not much difference between one and the next." One eighth of one
   range per step is a change nobody sees.

   A ladder is read by things APPEARING, not by a value creeping. So:

     t0  flat grey — no colour anywhere
     t1  + the pip takes the pack accent          (colour enters)
     t2  + the border takes it                    (the outline joins in)
     t3  + the WORD goes white and the border full accent
     t4  + an accent wash behind it               (the pill itself is tinted)
     t5  + an outer glow                          (it lights what is around it)
     t6  + a metallic two-tone sweep and a 2px edge
     t7  + the foil sheen travels across it       (motion, and only here)

   ⚠ EIGHT IS THE VOCABULARY'S, NOT ANY PACK'S. rarityChip() quantises a pack's
   rank onto it, so 6 rungs land on 0,1,3,4,6,7 and 5 on 0,2,4,5,7 — the ends
   are always the first and last treatment. A ninth grade in some future pack
   changes nothing here, and no pack has a class of its own.

   ⚠ THE BLOCKS ARE COMPLETE, NOT CUMULATIVE. Each level restates what it
   paints. It is more lines than chaining `.fp-t5, .fp-t6, .fp-t7`, and it is
   the version somebody can audit: what a rung looks like is one block, not a
   set of selector lists to intersect in your head. */
.fp-rar {
  --fp-r: 0;                        /* the slope, still used by the two things
                                       that genuinely read better as one */
  --fp-rc: var(--fp-accent, var(--rip-act));
  /* ⚠ inline-flex + a gap, because the pip is a real box in the line box rather
     than a decoration painted over the text. `isolation` is what keeps the foil
     sweep's `z-index:-1` INSIDE the chip: without a stacking context a negative
     child paints behind the chip's own background and disappears. */
  display: inline-flex; align-items: center; gap: 6px;
  position: relative; isolation: isolate; overflow: hidden;
  font-family: var(--rip-data); font-weight: 800; font-size: 9.5px;
  /* Tracking opens as the grade climbs — the rarest word is the most spaced,
     which is how a foil stamp is set on the real thing. */
  letter-spacing: calc(.05em + var(--fp-r) * .05em);
  text-transform: uppercase;
  padding: 6px 10px 6px 8px; border-radius: 999px;
  white-space: nowrap;
  color: #93a3b8;
  border: 1px solid rgba(255,255,255,.14);
  background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.015));
}
.fp-rar::before {
  content: ''; flex: 0 0 auto;
  width:  calc(4px + var(--fp-r) * 2px);
  height: calc(4px + var(--fp-r) * 2px);
  border-radius: 50%;
  background: #7c8a9c;
}

/* t0 — flat. The commonest grade must look like nothing has happened, or every
   step above it has less room to mean something. */

/* t1 — colour enters, and only in the pip. */
.fp-rar.fp-t1::before { background: var(--fp-rc); }

/* t2 — the outline joins in. */
.fp-rar.fp-t2::before { background: var(--fp-rc); }
.fp-rar.fp-t2 {
  border-color: color-mix(in srgb, var(--fp-rc) 55%, rgba(255,255,255,.14));
}

/* t3 — the word itself takes the light, and the edge goes full accent. */
.fp-rar.fp-t3::before { background: var(--fp-rc); box-shadow: 0 0 6px var(--fp-rc); }
.fp-rar.fp-t3 {
  color: #eef4fb;
  border-color: var(--fp-rc);
}

/* t4 — the pill is tinted, not just outlined. */
.fp-rar.fp-t4::before { background: var(--fp-rc); box-shadow: 0 0 6px var(--fp-rc); }
.fp-rar.fp-t4 {
  color: #fff;
  border-color: var(--fp-rc);
  background:
    linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.02)),
    color-mix(in srgb, var(--fp-rc) 22%, transparent);
}

/* t5 — it starts lighting what is around it. */
.fp-rar.fp-t5::before { background: #fff; box-shadow: 0 0 8px var(--fp-rc); }
.fp-rar.fp-t5 {
  color: #fff;
  border-color: var(--fp-rc);
  background:
    linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.03)),
    color-mix(in srgb, var(--fp-rc) 30%, transparent);
  box-shadow: 0 0 16px -4px var(--fp-rc);
  text-shadow: 0 0 10px var(--fp-rc);
}

/* t6 — metallic: a two-tone diagonal through the accent, and a real edge.
   ⚠ The extra border pixel comes out of the padding so the pill keeps height. */
.fp-rar.fp-t6::before { background: #fff; box-shadow: 0 0 10px var(--fp-rc); }
.fp-rar.fp-t6 {
  color: #fff;
  border: 2px solid var(--fp-rc);
  padding: 5px 9px 5px 7px;
  background:
    linear-gradient(115deg,
      color-mix(in srgb, var(--fp-rc) 46%, transparent) 0%,
      rgba(255,255,255,.20) 42%,
      color-mix(in srgb, var(--fp-rc) 46%, transparent) 78%);
  box-shadow: 0 0 22px -4px var(--fp-rc), inset 0 0 14px -8px #fff;
  text-shadow: 0 0 12px var(--fp-rc);
}

/* t7 — the top of any ladder: everything t6 has, brighter, and the only moving
   thing on the panel. Motion is the last mechanism precisely because it cannot
   be out-done by another step. */
.fp-rar.fp-t7::before { background: #fff; box-shadow: 0 0 12px var(--fp-rc), 0 0 3px #fff; }
.fp-rar.fp-t7 {
  color: #fff;
  border: 2px solid var(--fp-rc);
  padding: 5px 9px 5px 7px;
  background:
    linear-gradient(115deg,
      color-mix(in srgb, var(--fp-rc) 62%, transparent) 0%,
      rgba(255,255,255,.30) 42%,
      color-mix(in srgb, var(--fp-rc) 62%, transparent) 78%);
  box-shadow: 0 0 30px -3px var(--fp-rc), inset 0 0 16px -7px #fff;
  text-shadow: 0 0 14px var(--fp-rc), 0 0 4px rgba(255,255,255,.7);
}

/* ⚠⚠ NO STEP IS A SOLID ACCENT FILL, AND THAT RULE HAS NOT MOVED — A
   SCREENSHOT IS THE ONLY THING THAT FOUND IT. An early version filled the top
   rung with the pack accent, which measured perfectly (visible, contrast fine,
   zero overflow at 320/390/1280) and was still wrong: on Power Rangers the
   accent is #00b14f and the NEW badge is `--rip-up`, so a Holographic pull put
   two solid green pills side by side and the rarest card on the page read as a
   second NEW. The set pill is a third solid accent fill in the same row.
   t4-t7 wash and sweep the accent at 22-62% over the panel's dark ground, which
   is a tint rather than a fill: still unmistakable, still not a duplicate of
   either pill beside it, on ANY pack — the collision is structural, not a fact
   about green. */

/* ⚠ THE FOIL SWEEP. A holographic card is recognised by the light MOVING across
   it, so the rarest grade gets exactly that and nothing below it does. */
.fp-rar.fp-t7::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  border-radius: inherit;
  background: linear-gradient(105deg,
    transparent 34%, rgba(255,255,255,.18) 44%,
    rgba(255,255,255,.7) 50%,
    rgba(255,255,255,.18) 56%, transparent 66%);
  transform: translateX(-130%);
}
@media (prefers-reduced-motion: no-preference) {
  .fp-reveal.is-on .fp-rar.fp-t7 { animation: fpIn .34s ease-out both; }
  /* Starts after the panel has assembled, then rests for most of the cycle —
     a sheen that never stops reads as a loading shimmer, not as foil. */
  .fp-reveal.is-on .fp-rar.fp-t7::after { animation: fpRarFoil 3.2s ease-in-out .5s infinite; }
}
@keyframes fpRarFoil {
  0%        { transform: translateX(-130%); }
  38%, 100% { transform: translateX(130%); }
}

/* ⚠ ART + TEXT, NEVER ONE BOX — the same rule the binder pocket already
   states (rip/CLAUDE.md): a caption absolutely positioned over art is how
   "$10,660" became "$10,6(" once already. This is a plain flex row instead,
   so the value line always has the row's full width to print in. */
.op-result{display:flex;gap:14px;align-items:center;width:100%;text-align:left;
  background:var(--rip-panel);border:1px solid var(--rip-line);
  border-radius:var(--rip-r);padding:14px}
.op-result[hidden]{display:none}
.op-result__art{flex:0 0 auto;width:72px;aspect-ratio:5/7;border-radius:8px;
  overflow:hidden;background:var(--rip-panel-2)}
.op-result__art img{width:100%;height:100%;object-fit:cover;display:block}
.op-result__body{flex:1 1 auto;min-width:0}
.op-result__name{margin:0;font-family:var(--rip-display);font-weight:800;
  font-size:16px;line-height:1.3;color:var(--rip-fg);
  overflow-wrap:break-word}
.op-result__set{margin:4px 0 0;font-size:12.5px;color:var(--rip-fg-3);
  overflow-wrap:break-word}
/* Money in --rip-val everywhere else on the site; a grade chip is the same
   PSA vocabulary the slab label and the binder pocket already use, coloured
   by gradeColor() (rip-fmt.js) rather than a second colour table here. */
.op-result__val{margin:8px 0 0;display:flex;align-items:center;gap:8px;
  font-family:var(--rip-data);font-weight:800;font-size:17px;
  color:var(--rip-val);font-variant-numeric:tabular-nums}
.op-result__grade{display:inline-block;padding:2px 7px;border-radius:6px;
  font-family:var(--rip-data);font-weight:800;font-size:11px;color:#111}

/* ══════════════════════════════════════════════════════════════════════════
   ⚠⚠ THE REVEAL IS packs.htm's `#card-overlay`, VALUE FOR VALUE (2026-09-26).
   Aaron, with Pokémon, Marvel and One Piece reveals side by side: "Three
   overlays, all different. After I asked you to copy Pokémon. Fix it now."
   Every figure below was MEASURED off packs.htm's rendered overlay with
   getComputedStyle at 390px and 1440px — not read from its source, which
   spreads the same element across inline styles, !important overrides and
   four media blocks. It sits LAST in this sheet and every selector is scoped
   under `.fp-reveal`, so it outranks the per-rule history above without a
   single !important, and a page's own later <style> can still refine it.
     · one family: Bricolage (`--rip-display`), weight does the work
     · pills 9.5px/700, round, no glow — the grade look; NEW is the teal pill
     · the name is 15px solid white; the value is the headline
     · OPEN ANOTHER is the pack colour, flat; Share and Collection are cyan
   ══════════════════════════════════════════════════════════════════════════ */
.fp-reveal.is-on { background: rgba(0, 0, 0, .9); }
.fp-reveal { padding: 14px 11px; font-family: var(--rip-display); column-gap: 11px; }
/* packs.htm tops the identity column to the card, and its actions block spans
   BOTH columns (368 of 368 on a phone, 621 on desktop) — not a 380 island. */
.fp-reveal.is-on .fp-info { align-self: start; }
/* An empty mount (#fp-rar, #fp-star on a card with neither) is still a flex item
   and costs a whole gap — measured, 14px more between the pills than packs.htm. */
.fp-reveal .fp-idrow > span:empty { display: none; }
.fp-reveal .fp-tray { max-width: none; justify-self: stretch; }
/* ⚠ border-box: packs.htm has a global reset and this sheet does not, so a
   38px min-height + 20px of padding measured 60px tall here and 41 there. */
.fp-reveal .fp-box a.fp-collink, .fp-reveal .fp-acts button, .fp-reveal .fp-acts a { box-sizing: border-box; }
.fp-reveal .fp-idrow .fp-pill,
.fp-reveal .fp-new,
.fp-reveal .fp-dupe,
.fp-reveal .op-result__grade {
  font-family: var(--rip-display); font-size: 9.5px; font-weight: 700;
  letter-spacing: .04em; line-height: 1.5; text-transform: none;
  padding: 5px 11px; border-radius: 999px; border: 0; box-shadow: none;
  display: inline-block;
}
/* A fun pack has no grade; its set pill takes the grade pill's place AND look. */
.fp-reveal .fp-idrow .fp-pill { background: #9ca3af; color: #1a1a1a; }
.fp-reveal .op-result__grade { color: #1a1a1a; }
.fp-reveal .fp-dupe { color: #cbd5e1; border: 1px solid rgba(255,255,255,.18); padding: 4px 10px; }
.fp-reveal .fp-new {
  position: relative; overflow: hidden; color: #04211a;
  background: linear-gradient(135deg, #5eead4, #22d3ee);
  box-shadow: 0 0 14px rgba(34, 211, 238, .45);
}
.fp-reveal .fp-new::after {
  content: ""; position: absolute; top: 0; bottom: 0; left: -60%; width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.75), transparent);
  transform: skewX(-20deg); animation: fpNewShine 2.2s ease-in-out infinite;
}
@keyframes fpNewShine { 0% { left: -60%; } 60%, 100% { left: 130%; } }
@media (prefers-reduced-motion: reduce) { .fp-reveal .fp-new::after { animation: none; } }
.fp-reveal .fp-hero {
  font-family: var(--rip-display); font-size: 15px; font-weight: 800;
  line-height: 1.28; letter-spacing: -.02em;
  background: none; color: #f8fafc; -webkit-text-fill-color: #f8fafc;
  padding: 0; margin-left: 0; text-wrap: pretty;
}
.fp-reveal .fp-sub {
  font-family: var(--rip-display); font-size: 12px; font-weight: 500;
  color: #94a3b8; line-height: 1.28;
}
.fp-reveal .fp-colbar { font-family: var(--rip-display); font-size: 12.5px; font-weight: 800; color: #f8fafc; }
.fp-reveal .fp-colbar b { display: inline; }
.fp-reveal .fp-colbar u { display: inline; margin-left: 6px; font-size: 12.5px; font-weight: 800; color: #f8fafc; }
.fp-reveal .fp-colbar span { margin-top: 6px; background: rgba(255,255,255,.08); }
.fp-reveal .fp-colbar i { background: linear-gradient(90deg, var(--rip-act-2), var(--rip-act)); }
.fp-reveal .fp-acts button, .fp-reveal .fp-acts a {
  font-family: var(--rip-display); font-size: 12.5px; font-weight: 700;
  padding: 10px 8px; border-radius: 11px; min-height: 46px; box-shadow: none;
  background: rgba(34, 211, 238, .10); color: #e8fbff;
  border: 1px solid rgba(34, 211, 238, .38); border-left: 3px solid #22d3ee;
}
.fp-reveal .fp-acts button i, .fp-reveal .fp-acts a i,
.fp-reveal .fp-box a.fp-collink i { color: #22d3ee; font-size: 15px; }
/* Share is packs.htm's #btn-share: 13.5px / 12px at EVERY width — its phone
   rule names #ov-share-btn, an id that button does not carry. */
.fp-reveal .fp-acts button:not(.is-primary) { font-size: 13.5px; padding: 12px; }
.fp-reveal .fp-acts button.is-primary, .fp-reveal a.fp-primary:not(.rip-btn) {
  font-weight: 800; border: 0; box-shadow: none;
  background: var(--fp-accent, var(--rip-act));
  color: var(--fp-btn-text, #fff);
}
.fp-reveal .fp-acts button.is-primary i { color: inherit; font-size: inherit; }
.fp-reveal .fp-box a.fp-collink {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center; gap: 7px;
  font-family: var(--rip-display); font-size: 12.5px; font-weight: 700;
  padding: 10px 8px; border-radius: 9px; min-height: 38px; white-space: nowrap;
  text-decoration: none; background: rgba(34, 211, 238, .10); color: #e8fbff;
  border: 1px solid rgba(34, 211, 238, .38); border-left: 3px solid #22d3ee;
}
.fp-reveal .fp-hint {
  font-family: var(--rip-display); font-size: 12.5px; font-weight: 600;
  letter-spacing: .01em; color: #cbd5e1; opacity: 1;
}
@media (min-width: 560px) {
  .fp-reveal { padding: 16px 14px; column-gap: 16px; }
  .fp-reveal .fp-box a.fp-collink { padding: 9px 12px; }
  .fp-reveal .fp-idrow .fp-pill, .fp-reveal .op-result__grade { font-size: 11px; padding: 6px 13px; }
  .fp-reveal .fp-new { font-size: 10.5px; padding: 5px 12px; }
  .fp-reveal .fp-hero { font-size: 26px; }
  .fp-reveal .fp-sub  { font-size: 14px; }
  .fp-reveal .fp-acts button.is-primary { font-size: 14px; padding: 13px 10px; }
}
/* The rotating YouTube pill (rip/support-pill.js). Its inline style is
   packs.htm's, and packs.htm's own rules then widen it to the full column
   and leave 18px to the hint — measured 368 wide / 18 there vs 360 / 11 here.
   ⚠ The 45px gap above it copied packs.htm's EMPTY links row; with one panel
   for every page there is nothing to copy, so it sits on the tray's rhythm. */
.fp-reveal .fp-tray .rip-support { max-width: none !important; margin: 9px 0 7px !important; }

/* ══════════════════════════════════════════════════════════════════════════
   THE SHARED PANEL'S OWN PIECES — rip/reveal-panel.js (2026-09-26).
   ⚠ `[hidden]` MUST WIN: every panel element carries a class that sets its
   display (the pill is inline-block, the collection box is flex), and a class
   beats the UA's [hidden] rule — so a hidden value or collection box would
   still paint. One rule for the whole reveal, not one per element.
   ══════════════════════════════════════════════════════════════════════════ */
.fp-reveal [hidden] { display: none !important; }
/* The value headline — packs.htm's `.value-gold`, value for value. The band or
   tier gradient is set inline per pull; this is the gold it falls back to.
   ⚠ padding + box-decoration-break:clone are mandatory on background-clip:text
   (site rule); the negative margin keeps the INK on the name's left edge. */
.fp-reveal .rv-val {
  font-family: var(--rip-display); font-weight: 800; line-height: 1; letter-spacing: -0.03em;
  font-size: 46px; margin: 4px 0 0 -.14em; padding: 0 .14em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background: linear-gradient(135deg,#fffbe6 0%,#fde047 22%,#facc15 48%,#eab308 72%,#b45309 100%);
  -webkit-background-clip: text; background-clip: text;
  color: transparent; -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 2px 14px rgba(250,204,21,0.55)) drop-shadow(0 1px 2px rgba(0,0,0,0.7));
}
.fp-reveal .rv-note { font-family: var(--rip-display); font-weight: 600; font-size: 12px; color: #cbd5e1;
  font-style: italic; margin: 2px 0 0; line-height: 1.4; }
@media (min-width: 560px) { .fp-reveal .rv-val { font-size: 68px; } }
/* The page's extras (Watch a Short, out-of-pulls, reminder chip) — a column
   with the tray's own rhythm; empty, it costs nothing. */
.fp-reveal .rv-slot { display: flex; flex-direction: column; gap: 9px; }
.fp-reveal .rv-slot:empty { display: none; }
/* ⚠⚠ THE MODAL MUST BEAT THE REVEAL, AND TODAY IT ALREADY DOES — BY PARSE
   ORDER, WHICH IS NOT SOMETHING TO RELY ON. Both are `position:fixed` at 9999,
   so the tie is broken by DOM order: hoistReveal() appends `.fp-reveal` to
   <body> when ripFunPack() runs, and lib/card-modal.php's markup is parsed
   AFTER that script tag, so the modal ends up later and paints on top.

   ⚠ MEASURED, NOT ASSUMED — and the first version of this comment claimed the
   opposite. Removing this rule entirely leaves every assertion in E2b green,
   including the pixel-level "the modal paints ABOVE the reveal". So this line
   is INSURANCE, not the mechanism: it makes the outcome independent of where a
   page happens to emit the partial, and a page that moved it above the engine's
   script — or a host with no partial at all, where card-modal.js's ensureDom()
   creates the element at whatever the end of <body> is by then — would
   otherwise flip silently to the card opening behind the panel it was opened
   from. Say what a line does; do not credit it with a fix it did not make.

   ⚠ `body >` IS DELIBERATE: (0,1,1) beats binder-chrome.css's (0,1,0) whatever
   order the two sheets happen to load in. Raised here rather than in
   binder-chrome.css because this is a fact about these two overlays meeting on
   a pack page; the binder has no reveal and keeps the z-index it has. */
body > .col-dm-back { z-index: 10001; }
