/* ============================================================
   EON Library — locked preview + join overlay.
   Everything the no-membership state adds lives in this sheet:
   blurred artwork + lock chips, the topbar Join pill, and the
   tier/checkout overlay. Loaded AFTER app.css on purpose — the
   locked-image rules must out-cascade the library's hover resets.
   Reads the library's own theme tokens, so light/dark just work.
   ============================================================ */

/* --cta / --cta-ink moved to css/tokens.css when the player started
   sharing them (the player also runs on /watch, which does not load
   this sheet). Loaded before app.css; nothing else changed. */

/* ── locked artwork ──────────────────────────────────────────
   Mild blur: the shelf still sells itself — you can see there IS a
   film there — but nothing is watchable through the glass. Titles,
   durations and dates stay untouched and readable. */
/* The blur budget changed meaning on 2026-08-16. The art used to be a
   1280px jpeg, where blur(7px) was the ONLY thing softening it. It is
   now a 16px-wide still (videos.preview_art — the thumbnail URL had to
   stop being shipped, because for YouTube the URL is the video id), and
   scaling 16px up to a 320px card is itself a heavy blur. 7px on top of
   that was softer than the design ever asked for.
   At 16px, 1px of blur showed the interpolation itself — the art read
   as pixelated rather than frosted, because there simply were not
   enough pixels to carry a soft gradient.
   The source is 48px now (ART_W, src/lib/admin/preview-art.ts), which
   is enough to blur properly again, and 4px is the value that puts it
   back alongside the original full-res look.
   This number is free — a render-time filter, not stored data. The
   WIDTH is what costs: across the 153-video catalog, 16px = 87 KB,
   24px = 201 KB, 32px = 345 KB, 48px = 775 KB. Keep the two in step:
   more pixels need more blur, or the frame starts being legible and
   the blur stops being a paywall. */
/* Canvas artwork (locked preview) sizes like the img it replaces. */
.thumb canvas,
.upnext-thumb canvas {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* The blur stopped being a property of the SESSION. A member browsing an
   otherwise open library still meets locked tiles — any shelf the owner
   set to "locked" for their tier — so these hang off .locked itself.
   Only the packed-still overrides below stay scoped to .eon-locked,
   because a <canvas> is the one thing a member never has: they read
   videos_member, which carries no artwork, and their pictures come from
   the /t/ proxy as real images. */
.thumb.locked img,
.thumb.locked canvas {
  filter: blur(4px) saturate(0.85);
}
/* A blur only hides the pixel grid when its radius beats the distance
   between source pixels ON SCREEN — so the right value depends on how
   far the still is being stretched, not on taste:
     up-next rail  ~170px  /48 →  3.5px apart
     card          ~320px  /48 →  6.7px apart
     deck card     ~520px  /48 →  10.8px apart
   One number cannot serve all three, which is why the old 1200px
   featured hero looked pixelated at settings that were fine on the
   shelves — and why the deck, at 520px, sits between them. */
/* The deck card is now ~45% of the viewport and grows with the window,
   so a single number cannot serve it either — the blur has to track the
   width the still is being stretched to. 0.7vw is that spacing times the
   ~0.7 the calibration above works out at, floored at the shelf value and
   capped before it becomes fog. */
.eon-locked .deck-card .thumb.locked canvas {
  filter: blur(clamp(4px, 0.7vw, 16px)) saturate(0.85);
}
/* …and why the FRONT card fetches the real frame instead (frontArt / the
   preview-hero function) once it is big enough to show the grid. Nothing
   is left to hide there, so it goes back to 7px — the value calibrated
   against full-size artwork in the first place. The same 7px is what a
   MEMBER's locked card wants, and for the same reason: their picture is
   the real proxy image. One rule covers both. */
.deck-card .thumb.locked img {
  filter: blur(7px) saturate(0.85);
}
@media (max-width: 900px) {
  /* 300px there, which is inside the range the 4px base was calibrated
     for — and the front card keeps its packed still, so there is no
     full-size image to treat differently. */
  .eon-locked .deck-card .thumb.locked canvas {
    filter: blur(4px) saturate(0.85);
  }
}
/* Blur bleeds transparent at the edges; inside the clipped 16:9 thumb a
   nudge of scale hides it. The HERO must not scale: its caption lives
   inside the same frame, below the image, and a transformed image
   paints over it — there the soft edge just melts into the scrim. */
.thumb.locked img,
.thumb.locked canvas {
  transform: scale(1.06);
}
.card:hover .thumb.locked img,
.card:hover .thumb.locked canvas,
.card:focus-visible .thumb.locked img,
.card:focus-visible .thumb.locked canvas {
  transform: scale(1.06);
}

/* ── The Menu (guide): the join window's glass + pattern ────────
   Same background language as the Unlock-The-Matrix box — translucent
   glass over a slanted, blurred EON pattern — with every row, label
   and icon riding fully opaque above it at z-index 1. */
.sidebar {
  background: color-mix(in srgb, var(--bg-elev) 72%, transparent);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
}
.sidebar-pt {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0.10;
  filter: blur(2px);
}
.sidebar-pt svg {
  /* Bleed past the top/bottom so the -8° rotated field never bares
     a corner wedge at the sidebar's edges; the layer clips it. */
  position: absolute;
  top: -12%;
  left: 0;
  width: 100%;
  height: 124%;
  display: block;
  transform: rotate(-8deg) scale(1.5);
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .sidebar-pt { opacity: 0.14; }
}
:root[data-theme="light"] .sidebar-pt { opacity: 0.14; }
/* Dark theme sinks the field deeper into the glass. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .sidebar-pt { filter: blur(5px); }
}
:root[data-theme="dark"] .sidebar-pt { filter: blur(5px); }
.sidebar > *:not(.sidebar-pt) { position: relative; z-index: 1; }
/* Mobile drawer: the guide scrolls, but the field stays glued to the
   drawer's viewport — sticky, full-height, negative margins swallowing
   the 12px padding and its own flow height — so the pattern reaches
   the bottom no matter how long the guide gets. */
@media (max-width: 900px) {
  .sidebar-pt {
    position: sticky;
    inset: auto;
    top: 0;
    height: 100vh;
    height: 100dvh;
    margin: -12px -12px calc(12px - 100vh);
    margin-bottom: calc(12px - 100dvh);
  }
}

/* ── EON pattern, join window only ──────────────────────────────
   The library page is CLEAN (the full-page field is gone — owner's
   call); the pattern lives inside the Unlock-The-Matrix window,
   BEHIND the tier cards: the homepage band's EON/ONE/NEO grid in the
   full varying brand cycle, slanted as one piece (whole-pattern
   rotation, letters stay straight), softly blurred for depth. */
.join-pt-bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  border-radius: 16px;
  opacity: 0.13;
  filter: blur(2px);
}
.join-pt-bg svg {
  width: 100%;
  height: 100%;
  display: block;
  transform: rotate(-8deg) scale(1.35);
}
/* Phones: the panel is HEIGHT-bound for the slice-fitted svg, and the
   panel's height changes on every tap (a plan row unfolds, checkout
   swaps in) — so the pattern visibly re-zoomed on each click. Sizing
   the svg to the viewport instead makes its scale a constant; the
   panel just crops more or less of the same field. Desktop panels are
   width-bound and never had the jump — untouched. */
@media (max-width: 640px) {
  .join-pt-bg svg { height: 150vh; }
}
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .join-pt-bg { opacity: 0.17; }
}
:root[data-theme="light"] .join-pt-bg { opacity: 0.17; }
/* Everything in the panel rides above the pattern. */
.join-body { position: relative; z-index: 1; }

/* Preview tiles (locked library): no image exists at all — nothing to
   inspect. A quiet surface carries the lock chip. */
.thumb.noart,
.upnext-thumb.noart {
  background: var(--surface);
}

.lock-chip {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 54px; height: 54px;
  padding: 0 13px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.68);
  color: #fff;
  font-size: 27px;
  pointer-events: none;
  transition: color var(--dur-hover) ease;
}
/* "Unlock" hides folded inside the chip; hovering the card turns the
   lock Eon Yellow and unfolds the word beside it, also in yellow. The
   chip's own dark scrim keeps that readable in both themes. */
.lock-chip .lock-txt {
  max-width: 0;
  overflow: hidden;
  opacity: 0;
  font-family: var(--font);
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: #f3c516;
  white-space: nowrap;
  transition: max-width 0.28s var(--ease-move), opacity 0.2s ease, margin 0.28s var(--ease-move);
}
/* Unscoped from .eon-locked for the same reason the blur was: a member
   meets these tiles too, on any shelf their tier has set to "locked". */
.card:hover .lock-chip,
.card:focus-visible .lock-chip,
.deck-card:hover .lock-chip,
.deck-card:focus-visible .lock-chip {
  color: #f3c516;
}
.card:hover .lock-chip .lock-txt,
.card:focus-visible .lock-chip .lock-txt,
.deck-card:hover .lock-chip .lock-txt,
.deck-card:focus-visible .lock-chip .lock-txt {
  max-width: 80px;
  opacity: 1;
  margin-left: 7px;
}
/* A deck card is 520px at rest — the same scale the old featured
   hero was, and the chip is sized for the picture, not the page. */
.deck-card .lock-chip { min-width: 66px; height: 66px; font-size: 32px; padding: 0 16px; }
.deck-card .lock-chip .lock-txt { font-size: 17px; }
.upnext-thumb .lock-chip { min-width: 40px; height: 40px; font-size: 20px; padding: 0 6px; }
.upnext-thumb .lock-chip .lock-txt { display: none; }

/* ── Preview tiles (a member, outside their window) ──────────────
   Level 1, and deliberately NOT dressed like level 2. This tile plays.
   It plays to the cutoff and then asks, but the press is answered — so
   it keeps its real picture, unblurred and unscaled, and wears a small
   corner badge instead of a 44px chip planted in the middle of the
   frame. Blurring 143 of a paying member's 153 tiles would make the
   thing they bought look broken, and hiding the archive argues against
   the upgrade it is supposed to sell.

   Top-left because .dur owns bottom-right. No time on it: the cutoff is
   a percentage of each video, so any number here would be wrong on most
   tiles (see preview_cutoff_s). */
.pv-chip {
  position: absolute;
  left: 8px; top: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  height: 30px;
  padding: 0 11px 0 8px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  pointer-events: none;
}
.pv-chip .ms { font-size: 19px; line-height: 1; }
.pv-chip .pv-txt {
  font-family: var(--font);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Hovering the card warms it to the brand yellow — the same gesture the
   lock chip answers, at a quieter volume. */
.card:hover .pv-chip,
.card:focus-visible .pv-chip,
.deck-card:hover .pv-chip,
.deck-card:focus-visible .pv-chip {
  color: #f3c516;
}
.deck-card .pv-chip { height: 36px; padding: 0 14px 0 10px; gap: 6px; }
.deck-card .pv-chip .ms { font-size: 22px; }
.deck-card .pv-chip .pv-txt { font-size: 15px; }
/* The rail is 170px wide and the word does not fit beside a title. */
.upnext-thumb .pv-chip { left: 4px; top: 4px; height: 24px; padding: 0 6px; }
.upnext-thumb .pv-chip .ms { font-size: 17px; }
.upnext-thumb .pv-chip .pv-txt { display: none; }

/* The Listen half, on a video the member may only preview. grant_audio
   will refuse it — a signed URL is the whole file and cannot be capped —
   so the control says so before it is pressed rather than failing. */
.eon-mode-btn.eon-mode-locked { opacity: 0.72; }
.eon-mode-btn .eon-mode-lock { font-size: 16px; line-height: 1; }

@media (prefers-reduced-motion: reduce) {
  .lock-chip,
  .lock-chip .lock-txt,
  .pv-chip {
    transition: none;
  }
}

/* ── topbar Join pill ──────────────────────────────────────── */
.join-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 8px 16px;
  margin-right: 8px;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  color: var(--cta-ink);
  background: var(--cta);
  border: 1px solid transparent;
  border-radius: 20px;
  cursor: pointer;
  transition: opacity var(--dur-hover) ease;
}
.join-pill:hover { opacity: 0.88; }
.join-pill:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
/* The Join pill stays Eon Yellow in LIGHT too (owner's call) — the
   dark chip ink keeps it readable on cream.

   The ink was doing that job for the LABEL only. The chip itself
   measured 1.57:1 against the cream ground (#f3c516 on #fffae7), so
   the button had no edge at all in light — and on phones, where the
   label is short, the disc IS the affordance. The border was already
   in the box model at 1px transparent; inking it in light gives the
   component a 17.8:1 boundary without touching the brand colour. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .join-pill {
    background: #f3c516; color: #17101c; border-color: #17101c;
  }
}
:root[data-theme="light"] .join-pill {
  background: #f3c516; color: #17101c; border-color: #17101c;
}
.join-pill .ms { font-size: 18px; }
.join-pill-rest { white-space: pre; }

@media (max-width: 640px) {
  /* Keeps the verb, drops the object. 44px tall, still a real label. */
  .join-pill {
    min-height: 44px;
    padding: 8px 14px;
    margin-right: 0;
    gap: 6px;
  }
  .join-pill-rest { display: none; }
}

/* ── overlay shell ───────────────────────────────────────────
   A modal OVER the library, not a page of its own: the shelves stay
   visible behind a dimmed, softly blurred scrim — the pitch reads as
   an offer laid on top of the thing it unlocks, not a wall in front
   of it. Backdrop click and Esc dismiss (join.js). */
/* Above the player while a video is open (watch overlay is 100, its
   Close 110). At 60 the tiers opened UNDERNEATH the player: pressing a
   locked row in the up-next rail did exactly what it was supposed to
   and none of it was visible, so the press read as broken.
   Below the sign-in overlay at 150, because the tiers hand off to it
   when there is no account yet. */
body.eon-watching .join { z-index: 145; }

.join {
  position: fixed;
  inset: 0;
  z-index: 60;
  overflow-y: auto;
  color: var(--text);
  font-family: var(--font);
  display: none;
}
/* The scrim lives on a pseudo, NOT on .join itself: backdrop-filter
   on the scroll container would make it the containing block for the
   fixed-positioned Close and CTA bar, and they'd scroll away with
   the content. */
.join::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, #17101c 60%, transparent);
  -webkit-backdrop-filter: blur(7px);
  backdrop-filter: blur(7px);
}
/* Flex + margin:auto = the window sits dead-centre of any screen that
   fits it, and degrades to a normal scroll when one doesn't. */
.join.open { display: flex; }
.join { padding: 20px 0; }
body.join-open { overflow: hidden; }

/* Scrim fades, the card rises out of it a beat behind — same
   ease-out family as the rest of the library's motion. */
@media (prefers-reduced-motion: no-preference) {
  .join.open::before { animation: join-scrim 0.3s ease both; }
  .join.open .join-panel { animation: join-in 0.42s var(--ease-move) both; }
  .join.open .join-bar { animation: join-in 0.42s var(--ease-move) 0.05s both; }
  @keyframes join-scrim {
    from { opacity: 0; }
    to { opacity: 1; }
  }
  @keyframes join-in {
    from { opacity: 0; transform: translateY(16px) scale(0.985); }
    to { opacity: 1; transform: none; }
  }
}

.join-panel {
  position: relative;
  max-width: 1120px;
  width: calc(100% - 32px);
  margin: auto;
  padding: 44px 36px 52px;
  /* Glass window: the PANEL blurs what's behind it; the cards inside
     stay solid surfaces. Safe here — no fixed-position descendants
     (the Close button and CTA bar live on the overlay, not in the
     panel), so backdrop-filter can't re-anchor anything. */
  background: color-mix(in srgb, var(--bg-elev) 72%, transparent);
  -webkit-backdrop-filter: blur(18px);
  backdrop-filter: blur(18px);
  border: 1px solid var(--line);
  border-radius: 16px;
  /* Backlit like the E: a brand glow behind the window — Eon Yellow
     in dark, Eon Purple in light (--cta) — waking up on hover. */
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.45),
    0 0 90px color-mix(in srgb, var(--cta) 16%, transparent);
  transition: box-shadow 0.35s ease;
}
.join-panel:hover {
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5),
    0 0 140px color-mix(in srgb, var(--cta) 30%, transparent);
}

.join-close {
  position: fixed;
  top: 12px; right: 16px;
  z-index: 61;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 40px;
  padding: 8px 15px;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 500;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 20px;
  cursor: pointer;
}
.join-close:hover { background: var(--surface-2); }
.join-close:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

/* ── the access line (signature) ─────────────────────────────
   The login page's falling-drop rail, laid on its side as a status
   line — the one Matrix note in an otherwise disciplined page. */
.join-access {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 14px;
  font: 500 12px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-2);
}
.join-access strong { color: var(--cta); font-weight: 500; }

/* ── "Already a member? Sign in", on the eyebrow row ──────────
   COSTS NO HEIGHT, and that is the requirement, not a nicety: the tier
   ladder already overflows a phone, so a new row here would push the
   first price further off the screen. It rides the flex line that is
   already there, in the half of it that was empty.
   The eyebrow is uppercase monospace with .22em tracking — none of
   which suits a sentence — so the type is reset back to the UI font. */
.join-member {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Hard right, level with "access · preview" on the row's own centre
     line. The eyebrow is uppercase monospace with .22em tracking, none
     of which suits a sentence, so the type is reset to the UI font. */
  margin-left: auto;
  font: 400 14px/1 var(--font);
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-2);
}
/* The pill is taller than the 12px eyebrow it sits on, so it sets the
   row's height. Bought back out of the row's own bottom margin, not out
   of the ladder — the tier cards do not lose a pixel, and the pill
   separates itself from the heading better than empty space did.
   A class rather than :has(), which this audience's older phones do not
   all have; join.js adds it, and the row is unchanged without it.
   Desktop only: on a phone this sits out of flow entirely (below) and
   the row keeps its ordinary margin. */
@media (min-width: 641px) {
  .join-access.has-member { margin-bottom: 4px; }
}
/* Shaped like the topbar's Sign in (css/signin.css) on purpose: the two
   controls do the identical job in the two places somebody might look
   for it, and a link in one and a button in the other would read as two
   different offers. Slightly shorter than the topbar's 40px so it stays
   inside the headline's line box and costs the row no height. */
.join-member-go {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 32px;
  padding: 6px 15px;
  font: 600 14px/1 var(--font);
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: 20px;
  cursor: pointer;
  transition: background var(--dur-hover) ease, border-color var(--dur-hover) ease;
}
/* The pill is 32px because that is all the height this row can spare;
   the TAP TARGET is 44px, extended past it with a pseudo-element. A
   visible button cannot grow its own hit area with padding — that would
   inflate the shape itself — but a transparent child that overhangs it
   still belongs to the button for hit-testing. Vertical only: the
   question sits 10px to its left and must stay pressable-past. */
.join-member-go::after {
  content: "";
  position: absolute;
  inset: -6px 0;
}
.join-member-go:hover { background: var(--surface-2); border-color: var(--text-2); }
.join-member-go:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (max-width: 640px) {
  /* Top-left on a phone, and it costs the panel NOTHING.
   *
   * The mobile panel already carries 64px of top padding — reserved so
   * the fixed Close button has somewhere to sit — and only the right
   * half of that band is ever used. This drops into the empty left half:
   * out of flow, so the access row goes straight back to its own 12px
   * and the ladder below does not move down by a pixel.
   *
   * Offsets are against .join-body (position: relative), whose top edge
   * IS the end of that 64px padding — hence the negative top: -50 puts
   * the pill 14px below the panel's edge, mirroring the Close button's
   * 12px on the other side, and leaves 14px of air under it. left: 0
   * lines it up with "access · preview" and everything below. */
  .join-member {
    position: absolute;
    /* Offsets run from .join-body, whose top edge is the end of the
       panel's 64px of top padding — so this lifts the word up into that
       reserved band.
       Centred IN that band rather than pinned to the Close button's
       line: level with the Close put it hard against the panel's top
       edge, which read as falling off the top rather than as sitting in
       the window. 14px of text in a 64px band leaves 25px of air above
       and 25px below, which is the same breathing room the panel gives
       everything else. */
    top: -39px;
    left: 0;
    margin: 0;
    gap: 8px;
  }

  /* ── the phone gets less, not more ──────────────────────────────
     "Already a member?" followed by a filled grey pill put two heavy
     objects and seventeen characters of grey question into a corner
     that is 44px tall and already holds a Close chip. It read as
     clutter, not as an offer — the opposite of what this window is
     trying to look like.
     So the question goes and the chrome goes: a phone paywall says
     "Sign in" in plain text, which is what Netflix, Spotify and Apple
     all do at exactly this spot. No database match for "premium" in
     the UX guidance — this is the industry pattern, not a rule lookup.
     The word alone is unambiguous here: it sits above "access ·
     preview" on a window whose only other verb is "Continue".
     The hairline underline is the entire affordance. It is what keeps
     this a control rather than a caption, and it costs nothing. */
  .join-member-q { display: none; }
  .join-member-go {
    min-height: 0;
    padding: 0;
    font: 600 14px/1 var(--font);
    color: var(--text);
    background: none;
    border: 0;
    border-radius: 0;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 5px;
    text-decoration-color: color-mix(in srgb, var(--text) 32%, transparent);
  }
  .join-member-go:hover { background: none; }
  /* The text is 14px tall; the tap target is still 44. Widened
     sideways too, now that nothing sits beside it to press past. */
  .join-member-go::after { inset: -15px -14px; }
}
.join-rail {
  position: relative;
  display: block;
  width: 46px; height: 1px;
  background: var(--line-strong);
  overflow: hidden;
}
.join-rail i {
  position: absolute;
  top: 0; left: -18px;
  width: 18px; height: 1px;
  background: linear-gradient(to right, transparent, var(--cta));
  animation: join-slide 1.9s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
@keyframes join-slide { from { transform: translateX(0); } to { transform: translateX(64px); } }
@media (prefers-reduced-motion: reduce) {
  .join-rail i { animation: none; }
  /* NOTE: the accordion's own reduced-motion rules are at the FOOT of
     this file, not here — see the comment there. */
}

.join-head h2 {
  margin: 0 0 26px;
  font-size: clamp(24px, 4vw, 34px);
  font-weight: 700;
  letter-spacing: -0.01em;
}
.join-head .join-sub {
  margin: 0 0 36px;
  max-width: 64ch;
  font-size: 15px;
  line-height: 1.55;
  color: var(--text-2);
}

.join-note {
  margin: 0 0 20px;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
  line-height: 1.5;
  background: var(--accent-soft);
  color: var(--text);
}
.join-err {
  margin: 0 0 16px;
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
  background: rgba(240, 48, 48, 0.12);
  border: 1px solid rgba(240, 48, 48, 0.35);
  color: var(--danger);
  display: none;
}
.join-err.on { display: block; }

/* ── tier cards ──────────────────────────────────────────────
   Homepage grammar (number chip, colour-coded top rule, ✓ perks),
   tuned to convert: price is the biggest thing on the card, one CTA,
   tier 1 flagged and lit. --tc/--tink are set inline per tier. */
.join-tiers {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 16px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.jt {
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-top: 4px solid var(--tc);
  border-radius: var(--radius-lg);
  padding: 22px 22px 20px;
  cursor: pointer;
  transition: border-color var(--dur-hover) ease;
}
/* ── the card left over ────────────────────────────────────────
   Seven tiers in three columns leave one alone on its own row,
   and upgrade mode slices the list to any length at all, so this
   cannot be pinned to a tier. The last card, WHEN it is the one
   that started a row, takes the whole row and lays itself out
   sideways instead: identity left, perks in two columns, CTA
   right. Half the height, no hole.

   Written out twice because the "started a row" test depends on
   how many columns auto-fill actually produced — every 3rd card
   above 980px inside the 1120px panel, every 2nd below it. The
   two rules must NOT be merged: a 5-card upgrade list matches
   2n+1 but not 3n+1, and at three columns that card is not alone,
   so sharing the layout block would lay a one-column-wide card on
   its side. */
/* The perks take their OWN line across the whole band, and this is not
   a preference — the one-line version could not fit them.
   Measured on the seven-tier ladder at a 1046px band: identity 239 +
   price 154 + CTA 199 + gaps left the perk list 324px, and
   `grid-template-columns: 1fr 1fr` split that into 213px and 87px, not
   two halves. `1fr` is `minmax(auto, 1fr)`: the auto floor lets the
   first column keep its widest item and the second gets the remainder.
   At 87px "An EON mentor of your own" wrapped onto three lines and
   "Limited seats" onto two, which is what made the band 219px tall with
   a ragged hole down its middle.
   Giving them the full width instead: identity, price and CTA on the
   first line, four perks side by side underneath. auto-fit rather than
   a fixed count so a tier with two perks does not stretch them across
   a metre, and minmax(0,…) so a long perk shortens its column instead
   of starving its neighbour. */
@media (min-width: 980px) {
  .join-tiers > .jt:last-child:nth-child(3n + 1) {
    grid-column: 1 / -1;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 28px;
  }
  .join-tiers > .jt:last-child:nth-child(3n + 1) > .jt-head { flex: 0 0 auto; min-width: 210px; }
  /* Pushed to the end of the first line, so the space the perks left
     behind sits between the price and the CTA rather than inside the
     perk list. */
  .join-tiers > .jt:last-child:nth-child(3n + 1) > .jt-cta {
    flex: 0 0 auto;
    min-width: 190px;
    margin-left: auto;
  }
  .join-tiers > .jt:last-child:nth-child(3n + 1) > .jt-perks {
    order: 1;                /* after the CTA — i.e. on the next line */
    flex: 1 1 100%;
    grid-template-columns: repeat(auto-fit, minmax(0, 200px));
    column-gap: 24px;
  }
  /* The avatars + "all included" are a perk row of their own; left in
     the flow they would sit in a 200px column and wrap their label. */
  .join-tiers > .jt:last-child:nth-child(3n + 1) > .jt-perks > .jt-paths {
    grid-column: 1 / -1;
  }
}
@media (min-width: 641px) and (max-width: 979px) {
  .join-tiers > .jt:last-child:nth-child(2n + 1) {
    grid-column: 1 / -1;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 28px;
  }
  .join-tiers > .jt:last-child:nth-child(2n + 1) > .jt-head { flex: 0 0 auto; min-width: 210px; }
  .join-tiers > .jt:last-child:nth-child(2n + 1) > .jt-cta {
    flex: 0 0 auto;
    min-width: 190px;
    margin-left: auto;
  }
  /* Same reasoning as the wider band above — there is even less room
     here, so the 1fr 1fr split starved its second column harder. */
  .join-tiers > .jt:last-child:nth-child(2n + 1) > .jt-perks {
    order: 1;
    flex: 1 1 100%;
    grid-template-columns: repeat(auto-fit, minmax(0, 200px));
    column-gap: 24px;
  }
  .join-tiers > .jt:last-child:nth-child(2n + 1) > .jt-perks > .jt-paths {
    grid-column: 1 / -1;
  }
}

/* Hover = the card claims its colour: full outline in the tier colour
   and the CTA fills like its number square. (The permanent outline the
   emphasized card used to wear moved here, for everyone.) */
.jt:hover { border-color: var(--tc); }
.jt:hover .jt-cta { background: var(--tc); color: var(--tink); }
.jt-head { display: flex; align-items: flex-start; gap: 12px; }
.jt-n {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  background: var(--tc);
  color: var(--tink);
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-weight: 700;
}
.jt-head h3 { margin: 0; font-size: 16px; font-weight: 700; line-height: 1.25; }
.jt-tag {
  margin: 2px 0 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tc);
}
/* On cream, yellow/orange text can't carry 12px — the tag goes neutral
   and the tier keeps its colour in the chip, rule and flag. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .jt-tag { color: var(--text-2); }
}
:root[data-theme="light"] .jt-tag { color: var(--text-2); }
.jt-flag {
  margin-left: auto;
  align-self: flex-start;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  background: var(--tc);
  color: var(--tink);
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
/* ── the flag must not cost the title its line (desktop) ─────────────
   .jt-flag is a child of .jt-head, which is a flex row, so "MOST
   POPULAR" was taking ~110px of the head's width away from the name
   beside it. Only one card carries the flag, so only that card's title
   wrapped — "Matrix / Reloaded" over two lines — and a two-line title
   is a taller head, which pushes that card's price down a line.
   The damage is not the wrap, it is the price: ₨3,000 and ₨7,500 sit on
   one invisible line across the row and ₨10,000 sits below it, so the
   eye cannot compare the three by running along them. On a pricing
   table that is the one scan that has to work, and it broke on the
   tier the badge exists to sell.
   Lifting the flag out of the flow gives the name the full width back
   and puts every price on the same baseline again. It rides the card's
   4px top rule, in that rule's own colour (both are var(--tc)), so it
   reads as a tab ON the rule rather than as a label floating near it —
   and it is the same treatment the phone layout already uses, so the
   badge does not change identity between breakpoints.
   Desktop only, by request: the mobile rules below are untouched. */
@media (min-width: 641px) {
  .jt { position: relative; }
  .jt-flag {
    position: absolute;
    top: -9px;
    right: 18px;
    margin-left: 0;
    z-index: 1;
  }
}
.jt-price { margin: 2px 0 0; font-size: 28px; font-weight: 800; }
.jt-price span { font-size: 13px; font-weight: 400; color: var(--text-2); }
/* The rupee mark, unsquished: 800-weight condenses the ₨ ligature into
   a smudge — the currency sign sits lighter and a step smaller. */
.jt-cur { font-size: 0.72em; font-weight: 500; color: var(--text-2); margin-right: 2px; }
.jt-perks {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px;
  flex: 1;
  /* flex:1 pushes the CTA to the card's floor; without this, grid's
     default stretch hands that slack to the ROWS instead, and a
     three-perk card (Matrix Plus) reads as three lines drifting in
     space. Rows keep their natural height; the gap stays the gap. */
  align-content: start;
}
.jt-perks li {
  position: relative;
  padding-left: 24px;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text);
}
.jt-perks li::before {
  content: "✓";
  position: absolute;
  left: 0; top: 1px;
  display: grid;
  place-items: center;
  width: 16px; height: 16px;
  background: var(--tc);
  color: var(--tink);
  border-radius: var(--radius-sm);
  font-size: 11px;
  font-weight: 800;
  line-height: 1;
}
/* Each CTA wears its tier's colour — the number square's colour as a
   full outline; hovering fills it like the square (ink from --tink). */
.jt-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 44px;
  margin-top: 4px;
  padding: 0 16px;
  font-family: var(--font);
  font-size: 14px;
  font-weight: 600;
  color: var(--tc);
  background: transparent;
  border: 1px solid var(--tc);
  border-radius: 22px;
  cursor: pointer;
  transition: background var(--dur-hover) ease, color var(--dur-hover) ease;
}
.jt-cta:hover { background: var(--tc); color: var(--tink); }
.jt-cta:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.join-loading { color: var(--text-2); font-size: 15px; }
/* The Eon mark while the tiers load (styled inline in index.html). */
.join-body .eon-load { margin: 40px auto; }

/* ── desktop compact mode ──────────────────────────────────────
   Full-size chrome needs ~1250px of height; most desktops have less
   and were scrolling the modal. Below that, everything tightens one
   notch — margins, type, card innards — so as much of the grid as
   can physically fit sits on one screen. At seven tiers that is
   3 + 3 + a band rather than the 2×3 this was originally cut for,
   so the modal WILL scroll on a short desktop; the tightening still
   buys most of a row back. */
@media (min-width: 641px) and (max-height: 1260px) {
  .join-panel { margin: auto; padding: 26px 28px 30px; }
  .join-access { margin-bottom: 10px; }
  .join-head h2 { font-size: 24px; margin-bottom: 18px; }
  .join-tiers { gap: 12px; }
  .jt { padding: 16px 16px 14px; gap: 10px; }
  .jt-n { width: 28px; height: 28px; font-size: 14px; }
  .jt-head h3 { font-size: 15px; }
  .jt-tag { font-size: 11px; }
  .jt-price { font-size: 22px; }
  .jt-perks { gap: 6px; }
  .jt-perks li { font-size: 12.5px; line-height: 1.4; padding-left: 22px; }
  .jt-cta { min-height: 38px; }
}

/* ── phone plan picker ─────────────────────────────────────────
   Stacking the full ladder was two screens of scroll — a conversion
   killer, and worse now there are seven of them. On phones the SAME tier nodes restyle into an app-paywall
   picker: one-line radio rows (chip · name+tagline · price), the
   selected row tinted in its tier colour with its perks unfolding
   beneath, and a sticky bottom bar carrying the one CTA. Everything
   needed to decide sits in a single viewport. */
.join-bar { display: none; }

@media (max-width: 640px) {
  .join { padding: 16px 0 150px; } /* bottom clears the docked CTA bar */
  .join-panel {
    width: calc(100% - 20px);
    margin: auto;
    padding: 44px 16px 28px;
  }
  .join-tiers { gap: 10px; }
  .jt {
    position: relative;
    display: flex;
    flex-direction: row; /* the desktop card stacks; the row must not */
    flex-wrap: wrap;
    align-items: center;
    gap: 4px 12px;
    padding: 13px 14px;
    cursor: pointer;
    /* List grammar: hairline box + a tier-colour left rule.
       The desktop 4px top rule reads as clutter in a stack. */
    border: 1px solid var(--line);
    border-left: 4px solid var(--tc);
  }
  .jt.start { border-color: var(--line); border-left-color: var(--tc); }
  .jt.sel {
    border-color: var(--tc);
    background: color-mix(in srgb, var(--tc) 9%, var(--surface));
  }
  .jt:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
  .jt-head { flex: 1; min-width: 0; gap: 10px; align-items: center; }
  .jt-n { width: 28px; height: 28px; font-size: 13px; }
  .jt-names h3 { font-size: 15px; }
  .jt-tag { font-size: 10.5px; }
  .jt-flag {
    position: absolute;
    top: -9px; right: 10px;
    margin: 0;
    font-size: 10px;
    padding: 2px 8px;
  }
  .jt-price { margin: 0; font-size: 16px; white-space: nowrap; }
  .jt-price span { font-size: 11px; }
  /* ── the unfold ────────────────────────────────────────────────
     This was `max-height: 0 → 420px` over 300ms, and 420 is a guess —
     the real content is nearer 170. That is what made it feel cheap,
     and the reason is arithmetic rather than taste: the browser
     interpolates to 420 whatever the content needs, so opening reaches
     the true height about 40% of the way through and then spends the
     rest of the duration animating empty space, while closing sits
     dead-still for that same stretch before anything visibly moves.
     Fast-then-stuck opening, stuck-then-fast closing — the classic
     max-height accordion tell.

     Fixed in three parts.

     1. A ceiling that is close to the real height, so almost none of
        the duration is spent on air. Still a ceiling, not a guess at
        the exact value — it only has to not clip.
     2. Asymmetric motion. Arriving decelerates and takes its time;
        leaving accelerates and gets out of the way. One curve in both
        directions is what makes a panel feel mechanical.
     3. The content settles in rather than being wiped in by a clip.
        Each line lifts a few pixels and fades, staggered a frame or two
        apart, so the eye reads a list arriving instead of a box
        growing. The stagger is the whole difference between "expanded"
        and "unfolded" — it costs nothing and it is what every native
        list transition does.

     The row's own colour change is transitioned too; it used to snap. */
  .jt-perks {
    width: 100%;
    flex: none;
    max-height: 0;
    overflow: hidden;
    margin: 0;
    /* leaving */
    transition: max-height 200ms cubic-bezier(0.4, 0.0, 1, 1),
                margin     200ms cubic-bezier(0.4, 0.0, 1, 1);
  }
  .jt.sel .jt-perks {
    max-height: 260px;
    margin: 8px 0 2px;
    /* arriving */
    transition: max-height 380ms var(--ease-move),
                margin     380ms var(--ease-move);
  }
  .jt-perks li {
    opacity: 0;
    transform: translateY(-6px);
    transition: opacity 140ms ease-in, transform 140ms ease-in;
  }
  .jt.sel .jt-perks li {
    opacity: 1;
    transform: none;
    transition: opacity 300ms ease-out, transform 340ms var(--ease-move);
  }
  .jt.sel .jt-perks li:nth-child(1) { transition-delay: 70ms; }
  .jt.sel .jt-perks li:nth-child(2) { transition-delay: 110ms; }
  .jt.sel .jt-perks li:nth-child(3) { transition-delay: 150ms; }
  .jt.sel .jt-perks li:nth-child(4) { transition-delay: 190ms; }
  .jt.sel .jt-perks li:nth-child(5) { transition-delay: 230ms; }
  .jt {
    transition: background 280ms var(--ease-move),
                border-color 280ms var(--ease-move);
  }
  .jt-cta { display: none; }

  .join-bar {
    display: block;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 62;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
    background: var(--bg);
    border-top: 1px solid var(--line);
  }
  .join-bar .join-go { width: 100%; }
  .join-bar-note {
    margin: 7px 0 0;
    text-align: center;
    font-size: 12px;
    color: var(--text-2);
  }
}

/* ── checkout step ─────────────────────────────────────────── */
.join-co { max-width: 560px; }
.join-back {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin: 0 0 6px;
  padding: 0;
  font-family: var(--font);
  font-size: 14px;
  color: var(--text-2);
  background: none;
  border: 0;
  cursor: pointer;
}
.join-back:hover { color: var(--text); }
.join-back:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 4px; }

.join-sum {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 0 0 22px;
  padding: 14px 16px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 4px solid var(--tc);
  border-radius: var(--radius-lg);
}
.join-sum .jt-n { width: 30px; height: 30px; font-size: 14px; }
.join-sum-name { font-size: 15px; font-weight: 700; }
.join-sum-price { margin-left: auto; font-size: 15px; font-weight: 800; white-space: nowrap; }
.join-sum-price span { font-weight: 400; font-size: 12px; color: var(--text-2); }

.join-co form { display: grid; gap: 16px; }
.jf label { display: block; margin-bottom: 6px; font-size: 14px; font-weight: 500; color: var(--text-2); }
.jf input {
  width: 100%;
  box-sizing: border-box;
  min-height: 44px;
  padding: 10px 13px;
  font-family: var(--font);
  font-size: 16px; /* stops iOS zooming on focus */
  color: var(--text);
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  outline: none;
  transition: border-color var(--dur-hover) ease;
}
.jf input:focus { border-color: var(--focus); }
.jf .jf-err { display: block; margin-top: 6px; font-size: 13px; color: var(--danger); }

/* ── upgrade mode (members) ─────────────────────────────────
   The current plan pins above the grid as a settled fact — not a
   card, not clickable — and every card below it is a step UP. */
.join-cur {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 4px 0 2px;
  padding: 12px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 10px;
}
.join-cur .jt-n { width: 30px; height: 30px; font-size: 14px; }
.join-cur-names { display: flex; flex-direction: column; min-width: 0; }
.join-cur-names strong { font-size: 15px; }
.join-cur-names span { font-size: 12.5px; color: var(--text-2); }
.join-cur-flag {
  margin-left: auto;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
  border: 1px solid var(--line-strong);
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.join-up-lede { margin: 10px 0 14px; font-size: 13.5px; color: var(--text-2); }

/* The one-line upsell on an upgrade card: what this step adds. */
.jt-gain {
  align-self: flex-start;
  margin: 0;
  font-size: 12px;
  font-weight: 800;
  letter-spacing: 0.04em;
  color: var(--tink);
  background: var(--tc);
  padding: 3px 9px;
  border-radius: 999px;
}
/* Two gains can stack now — the archive and the learning paths are
   separate promises and are counted separately. Chips carry no margin of
   their own, so the second one asks for the gap. */
.jt-gain + .jt-gain { margin-top: 6px; }
@media (max-width: 640px) {
  .jt-gain { order: 1; } /* rides the top row beside the price */
  /* Side by side in the wrapped row, so the vertical rule above is
     wrong here — it would push the second chip onto its own line. */
  .jt-gain + .jt-gain { margin-top: 0; margin-left: 6px; }
}

/* ── learning paths ─────────────────────────────────────────
   The bouquet on the tier cards (a perks-list row without the ✓)
   and the picker / all-included row on the checkout step. */
.jt-perks .jt-paths { padding-left: 0; display: flex; align-items: center; margin-top: 2px; }
.jt-perks .jt-paths::before { content: none; }
.jt-paths img {
  /* Host portraits, not glyphs — keep them big enough to read faces. */
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  border: 2px solid var(--surface);
  background: var(--bg-elev);
}
.jt-paths img + img { margin-left: -14px; }
.jt-paths span {
  margin-left: 10px;
  font-size: 11px; font-weight: 800;
  letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--text-2);
}

.join-paths { margin: 14px 0 2px; }
.join-paths-hint { margin: 0 0 10px; font-size: 14px; font-weight: 700; }
.join-paths-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.jp {
  display: grid; justify-items: center; gap: 8px;
  padding: 14px 10px;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  cursor: pointer;
  font-family: var(--font);
  font-size: 13px; font-weight: 700; color: var(--text);
  text-align: center;
  transition: border-color var(--dur-hover) ease;
}
/* Fluid portrait: fills the card column up to 88px, shrinking with it
   on narrow phones so three-across never overflows. */
.jp img { width: 100%; max-width: 88px; aspect-ratio: 1; height: auto; border-radius: 50%; object-fit: cover; }
.jp[aria-pressed="true"] {
  border-color: var(--cta);
  background: color-mix(in srgb, var(--cta) 9%, var(--bg-elev));
}
.jp:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.join-paths-err { margin: 8px 0 0; font-size: 13px; font-weight: 700; color: var(--danger); }
.join-paths-err:empty { display: none; }
.join-paths-all {
  display: flex; align-items: center;
  padding: 12px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
}
.join-paths-all img {
  width: 64px; height: 64px;
  border-radius: 50%; object-fit: cover;
  border: 2px solid var(--bg-elev);
}
.join-paths-all img + img { margin-left: -16px; }
.join-paths-all p { margin: 0 0 0 12px; font-size: 14px; font-weight: 700; }
@media (max-width: 640px) {
  .jt-paths img { width: 48px; height: 48px; }
}

.join-pay { border: 0; margin: 0; padding: 0; display: grid; gap: 10px; }
.join-pay legend { padding: 0; margin-bottom: 8px; font-size: 14px; font-weight: 700; }
.payrow {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 13px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--line-strong);
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
}
.payrow:has(input:checked) { border-color: var(--cta); }
.payrow input { width: 18px; height: 18px; margin-top: 1px; accent-color: var(--cta); }
.payrow small { display: block; margin-top: 2px; color: var(--text-2); }
.payrow.soon { cursor: default; opacity: 0.6; }
.payrow.soon input { pointer-events: none; }
.pay-soon {
  margin-left: auto;
  align-self: center;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-2);
  border: 1px solid var(--line-strong);
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
}

.join-go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  width: 100%;
  min-height: 48px;
  font-family: var(--font);
  font-size: 15px;
  font-weight: 700;
  color: var(--cta-ink);
  background: var(--cta);
  border: 0;
  border-radius: 24px;
  cursor: pointer;
}
.join-go:hover { opacity: 0.88; }
.join-go:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }
.join-go[disabled] { opacity: 0.55; cursor: default; }
.join-fine { margin: 0; font-size: 13px; line-height: 1.5; color: var(--text-2); }

/* ── success step ──────────────────────────────────────────── */
.join-done h2 { margin: 0 0 8px; font-size: clamp(24px, 4vw, 32px); font-weight: 800; }
.join-done .join-sub { margin-bottom: 26px; }
.join-accts { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 0 0 22px; }
.join-acct {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: 18px;
}
.join-acct h3 {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--cta);
}
.join-acct p { margin: 0; font-size: 14px; line-height: 1.75; }
.join-acct .iban { font-weight: 700; }
.join-done-ctas { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px; }
.join-done-ctas .join-go { width: auto; padding: 0 22px; }
.join-ghost {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 48px;
  padding: 0 22px;
  font-family: var(--font);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  background: transparent;
  border: 1px solid var(--line-strong);
  border-radius: 24px;
  cursor: pointer;
}
.join-ghost:hover { background: var(--surface); }
.join-ghost:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

@media (max-width: 640px) {
  .join-panel { padding: 64px 16px 48px; }
  .join-accts { grid-template-columns: 1fr; }
  .join-close span { display: none; }
  /* The close chip, as glass rather than as a grey disc.
     It was an opaque --surface circle with a hairline --line edge,
     dropped on top of a blurred pattern: a flat grey coin sitting on a
     rich ground, which is the one element on this window that looked
     cheap. It borrows the panel's own language instead — translucent,
     blurred, faintly lifted — so it reads as part of the same glass the
     window is cut from. 44px is kept exactly; only the surface changes.
     The stroke comes down from 2.2 to 1.75: a heavy X at 16px reads as
     a warning, and this is a dismissal. */
  .join-close {
    width: 44px;
    min-height: 44px;
    padding: 0;
    justify-content: center;
    border-radius: 50%;
    background: color-mix(in srgb, var(--bg-elev) 58%, transparent);
    -webkit-backdrop-filter: blur(16px) saturate(1.25);
    backdrop-filter: blur(16px) saturate(1.25);
    border: 1px solid color-mix(in srgb, var(--text) 13%, transparent);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.22);
    transition: transform 180ms var(--ease-move), background 180ms ease;
  }
  .join-close svg { width: 16px; height: 16px; stroke-width: 1.75; }
  /* Presses in rather than lighting up — a hover state on a touch
     screen only ever fires as a stuck highlight after the tap. */
  .join-close:hover { background: color-mix(in srgb, var(--bg-elev) 58%, transparent); }
  .join-close:active { transform: scale(0.93); }
}

/* ── reduced motion, and it has to be LAST ───────────────────────────
   The rule it overrides — .jt-perks's transition — lives inside the
   `max-width: 640px` block far above, at the same specificity (0-1-0).
   Source order therefore decides, and the old
   `@media (prefers-reduced-motion: reduce) { .jt-perks { transition:
   none } }` near the top of this file lost that race: it was declared
   before the mobile rule and was silently doing nothing. Anyone who had
   asked their phone for less motion got the full unfold anyway.

   Kept at the foot of the file for that reason. Anything added here
   must stay here.

   The transforms are the part that matters — a fade is tolerable where
   movement is not — so those are neutralised as well as switched off,
   and the staggered delays are cleared so nothing arrives late. The
   panel still opens and closes; it simply arrives already there. */
@media (prefers-reduced-motion: reduce) {
  .jt,
  .jt-perks,
  .jt-perks li,
  .join-close,
  .join-member-go { transition: none; }
  .jt-perks li { opacity: 1; transform: none; }
  .jt.sel .jt-perks li { transition-delay: 0s; }
  .join-close:active { transform: none; }
}
