/* Whitestone Juniors — components
   Built on tokens.css. Mapping: evals/order-sheet.md §6. */

/* ============================================================
   Buttons
   Spec: kit design.md §4.1 — min-height 52px, padding 16px 32px,
   700 16px, radius pill, transition 250ms cubic-bezier(.4,0,.2,1).
   ============================================================ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 52px;
  /* 16px 24px under 768px so "Gabung lewat Reclub" + the 20px glyph clears a
     375px column without wrapping; 16px 32px (kit §4.1, buenasopen measured) above. */
  padding: var(--space-4) var(--space-6);
  border: none;
  border-radius: var(--radius-pill);
  font-family: var(--font-text);
  font-size: var(--fs-btn);
  font-weight: 700;
  letter-spacing: 0.01em;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
  transition:
    transform var(--dur-ui) var(--ease-material),
    box-shadow var(--dur-ui) var(--ease-material),
    background-color var(--dur-ui) var(--ease-material);
}

/* The ONE bright surface on the page. Join actions only. */
.btn--join {
  background: var(--volt);
  color: var(--navy);
  box-shadow: 0 4px 14px rgba(6, 12, 51, 0.16);
}
.btn--join:hover {
  transform: translateY(-2px);
  box-shadow: 0 8px 24px rgba(6, 12, 51, 0.24);
}
.btn--join:active {
  background: var(--volt-press);
  transform: translateY(0);
}

/* Full-bleed on mobile, hugs its text from 768px — kit design.md §4.1 */
.btn--block { display: flex; width: 100%; }
@media (min-width: 768px) {
  .btn { padding: var(--space-4) var(--space-8); }
  .btn--block { display: inline-flex; width: auto; }
}

.btn__glyph { width: 20px; height: 20px; flex: none; opacity: 0.72; }

/* Header text link. Never a second filled button 200px above the hero button. */
.btn--text {
  min-height: 0;
  padding: var(--space-2) 0;
  background: none;
  border-radius: 0;
  color: inherit;
  font-size: var(--fs-small);
  font-weight: 700;
  border-bottom: 2px solid var(--volt);
  transition: opacity var(--dur-micro) var(--ease-material);
}
.btn--text:hover { opacity: 0.7; }

/* ============================================================
   Glass surface
   DERIVED layer (order-sheet §3). Blur is decoration — the scrim
   underneath does the contrast work, never backdrop-filter alone.
   ============================================================ */

/* Real glass, not a tinted panel. A fill plus a blur on its own is exactly what
   reads as "a card with a blue shade" — it needs all four of these:
     1. a LOWER fill opacity, so the photograph behind is genuinely visible
     2. a bigger blur with saturation + brightness lift — that is what makes
        refracted light look like light rather than mud
     3. an inset ring: bright top-left, dark bottom-right. This is the bevel,
        and it is the single biggest tell that a pane has thickness
     4. an outer shadow that lifts the pane off the image */
.glass {
  position: relative;
  padding: var(--space-6);
  /* Gradient, not a flat fill. A flat fill has to be opaque enough for the
     WORST text on the card, which means the photo is gone everywhere. Graded
     top-left (glassy, 0.20) to bottom-right (readable, 0.55), so the material
     reads where you look at it and the copy stays legible where it sits. */
  background: linear-gradient(158deg,
    rgba(255, 255, 255, 0.20) 0%,
    rgba(255, 255, 255, 0.38) 46%,
    rgba(255, 255, 255, 0.55) 100%);
  border: 1px solid var(--glass-stroke);
  border-radius: var(--radius-l);
  box-shadow:
    var(--glass-shadow),
    inset 1.5px 1.5px 0 rgba(255, 255, 255, 0.40),
    inset -1.5px -1.5px 0 rgba(6, 12, 51, 0.22);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(1.25) brightness(1.03);
  backdrop-filter: blur(var(--glass-blur)) saturate(1.25) brightness(1.03);
  overflow: hidden;
}

/* Diagonal sheen — the highlight a real sheet of glass catches off the sky.
   On ::before deliberately: .tilt::after is the pointer glare and is defined
   later in this file, so putting the sheen on ::after would silently delete it
   on every card that is also a .tilt. The 1px top-edge line this replaces is
   now handled better by the inset ring in the box-shadow above. */
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: linear-gradient(135deg,
    rgba(255, 255, 255, 0.28) 0%,
    rgba(255, 255, 255, 0.08) 30%,
    rgba(255, 255, 255, 0) 54%);
  pointer-events: none;
}
.glass > * { position: relative; z-index: 1; }

/* On bright sky the light fill disappears — invert it. */
.glass--dark {
  background: linear-gradient(158deg,
    rgba(11, 18, 77, 0.20) 0%,
    rgba(11, 18, 77, 0.40) 44%,
    rgba(11, 18, 77, 0.62) 100%);
  border-color: rgba(255, 255, 255, 0.34);
  color: #FFFFFF;
}
.glass--dark .micro,
.glass--dark .lead,
.glass--dark .small { color: rgba(255, 255, 255, 0.76); }

@media (min-width: 768px) { .glass { padding: var(--space-8); } }

/* No-backdrop-filter fallback.
   THIS IS WHY THE SAME CARD RENDERED AS TWO DIFFERENT COLOURS: the old fallback
   was a FLAT rgba(11,18,77,0.90) — a solid navy panel — while the supported path
   is a graded, mostly-transparent pane. A browser with graphics acceleration off
   (or any build exposing only the -webkit- property, which the old test did not
   check for) silently got a completely different design.

   Two fixes: test BOTH property names, and make the fallback the same gradient,
   only denser — there is no blur here to carry legibility, so the fill has to.
   The two paths now differ by a shade, not by a design. */
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .glass {
    background: linear-gradient(158deg,
      rgba(255, 255, 255, 0.26) 0%,
      rgba(255, 255, 255, 0.50) 46%,
      rgba(255, 255, 255, 0.72) 100%);
  }
  .glass--dark {
    background: linear-gradient(158deg,
      rgba(11, 18, 77, 0.24) 0%,
      rgba(11, 18, 77, 0.48) 44%,
      rgba(11, 18, 77, 0.70) 100%);
  }
}

/* ------------------------------------------------------------
   Tilt + glare
   Adapted from ~/design-library/_shared/scouted/pp-tilt-card.html
   (VERIFIED, zero-dep). PP's dark tokens stripped. Tilt cut from
   10deg to 6deg. Reduced-motion guard added (source had none);
   source's (hover: hover) gate kept in main.js.
   ------------------------------------------------------------ */

.tilt {
  transform: perspective(900px) rotateX(var(--tx, 0deg)) rotateY(var(--ty, 0deg));
  transition: transform var(--dur-micro) var(--ease-material);
  will-change: transform;
}
.tilt::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: radial-gradient(circle at var(--gx, 50%) var(--gy, 50%),
    rgba(255, 255, 255, 0.30) 0%, transparent 62%);
  opacity: var(--go, 0);
  transition: opacity var(--dur-ui) var(--ease-material);
  pointer-events: none;
  z-index: 2;
}

@media (prefers-reduced-motion: reduce) {
  .tilt { transform: none !important; }
  .tilt::after { opacity: 0 !important; }
}

/* ============================================================
   Photo placeholder — .ph-frame
   Deliberately self-documenting: carries the shot number from
   page-plan.md and the brief for whoever runs the shoot. Not a
   grey box, and not invented filler content.
   ============================================================ */

.ph-frame {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-6);
  aspect-ratio: 4 / 5;
  background:
    repeating-linear-gradient(135deg,
      rgba(11, 18, 77, 0.035) 0 10px, transparent 10px 20px),
    var(--navy-tint);
  border: 2px dashed rgba(11, 18, 77, 0.30);
  border-radius: var(--radius-m);
  text-align: center;
  color: var(--navy);
  overflow: hidden;
}
.ph-frame--wide { aspect-ratio: 16 / 10; }
.ph-frame--square { aspect-ratio: 1 / 1; }

.ph-frame__icon {
  width: 34px;
  opacity: 0.34;
  flex: none;
}
.ph-frame__id {
  font-family: var(--font-display);
  font-size: 1.125rem;
  letter-spacing: 0.06em;
}
.ph-frame__brief {
  font-size: var(--fs-micro);
  line-height: 1.45;
  letter-spacing: 0.01em;
  color: rgba(11, 18, 77, 0.68);
  max-width: 30ch;
}

.band--navy .ph-frame {
  background:
    repeating-linear-gradient(135deg,
      rgba(255, 255, 255, 0.05) 0 10px, transparent 10px 20px),
    rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.28);
  color: #FFFFFF;
}
.band--navy .ph-frame__brief { color: rgba(255, 255, 255, 0.68); }
.band--navy .ph-frame__icon { filter: brightness(0) invert(1); opacity: 0.4; }

/* ============================================================
   Copy-slot chips
   Every placeholder string carries its page-plan.md slot id and
   word budget. Drop .show-slots from <body> to hide them all.
   ============================================================ */

.slot {
  display: none;
  margin-bottom: var(--space-1);
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  background: rgba(11, 18, 77, 0.10);
  color: var(--navy);
  font-family: var(--font-text);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
/* Block, not inline — an inline chip shunts the first line of its own heading
   sideways and makes the type impossible to judge. */
.show-slots .slot { display: block; width: fit-content; }
.band--navy .slot,
.on-media .slot {
  background: rgba(255, 255, 255, 0.18);
  color: #FFFFFF;
}

/* ============================================================
   Floating join pill
   rocketpadel.com measured: fixed, bottom/right 20px, 72px tall.
   Kit §4.2 tightens to a 16px inset + safe-area.
   ============================================================ */

.float-join {
  position: fixed;
  right: var(--space-4);
  bottom: calc(var(--space-4) + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  min-height: 56px;
  padding: 0 var(--space-6);
  box-shadow: 0 6px 20px rgba(6, 12, 51, 0.30);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition:
    opacity var(--dur-ui) var(--ease-out-quart),
    transform var(--dur-ui) var(--ease-out-quart);
}
.float-join.is-visible { opacity: 1; transform: translateY(0); pointer-events: auto; }

/* ============================================================
   Header
   ============================================================ */

/* Floating pill. Not edge-to-edge: it sits inset from all three sides so the
   hero photo runs behind and past it, which is what makes it read as an object
   on the page rather than a browser chrome bar. */
.header {
  position: fixed;
  top: var(--header-top);
  left: var(--gutter);
  right: var(--gutter);
  z-index: 50;
  width: auto;
  max-width: var(--container);
  margin-inline: auto;
  height: var(--header-h);
  display: flex;
  align-items: center;
  padding-inline: var(--space-6);
  border-radius: var(--radius-pill);
  background: rgba(245, 244, 239, 0.70);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  border: 1px solid rgba(11, 18, 77, 0.10);
  box-shadow: 0 8px 28px rgba(6, 12, 51, 0.14);
  transition:
    background var(--dur-ui) var(--ease-material),
    border-color var(--dur-ui) var(--ease-material),
    box-shadow var(--dur-ui) var(--ease-material),
    color var(--dur-ui) var(--ease-material);
}
@supports not ((backdrop-filter: blur(2px)) or (-webkit-backdrop-filter: blur(2px))) {
  .header { background: rgba(245, 244, 239, 0.94); }
}
.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  width: 100%;
}
@media (min-width: 768px) { .header { padding-inline: var(--space-8); } }
.header__brand { display: flex; align-items: baseline; gap: var(--space-3); text-decoration: none; }
.header__wordmark { width: 108px; }
@media (min-width: 768px) { .header__wordmark { width: 132px; } }
.header__sub {
  font-size: 8px;
  font-weight: 700;
  letter-spacing: var(--ls-micro);
  text-transform: uppercase;
  color: var(--ink-70);
}

/* Scroll-triggered theme inversion over navy bands.
   Pattern measured on dennissnellenberg.com (3 ScrollTriggers, scrub 0). */
.header.is-inverted {
  background: rgba(6, 12, 51, 0.72);
  border-bottom-color: rgba(255, 255, 255, 0.12);
  color: #FFFFFF;
}
.header.is-inverted .header__wordmark { filter: brightness(0) invert(1); }
.header.is-inverted .header__sub { color: rgba(255, 255, 255, 0.72); }

.header__actions { display: flex; align-items: center; gap: var(--space-4); }

/* Burger. Bars are currentColor so the header's own inversion over navy bands
   carries them — no second rule, no second colour to keep in step. */
.burger {
  display: grid;
  place-content: center;
  gap: 5px;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: rgba(11, 18, 77, 0.08);
  color: inherit;
  cursor: pointer;
  transition: background var(--dur-micro) var(--ease-material);
}
.burger:hover { background: rgba(11, 18, 77, 0.16); }
.header.is-inverted .burger { background: rgba(255, 255, 255, 0.14); }
.header.is-inverted .burger:hover { background: rgba(255, 255, 255, 0.24); }

.burger__bar {
  display: block;
  width: 16px;
  height: 1.5px;
  background: currentColor;
  border-radius: 2px;
  transition: transform var(--dur-micro) var(--ease-material);
}
.burger[aria-expanded="true"] .burger__bar:first-child { transform: translateY(3.25px) rotate(45deg); }
.burger[aria-expanded="true"] .burger__bar:last-child  { transform: translateY(-3.25px) rotate(-45deg); }

/* ============================================================
   Fullscreen menu
   ============================================================ */

/* [hidden] has to be forced: the display:flex below out-specifies the UA's
   `[hidden] { display: none }`, and the attribute is what keeps the links out
   of the tab order while closed. */
.menu[hidden] { display: none; }

.menu {
  position: fixed;
  inset: 0;
  z-index: 60;              /* .header is 50, the intro sheet 200 */
  display: flex;
}

.menu__scrim { position: absolute; inset: 0; background: var(--navy-deep); }

.menu__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  /* svh, not vh: on iOS a vh panel sits under the address bar and the join pill
     at its foot is the part that goes missing. */
  height: 100svh;
  padding: calc(var(--header-top) + var(--space-2)) var(--gutter) var(--space-8);
  color: #FFFFFF;
}

/* Mirrors the header pill's own inset so the close button lands where the
   burger was, rather than near it. */
.menu__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  height: var(--header-h);
  padding-inline: var(--space-6);
  max-width: var(--container);
  width: 100%;
  margin-inline: auto;
}
@media (min-width: 768px) { .menu__top { padding-inline: var(--space-8); } }

.menu__wordmark { width: 108px; filter: brightness(0) invert(1); }
@media (min-width: 768px) { .menu__wordmark { width: 132px; } }

.menu__close {
  display: grid;
  place-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.14);
  color: #FFFFFF;
  cursor: pointer;
  transition: background var(--dur-micro) var(--ease-material);
}
.menu__close:hover { background: rgba(255, 255, 255, 0.24); }
.menu__close svg { width: 18px; height: 18px; transition: transform var(--dur-ui) var(--ease-out-quart); }
@media (hover: hover) { .menu__close:hover svg { transform: rotate(90deg); } }

.menu__nav {
  display: flex;
  flex: 1;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-2);
  max-width: var(--container);
  width: 100%;
  margin-inline: auto;
  padding-inline: var(--space-6);
}
@media (min-width: 768px) { .menu__nav { padding-inline: var(--space-8); } }

.menu__nav a {
  position: relative;
  display: block;
  width: fit-content;
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 1.5rem + 5vw, 5rem);
  line-height: 1.02;
  letter-spacing: var(--ls-display);
  text-transform: uppercase;
  text-decoration: none;
  color: #FFFFFF;
  /* Clearance for the underline below. Anton's Q drops a tail past the baseline
     and it crossed the rule without this. */
  padding-bottom: 0.1em;
  transition: color var(--dur-micro) var(--ease-material), transform var(--dur-ui) var(--ease-out-quart);
}

/* The hover underline. Wipes in from the left rather than fading, because a
   fade is a state change and a wipe is a direction — it reads as the eye being
   led along the word it is already reading.

   scaleX on a pseudo-element, not a border or a background-size trick: it is
   the only version that stays on the compositor, and the link itself is
   already spending its transform on the 10px nudge.

   These items are NOT join actions, so this is the one place --volt appears
   outside one. It survives dev-check (that rule tests element background
   colours and this is a ::after), but the rule it bends is the order sheet's,
   not the linter's: volt as the single repeated device is what makes the join
   pill read as the only thing on the page you can press. Mitigating it: the
   underline is transient, never more than one is lit, touch never sees it at
   all, and the pill sitting under this nav is volt-FILLED, so a 4px rule and a
   filled pill are not competing for the same job. Swap `--volt` for
   `--navy-tint` here if the dilution ever shows. */
.menu__nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0.02em;              /* under the Q tail, on the link's padding edge */
  height: max(2px, 0.045em);
  background: var(--volt);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 340ms var(--ease-out-expo);
}

@media (hover: hover) {
  .menu__nav a:hover { color: var(--navy-tint); transform: translateX(10px); }
  .menu__nav a:hover::after { transform: scaleX(1); }
}

/* Keyboard gets the same underline. The focus ring alone already passes, but a
   sighted keyboard user should see the same affordance a mouse user does. */
.menu__nav a:focus-visible::after { transform: scaleX(1); }

/* Reduced motion keeps the underline and drops the wipe — the affordance is
   the point, the direction is the decoration. */
@media (prefers-reduced-motion: reduce) {
  .menu__nav a::after { transition: none; }
}

/* Group captions. The menu had none, because with one group there was nothing
   to tell apart. */
.menu__label {
  margin: 0;
  color: rgba(255, 255, 255, 0.45);
}
/* No margin of its own inside .menu__nav: that block is vertically centred, so
   any gap here pushes the caption up toward the wordmark and it starts reading
   as part of the logo rather than as the heading of the list under it. The
   nav's own 8px gap is the whole spacing. */

/* The pages that are not this one. Set at body scale on purpose: they are real
   destinations, but they are not what anyone came for, and matching the display
   size above would say otherwise. */
.menu__pages {
  max-width: var(--container);
  width: 100%;
  margin-inline: auto;
  padding: var(--space-6) var(--space-6) var(--space-8);
}
@media (min-width: 768px) { .menu__pages { padding-inline: var(--space-8); } }

.menu__pages .menu__label { margin-bottom: var(--space-2); }

.menu__pages a {
  display: inline-block;
  margin-right: var(--space-6);
  font-size: var(--fs-body);
  font-weight: 700;
  color: rgba(255, 255, 255, 0.82);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color var(--dur-micro) var(--ease-material),
              border-color var(--dur-micro) var(--ease-material);
}
/* Deliberately NOT the volt wipe the big links get. That underline is the
   flourish on the four items this menu exists for; spending it here too would
   flatten the hierarchy the sizing just built. */
.menu__pages a:hover,
.menu__pages a:focus-visible { color: #FFFFFF; border-bottom-color: rgba(255, 255, 255, 0.5); }

.menu__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  max-width: var(--container);
  width: 100%;
  margin-inline: auto;
  padding: var(--space-6) var(--space-6) 0;
  border-top: 1px solid rgba(255, 255, 255, 0.15);
}
@media (min-width: 768px) { .menu__foot { padding-inline: var(--space-8); } }

.menu__ig { font-size: var(--fs-small); color: rgba(255, 255, 255, 0.70); }

.menu :focus-visible { outline-color: var(--volt); }

/* ============================================================
   FAQ — native <details>, so disclosure semantics come free
   ============================================================ */

.faq__item {
  border-bottom: 1px solid rgba(255, 255, 255, 0.16);
}
.faq__item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-6) 0;
  font-size: var(--fs-h3);
  font-weight: 700;
  cursor: pointer;
  list-style: none;
  transition: color var(--dur-micro) var(--ease-material);
}
.faq__item > summary::-webkit-details-marker { display: none; }
/* Chevron-down, rotating 180deg on open at 200ms and sitting at 60% opacity —
   prompt-accordion's ChevronDownIcon spec, drawn with two borders so the page
   ships no icon dependency. Replaces a "+" that was tinted --volt; that colour
   is reserved for join actions and a disclosure marker is not one. */
.faq__item > summary::after {
  content: "";
  flex: none;
  width: 9px;
  height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  opacity: 0.6;
  transform: translateY(-3px) rotate(45deg);
  transition: transform 200ms var(--ease-material);
}
.faq__item[open] > summary::after { transform: translateY(2px) rotate(225deg); }
.faq__item > summary:hover::after { opacity: 1; }
.faq__answer { padding-bottom: var(--space-6); max-width: 62ch; color: var(--ink-70); }

/* ============================================================
   Photo deck — .deck
   Port of the hub's prompt-image-stack (React + Next/Image +
   Framer Motion + Tailwind). Behaviour is in js/deck.js, and the
   header there lists what survived the port and what did not.
   This file is only the surface.

   Sizing is the one thing the source has no answer for: it hard-
   codes w-96 / w-64 with no responsive branch and does not fit a
   375px column. One custom property drives the card here and the
   stage derives its height from it, so the fan cannot overflow.
   ============================================================ */

.deck {
  /* 268px was the source's w-64 scaled to this grid, and at 1440 it left the
     deck marooned in the middle of a 1120 container with a left-aligned
     heading. 340 gives the fan enough width to hold the band on its own. */
  --deck-card-w: clamp(184px, 46vw, 340px);
  --deck-card-h: calc(var(--deck-card-w) * 1.4);   /* the source's 5:7 */
  display: grid;
  justify-items: center;
  gap: var(--space-5);
}

.deck__stage {
  position: relative;
  width: 100%;
  /* The card plus the fan's own travel. The cards are centred and the fan only
     ever moves up, so the box needs twice the total rise (4 x --fan-y) to keep
     the back card inside it — plus a little for the 1.1 drag scale. */
  height: calc(var(--deck-card-h) + var(--deck-card-w) * 0.28);
  /* The top card is pointer-captured while dragging, so the page must not
     scroll out from under a finger that is moving it. */
  touch-action: none;
}

.deck__card {
  position: absolute;
  inset: 0;
  margin: auto;
  width: var(--deck-card-w);
  height: var(--deck-card-h);
  overflow: hidden;
  border-radius: var(--radius-l);
  background: var(--paper-raised);
  border: 1px solid rgba(11, 18, 77, 0.10);
  box-shadow: 0 14px 34px rgba(6, 12, 51, 0.18);
  /* Only the top card takes the pointer. The fan behind it is a picture and
     must not swallow a tap meant for the photo on top. */
  pointer-events: none;
  will-change: transform;
}
.deck__card.is-top { pointer-events: auto; cursor: grab; }
.deck__card.is-dragging {
  cursor: grabbing;
  box-shadow: 0 25px 50px -12px rgba(6, 12, 51, 0.35);
}

/* Pre-init fan, and the no-JS one. gsap.set() overwrites these the instant
   deck.js runs; without them the five cards sit dead-centre on top of each
   other and the deck reads as a single photo until the script lands. */
.deck { --fan-x: calc(var(--deck-card-w) * -0.0469); --fan-y: calc(var(--deck-card-w) * -0.03125); }
.deck__card:nth-child(1) { z-index: 50; }
.deck__card:nth-child(2) { z-index: 40; transform: translate(var(--fan-x), var(--fan-y)) rotate(-5deg); }
.deck__card:nth-child(3) { z-index: 30; transform: translate(calc(var(--fan-x) * 2), calc(var(--fan-y) * 2)) rotate(-8deg); }
.deck__card:nth-child(4) { z-index: 20; transform: translate(calc(var(--fan-x) * 3), calc(var(--fan-y) * 3)) rotate(-11deg); }
.deck__card:nth-child(5) { z-index: 10; transform: translate(calc(var(--fan-x) * 4), calc(var(--fan-y) * 4)) rotate(-14deg); }

.deck__card img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  user-select: none;
  -webkit-user-drag: none;
  /* The source scales the whole card 1.05 on hover. GSAP owns this element's
     transform for the fan and the drag, so the lift moves to the photograph
     rather than fighting it. */
  transition: transform var(--dur-ui) var(--ease-out-quart);
}
@media (hover: hover) {
  .deck__card.is-top:hover img { transform: scale(1.05); }
}
/* Same branded fallback as .board: a blocked or failed hotlink fades the photo
   out and the shot label behind it shows through, not a broken-image icon. */
.deck__card img.is-failed { opacity: 0; }
.deck__card::after {
  content: attr(data-title);
  position: absolute;
  inset: 0;
  z-index: -1;              /* behind the photo, above the card's own fill */
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background:
    repeating-linear-gradient(135deg,
      rgba(11, 18, 77, 0.035) 0 10px, transparent 10px 20px),
    var(--navy-tint);
  font-family: var(--font-display);
  font-size: 1.125rem;
  letter-spacing: 0.06em;
  color: rgba(11, 18, 77, 0.55);
}

.deck__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
}
.deck__status {
  margin: 0;
  color: var(--ink-45);
  font-variant-numeric: tabular-nums;
}

/* Dragging is the source's only affordance, which leaves out anyone on a
   keyboard and anyone who never discovers it. Not a join action, so not volt,
   and not a second filled button either — the page spends its one filled
   surface on Reclub. */
.deck__next {
  min-height: 44px;
  padding: var(--space-3) var(--space-5);
  background: transparent;
  border: 1px solid rgba(11, 18, 77, 0.22);
  color: var(--navy);
  font-size: var(--fs-small);
}
@media (min-width: 768px) {
  .deck__next { padding: var(--space-3) var(--space-5); }
}
.deck__next:hover { background: rgba(11, 18, 77, 0.06); }
.deck__next:active { transform: translateY(1px); }
