/* ══ SECTION 2'S CARDS ══════════════════════════════════════════════════════
   Spec: 171 §§4–11, as amended by 173 and 174 §1's checklist. Xinlu's
   description is 170 §1, §1.2, §1.3, §1.5 and §1.6; the reference component is
   170 §2 and is React, so what is ported is the geometry and the timing, never
   the component (000 §3.1).

   ⚠️⚠️ THE FLATTENING RULE, IN THE SHEET RATHER THAN ONLY IN A DOCUMENT
   (172 §5, adopted verbatim by 173 §1.4, and it replaces 171 §4.3's version
   which was wrong in the safe direction and silent in the dangerous one):

     `perspective` reaches only the perspective element's OWN CHILDREN. The
     clip and the slide may sit ON the perspective element or ABOVE it,
     freely — measured, clip + slide + perspective all on one element still
     renders the fan at 0.7143. What must not happen is ANY ELEMENT AT ALL
     between the perspective element and the cards: a bare <div> with no
     overflow and no transform flattens it. If one is unavoidable it must
     carry transform-style: preserve-3d AND have no overflow other than
     visible and no grouping property — a zero-radius filter: blur(0px) was
     enough to flatten it.

   ⚠️ SO: DO NOT PUT A WRAPPER INSIDE .intro__deck. The failure is silent —
   the cards still render, they just stop having depth, and C3 is the only
   thing that would tell you. 171 §4.3 warned against putting the clip on the
   perspective element, which is harmless, and never named this.

   ── THE DIVISION, UNCHANGED (171 §4.2) ────────────────────────────────────
   cards.js writes custom properties and toggles classes. Every colour, blur,
   length, easing and transform is here. If a colour is being set from JS,
   that is the wrong file.

   ── WHAT IS DERIVED AND WHAT IS TYPED ─────────────────────────────────────
   cards.js measures the rendered fan once (and again on resize and after
   document.fonts.ready) and writes four px values: --card-ax and --card-ay
   place the block, --card-travel is how far it slides, --card-glow sizes the
   field. Everything else here is a dial. The literals below are only what
   renders for the frame before init runs, and on the ?cards=0 path. */

/* ⚠️ position: absolute, NOT a flex child, and this is 000 §5 entry 48.
   171 §4.3 made this a flex child of #intro pushed right with margin-left:
   auto, on the argument that the box model then puts its right edge on
   --rail-x for free. Measured, that holds only while the block FITS: at a
   900px block the right edge landed 130.9px past the boundary, with no error
   anywhere, because an auto margin resolves to zero once the flex line is
   full. And the space it competes for is the rail's, whose width is its
   longest TITLE — so real copy in the copy session would have broken this
   months after every looking decision was made (172 §3, 173 §1.1).

   ⚠️ right: calc() IS NOT A SECOND NUMBER. It reads the same --rail-x #intro's
   own padding reads, so ?railx= still moves both sides together and they
   cannot drift. 170 §1.5 rejected this form as "a second expression of the
   same fact" and was wrong on its own terms — there was never a second
   literal. C2's third arm is the one that catches a build where there is.

   ⚠️ AND IT NEEDS A REAL BOX (173 §1.2). 172 §3.1 offered "the block's layout
   width is 0, so there is nothing for the flex line to squeeze" as a virtue.
   It is a virtue for anchoring and fatal for clipping: this element carries
   overflow: hidden, and a zero-width box with overflow: hidden renders
   nothing at all. top/bottom: 0 also deletes any derived height — the fan
   measures 588px at 500x400 cards and needs no measurement to fit a container
   as tall as the section. */
/* ⚠️⚠️ ONE EXPRESSION, TWO CONSUMERS — NOT TWO COPIES OF ONE TRANSFORM
   (181 §3, 180 §1). .intro__cards-slide moves the cards INSIDE the clip and
   .intro__glow moves the glow OUTSIDE it, and they must never disagree about
   where the block is. Two transform declarations that agree today are 000 §5
   entry 48's shape, so there is one declaration and both read it.

   ⚠️ AND --card-slide / --card-travel / --card-ax / --card-ay NOW LIVE ON
   #intro rather than on .intro__cards-slide, because a property written on the
   inner element is invisible to the glow outside it. cards.js writes them here
   and measure()'s Trap 1 neutralisation targets the same element — if one moves
   without the other, the measurement silently feeds on its own output, which is
   000 §5 entry 11.

   ⚠️ ORDER, RIGHTMOST APPLIED FIRST: scale about the wrapper's centre (which is
   where the deck's origin sits, so the origin does not move), then place with
   the measured anchor, then slide. Both translates are therefore in PAGE
   pixels and the scale does not multiply them — 173 §3.6's trap, met by
   construction. */
#intro {
  /* ⚠️⚠️ --card-slide DEFAULTS TO 1 — THE FINAL POSITION — AND THE DEFAULT IS
     THE WHOLE POINT (171 §11, 036 §7.5, and .intro__rail's identical block in
     intro.css). Under prefers-reduced-motion scroll.js creates NO
     ScrollTrigger, so nothing writes this. A default of 0 would park the block
     half off screen permanently with no error anywhere. Stated in three places
     on purpose: here, in the reduce block at the foot of this file, and
     cards.js writes it explicitly on the reduced path with `reduced` first in
     the condition. 157 §4.5 records this exact bug shipping once already and
     rendering correctly by luck. C8 kills the JS writer and reads the computed
     value.

     ⚠️ THEY LIVE HERE AND NOT ON .intro__cards-slide ANY MORE. A declaration on
     the slide wrapper would SHADOW the inherited value for that element while
     .intro__glow kept reading this one — two sources for one fact, which is the
     thing --card-shift exists to prevent. */
  --card-slide: 1;
  --card-travel: 50%;      /* over-generous fallback for frame zero; cards.js
                              writes the measured px value */
  --card-scale: 1;         /* ?cardscale= — 173 §3.6, default set by 178 §1.6 */

  --card-shift:
    translate3d(calc(var(--card-travel, 50%) * (1 - var(--card-slide, 1))), 0, 0)
    translate(var(--card-ax, 0px), var(--card-ay, 0px))
    scale(var(--card-scale, 1));
}

/* ⚠️ THE GLOW LIVES OUTSIDE THE CLIP NOW, AND THAT WAS A SHIPPED DEFECT
   (180 §1, 181 §3). It used to sit inside .intro__cards, whose right edge IS
   the --rail-x boundary, so it was cut off at exactly the vertical line Xinlu's
   arrow pointed at. ⚠️ THE HAZARD WAS WRITTEN DOWN TWICE BEFORE IT WAS BUILT —
   170 §3.1 ("dangerous for the glow, which will be silently cut at the section
   edge") and 171 §5.3 ("it would clip the glow at a different place from the
   cards") — and the glow went inside the clip anyway. 000 §5 entry 28's family.

   ⚠️ BOTH CONDITIONS HAVE TO HOLD AND THEY PULL APART. The glow must overflow
   the boundary freely, AND it must still travel with the block — fixed instead,
   it would sit glowing in empty space while the block is half off, which is
   174's reason for putting it inside in the first place. So: same box as the
   clip, same --card-shift, no overflow.

   ⚠️ NO overflow PROPERTY HERE AT ALL. Adding one re-creates the defect, and it
   would look like tidying. */
.intro__glow {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: var(--intro-inset);
  pointer-events: none;
  transform: var(--card-shift);
}

.intro__cards {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: var(--intro-inset);
  overflow: hidden;

  /* ⚠️ LOAD-BEARING, NOT DECORATION. left: 0 means this box lies OVER the
     rail, so without this it would swallow every click on it — and the rail's
     five buttons are the section's only controls. C13's second arm removes
     this line and asserts elementFromPoint() then returns .intro__cards.

     left: 50% was considered and rejected (173 §1.2): it clears the rail at
     1440, 1280 and 1024 but not at 900, where the rail ends at 473 and the
     half-way point is 450 — and 900 is a desktop width, above the 768
     breakpoint. */
  pointer-events: none;
}

.intro__cards-slide {
  position: absolute;
  inset: 0;
  pointer-events: none;

  /* ⚠️ ORDER, STATED BECAUSE 171 §6.1 ASKS FOR IT. Rightmost is applied
     first, so: scale the block about the wrapper's centre (which is where the
     deck's origin sits, so the origin does not move), then place it with the
     measured anchor, then slide it. Both translates are therefore in PAGE px
     and the scale does not multiply them — which is 173 §3.6's trap, handled
     by construction rather than by remembering to divide.

     The block is ONE RIGID BLOCK and nothing inside it knows the slide
     exists — .intro__rail's mobile slide, mirrored (171 §5.1, intro.css:53). */
  transform: var(--card-shift);
}

/* One field, INSIDE the slide wrapper so it travels with the block (171 §7.4).
   Fixed instead, it would sit glowing in empty space while the block is half
   off — a lit area with no cards in it. Centred on the deck's own origin, so
   the two cannot disagree; ?cardglowx= / ?cardglowy= move it, and they exist
   because the fan climbs up and to the right and "behind the stack" therefore
   has a position (172 §11).

   --accent is #6E7FB8, base.css:31 — the same colour that lights the rail's
   titles, so one light crosses both halves of the section (170 §3.2.2 P4). */
.intro__glow-field {
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--card-glow, 600px);
  height: var(--card-glow, 600px);
  transform: translate(calc(-50% + var(--card-glow-x, 0px)),
                       calc(-50% + var(--card-glow-y, 0px)));
  background: radial-gradient(closest-side,
    color-mix(in srgb, var(--card-glow-c, var(--accent))
      calc(var(--card-glow-a, 0.3) * var(--card-in, 1) * 100%), transparent) 0%,
    transparent 100%);
  pointer-events: none;
}

/* ⚠️ A POINT, NOT A BOX, and the cards are centred on it. The block's position
   comes from --card-ax / --card-ay on the wrapper above rather than from this
   element, so that the anchor and the slide are expressed once, together, in
   page pixels. NOTHING GOES INSIDE THIS ELEMENT EXCEPT .card — see the rule at
   the top of this block. */
.intro__deck {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 0;
  height: 0;
  perspective: var(--card-persp, 900px);
}

/* ⚠️ THE RESTING STACK IS STATIC LAYOUT, NOT ANIMATION (171 §6.1). No GSAP
   touches it: --card-i is written once, in the markup, and this rule derives
   every slot from it. makeSlot(i) in the reference gives x: i*dx, y: -i*dy,
   z: -i*dx*1.5, zIndex: total - i, and that is exactly what is below.

   ⚠️ TRANSFORM ORDER, STATED (171 §6.1). Rightmost applied first: skew the
   card about its own centre, then centre it on the deck's origin, then move it
   to its slot. That is what gsap.set({x, y, z, xPercent: -50, yPercent: -50,
   skewY}) composes to, so the port matches the reference rather than
   resembling it. The two translations commute; the skew being innermost does
   not, and putting it outermost would shear the whole fan instead of each
   card.

   ✅ THE FAN DIRECTION IS CORRECT AS-IS AND MUST NOT BE MIRRORED. 170 §3.3
   item 1: both of Xinlu's sketches show the front card lowest and left-most
   with the others stepping up, right and narrower. An earlier note proposing a
   mirror was withdrawn by the drawing.

   ⚠️ NO transform-style: preserve-3d AND NO backface-visibility HERE, AND
   THAT IS DELIBERATE (172 §7, 173 §2 item 3). The reference sets both. Ours
   are LEAVES — their depth comes from .intro__deck's perspective, not from
   their own transform style — and measured, the fan holds at 0.7143 with
   transform-style: flat forced. Nothing ever rotates past 90 degrees, so
   backface-visibility can never do anything either. Two grouping-adjacent
   properties and five promoted layers removed, on the one element that already
   carries a backdrop-filter.

   ⚠️ NO will-change EITHER (173 §2 item 4, and it is REASONING not a
   measurement): five permanently promoted layers each carrying a backdrop
   filter, for a stack that is static except during the slide and the open. If
   it is wanted at all it belongs on the open card for the life of the
   timeline. */
.card {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--card-w, 440px);
  height: var(--card-h, 352px);
  box-sizing: border-box;

  --card-open: 0;          /* 0 resting, 1 fully open. cards.js writes it; the
                              shared timeline is what moves it (171 §8.3) */

  /* ⚠️⚠️ DECLARED HERE AND NOWHERE ELSE, AND THAT IS 000 §5's SCOPING BULLET
     BEING OBEYED RATHER THAN RE-LEARNED. --card-k is the one scalar the bar's
     height, the head's box and the title's size all ride when a card opens
     (198 §3 point 6), and it reads var(--card-open) — which cards.js writes on
     EACH .card element, at :784. A custom property's var()s resolve in the
     element that DECLARES it, so hoisting this to #intro would resolve
     --card-open against #intro, where it is unset, and every card would get the
     resting value forever. That is exactly the failure that broke the fan on
     2026-09-06. Declared on .card, it resolves per card and .card__head and
     .card__title inherit the finished number.

     ⚠️ 1 ON DESKTOP BY DEFAULT, AND THAT IS DELIBERATE. Desktop's open card is
     settled and approved (194 §1); ?cardbargrow= is 1 there, so this whole
     expression collapses to 1 and nothing desktop renders changes. */
  --card-k: calc(1 + (var(--card-bar-grow, 1) - 1) * var(--card-open));

  /* ⚠️ THE OPEN CARD TRAVELS TO z: 0, AND THAT IS NOT COSMETIC. Built with the
     open state as a margin offset on top of a fixed slot, card 3 opened to
     551.17px wide instead of the 661.41 it was given — exactly 0.8333x, which
     is s = 900/(900+180), its own slot's perspective foreshortening. The card
     was growing at the depth it was resting at, so both its size and its
     landing point were being projected by a factor nothing had accounted for,
     and it came to rest 12px from the rail instead of 54. Measured, then
     fixed: every slot term is multiplied by (1 - --card-open), so at 1 the
     card sits at (open-dx, open-dy, 0), s = 1, and the page-pixel destination
     cards.js measured is the page-pixel destination it arrives at.

     ⚠️ AND IT IS WHY THE OPEN LOOKS LIKE COMING FORWARD rather than growing in
     place — card 5 travels 540px of z as well as its x and y, which is what
     170 §1.2's "pops from the last position in the stacking queue" describes. */
  /* The placement and the box are shared with .deck-glow through a SELECTOR
     LIST below, not through a custom property — see the block above it. */

  z-index: calc(var(--card-n, 5) - var(--card-i));

  /* ⚠️ NO `border` PROPERTY — THE EDGE IS A RING ON ::before (189 §2, and see
     the block beside .card::before below for why B and not A). The card's box
     model is unchanged in the only way that matters, its OUTER size: box-sizing
     is border-box and width/height are the flags, so dropping a 1px border
     moves nothing outward. It does give the content box 2px more in each
     dimension; C18 and C19 were re-run for exactly that and both still pass. */
  border-radius: var(--card-radius, 12px);

  /* ⚠️ NO PADDING HERE ANY MORE, AND THAT IS WHAT MAKES THE BAR A BAR
     (176 §4.1, 178 §1.4). The padding moved onto .card__head and .card__body
     separately, because a band inside a 20px inset is a stripe, not a bar. The
     overflow: hidden above plus this border-radius then clip the bar's top
     corners to the card's own radius for free — which is what
     176-reference-card-stack.png shows. */
  display: flex;
  flex-direction: column;
  overflow: hidden;
  color: #fff;
  pointer-events: auto;

  /* ⚠️ NO box-shadow, AND ITS ABSENCE IS A FINDING (179 §3.5, 180 §7, 181 §7).
     ?cardshadow= was built in round 1 and measured not to read: on a black page
     an outward shadow has nothing to darken into, and the cards now carry a
     face 27.7 above black while the page is 0. Removed rather than left at a
     default nobody can see. ⚠️ What was actually wanted is ?carddepthdim=
     below — each card behind dimmer than the one in front, which is what makes
     176-reference-card-stack.png read as depth. */
}

/* ⚠️ THE GLASS: bar.css's RECIPE, NOT A SECOND ONE (092 §6's rule, 171 §7.1).
   bar.css:91-93 is rgba(5, 6, 13, 0.667) under blur(16px) saturate(130%), and
   0.667 is a tint Xinlu has already approved by looking at the capsule.

   ⚠️ BOTH SPELLINGS AND THE @supports PAIR, testing each spelling separately —
   the same pair bar.css:97 carries.

   ⚠️ THE OPEN CARD GETS ITS OWN TINT AND THAT IS A LEGIBILITY DECISION, NOT A
   flourish (171 §7.5). Measured (172 §7): a frosted card DOES take its frosted
   siblings into its backdrop — the back card's stripes read a first-difference
   of 1 through the front card against 112 with the filter off — so the stack
   behind genuinely enhances the glass, free, and answers Xinlu's question in
   170 §1.2 yes. But the same measurement is why the open tint has to be
   higher: the region with a card behind it keeps a residual range of 17
   against plain background's 4, so 120 words of body text sit over four times
   the structure. bar.css:65-88 is the precedent for what that costs — the
   capsule went 0.10 -> 0.667 because the dots measured 1.18:1 — and those
   numbers are the BAR's, over the AURORA, and do not transfer (000 §5 entry
   22). What transfers is the shape: two dials, not one. */
/* ⚠️⚠️ THE CARD GETS A FACE, AND IT IS A DELIBERATE DIVERGENCE FROM bar.css
   (177 §3, §3.1; 178 §1.1, §2.1). READ THIS BEFORE "CORRECTING" IT BACK.

   092 §6's rule — reuse bar.css's glass recipe, do not invent a second one —
   is what put rgba(5, 6, 13, 0.667) on these cards, and the rule is not wrong.
   ITS PRECONDITION IS ABSENT HERE. The bar's tint is a DARK tint over a
   BRIGHT, MOVING backdrop: its job is to knock the aurora down so the status
   dots stay legible, and 098 §5 measured it doing exactly that. Section 2's
   backdrop is BLACK. A dark tint over black subtracts nothing and adds
   nothing, so the card rendered as the page.

   ⚠️ THAT WAS THE WHOLE DIFFERENCE, AND IT IS MEASURED (177 §3), not a taste
   call. Every reference card is LIGHTER than the surface it sits on:

     176-reference-card-stack   page #131116   face #19171E   border #FFFFFF
     176-uiverse-glass-flat     page #1E1E1E   face #383838   border 0.1 white
     177-glass-card-face        page #1E1E1E   face #484E56   border none
     ours, before this change   page #000000   face = black   border 0.18

   A bright border on a face that matches the page reads as a floating OUTLINE.
   A bright border on a face lighter than the page reads as the EDGE OF A
   PANEL. Xinlu was looking at outlines and asking why they were not panels; no
   border value fixes that, because the border was not what was missing.

   ⚠️ SO IF YOU ARE HERE TO RESTORE bar.css's RECIPE, DON'T. Put something
   bright behind section 2 first, and then it would be the right change.

   THREE LAYERS, from 177 §7's example verbatim (items 1, 2 and 4 of §8):
     1. a vertical lift,    linear  #fff 0.06 -> 0.02
     2. an upper-left       radial  120% 120% at 20% 0%, #fff 0.08 -> transparent
        highlight, over it
     3. a diagonal sheen,   ::after linear 135deg #fff 0.12 -> transparent 40%
   ⚠️ Three gradients. NO FILTER, NO NEW ELEMENT — so they cost nothing in C9's
   terms, and C9 asserts that rather than assuming it.

   ⚠️ ?cardface= MULTIPLIES ALL THREE AND DEFAULTS TO 1x, NOT 3x. 177 §8.1: the
   PM predicted these low alphas would not transfer to a black page and the
   arithmetic corrected him — the same layers over #000000 give #3B404A, which
   is darker in absolute terms than the example's #484E56 but stands +59/+64/+74
   above its own page against the example's +42/+48/+56. More contrast, not
   less. ?cardface=0 removes the face and is C16's control.

   ⚠️ THE TINT STAYS AS THE BASE LAYER. It is inert on black (above), but it is
   what the @supports fallback and the open state both move, and the open card
   over four frosted siblings is the one place a dark tint does have something
   to subtract (172 §7 measured a residual range of 17 there). */
.card {
  --card-tint: rgba(5, 6, 13,
    calc(var(--card-alpha, 0.667)
         + (var(--card-open-alpha, 0.82) - var(--card-alpha, 0.667)) * var(--card-open)));

  /* ⚠️ THE TWO PER-CARD BLOBS, AND THEY ARE 177 §9 item 3 AFTER ALL — for a
     different reason than it predicted (181 §5, 180 §5). C16 confirmed the
     shared glow reaches the faces and carries the hue, landing on B - R = +14.
     What one radial behind the whole stack CANNOT do is give each card its own
     diagonal: each card receives whatever slice of the glow sits behind it,
     and the reference's effect is WITHIN each card. The glow did not fail on
     hue; it fails on being shared.

     ⚠️ SOFT STOPS, AND NO `filter`. 177 §7's reference blurs its blobs because
     its stops are hard (`transparent 45%`); a gradient that fades all the way
     to 100% is already blurry, and it is PAINT rather than a filter — so it
     adds nothing to the five backdrop filters C9 already prices at +433 us per
     composited frame while sliding. C20 measures soft-stop against blurred
     hard-stop, with a flat fill as the control that must fail.

     Alphas are 177 §7's, pre-multiplied by its own `opacity: 0.55`:
       rgba(120,170,255,0.22) x 0.55 = 0.121   upper-left, white-blue
       rgba(160,90,255,0.18)  x 0.55 = 0.099   lower-right, violet
     ?cardblob= multiplies both; 0 removes them.

     Painted top-first: the two blobs, then the radial highlight, then the
     vertical lift, then the tint — the same order 177 §7 composes in. */
  /* ⚠️⚠️ EVERY ADDITIVE LAYER IS MULTIPLIED BY --card-in, AND THAT IS THE FADE
     (187 §3.2). Xinlu: "the cards appear from the starting position by
     gradually changing from dark to bright, using this transition in brightness
     to conceal the starting line."

     ⚠️ NO GROUPING PROPERTY IS USED, AND THAT WAS THE WHOLE QUESTION. C24
     measured all three candidates and 188 carries the numbers; the deciding one
     was not the glass, which survived in every arm, but the GLOW. `opacity` and
     `filter: brightness()` both sit on .intro__cards-slide, and the glow has
     been a SIBLING of that since 181 §3 — so both would darken the cards while
     leaving the glow at full strength, which is a lit patch with faint cards in
     it rather than a block emerging from darkness. 171 §7.4's original worry,
     arriving from the other direction. Ramping the paint reaches the glow too.

     ⚠️ THE TINT IS NOT RAMPED. It is dark and inert on black (177 §3.1), so
     fading it would subtract nothing — the card would not get darker, it would
     get more transparent, which is not what was asked for. Everything ABOVE the
     tint is what makes the card visible, and that is exactly what ramps. */
  background:
    linear-gradient(0deg,
      rgba(0, 0, 0, calc(var(--card-depthdim, 0) * var(--card-i))) 0 100%),
    radial-gradient(70% 70% at 30% 20%,
      rgba(120, 170, 255, calc(0.121 * var(--card-blob, 1) * var(--card-in, 1))), transparent 100%),
    radial-gradient(70% 70% at 70% 80%,
      rgba(160, 90, 255, calc(0.099 * var(--card-blob, 1) * var(--card-in, 1))), transparent 100%),
    radial-gradient(120% 120% at 20% 0%,
      rgba(255, 255, 255, calc(0.08 * var(--card-face, 1) * var(--card-in, 1))), transparent 55%),
    linear-gradient(180deg,
      rgba(255, 255, 255, calc(0.06 * var(--card-face, 1) * var(--card-in, 1))),
      rgba(255, 255, 255, calc(0.02 * var(--card-face, 1) * var(--card-in, 1)))),
    var(--card-tint);
}

/* ⚠️⚠️ THE BORDER IS A RADIAL, LIT FROM ONE CORNER (189 §2). Xinlu: "the two
   edges that form the upper-left corner of each card need to be highlighted …
   the further an edge is from the highlight, the darker it gets."

   ⚠️ "FURTHER FROM THE HIGHLIGHT" IS DISTANCE FROM A POINT, SO IT IS A RADIAL
   AND NOT A LINEAR AT AN ANGLE (189 §2.3). The difference is visible, not
   pedantic: a linear at 135deg puts top-right and bottom-left at the same
   position along its axis, so they read EQUALLY bright. A radial from the
   corner varies with distance, and our cards are 440 x 352 — so bottom-left
   (352px away) must read BRIGHTER than top-right (440px away). C29's second
   control is a linear arm that must FAIL that comparison.

   ⚠️ AND THE ORIGIN AND EXTENT ARE THE FACE'S OWN. The face is already lit by
   radial-gradient(120% 120% at 20% 0%) above. Matching both means the border
   and the face are lit by ONE lamp — same kind of light, same place, same reach
   — instead of a card lit from a corner and outlined by a light that is
   everywhere. That is 189 §2.1's argument, and it is why this is derived from
   what is built rather than borrowed from the reference.

   ── WHY ::before AND NOT background-clip (189 §2.2, decided on measurement) ──
   ⚠️ A — `border: 1px solid transparent` with the face layers clipped to
   padding-box and the ring clipped to border-box — IS WRONG FOR A TRANSLUCENT
   CARD, and 190 carries the numbers. The ring layer sits UNDER the face layers,
   and our face is not opaque: it is a 0.667 tint over a backdrop-filter. So the
   ring bleeds through the whole interior instead of staying in the 1px edge.
   The technique assumes an opaque padding-box layer and we do not have one.

   ✅ B is this. ::before was free — only ::after was taken, by the sheen.
   ⚠️ A mask on `.card` was measured to DESTROY the glass (first-difference 215,
   see .card__body's block below). This is a mask on a CHILD, which carries no
   backdrop-filter of its own, so it should be safe — and "should be" is why
   C28 measures it rather than asserting it.

   ⚠️ inset: 0 AND NO BORDER ON .card, and the two go together. .card has
   overflow: hidden, which clips to the PADDING box — so a ring at inset: -1px
   over a transparent border would be clipped away entirely, which is the
   obvious-looking construction and it does not work.

   ⚠️ IT MUST KEEP RIDING --card-in or 188's arrival breaks: the border would
   stay lit while the rest of the card faded up out of darkness. Both ends are
   multiplied by it. */
.card::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  /* ⚠️ `circle`, AND NOT THE FACE'S `120% 120%`, AND THAT IS A CORRECTION TO
     189 §2.3 RATHER THAN A DEVIATION FROM IT. Two-value radial sizing makes an
     ELLIPSE with the box's own aspect: rx = 1.2w, ry = 1.2h. Under that metric
     top-right sits at t 0.667 and bottom-left at t 0.850, so the border gets
     BRIGHTER toward the top-right — the opposite of Xinlu's "the further an
     edge is from the highlight, the darker it gets", and the opposite of what
     C29 exists to assert. Built it the specified way first and measured
     TR 178.7 against BL 146.3; the arithmetic is in 190 §3.

     A circle is what distance-from-a-point means. ?cardborderfall= is its
     radius as a % of the card's WIDTH, so the falloff does not change shape
     when ?cardw= / ?cardh= move. */
  background: radial-gradient(
    circle calc(var(--card-w, 440px) * var(--card-border-fall, 120) / 100)
      at var(--card-border-pos, 20% 0%),
    rgba(255, 255, 255, calc(var(--card-border-hi, 1) * var(--card-in, 1))),
    rgba(255, 255, 255, calc(var(--card-border, 0.45) * var(--card-in, 1))));

  /* The ring: mask the whole box, subtract the content box, leave the padding.
     Both spellings, and `xor` is the -webkit- spelling of `exclude`. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;

  /* ⚠️ Load-bearing, exactly as on ::after: this covers the whole card, and
     without it C7's arm (c) would be testing this pseudo-element. */
  pointer-events: none;
}

/* ⚠️⚠️ ONE PLACEMENT RULE, TWO CONSUMERS — AND IT HAD TO BE A SELECTOR LIST
   RATHER THAN A CUSTOM PROPERTY (192 §3.3). .card and .deck-glow must never
   disagree about where the open card is, and two transform declarations that
   agree today are 000 §5 entry 48's shape.

   ⚠️ THE OBVIOUS FACTORING DOES NOT WORK AND IT FAILS SILENTLY. Declaring
   `--card-place` on #intro and reading it from both looks identical and breaks
   the fan: a custom property's COMPUTED value has its own var()s substituted in
   the context of the element that DECLARES it, and only then inherits. So
   var(--card-i) resolved against #intro — where it is unset — and every card
   got slot 0. Measured: C3's ratio went to 1.0000 with all five cards at 550px
   wide, and C6's open width to 550 instead of 661. A selector list substitutes
   nothing and each element supplies its own --card-i and --card-open.

   ⚠️ THE SKEW IS IN HERE AND THAT IS THE POINT. The card un-skews as it opens;
   a glow that did not follow would detach during the movement, which is the one
   moment it is most visible. C32 asserts the two rects agree within 1px at
   --card-open 0.25, 0.5, 0.75 and 1. */
.card,
.deck-glow {
  transform:
    translate3d(
      calc(var(--card-i) * var(--card-dx, 60px) * (1 - var(--card-open))
           + var(--card-open-dx, 0px) * var(--card-open)),
      calc(var(--card-i) * var(--card-dy, 70px) * -1 * (1 - var(--card-open))
           + var(--card-open-dy, 0px) * var(--card-open)),
      calc(var(--card-i) * var(--card-dx, 60px) * var(--card-dz, 1.5) * -1
           * (1 - var(--card-open))))
    translate(-50%, -50%)
    skewY(calc(var(--card-skew, 6deg) * (1 - var(--card-open))));
}

.card,
.deck-glow {
  width: calc(var(--card-w, 440px)
              + (var(--card-open-w, 660px) - var(--card-w, 440px)) * var(--card-open));
  height: calc(var(--card-h, 352px)
               + (var(--card-open-h, 700px) - var(--card-h, 352px)) * var(--card-open));
}

/* Layer 3 — the diagonal sheen. 177 §4 read "the light is at the upper left"
   off the pixels before this code was in the session, and 177 §8 found the
   135deg gradient in the code that produced them: two independent readings,
   same answer.

   ⚠️ pointer-events: none IS LOAD-BEARING. This covers the whole card, and
   without it C7's arm (c) — a click on the open card does nothing — would be
   testing this pseudo-element instead of the card, and the closed cards' grip
   buttons would stop receiving clicks entirely. */
.card::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(135deg,
    rgba(255, 255, 255, calc(0.12 * var(--card-face, 1) * var(--card-in, 1))), transparent 40%);
  opacity: 0.55;
  pointer-events: none;
}

/* ⚠️ THE CONTENT SITS ABOVE THE SHEEN, which is what the example does too —
   its .img and .textBox carry z-index 1 and 2 over a z-index: auto ::after.
   Without this the sheen would wash ~0.066 white over the title and the body
   text. .card is already a stacking context (positioned, with a z-index), so
   these resolve inside it and cannot escape into the deck's ordering. */
.card__head,
.card__body { position: relative; z-index: 1; }

.cards-glass .card {
  -webkit-backdrop-filter: blur(var(--card-blur, 16px)) saturate(130%);
          backdrop-filter: blur(var(--card-blur, 16px)) saturate(130%);
}

/* ⚠️ MOVES THE TINT VARIABLE, NOT `background`. Setting the shorthand here
   would wipe the two face gradients above and hand the no-backdrop-filter path
   a card with no face at all — the exact regression this round exists to fix,
   reappearing only on the browsers nobody tests. */
@supports not ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .card { --card-tint: rgba(5, 6, 13, 0.9); }
}

/* ⚠️ THE STRIP ALONG THE TOP IS THE ONLY PART OF A BACK CARD THE FAN LEAVES
   VISIBLE (171 §10.1, 170 §3.2.2 P1) — each card is offset up and to the
   right, so everything below this line is behind the card in front. Hence the
   index and the title live here and nothing else does.

   The <h3> is 173 §3.2's ruling and the outline entry is the feature: an open
   card is ~120 words of substantive content and a heading is how a
   screen-reader user finds and re-finds it. The five headings become section
   2's table of contents; the rail's five buttons do not appear in a heading
   list, so this duplicates nothing. */