/* Creator wheel cards — depth pass (Mena 2026-07-14: "needs a shadow
   or a glow"; white ring removed same day — "i hate the stroke").
   Layered drop shadow lifts the profile cards off the sky: a tight
   contact shadow + a soft ambient, blue-tinted for the hero.
   2026-07-16 (Mena): shadow reduced — roughly half the strength and
   tighter spread than the original pass. */

/* Shadow lives on BOTH selectors on purpose: with the .hvo-card-fx
   wrapper present (roster.js injects it — see below) the img's own
   shadow is clipped away by the wrapper's overflow:hidden and the
   wrapper's shadow shows; if roster.js ever doesn't run, the bare img
   keeps its shadow. Zero-JS resilient. */
.img3d .image3d,
.img3d .hvo-card-fx {
  box-shadow:
    0 4px 10px -6px rgba(9, 30, 60, 0.2),
    0 14px 28px -14px rgba(9, 30, 60, 0.22);
}

/* ── .hvo-card-fx — card composition layer (Task 3, 2026-07-19) ──────
   roster.js wraps each img.image3d in this span at init. It is the
   hover grow/part transform surface, the mobile press-feedback
   surface, and the Task-4 FLIP measurement source.

   GEOMETRY (why the wrapper carries rotateX(90deg)): the template's
   .image3d rule stands each card up out of the wheel plane with
   `transform: rotateX(90deg) rotateY(0) rotate(0)`. A plain wrapper
   span would lie EDGE-ON in the spoke plane — degenerate screen rect
   (useless for FLIP), invisible ::after, and its overflow:hidden would
   flatten the 3D chain. So the wrapper TAKES OVER the facing transform
   and the img inside is flattened to transform:none. Verified
   2026-07-19: GSAP never writes inline styles on .image3d or .img3d
   (it animates .group only), so this static re-attribution fights
   nothing. Net projection is IDENTICAL — same transform chain, one
   level up.

   ⚠ HARD RULE (README): .group and .img3d spokes stay untouched.
   ⚠ Every hover transform below MUST restate rotateX(90deg) first —
   a bare scale()/translateX() would lay the card flat in the wheel
   plane. CSS list interpolation keeps the transition smooth (shared
   prefix, missing functions pad to identity). */
.img3d .hvo-card-fx {
  display: block;
  position: relative;
  width: 100%;
  aspect-ratio: 1;
  transform: rotateX(90deg);
  backface-visibility: hidden;
  /* visible, not hidden (2026-08-23): the sub-count pill hangs half below
     the card edge, which a clipping wrapper would cut off. The two jobs the
     clip used to do moved onto the img below: its own squircle radius
     rounds it, and its shadow is disabled so only the wrapper's shows. */
  overflow: visible;
  border-radius: 18%;
  corner-shape: squircle;
  transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1);
}
.img3d .hvo-card-fx > .image3d {
  transform: none;        /* facing transform moved to the wrapper */
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  border-radius: inherit; /* self-rounds now that the wrapper no longer clips */
  corner-shape: squircle;
  box-shadow: none;       /* wrapper carries the shadow; unclipped, both would show */
}

/* Hover states (desktop, roster.js applies the classes; gated off
   while body.hvo-wheel-dragging). RIPPLE = SLIDE-APART (2026-07-20,
   fourth form — Mena rejected the interim scale-down version: the
   hovered card grows 1.18× and its neighbors MOVE ASIDE, ±12px screen
   immediate / ±5px second). The local (x,y) for that screen shift is
   probed per assignment in roster.js (--hvo-part-x/y): the cards'
   local axes rotate with the GSAP rings, so constants here would
   point the wrong way half the time. Stability lives in roster.js's
   two rules: freeze while the hovered card is unchanged, and
   diff-apply on handoffs (unchanged roles never re-animate).
   Every transform must restate rotateX(90deg) first. */
/* Hover states, FINAL form (2026-07-20 seventh iteration): grow +
   full-chain slide, 350ms eased by the base .hvo-card-fx transition,
   NO staggers. Local (x,y) per card via READ-ONLY matrix composition
   in roster.js — the old DOM probes wrote transition:none to measure,
   which CANCELS an in-flight CSS transition and SNAPS the card to its
   end pose: the invisible source of every "choppy" report. NEVER
   reintroduce write-based probing on transitioning elements. */
.img3d .hvo-card-fx.is-hot {
  transform: rotateX(90deg) scale(1.18);
}
.img3d .hvo-card-fx.is-parted {
  transform: rotateX(90deg)
    translate(var(--hvo-part-x, 0px), var(--hvo-part-y, 0px));
}

/* 2026-08-27: touch-action:none is GONE from the cards on purpose. It made
   the gesture depend on whether the finger happened to land on a moving 3D
   card or in the gap beside it — cards swallowed vertical page scroll, gaps
   handed the swipe to Safari — and the whole band now behaves the same way
   instead. roster.js claims a horizontal swipe with a non-passive touchmove
   preventDefault, which is the only thing that actually stops iOS taking the
   gesture, and hands a vertical one straight back to the page. The rejected
   white :active glare and the native tap highlight stay removed. */
@media (hover: none), (pointer: coarse) {
  .img3d .hvo-card-fx {
    -webkit-tap-highlight-color: transparent;
  }

  .img3d .hvo-card-fx::after {
    content: none;
  }
}
@media (prefers-reduced-motion: reduce) {
  .img3d .hvo-card-fx { transition: none; }
}

/* Task 4 (2026-07-19): the .img3d spokes are large TRANSPARENT 3D
   planes that crisscross in front of other rings' visible cards —
   they hit-test even though invisible, so clicks/hovers at many card
   pixels landed on a bare crossing spoke and the popup never opened
   (caught by the Task-4 clean-click verification). Spokes become
   non-interactive; the visible wrappers (and bare imgs, zero-JS case)
   re-enable. Presses in the gaps now fall through to the section —
   the document-level band press logic is coordinate-based and doesn't
   care. NO transform involved (GSAP rule untouched). */
.img3d {
  pointer-events: none;
}
.img3d .hvo-card-fx,
.img3d .image3d {
  pointer-events: auto;
}

/* Task 4 (2026-07-19): HVORoster.clearHoverInstant() strips hover
   classes under this — a hover-scale mid-transition would corrupt the
   popup's FLIP source measurement. */
.img3d .hvo-card-fx.hvo-notransition {
  transition: none !important;
}

/* Task 4: cards with creator data are keyboard-openable (role=button,
   tabindex=0 — roster.js). Site-language focus ring. */
.img3d .hvo-card-fx:focus-visible {
  outline: 2px solid var(--base--green, #d6fd70);
  outline-offset: 3px;
}

/* Grab-to-spin control (2026-07-16): the wheel is draggable — physics
   in roster.js. pan-y keeps vertical page scroll working on touch;
   horizontal drags drive the wheel. 2026-07-17: native grab/grabbing
   cursors removed. 2026-07-19: the dot's lime-ring grab state removed
   too (Mena) — no cursor change over the wheel at all. */
[data-hero-visual]._3d {
  touch-action: pan-y;
  user-select: none;
  -webkit-user-select: none;
}

/* The visual wheel still owns its original zero-layout 3D geometry, while the
   hero becomes the real gesture surface used by roster.js. Vertical page
   scrolling remains native; horizontal intent belongs to the wheel. */
.section_hero {
  touch-action: pan-y;
}

/* the spinning hub gets its own compositor layer — without this a fast
   drag repaints all ~27 card images per frame and stutters */
[data-hero-visual]._3d .wrap {
  will-change: transform;
}

/* while a wheel drag is live (roster.js toggles this on <body>), don't
   let the sweep select page text */
body.hvo-wheel-dragging {
  user-select: none;
  -webkit-user-select: none;
}

/* Slot where the deleted hero rating block sat ("Trusted by top
   creators worldwide" + stars, removed 2026-07-19 per Mena).
   FINAL spacing rule (Mena, 2026-07-19, settled after too many wrong
   readings — do not relitigate): gap(video top → lowest FRONTAL card,
   i.e. cards ≥70% of full width, across the wheel's sweep) ==
   gap(buttons → carousel top) ≈ 39px. The foreshortened edge slivers
   DO dip over the video's top corners at some rotation phases — that
   is accepted; the reference edge is the frontal band, full stop.
   ⚠ When re-measuring: sample for ≥20s (one full card-slot period at
   0.7875°/s — shorter windows lie about the envelope). 23px =
   measured equalizer; +10px, then +35px on Mena's eyeball calls
   (2026-07-19) → 68px. */
.hvo-rating-gap {
  width: 100%;
  /* 68 −100 −100 +30 +10 per Mena's calls (2026-07-20) → net −92:
     1px height + −93px margin (empty-div margin-collapse pattern) */
  height: 1px;
  margin-bottom: -93px;
  /* Task 4 (2026-07-19): pure spacer — must not swallow clicks meant
     for wheel cards whose projected quads dip into its box (measured:
     it occluded ~24% of frontal card-center hits across a period). */
  pointer-events: none;
}

/* Hero reorder (Mena 2026-07-20): the wheel moved UP — order is now
   h1 "multiplied." → wheel → "YouTube is global" paragraph → buttons →
   video. This clearance sits between the wheel block and the paragraph
   so the FRONTAL band clears the text; the arc's drooping edge cards
   flank the (narrow, centered) paragraph laterally by design. Tune by
   the sweep-measurement method only (README trap note). */
.hvo-wheel-clear {
  width: 100%;
  height: 48px;
  pointer-events: none;
}
/* pushes the wheel's projected band below the h1 ("multiplied." was
   hidden behind cards without it). 97px measured → h1→band = 39px,
   band→paragraph = 44px (sweep-measured 2026-07-20). */
.hvo-wheel-drop {
  width: 100%;
  height: 97px;
  pointer-events: none;
}
[data-hero-visual]._3d .image3d {
  -webkit-user-drag: none;
}

/* ── Sub-count pill (2026-08-23, Mena: "live subcount in a pill ... a black
   pill that looks like the now playing pill") ─────────────────────────────
   Geometry copied from the player's now-playing pill (.mlp_lang: inline-flex,
   5rem radius) at card scale, but BLACK per the ask. Absolutely positioned
   inside .hvo-card-fx so it rides the card's 3D plane, its hover grow and
   the Task-4 FLIP without any of its own JS. pointer-events none keeps the
   drag arbiter, hover states and popup activation exactly as they were. */
.img3d .hvo-card-fx .hvo-card-subs {
  position: absolute;
  left: 50%;
  /* Straddles the card edge (Mena 2026-08-23: "half of the pill should be
     outside the pfp and lower"): bottom 0 + translateY(50%) hangs exactly
     half the pill below the card at any pill size. */
  /* 2026-08-24 (Mena): the pill is hover-only now and straddles the STROKE
     edge, not the pfp edge. Static --hvo-ring-w (not the live width) so it
     never jitters while the stroke fattens during the morph. */
  bottom: calc(-1 * (var(--hvo-ring-gap) + var(--hvo-ring-w)));
  transform: translate(-50%, 50%);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  /* White pill / black text in BOTH themes (Mena 2026-08-24 — the earlier
     dark-mode polarity flip was removed). */
  background: #fff;
  color: #090a08;
  border-radius: 5rem;
  /* 2026-08-24 (Mena): pill + text shrunk 20%. Padding and font-size are the
     two dimensions that set the pill's size (border-radius 5rem is a
     round-it-fully value, capped by height, so it needs no change).
     0.18 -> 0.144, 0.55 -> 0.44, font 0.68 -> 0.544 = exactly 0.8x. */
  padding: 0.144rem 0.44rem;
  /* Same face as the now-playing pill (Mena 2026-08-23): the player's
     .mlp_lang mono stack, its weight and its no-tracking, at card size. */
  font-family: var(--_typography---font-family--alternate, Geistmono, Arial, sans-serif);
  font-size: 0.544rem;
  font-weight: 400;
  letter-spacing: normal; /* the hero's tight tracking would leak in via inheritance */
  line-height: 1.2;
  white-space: nowrap;
  pointer-events: none;
}

/* 2026-08-24 (Mena): dark mode matches LIGHT — white pill, black text. The
   polarity flip inherited from the player's Select-language pill is dropped
   for this one, so the pill reads identically in both themes. Base rule
   already sets #fff / #090a08, so dark simply has no override. */


/* ── Hover stroke + hover-only subs pill (2026-08-24, Mena) ──────────────
   "add a stroke around the creator profile thats hovered over. morph it in
   using the morph we use in the hero ... the stroke should have a small gap
   and not stick directly to the pfp" + "make the 24M pill also only appear
   on hover. move it down so its aligned against the stroke edge ... add a
   space and Subs after the 24M".

   The stroke morphs in with the hero's gooey url(#threshold) filter (defined
   inline in index.html, same document). TWO measured findings shaped this:

   1. A hairline stroke CANNOT take a hero-style blur. #threshold cuts alpha
      at ~0.549 and a 2px stroke's peak alpha falls under that at ~1.5px blur
      (measured in an isolated rig: visible at blur 1, GONE at 1.5/2/3/4).
      An 8/f-8 curve would leave the ring invisible for most of the morph and
      then pop. So the stroke FATTENS while blurred (7px -> 2px) as blur
      falls (4.2px -> 0), which keeps it visible at every f and reads as a
      real congeal. Curve lives in roster.js; --hvo-ring-w-live is its output.
   2. inset tracks the LIVE width so the gap to the pfp stays CONSTANT while
      the stroke fattens — the blob puffs outward, never inward over the
      face. Verified: inner edge stays at exactly -6.0px for f=0..1. */
:root {
  --hvo-ring-gap: 6px;   /* ~11.5% of a 52px card */
  --hvo-ring-w: 4.8px;   /* settled stroke width. 2px -> 6px (matched to
                            Mena's reference crop) -> 4.8px (-20%). */
  --hvo-ring-color: #d6fd70;
}

.img3d .hvo-card-fx::before { content: none; }
.img3d .hvo-card-fx.hvo-has-ring::before {
  content: "";
  position: absolute;
  inset: calc(-1 * (var(--hvo-ring-gap) + var(--hvo-ring-w-live, 4.8px)));
  border: var(--hvo-ring-w-live, 4.8px) solid var(--hvo-ring-color);
  border-radius: calc(18% + var(--hvo-ring-gap) + var(--hvo-ring-w-live, 4.8px));
  corner-shape: squircle;   /* matches .hvo-card-fx */
  pointer-events: none;     /* drag arbiter, hover states, popup untouched */
  opacity: var(--hvo-ring-o, 0);
  /* Driven whole by roster.js, because #threshold must NOT run at rest.
     It is a hard alpha cutoff (alpha*255 - 140) — it deliberately destroys
     anti-aliasing, which is what makes the hero's big text look gooey but
     leaves a curved hairline visibly jagged. It is also rasterised BEFORE
     the card's hover scale and the wheel's 3D scale, so those magnify the
     jaggies. So: threshold only while morphing (where the aliasing IS the
     goo), then `none` once settled, leaving a natively anti-aliased border.
     Handover happens at f>=0.92 where blur is ~0.3px, so it is invisible. */
  filter: var(--hvo-ring-filter, none);
  will-change: filter, opacity;
}

/* Pill fades in with the stroke. The suffix is a pseudo-element because
   roster.js rewrites the pill's textContent on every subs poll — a text
   node would be wiped. \00a0, not a plain space: CSS trims a leading space
   in content (that is why it first rendered "16MSubs"). */
/* Hover-only ONLY where hover exists. On touch the ring driver bails out
   (hover: none), so an unscoped opacity:0 here would hide the pill forever
   on phones — it stays always-visible there, exactly as before. */
@media (hover: hover) and (pointer: fine) {
  .img3d .hvo-card-fx .hvo-card-subs { opacity: 0; }
  .img3d .hvo-card-fx.hvo-has-ring .hvo-card-subs {
    opacity: var(--hvo-ring-pill-o, 0);
  }
}

@media (hover: none), (pointer: coarse) {
  .img3d .hvo-card-fx .hvo-card-subs {
    display: none;
  }
}
.img3d .hvo-card-fx .hvo-card-subs:not(:empty)::after { content: "\00a0Subs"; }

/* YouTube glyph before the count (2026-08-24, Mena). A pseudo-element for the
   same reason the "Subs" suffix is one: roster.js rewrites the pill's
   textContent on every subs poll, so any real child node would be wiped.
   Sized in `em` so it tracks the pill's font (it just shrank 20%, and the
   glyph followed with no second edit). aspect-ratio comes from the asset's
   own 120x100. `:not(:empty)` matches the suffix guard — a pill with no subs
   data yet must not show a lone floating icon. */
.img3d .hvo-card-fx .hvo-card-subs:not(:empty)::before {
  content: "";
  display: inline-block;
  /* 1em was visually tiny next to the mono digits (Mena 2026-08-24). 1.5em
     fills the pill's content box — the text's own line-height is 1.2em, so
     the glyph now sets the height and the pill grows only ~2px. Still `em`,
     so it keeps tracking the font. */
  height: 1.5em;
  aspect-ratio: 120 / 100;
  margin-right: 0.3em;
  background: url("../images/brands/youtube-icon.png") center / contain no-repeat;
  vertical-align: -0.12em;   /* optical centre against the mono digits */
}

/* ── Mobile "Swipe & Tap" doodle (Mena 2026-09-01) ────────────────────────
   His Art School Dropout lockup (images/swipe-tap-final.png, 1561x459,
   trimmed transparent PNG in HVO Lime). Lives in the wheel-clear band
   between the card wheel and the hero paragraph. Mobile/touch only: the
   desktop wheel is mouse-driven, so the hint would be noise there. */
.hvo-swipe-doodle {
  display: none;
}
@media (max-width: 767px) {
  .hvo-swipe-doodle {
    display: block;
    width: min(32vw, 145px);
    height: auto;
    /* Mena's iPhone verdict 2026-09-01 overrides the sweep numbers: less
       air above, more below. (The measured 55px band bottom is the edge
       cards dipping at the arc ends, not the visual center band.) */
    margin: -44px auto 52px;
    pointer-events: none;
  }
}
