/* GameDay DJ Web Console — DEMO styles. Dark stadium theme, dependency-free. */
:root {
  --bg: #0c1118;
  --bg-2: #121925;
  --surface: #161e2b;
  --surface-2: #1b2433;
  /* Opaque RAISED plate for floating notification surfaces (.ach-toast). Android
     `surfaceVariant` verbatim + iOS NeonAiBorder's dark fill — byte-identical on
     purpose, so all three platforms render the same raised step. Opaque BY
     CONTRACT (NEON_AI_BORDER_STANDARD §6/§7.1.1: a translucent fill here shipped
     as a real defect — background content read straight through the card).
     NOT --surface-2: that's the generic `.toast` pill's fill. */
  --surface-raised: #232A38;
  --line: #243047;
  --line-2: #2f3e59;
  --text: #e7edf5;
  --text-2: #9fb0c8;
  --text-3: #6b7c98;
  --accent: #ff571f;      /* app orange (iOS brand accent) */
  --accent-d: #d8431a;
  --accent-bg: #341a10;
  --ic-orange: #e0894a;   /* calmed brand orange for the Press Box mic — same brightness range as the muted nav icons but pulled toward the brand hue with more saturation, so it reads as "our orange, quieted," not a muddy tan (design-consult tuning). Still clearly distinct from the hot active --accent — not the bright "pop". */
  /* ── Brand cool register (identity / chrome / ambient — NOT actions) ──
     COLOR JOBS — do not blur these:
       --brand-* (blue/purple/chrome): IDENTITY only — sidebar rail, header edge,
         wordmark glow, hero/empty/onboarding zones, KPI numerals, field-focus rings.
         NEVER inside data tables, status pills, or dense forms.
       --accent (orange): ACTION only — primary CTA, active nav, in-progress, live.
       --green/--amber/--red: STATUS only. Tables/forms stay neutral grayscale. */
  --brand-blue:    #3b6fff;   /* electric blue — the wordmark's dominant */
  --brand-blue-d:  #2a52d8;
  --brand-purple:  #a64dff;   /* wordmark purple */
  --brand-chrome:  #cfe0ff;   /* cool silver highlight (gradient-text top stop) */
  --bg-cool-top:   #141d31;   /* sidebar gradient top */
  --bg-cool-bot:   #0f1521;   /* sidebar gradient bottom */
  --brand-glow:    0 6px 26px -8px rgba(59,111,255,.28);   /* wordmark/hero bloom */
  /* ── Wallpaper + glass-panel system (2026-06-30) ──────────────────────────
     The console now sits on a full-bleed dark topo/soundwave wallpaper
     (assets/app-bg.webp — 2× (3080px) for hi-DPI/Retina sharpness; cyan glow
     upper-left, purple lower-right, matching the
     app icon + marketing site). Panels read as floating glass ABOVE it: a
     semi-transparent navy fill + cool hairline + a neutral drop shadow. Colored
     BLUE glow stays reserved for identity/chrome (contract above) — panels get
     a NEUTRAL shadow so data-dense tables never bloom. --accent-cyan is the
     wallpaper's cyan register (nav active/hover + field-focus rings); it is
     IDENTITY/chrome, NOT an action — orange (--accent) is still actions only. */
  --bg-base:       #070b14;                        /* deepest base behind the wallpaper */
  --accent-cyan:   #57c8ff;                        /* wallpaper cyan — nav active + focus rings */
  --accent-purple: var(--brand-purple);            /* alias — wallpaper purple register */
  --action-orange: var(--accent);                  /* alias — orange stays actions-only */
  --panel-bg:      rgba(16, 24, 39, 0.86);         /* glass panel fill over the wallpaper */
  --panel-bg-2:    rgba(22, 31, 48, 0.90);         /* raised/nested glass (switcher, inputs) */
  --panel-border:  rgba(122, 160, 220, 0.20);      /* cool hairline on glass panels */
  --panel-shadow:  0 10px 30px -12px rgba(0,0,0,.55), 0 2px 10px -6px rgba(0,0,0,.45);
  --panel-top-hi:  inset 0 1px 0 rgba(150, 182, 235, 0.10);  /* faint top highlight line */
  --green: #22c08a;
  --green-bg: #103a2c;
  --amber: #f0a500;
  --amber-bg: #3d2f08;
  --red: #ef5350;
  --red-bg: #3a1715;
  --radius: 10px;
  --radius-lg: 14px;
  --nav-w: 232px;
  /* Height of the top demo ribbon. 0 by default (authed console has no ribbon);
     set to 31px only when the .demobar is actually rendered (demo mode), so the
     full-height layout below tracks reality instead of always assuming a ribbon
     — otherwise the authed sidebar ends 31px short and the account row floats. */
  --demobar-h: 0px;
  font-synthesis: none;
}
body.has-demobar { --demobar-h: 31px; }
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
html { background: var(--bg-base); } /* deepest base — the wallpaper (body::before, z-index:-1) sits above it, and any gap falls back to this navy, not black */
body {
  position: relative;
  background: transparent;
  color: var(--text);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: 14px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

/* Preview ribbon */
.demobar {
  background: var(--accent-bg);
  color: #ffd9c9;
  font-size: 12.5px; font-weight: 500;
  text-align: center; padding: 7px 12px;
  border-bottom: 1px solid var(--line); /* neutral hairline — was orange (--accent-d), which clashed as a 2nd parallel orange line under the tour spotlight. "Preview" is still signalled by the dot + tinted bg. */
}
.demobar .dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; background: var(--accent); margin-right: 4px; vertical-align: middle; }
.demobar a { color: #fff; font-weight: 600; cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
.demobar a:hover { color: #ffd9c9; }

/* Guided tour */
.tour-wrap { position: fixed; inset: 0; z-index: 90; }
.tour-wrap.tour-center { background: rgba(5,8,13,.62); }
.tour-spotlight { position: fixed; border-radius: 10px; box-shadow: 0 0 0 9999px rgba(5,8,13,.62), 0 0 0 2px var(--accent); pointer-events: none; transition: top .22s ease, left .22s ease, width .22s ease, height .22s ease; }
.tour-card { position: fixed; background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--radius-lg); padding: 20px 22px; box-shadow: 0 18px 50px rgba(0,0,0,.55); transition: top .22s ease, left .22s ease; }
.tour-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.tour-step { font-size: 11px; color: var(--text-3); letter-spacing: .05em; }
.tour-skip { background: none; border: none; color: var(--text-3); font-size: 12px; cursor: pointer; }
.tour-skip:hover { color: var(--text); }
.tour-title { font-size: 17px; font-weight: 700; margin-bottom: 6px; }
.tour-body { font-size: 13.5px; color: var(--text-2); line-height: 1.55; }
.tour-dots { display: flex; gap: 6px; margin: 16px 0 14px; }
.tour-dots span { width: 6px; height: 6px; border-radius: 50%; background: var(--line-2); }
.tour-dots span.on { background: var(--accent); }
.tour-nav { display: flex; align-items: center; justify-content: space-between; }

/* Mobile gate — desktop-first console */
.mobile-note { display: none; }
@media (max-width: 820px) {
  .mobile-note { display: block; background: var(--amber-bg); color: #ffd98a; text-align: center; font-size: 12.5px; padding: 8px 12px; border-bottom: 1px solid var(--amber); }
}

/* App layout */
.app { display: flex; min-height: calc(100vh - var(--demobar-h)); }

/* Sidebar */
.nav {
  width: var(--nav-w); flex-shrink: 0;
  /* Raised glass rail over the wallpaper. The tall sidebar artwork
     (assets/sidebar-bg.jpg — a neon blue→purple "river" flowing top-to-bottom
     through topo lines + soundwaves) fills the WHOLE rail height via cover,
     left-anchored so the art-rich left column shows (the source's dark right
     third + its baked divider line crop away — we draw our own edge below). A
     dark navy scrim rides on top for label legibility; the neon still reads
     through. The right border + drop shadow lift the rail so it floats above
     the main wallpaper as a deeper glass panel. Background is painted on .nav
     itself (default local attachment), so the art stays anchored while nav
     content scrolls. */
  background:
    linear-gradient(180deg, rgba(7,11,20,.52) 0%, rgba(8,12,22,.60) 55%, rgba(9,13,23,.72) 100%),
    url(assets/sidebar-bg.webp?v=4) top center / 100% 100% no-repeat, /* full art FIT to the rail's displayed size (no crop — the whole composition: cyan top-left, river on the right, purple bottom). ?v bumped on each re-encode to bust the CSS bg-image cache */
    var(--bg-cool-bot);
  border-right: 1px solid rgba(90, 140, 255, 0.22);
  box-shadow: 12px 0 32px rgba(0,0,0,.35), inset -1px 0 0 rgba(120,180,255,.12);
  padding: 14px 10px; display: flex; flex-direction: column;
  position: sticky; top: 0; height: calc(100vh - var(--demobar-h)); overflow-y: auto; /* scroll if the rail's content exceeds the viewport — nothing clipped below the fold */
  z-index: 1; /* lift above .main so the rail's right shadow lands ON the content, not under it */
}
.brand { display: flex; align-items: center; gap: 9px; padding: 6px 8px 14px; }
.brand .logo { width: 28px; height: 28px; border-radius: 7px; background: linear-gradient(135deg, var(--accent), var(--accent-d)); display: grid; place-items: center; font-weight: 800; color: #fff; font-size: 15px; }
.brand .logo-img { width: 28px; height: 28px; border-radius: 7px; display: block; }
.brand .name { font-weight: 700; font-size: 15px; }
/* Landscape wordmark lockup (GD glyph + "GAMEDAY DJ"). The PNG has an opaque dark
   topo background with a baked blue/purple glow. We DON'T fence it — the old inset
   hairline capped the glow at the chip's edge and made it read as a pasted-on sticker.
   Instead the rail (.nav) now carries a cool bloom and the chip gets a soft cool
   drop-glow, so its baked light continues into the panel: a lit sign, not a sticker. */
/* Edge-to-edge masthead: the wordmark fills the rail's content width and shares the
   SAME left/right insets as the CURRENT LEAGUE card below it (its margin is 0 4px), so
   the two top blocks read as one unified header. Two design lenses: widen, don't center
   — centering would make it the lone centered element breaking the rail's left rhythm
   (and the lockup is already centered INSIDE its chip). Anchored left, no cap. */
/* Clickable → league overview (logo = "back to home/top"; the switcher chip below
   stays separate = change league). A real <a href> so it's keyboard/middle-clickable;
   subtle brightness-on-hover, NOT button chrome; focus ring for keyboard users only. */
.brand--wordmark { display: block; padding: 6px 4px; margin-bottom: 14px; cursor: pointer; border-radius: 18px; transition: filter .12s; }
.brand--wordmark:hover { filter: brightness(1.08); }
.brand--wordmark:focus { outline: none; }
.brand--wordmark:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.brand-wm { display: block; width: 100%; max-width: 100%; height: auto; border-radius: 16px;
  box-shadow:
    0 0 0 1px rgba(120,150,255,.07),        /* whisper cool edge (not white) */
    0 8px 34px -8px rgba(59,111,255,.28),   /* blue bloom downward (broadened to match rail) */
    0 3px 18px -6px rgba(166,77,255,.22);   /* purple bloom, offset */
}
.navitem .ic svg, .switcher .ic svg { display: block; }
.rolebox { margin-top: 0; padding: 10px 8px; border-top: 1px solid var(--line); }
.rolebox .sw-label { display: block; margin-bottom: 6px; }
/* persona segments: emoji stacked above the label so both align identically
   (long "Commissioner" would otherwise wrap the crown onto its own line) */
.role-seg .seg-b { display: flex; flex-direction: column; align-items: center; gap: 2px; line-height: 1.15; white-space: nowrap; }
.role-seg .role-emoji { font-size: 14px; line-height: 1; }
/* Active persona reads like a raised slider thumb (lifted neutral surface +
   drop shadow for ~2-4px depth; warm text keeps the "active = orange" cue). */
.role-seg .seg-b.on { background: #262b33; color: #ffd9c9; box-shadow: 0 1px 2px rgba(0,0,0,.4), 0 0 0 .5px rgba(0,0,0,.45), inset 0 1px 0 rgba(255,255,255,.06); }
/* Sponsors — bottom of the sidebar (console + email only; never in the app) */
.bottom-sponsors { margin-top: 10px; border-top: 1px solid var(--line); padding-top: 6px; }
.sponsor-pitch { margin: 0 0 6px; padding: 0 8px; font-size: 11px; line-height: 1.4; color: var(--text-3); }
.sponsors { padding: 0 6px 4px; display: flex; flex-direction: column; gap: 6px; }
.sponsor-chip { display: flex; align-items: center; gap: 8px; padding: 7px 9px; border: 1px solid var(--line-2); border-radius: 8px; background: var(--surface); color: var(--text); font-size: 12.5px; text-decoration: none; }
a.sponsor-chip { cursor: pointer; }
.sponsor-chip:hover { border-color: var(--accent); }
.sponsor-logo { width: 20px; height: 20px; border-radius: 5px; background: var(--accent-bg); color: #ffd9c9; display: grid; place-items: center; font-size: 9.5px; font-weight: 700; flex-shrink: 0; overflow: hidden; }
.sponsor-logo img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; display: block; }
.sponsor-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sponsor-ext { margin-left: auto; color: var(--text-3); display: flex; }
.sponsor-add { display: flex; align-items: center; justify-content: center; gap: 6px; padding: 7px 9px; border: 1px dashed var(--line-2); border-radius: 8px; color: var(--text-3); font-size: 12px; cursor: pointer; }
.sponsor-add:hover { border-color: var(--accent); color: var(--text); }
.seg { display: flex; background: var(--surface); border: 1px solid var(--line); border-radius: 8px; padding: 2px; }
.seg-b { flex: 1; display: flex; align-items: center; justify-content: center; text-align: center; font-size: 12px; padding: 5px 6px; border-radius: 6px; color: var(--text-2); cursor: pointer; }
.seg-b.on { background: var(--accent-bg); color: #ffd9c9; }
/* Song-picker scope bar: iOS-style scrollable glyph pills (5 don't fit fixed-
   width, so let them scroll horizontally instead of cramming). */
.sp-scopes { overflow-x: auto; scrollbar-width: none; gap: 4px; }
.sp-scopes::-webkit-scrollbar { display: none; }
.sp-scopes .seg-b { flex: 0 0 auto; gap: 5px; white-space: nowrap; padding: 6px 12px; }
/* Account row floats to the very bottom of the rail — leaves the space above
   for league sponsors / logos, and never sits tight against the team nav. */
.nav .account { margin-top: auto; }
.switcher {
  margin: 0 4px 12px; border: 1px solid rgba(120, 160, 255, .30); border-radius: var(--radius);
  padding: 12px; cursor: pointer; display: flex; flex-direction: column; gap: 8px;
  /* Frosted see-through glass: a translucent cool tint over a blur of the rail
     art behind it, so the sidebar's neon glow diffuses through the panel. Text
     stays readable on the tint; the brand bloom + top highlight keep it reading
     as a raised, lit glass panel (not a flat card). */
  background: rgba(26, 40, 68, 0.42);
  -webkit-backdrop-filter: blur(14px) saturate(1.15);
          backdrop-filter: blur(14px) saturate(1.15);
  box-shadow: var(--brand-glow), var(--panel-top-hi);
  transition: border-color .16s ease, background .16s ease;
}
.switcher:hover { border-color: rgba(150, 185, 255, .45); background: rgba(30, 46, 78, 0.48); }
/* label on its own row; a 32px logo (≈ the height of a 2-line name) lets the
   logo, name, and swap share one true centerline so a long email-league name
   never leaves the tiny logo sitting low/diagonal. */
.sw-row { display: flex; align-items: center; gap: 10px; }
.sw-swap { color: var(--accent); display: flex; align-items: center; flex-shrink: 0; }
.switcher .sw-label { font-size: 10px; color: var(--text-3); text-transform: uppercase; letter-spacing: .11em; } /* matches .navsec quiet register (dim via token, not opacity — see .navsec) */
.switcher .sw-val { flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; }
.switcher .sw-val > :first-child { flex-shrink: 0; } /* the logo/jersey tile */
.sw-name { font-weight: 600; font-size: 13px; line-height: 1.3; color: var(--text); min-width: 0; overflow-wrap: anywhere; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* League switcher card: identity (crest+name+sub) on the left, a labeled Switch
   pill on the right. The card always shows the CURRENT LEAGUE (never the team),
   so league context never disappears when you drill into a team. */
.sw-id { flex: 1; min-width: 0; }
.sw-id .sw-name { -webkit-line-clamp: 2; }  /* long league names wrap to 2 lines, not truncate (regressed when the compact pill returned) */
.sw-sub { font-size: 11px; color: var(--text-3); margin-top: 1px; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
/* Compact arrows pill — switches league OR team (whole card is the click
   target, so the pill has NO own :hover; the card's hover is the single
   highlight, avoiding a double-highlight when you mouse over the switcher). */
.sw-switch { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; padding: 6px 9px; border-radius: 8px; border: 1px solid var(--line-2); background: var(--bg-2); color: var(--accent); }
.sw-switch svg { display: block; }
.navsec { font-size: 10px; color: var(--text-3); text-transform: uppercase; letter-spacing: .11em; padding: 12px 12px 5px; } /* quieter than real content — smaller + wider-tracked so it recedes; dimming comes from the --text-3 token, NOT an opacity multiplier (which pushed 10px text under WCAG AA over the sidebar scrim) */
.navitem {
  display: flex; align-items: center; gap: 10px; padding: 8px 11px; border-radius: 8px;
  color: var(--text-2); font-size: 13.5px; cursor: pointer; margin: 1px 2px;
  border: 1px solid transparent; /* reserve space so the active/hover border doesn't shift rows */
  transition: background .16s ease, color .16s ease, border-color .16s ease;
}
.navitem:hover { background: rgba(50, 90, 180, 0.12); color: var(--text); }
.navitem:hover .ic { opacity: 1; }
/* Active = confident "you are here". Cyan/blue is the wallpaper's IDENTITY
   register (orange stays actions-only per the color-jobs contract, at the
   owner's direction 2026-06-30): a cool tinted fill + hairline + a cyan→violet
   left accent bar + a glowing glyph + the brightest label. */
.navitem.active {
  background: rgba(50, 90, 180, 0.16); color: #f4f8ff;
  border-color: rgba(100, 160, 255, 0.20);
  position: relative; padding-left: 12px;
}
.navitem.active::before {
  content: ""; position: absolute; left: 3px; top: 7px; bottom: 7px; width: 3px; border-radius: 3px;
  background: linear-gradient(180deg, var(--accent-cyan), var(--brand-purple));
}
.navitem.active .ic { color: #dcebff; opacity: 1; filter: drop-shadow(0 0 5px rgba(87, 200, 255, .45)); }
/* Press Box: the operational home. When it's NOT the current page, the ONLY cue is its
   mic icon tinted a muted orange (--ic-orange) — same brightness as the other nav icons,
   just hued orange. No left bar and no bolder label: the left-orange-bar is the active
   "you are here" signature, and reusing it (even dimmed) made Press Box read as selected.
   When Press Box IS active it gets the normal full active treatment (bright orange). */
.navitem.pressbox:not(.active) .ic { color: var(--ic-orange); opacity: .9; }
.navitem .ic { width: 18px; text-align: center; opacity: .9; }
.nav .account {
  margin-top: auto; display: flex; align-items: center; gap: 9px; padding: 10px 8px 8px; margin-top: auto;
  border-top: 1px solid rgba(120, 160, 220, 0.14); border-radius: 8px;
  color: var(--text-2); font-size: 13px; cursor: pointer;
  transition: background .16s ease, color .16s ease;
}
.nav .account:hover { background: rgba(50, 90, 180, 0.10); color: var(--text); }
/* Two-line identity: name (or email local-part when no display name) over role.
   Never wraps — the rail is fixed-width; the full email lives in the row's title
   tooltip + the account popover header (design consult 2026-07-01). */
.nav .account .acct-id { min-width: 0; flex: 1; }
.nav .account .acct-name { color: var(--text); font-weight: 600; font-size: 13px; line-height: 16px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nav .account .acct-role { color: var(--text-3); font-weight: 400; font-size: 11px; line-height: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Legal fine-print — pinned to the bottom of the content pane, right-aligned.
   The least-prominent home for boilerplate: out of the account menu AND the
   sidebar (owner: below-account was still too prominent). */
.main-legal { margin-top: auto; display: flex; justify-content: flex-end; gap: 7px; align-items: center; padding: 10px 26px 18px; font-size: 11px; }
.main-legal a { color: var(--text-3); text-decoration: none; }
.main-legal a:hover { color: var(--text-2); text-decoration: underline; }
.main-legal span { color: var(--text-3); opacity: 0.5; }
/* Identity avatar = chrome, NOT action. Orange (--accent) is actions-only, so
   the initials chip uses a low-alpha brand-blue tint + chrome text (2026-07-01). */
.avatar { width: 28px; height: 28px; border-radius: 50%; background: rgba(59, 111, 255, 0.16); color: var(--brand-chrome); display: grid; place-items: center; font-size: 12px; font-weight: 700; }

/* Main */
.main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.topbar { display: flex; align-items: center; justify-content: space-between; padding: 16px 26px; border-bottom: 1px solid rgba(100,130,180,.14); position: sticky; top: 0; background: rgba(9,14,22,.72); backdrop-filter: blur(10px); z-index: 5; box-shadow: inset 0 1px 0 rgba(99,134,255,.14); /* cool top-edge + blurred glass so the wallpaper glows faintly behind the header */ }
.crumbs { font-size: 12px; color: var(--text-3); margin-bottom: 2px; }
.crumbs a:hover { color: var(--text-2); }
.title { font-size: 19px; font-weight: 700; }
.content { padding: 22px 26px 60px; max-width: 1120px; }

/* Buttons */
.btn { background: var(--surface); color: var(--text); border: 1px solid var(--line-2); border-radius: 8px; padding: 8px 13px; font-size: 13px; font-weight: 500; cursor: pointer; display: inline-flex; align-items: center; gap: 7px; }
.btn:hover { border-color: var(--accent); }
.btn:active { transform: scale(.98); }
.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn.primary:hover { background: var(--accent-d); }
.btn.ghost { background: transparent; border-color: transparent; color: var(--text-2); }
.btn.ghost:hover { background: var(--surface); color: var(--text); }
.btn.sm { padding: 5px 10px; font-size: 12px; }
.btn.danger { color: var(--red); border-color: #4a2220; }

/* Cards & grids */
.cards { display: grid; gap: 12px; }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); }
/* Floating glass panel over the wallpaper: semi-transparent navy fill (the
   topo art glows faintly around/behind it), a cool hairline, a NEUTRAL drop
   shadow (no colored bloom — keeps data-dense tables clean per the contract),
   and a faint top-edge highlight so it reads as lifted glass. */
.card { background: var(--panel-bg); border: 1px solid var(--panel-border); border-radius: var(--radius-lg); padding: 16px 18px; box-shadow: var(--panel-shadow), var(--panel-top-hi); }
.card:hover { border-color: rgba(122, 160, 220, 0.30); }
/* Edge-lit panel: a luminous cyan/blue ring, brightest across the TOP edge and
   corners, wrapping down the sides and fading toward the bottom — like a glass
   panel under an off-screen rim light. OPT-IN (.card--lit) for hero/identity +
   primary container panels; still avoid the densest data grids/tables. A masked
   ~1.3px gradient ring draws ONLY on the border; a soft outer halo lifts the
   whole panel off the page. Intensity tuned to read clearly (owner 2026-07-02:
   the earlier "weaken it" went too far — this is the visible target). */
.card--lit { position: relative; box-shadow: var(--panel-shadow), var(--panel-top-hi), 0 8px 30px -16px rgba(87, 200, 255, .22); }
.card--lit::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none; padding: 1.2px;
  background:
    radial-gradient(140% 100% at 50% 0%, rgba(120, 215, 255, .80), rgba(120, 150, 255, .42) 42%, rgba(120, 150, 255, .10) 70%, transparent 84%),
    linear-gradient(180deg, rgba(120, 150, 255, .32), rgba(120, 150, 255, .05) 58%, transparent 80%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
/* Sitewide reach of the rim-light, per the color-jobs contract: it marks
   CONTAINER/identity panels (things you look AT), never content grids/rows/
   tables (things you look INTO). Modals (one at a time) + the master-detail
   workspace panes carry it; metric tiles, teamcards, and .tablewrap do NOT. */
.modal { position: relative; }
.listpane.card--lit, .detail.card--lit { position: relative; }
.metric .ml { font-size: 12px; color: var(--text-2); }
.metric .mv { font-size: 26px; font-weight: 700; margin-top: 3px; }
.metric .mv small { font-size: 14px; color: var(--text-3); font-weight: 500; }
.metric.link { cursor: pointer; transition: border-color .12s, transform .12s; }
.metric.link:hover { border-color: var(--accent); transform: translateY(-1px); }
.metric.link .ml { display: flex; align-items: center; justify-content: space-between; }
.metric-go { color: var(--text-3); display: inline-flex; }
.metric.link:hover .metric-go { color: var(--accent); }
.listrow.blocked { opacity: .6; }
.listrow.blocked .art { color: var(--red); }

/* Team cards */
.teamcard { cursor: pointer; display: flex; flex-direction: column; gap: 12px; }
/* Top-align so the jersey sits at the SAME position on every card — a 2-line
   team name must not push it down (center-align drifted jerseys by name length). */
.teamcard .head { display: flex; align-items: flex-start; gap: 12px; }
.jersey { width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0; }
.teamcard .tname { font-weight: 700; font-size: 15px; }
.teamcard .tcoach { font-size: 12px; color: var(--text-3); }
/* Stat row pinned to the card bottom (margin-top:auto) so it rides the same
   baseline across cards regardless of team-name length — cards stretch to equal
   height in the grid. Mirrors the iOS Teams row: players / coaches / parents. */
.teamcard .tstats { display: flex; gap: 18px; margin-top: auto; padding-top: 4px; font-size: 13px; font-weight: 600; color: var(--text-2); }
.teamcard .tstat { display: inline-flex; align-items: center; gap: 5px; }
.teamcard .tstat svg { color: var(--text-3); }

/* Table */
table.tbl { width: 100%; border-collapse: collapse; }
.tbl th { text-align: left; font-size: 11.5px; color: var(--text-3); text-transform: uppercase; letter-spacing: .04em; font-weight: 600; padding: 9px 12px; border-bottom: 1px solid var(--line); }
.tbl td { padding: 11px 12px; border-bottom: 1px solid var(--line); font-size: 13.5px; vertical-align: middle; }
.tbl tr:last-child td { border-bottom: none; }
.tbl tr.clickable { cursor: pointer; }
.tbl tr.clickable:hover td { background: var(--surface-2); }
.tablewrap { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; overflow-x: auto; }

/* Progress bar */
.bar { height: 6px; background: #20293a; border-radius: 6px; overflow: hidden; min-width: 70px; }
.bar > i { display: block; height: 100%; border-radius: 6px; }

/* Badges */
.badge { font-size: 11px; padding: 3px 9px; border-radius: 7px; font-weight: 600; white-space: nowrap; display: inline-block; }
.badge.ready { background: var(--green-bg); color: var(--green); }
.badge.progress { background: var(--amber-bg); color: var(--amber); }
.badge.unclaimed, .badge.none { background: var(--red-bg); color: var(--red); }
.badge.pending { background: var(--accent-bg); color: #ffb89e; }
.badge.neutral { background: #20293a; color: var(--text-2); }

/* Forms */
input[type=text], input[type=email], input[type=number], input[type=date], select, textarea {
  width: 100%; background: var(--bg-2); border: 1px solid var(--line-2); color: var(--text);
  border-radius: 8px; padding: 8px 11px; font-size: 13.5px; font-family: inherit;
}
/* Cyan/blue focus ring — field-focus is IDENTITY chrome per the color-jobs
   contract (was orange, which the contract reserves for actions). */
input:focus, select:focus, textarea:focus { outline: none; border-color: rgba(87, 200, 255, .55); box-shadow: 0 0 0 3px rgba(59, 111, 255, .28); }
input[type=date] { color-scheme: dark; }
label.fld { display: block; font-size: 12px; color: var(--text-2); margin: 0 0 5px; }

/* Labeled-field rows — one-line label above control, controls TOP-aligned so
   unequal label text never floats fields out of line. Controls inherit the
   shared input/select rule above. Reusable across the console. */
.fld-row { display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-start; }
.fld-col { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.fld-col > .fld-lbl { font-size: 11.5px; font-weight: 600; letter-spacing: .02em; color: var(--text-2); white-space: nowrap; line-height: 1.2; }
.fld-lbl .opt { color: var(--text-3); font-weight: 400; }
.fld-col.grow { flex: 1; min-width: 160px; }
.fld-col.w-voice { flex: 0 0 210px; }
.fld-col.w-date  { flex: 0 0 168px; }

/* ---------- iOS-style on/off switch (reusable) ----------
   Markup:  <label class="gdj-switch-row"><span class="gdj-switch">
              <input type="checkbox" id="…"><span class="gdj-switch-track"></span>
            </span> Label text</label>
   The native checkbox stays the form control (keeps .checked / id), visually
   hidden over the track; the track + sliding thumb are the iOS-style affordance. */
.gdj-switch-row { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font-size: 13px; }
.gdj-switch { position: relative; display: inline-flex; flex-shrink: 0; width: 42px; height: 25px; }
.gdj-switch input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 1; }
.gdj-switch-track { width: 42px; height: 25px; border-radius: 999px; background: var(--line-2); transition: background .18s ease; }
.gdj-switch-track::after { content: ""; position: absolute; top: 2.5px; left: 2.5px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform .18s ease; }
.gdj-switch input:checked + .gdj-switch-track { background: var(--green); }
.gdj-switch input:checked + .gdj-switch-track::after { transform: translateX(17px); }
.gdj-switch input:focus-visible + .gdj-switch-track { box-shadow: 0 0 0 3px rgba(34,192,138,.35); }
.field { margin-bottom: 14px; }

/* Master-detail (player editor, songs) */
.split { display: grid; grid-template-columns: minmax(360px, 420px) 1fr; gap: 16px; align-items: start; }
.listpane { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); overflow: hidden; }
.listrow { display: flex; align-items: center; gap: 10px; padding: 10px 13px; border-bottom: 1px solid var(--line); cursor: pointer; }
.listrow:last-child { border-bottom: none; }
.listrow:hover { background: var(--surface-2); }
.listrow.active { background: var(--accent-bg); }
/* Context switcher hierarchy: distinct league tile + nested team rail */
.ctx-tile { width: 26px; height: 26px; border-radius: 7px; background: var(--accent-bg); color: #ffd9c9; display: grid; place-items: center; flex-shrink: 0; }
.ctx-teamlist { border-left: 2px solid var(--line-2); border-top-left-radius: 0; border-bottom-left-radius: 0; margin-left: 9px; }
.ctx-team { padding-left: 14px; }
/* Switch-context team grid: tile teams 2-up so many fit; capped-height scroll
   keeps 50 teams from blowing out the modal; a filter appears above once there
   are enough to search. Tiles stay OPAQUE over the glass panel (legibility). */
/* Definite width (not width:100% + max-width) — the modal is a shrink-to-fit
   centered grid item, and the tile grid's minmax(0,1fr) tracks collapse, so
   without a definite width the modal shrinks to the header text. Own class
   (NOT modal-wide, which is already a 560px variant used elsewhere). */
.modal.modal-switch { width: 640px; max-width: 100%; }
.ctx-filter { width: 100%; box-sizing: border-box; margin: 0 0 9px; padding: 9px 12px; background: var(--surface-2); border: 1px solid var(--line-2); border-radius: 9px; color: var(--text); font: inherit; font-size: 13.5px; }
.ctx-filter:focus { outline: none; border-color: rgba(122,160,220,.5); }
.ctx-filter::placeholder { color: var(--text-3); }
.ctx-teamgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; max-height: 328px; overflow: auto; padding: 2px; }
.ctx-tt { display: flex; align-items: center; gap: 10px; width: 100%; min-width: 0; text-align: left; background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px; padding: 9px 11px; cursor: pointer; color: var(--text); font: inherit; }
.ctx-tt:hover { border-color: rgba(122,160,220,.35); background: var(--surface); }
.ctx-tt.active { background: var(--accent-bg); border-color: rgba(255,87,31,.4); }
.ctx-tt-body { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.ctx-tt-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; font-size: 13.5px; }
.ctx-tt-sub { color: var(--text-3); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ctx-tt-chk { color: var(--accent); display: flex; flex-shrink: 0; }
.ctx-nomatch { padding: 10px 2px 2px; font-size: 12.5px; }
.num { width: 22px; height: 22px; border-radius: 6px; background: #20293a; color: var(--text-2); font-size: 11px; font-weight: 700; display: grid; place-items: center; flex-shrink: 0; }
.detail { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-lg); padding: 20px 22px; }

/* Drag rows */
.drow { display: grid; grid-template-columns: 30px 60px 1fr 70px 1fr 110px 40px; gap: 10px; align-items: center; padding: 9px 12px; border-bottom: 1px solid var(--line); background: var(--surface); }
.drow:last-child { border-bottom: none; }
.drow.dragging { opacity: .4; }
.drow.over { box-shadow: inset 0 2px 0 var(--accent); }
.grip { cursor: grab; color: var(--text-3); text-align: center; }
.grip:active { cursor: grabbing; }

/* album art */
.art { width: 38px; height: 38px; border-radius: 6px; background: linear-gradient(135deg, #25324a, #1a2436); display: grid; place-items: center; color: var(--text-3); flex-shrink: 0; font-size: 16px; }
.plr-song { display: inline-flex; flex-shrink: 0; }
.plr-song .art { box-shadow: 0 1px 3px rgba(0,0,0,.35); }
.plr-song.nosong { width: 34px; height: 34px; border-radius: 6px; display: grid; place-items: center; color: var(--text-3); border: 1px dashed var(--line-2); background: var(--bg-2); }
/* Overlapping multi-song stack (mirrors iOS RosterView.artworkStack). Each art
   is absolutely placed; a thin row-colored border reads as the seam between
   overlapping thumbnails. */
.plr-song-stack { position: relative; display: inline-block; }
.plr-song-stack .stack-art { position: absolute; top: 0; }
.plr-song-stack .art { border: 2px solid var(--surface); }

/* modal */
/* Frosted-glass overlay: the page behind blurs (not just dims) and the panel is
   a translucent glass sheet. Entrance = a small spring-in; exit = a quick fade
   (JS adds .closing, then removes after the animation). Honors reduced-motion. */
/* Overlay starts at the sidebar's right edge (left: --nav-w) so the left nav
   is never dimmed/blurred — it stays crisp behind the modal. */
.modal-bg { position: fixed; top: 0; right: 0; bottom: 0; left: var(--nav-w); background: rgba(5,8,13,.14); -webkit-backdrop-filter: blur(1px); backdrop-filter: blur(1px); display: grid; place-items: center; z-index: 50; padding: 20px; animation: modalBgIn .18s ease-out both; }
.modal-bg.closing { animation: modalBgOut .14s ease-in both; }
/* The frosted-glass look comes from the .modal-bg backdrop blur (which is NEVER
   transformed, so it stays crisp) + this translucent panel over it. The panel
   itself must NOT carry a backdrop-filter: a backdrop-filter + a scale animation
   on the same element leaves the blur cached-blurry until the next repaint (the
   "stuck blur on open" bug). No filter here = nothing to get stuck; the spring
   animation stays. Inner rows stay OPAQUE (--surface tokens) for legibility. */
.modal { background: rgba(20,26,37,.9); border: 1px solid var(--line-2); border-radius: var(--radius-lg); width: 100%; max-width: 520px; padding: 22px; animation: modalIn .2s cubic-bezier(.2,.85,.25,1) both; transform-origin: center 40%; }
.modal-bg.closing .modal { animation: modalOut .13s ease-in both; }
@keyframes modalBgIn { from { opacity: 0; } to { opacity: 1; } }
@keyframes modalBgOut { from { opacity: 1; } to { opacity: 0; } }
@keyframes modalIn { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes modalOut { from { opacity: 1; transform: none; } to { opacity: 0; transform: translateY(6px) scale(.98); } }
@media (prefers-reduced-motion: reduce) { .modal-bg, .modal, .modal-bg.closing, .modal-bg.closing .modal { animation-duration: .001ms !important; } }
.modal h3 { margin: 0 0 4px; font-size: 17px; }
.modal .sub { color: var(--text-2); font-size: 13px; margin: 0 0 16px; }
.row-end { display: flex; justify-content: flex-end; gap: 10px; margin-top: 18px; }

/* Contact-support / report-a-problem dialog: multi-select problem pills + text. */
.sup-lbl { font-size: 12.5px; font-weight: 600; color: var(--text-2); margin: 15px 0 7px; }
.sup-pills { display: flex; flex-wrap: wrap; gap: 7px; }
.sup-pill { background: var(--surface); color: var(--text-2); border: 1px solid var(--line-2); border-radius: 999px; padding: 6px 12px; font-size: 12.5px; font-weight: 500; cursor: pointer; transition: border-color .12s, background .12s, color .12s; }
.sup-pill:hover { border-color: var(--accent); color: var(--text); }
/* Selected state uses the console's SELECTION idiom (tinted fill + peachy text,
   as .seg-b.on / .ctx-tt.active) — NOT the solid --accent, which is reserved
   for the one action (Send report). Keeps the CTA the single hottest element. */
.sup-pill.on { background: var(--accent-bg); border-color: rgba(255,87,31,.4); color: #ffd9c9; }
.sup-select, .sup-text { width: 100%; background: var(--surface); color: var(--text); border: 1px solid var(--line-2); border-radius: 8px; padding: 9px 11px; font: inherit; font-size: 13px; box-sizing: border-box; }
.sup-text { resize: vertical; min-height: 84px; }
.sup-select:focus, .sup-text:focus { outline: none; border-color: var(--accent); }
.sup-ctx { font-size: 11.5px; margin-top: 11px; line-height: 1.5; }

/* dropzone */
.dropzone { border: 1.5px dashed var(--line-2); border-radius: var(--radius-lg); padding: 28px; text-align: center; color: var(--text-2); background: var(--bg-2); }
.dropzone:hover { border-color: var(--accent); color: var(--text); }
.dropzone.dragover { border-color: var(--accent); background: var(--accent-bg); color: var(--text); }

/* misc */
.spacer { flex: 1; }
.flex { display: flex; align-items: center; gap: 10px; }
.between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.muted { color: var(--text-3); }
.sec-title { font-size: 13px; font-weight: 700; color: var(--text-2); margin: 22px 0 10px; text-transform: uppercase; letter-spacing: .04em; }
.pill-play { width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--line-2); background: var(--bg-2); color: var(--text); cursor: pointer; display: grid; place-items: center; }
.pill-play:hover { border-color: var(--accent); color: var(--accent); }
/* Destructive row action (Remove): signal "this one deletes" on approach, not only
   after the click — and segregate it from the benign Preview/Edit icons beside it. */
.pill-play.danger { margin-left: 4px; }
.pill-play.danger:hover { border-color: var(--red); color: var(--red); }
.locked-state { display: flex; align-items: center; gap: 10px; background: var(--green-bg); border: 1px solid #1c5a44; border-radius: var(--radius); padding: 8px 12px; }
.locked-state .seal { color: var(--green); display: flex; }
.ready-state { display: flex; align-items: center; gap: 10px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 8px 14px; }
/* Confirmation pill: top-center just below the sticky title bar so it lands
   where the eye already is after clicking Save, matching iOS. `top` is a
   fallback only — toast() measures the live .topbar bottom edge at creation
   time and sets an inline top, since the bar's height varies by page (a
   hardcoded px here drifted out of sync with taller headers and rode the
   border of whatever content happened to sit at that fixed offset). Slides
   down in; z-index above the bar (5). */
.toast { position: fixed; top: 72px; left: 50%; transform: translateX(-50%); display: flex; align-items: center; gap: 8px; background: var(--surface-2); color: var(--green); border: 1px solid #1c5a44; border-left: 3px solid var(--green); padding: 10px 18px; border-radius: var(--radius-lg); font-size: 13px; font-weight: 600; z-index: 80; box-shadow: 0 4px 12px rgba(0,0,0,.3), 0 12px 32px rgba(0,0,0,.35); animation: toastIn .22s ease-out; }
@keyframes toastIn { from { opacity: 0; transform: translate(-50%, -10px); } to { opacity: 1; transform: translate(-50%, 0); } }

/* ── Achievement unlock toast + the neon AI border ────────────────────────────
   The cross-platform standard is docs/NEON_AI_BORDER_STANDARD.md — READ IT
   before restyling or retuning any of this. The stop budget below is the
   approved result of four rounds of Zach's specific notes on coverage, colour
   proportion, and blend smoothness; none of it is guessable from the look.
   Android (NeonAiBorder.kt) is the reference; iOS is NeonAiBorder.swift.

   ⚠️ This is a SEPARATE surface from `.toast` on purpose. `.toast` is the
   console's generic confirmation pill with ~167 call sites ("Saved", failures,
   …). Cyan→purple is the app's AI identity, and the standard is explicit that
   it must NOT land on chrome that implies work-in-progress — a comet on every
   "Saved" would say "the app is generating something". It also means a routine
   toast can't nuke the badge: `.toast` clears its own class only. */
@property --neon-a { syntax: "<angle>"; initial-value: 0deg; inherits: false; }
/* The sweep CENTRE, which drifts — see the note on .ach-toast::before/::after. */
@property --neon-x { syntax: "<percentage>"; initial-value: 50%; inherits: false; }
@property --neon-y { syntax: "<percentage>"; initial-value: 50%; inherits: false; }

.ach-toast {
  position: fixed; top: 72px; left: 50%; transform: translateX(-50%);
  width: min(430px, calc(100vw - 36px));
  display: flex; align-items: center; gap: 14px;
  /* ⚠️ OPAQUE, and a step ABOVE the surrounding card surface. Contrast and
     separation are different properties (standard §6): the console's cards are
     glass (--panel-bg, 86%), so a translucent fill here would read as one more
     panel — "row zero" — rather than a floating notification, and the wallpaper
     would show straight through it. #232A38 is Android's `surfaceVariant`
     verbatim, so all three platforms render the same raised step. */
  background: var(--surface-raised);
  border-radius: 16px; padding: 12px 14px; z-index: 90;
  box-shadow: 0 8px 20px rgba(0,0,0,.3), 0 20px 48px rgba(0,0,0,.42);
  animation: toastIn .22s ease-out;
  cursor: pointer;                    /* the whole card opens the trophy case */
  transition: transform .12s ease;
  /* Fallback: without @property support the animations can't run, but this keeps
     `var(--neon-*)` resolvable so the conic-gradient stays valid and the border
     renders STATIC rather than disappearing entirely. */
  --neon-a: 0deg; --neon-x: 50%; --neon-y: 50%;
}
/* Badge glyph entry pop — ONE-SHOT, spent and gone. Motion belongs at the entry
   of a completed event; a loop on the content would say "still happening". */
.ach-toast .ach-ico { width: 44px; height: 44px; flex: none; animation: achPop .42s cubic-bezier(.34,1.56,.64,1); }
@keyframes achPop { from { transform: scale(.85); } to { transform: scale(1); } }
/* Amber, NOT --accent. Orange is contractually ACTIONS-ONLY in this console
   ("primary CTA, active nav, in-progress, live") and this is a passive label on
   a 6s auto-dismissing card — an orange "in-progress/live" read is precisely
   what the standard §2 forbids. --amber is already this file's earned-badge
   signal (.ach-sub.earned) and is the medallion's own family, so the card
   carries two meanings (gold = achievement, cyan→purple = AI) instead of three.
   Also a contrast win on #232A38: 4.55:1 (orange) → 6.91:1 (amber). */
.ach-toast .ach-t { font-size: 11px; font-weight: 700; letter-spacing: .05em; color: var(--amber); text-transform: uppercase; }
.ach-toast .ach-n { font-size: 15px; font-weight: 800; color: var(--text); margin-top: 1px; }
.ach-toast .ach-b { font-size: 12px; color: var(--text-2); margin-top: 2px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ach-toast:hover { transform: translateX(-50%) translateY(-1px); }
.ach-toast .ach-link { flex: 1; min-width: 0; text-decoration: none; color: inherit; }
.ach-toast .ach-x { margin-left: auto; align-self: flex-start; cursor: pointer; color: var(--text-3); font-weight: 700; line-height: 1; padding: 2px 2px 6px 6px; background: none; border: 0; font-size: 13px; font-family: inherit; }
.ach-toast .ach-x:hover { color: var(--text); }
.ach-toast .ach-link:focus-visible, .ach-toast .ach-x:focus-visible { outline: 2px solid var(--accent-cyan); outline-offset: 2px; border-radius: 6px; }

/* The travelling comet. CSS does natively what Compose needed a ring-clip for,
   so this skips three of the four rendering traps outright (no rotating path,
   no blend-mode masking, no rotation-invariant shape needed). The ring is a
   conic-gradient painted over the padding box with the content box masked out. */
.ach-toast .ach-neon::before, .ach-toast::after {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 2px;                       /* core width */
  /* ⚠️ The sweep centre DRIFTS (`at var(--neon-x) var(--neon-y)`) and that is
     LOAD-BEARING, not decoration. The comet is driven by a constant ANGULAR
     sweep, but the card is a wide, short rect: a ray from the centre crosses an
     edge slowest where it meets that edge perpendicularly — dead centre of the
     top and bottom — and speeds up toward the corners (rate goes as csc²θ). A
     FIXED centre therefore pins a slow-point at top-middle and bottom-middle on
     every lap, forever, which reads as mechanical.
     Wobbling the SPEED does NOT fix it — measured: the csc² term overwhelms any
     timing wobble gentle enough to keep the comet from reversing (±26% moved the
     slow-point ~0.01 of the half-width). The slow-point is pinned by where the
     perpendicular foot IS, so you must move the point angles are measured from.
     See NEON_AI_BORDER_STANDARD §3. */
  background: conic-gradient(from var(--neon-a) at var(--neon-x) var(--neon-y),
    rgba(26,217,255,0)    0%,
    rgba(26,217,255,0)   18%,         /* ── dark gap (~18%). KEEP IT: a fully-lit
                                             ring reads as a static outline, not
                                             a travelling light. */
    rgba(26,217,255,.40) 24%,         /* shoulder in */
    #1AD9FF              30%,         /* ┐ */
    #1AD9FF              40%,         /* ┘ cyan-A plateau (10%) */
    #AD40FF              56%,         /* ← 16% blend cyan→purple */
    #AD40FF              74%,         /* ┘ purple plateau (18%) */
    #1AD9FF              90%,         /* ← 16% blend purple→cyan */
    #1AD9FF              96%,         /* ┘ cyan-B plateau (6%) */
    rgba(26,217,255,0)  100%);        /* shoulder out → seamless wrap */
  /* ⚠️ Both ends are `rgba(26,217,255,0)`, NOT `transparent`. `transparent` is
     transparent BLACK; premultiplied gradient interpolation usually saves you,
     but a zero-alpha cyan is unambiguous across engines and is the same
     endpoint. Both ends transparent ⇒ the 100%→0% wrap is seamless. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask-composite: exclude;
  /* One 3s lap (deliberately slow — a fast lap reads "working…"), plus the two
     drift axes on periods incommensurate with the lap AND with each other
     (×2.37 and ×3.71, matching iOS/Android). Three separate animations rather
     than one, because that's what makes the centre never return to the same
     place on the same phase — a single keyframe set would just repeat. */
  animation: neon-spin 3s linear infinite,
             neon-drift-x 7.11s ease-in-out infinite,
             neon-drift-y 11.13s ease-in-out infinite;
  pointer-events: none;
}
/* ── The glow, and why it needs its own ELEMENT ───────────────────────────────
   ⚠️ CSS applies `filter` BEFORE `mask`. So you cannot blur the same element you
   mask: the bloom is painted, then the mask cuts it back to the ring band and
   the falloff is thrown away. Two wrong turns were taken here before this:
     1. blur on `::before` sharing the core's box (inset:0/padding:2px) → the
        glow rendered NOTHING (pixel-identical to `::before{display:none}`).
     2. widening the band to compensate (inset:-8/padding:10) → the band IS the
        glow, so it read as a fat uniform neon tube with no falloff at all
        (Zach: "too fat of a neon bleed").
   Neither is fixable by tuning numbers — the mask is the constraint.
   The fix is one extra element: `.ach-neon` carries the BLUR, and its ::before
   carries the masked ring. The mask applies to the child, the blur applies to
   the already-masked parent, so the bloom survives OUTSIDE the ring — a thin
   core with soft falloff, which is what iOS/Android render (NeonAiBorder.swift
   blurs a 3pt stroke at 0.55; this mirrors that). Nothing in the toast's
   ancestry may clip or `overflow: hidden` the spill. */
.ach-toast .ach-neon { position: absolute; inset: 0; border-radius: inherit; pointer-events: none; filter: blur(7px); opacity: .55; }
.ach-toast .ach-neon::before { padding: 3px; }   /* thin ring; the blur makes the halo, not the width */

@keyframes neon-spin { to { --neon-a: 360deg; } }
/* 50% ± 21% / ± 15% — the same amplitudes iOS uses (NeonAiBorder.swift `drift`).
   The quarter-point stops + ease-in-out approximate the sine the native clients
   compute directly; exact sine fidelity isn't the point, breaking the pin is. */
@keyframes neon-drift-x { 0%, 100% { --neon-x: 50%; } 25% { --neon-x: 71%; } 75% { --neon-x: 29%; } }
@keyframes neon-drift-y { 0%, 100% { --neon-y: 50%; } 25% { --neon-y: 65%; } 75% { --neon-y: 35%; } }

/* Standard §8: reduced motion falls back to a STATIC cyan→purple gradient
   border — the same artwork, frozen. Never a bare card. `.ach-ico` is listed
   explicitly: the glyph pop is its own animation and would otherwise survive. */
@media (prefers-reduced-motion: reduce) {
  .ach-toast, .ach-toast .ach-ico, .ach-toast .ach-neon::before, .ach-toast::after,
  .ach-toast:hover { animation: none; transition: none; transform: translateX(-50%); }
}

/* login */
.login { min-height: calc(100vh - var(--demobar-h)); display: grid; place-items: center; padding: 24px; }
.login .box { width: 100%; max-width: 380px; text-align: center; }
.login .logo-lg { width: 56px; height: 56px; border-radius: 14px; margin: 0 auto 16px; display: block; }
.login h1 { font-size: 22px; margin: 0 0 6px; }
.login p.sub { color: var(--text-2); margin: 0 0 22px; font-size: 14px; }
.divider { display: flex; align-items: center; gap: 12px; color: var(--text-3); font-size: 12px; margin: 16px 0; }
.divider::before, .divider::after { content: ""; height: 1px; background: var(--line); flex: 1; }
.sso { width: 100%; justify-content: center; margin-bottom: 9px; }

/* ── iOS jersey badge (tshirt + stroked number) ── */
.jbadge { display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.jbadge svg { display: block; overflow: visible; }

/* ── iOS-style waveform snippet + transport (SongSnippetView on web) ── */
.wf { margin: 12px 0 4px; }
.wf-strip {
  position: relative; height: 96px; background: var(--bg-2);
  border: 1px solid var(--line); border-radius: 12px; overflow: hidden;
}
.wf-sel { position: absolute; top: 0; bottom: 0; background: rgba(255,87,31,0.14); pointer-events: none; }
.wf-duck {
  position: absolute; top: 0; bottom: 0; pointer-events: none;
  background-color: rgba(139,92,246,0.14);
  background-image: repeating-linear-gradient(45deg, transparent 0, transparent 6px, rgba(139,92,246,0.38) 6px, rgba(139,92,246,0.38) 7px);
}
.wf-mic { position: absolute; top: 5px; left: 5px; color: #a78bfa; display: flex; }
.wf-bars { position: absolute; inset: 0; display: flex; align-items: center; gap: 1px; padding: 0 9px; }
.wf-bar { flex: 1 1 0; min-width: 1px; border-radius: 1px; background: var(--text-3); opacity: 0.45; }
.wf-bar.in { background: var(--accent); opacity: 1; }
.wf-marker { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent); transform: translateX(-1px); }
.wf-marker::before { content: ""; position: absolute; top: -1px; left: -3px; width: 8px; height: 8px; border-radius: 2px; background: var(--accent); }
.wf-playhead { position: absolute; top: 0; bottom: 0; width: 2px; background: #fff; left: var(--a); opacity: 0; transform: translateX(-1px); pointer-events: none; z-index: 3; }
.wf-playhead::before { content: ""; position: absolute; top: -2px; left: -3px; width: 8px; height: 8px; border-radius: 50%; background: #fff; }
.wf-playhead.on { opacity: 1; }
.wf-strip.playing .wf-playhead { opacity: 1; animation: wf-scan 3s linear forwards; }
@keyframes wf-scan { from { left: var(--a); } to { left: var(--b); } }

.wf-duckcap { display: flex; align-items: center; gap: 6px; font-size: 11.5px; color: #a78bfa; margin: 8px 2px 0; }
.wf-times { display: flex; align-items: flex-start; justify-content: space-between; margin: 10px 2px 0; }
.wf-lbl { font-size: 10px; letter-spacing: 0.06em; color: var(--text-3); text-transform: uppercase; }
.wf-t { font-size: 13px; font-variant-numeric: tabular-nums; color: var(--accent); font-weight: 600; }
.wf-now { font-size: 14px; font-variant-numeric: tabular-nums; color: var(--text); text-align: center; }
.wf-now .muted { font-size: 11px; }
.wf-transport { display: flex; align-items: center; justify-content: center; gap: 28px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--line); }
.wf-skip { background: none; border: none; color: var(--text); font-size: 15px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 1px; padding: 6px; }
.wf-skip small { font-size: 11px; }
.wf-skip:hover { color: var(--accent); }
.wf-playbtn { background: none; border: none; cursor: pointer; padding: 0; line-height: 0; }
.wf-playbtn:active { transform: scale(0.95); }

/* ── Unified roster list: order # + drag-to-reorder ── */
.listrow.plr { gap: 9px; }
.listrow .ord { width: 18px; flex-shrink: 0; text-align: center; font-size: 12px; font-weight: 700; color: var(--text-3); font-variant-numeric: tabular-nums; }
.listrow.plr { cursor: grab; }
.listrow.plr.dragging { opacity: 0.5; }
.listrow.plr.over { box-shadow: inset 0 2px 0 var(--accent); }

/* ── Announcer-intro Studio ── */
.studio { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; }
.studio-compact { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px; }
.tpl-row { display: flex; flex-wrap: wrap; gap: 7px; }
.tpl-chip { display: inline-flex; align-items: center; gap: 6px; background: var(--surface); border: 1px solid var(--line-2); color: var(--text); border-radius: 8px; padding: 6px 10px; font-size: 12.5px; cursor: pointer; font-family: inherit; }
.tpl-chip:hover { border-color: var(--accent); }
.tpl-chip.on { border-color: var(--accent); background: var(--accent-bg); color: #ffd9c9; }
.tpl-chip.mine { border-style: dashed; }
.tpl-badge { font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--accent); background: var(--accent-bg); border-radius: 5px; padding: 1px 5px; }
.token-row { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.token { background: var(--accent-bg); color: #ffd9c9; border: 1px solid var(--line-2); border-radius: 7px; padding: 4px 9px; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; cursor: pointer; }
.token:hover { border-color: var(--accent); }
.preview-line { display: flex; align-items: baseline; gap: 10px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line); }
.prev-lbl { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-3); flex-shrink: 0; }
.prev-text { font-size: 14px; color: var(--text); font-weight: 500; line-height: 1.5; }
/* Variety example set — grouped as one rotating stack; the FULL per-level set
   renders (no cap), so the list length (3 vs 10) is itself the signal — a
   taller stack visibly reads as "more variety" without needing to scroll to
   feel it. Border is --line (neutral hairline), NOT --accent* — a left orange
   bar is this console's "active/selected" signature (see the segmented
   control right above), so borrowing it here for a non-interactive list would
   misread as "selected." */
.variety-lines { border-left: 2px solid var(--line); padding-left: 10px; margin-top: 2px; }
.variety-lines .prev-text { font-size: 13px; margin: 3px 0; }

/* Drag handle on roster rows + Studio top-right button */
.listrow .grip { color: var(--text-3); display: flex; align-items: center; flex-shrink: 0; cursor: grab; }
.listrow.plr:hover .grip { color: var(--text-2); }
.listrow.plr.dragging .grip { cursor: grabbing; }
.studio-btn { border-color: var(--line-2); }
.studio-btn:hover { border-color: var(--accent); color: var(--accent); }

/* ── Studio (consolidated audio surface) ── */
.studio-seg { width: 100%; }
.dropzone.hero { padding: 30px 20px; text-align: center; border-width: 2px; }
.dropzone.hero svg { color: var(--accent); }
.sfx-grid { gap: 12px; }
.sfx-chip { display: flex; flex-direction: column; align-items: center; gap: 9px; padding: 18px 10px; cursor: pointer; text-align: center; }
.sfx-chip:hover { border-color: var(--accent); }
.sfx-chip .sfx-ico { width: 36px; height: 36px; border-radius: 50%; display: grid; place-items: center; background: var(--accent-bg); color: var(--accent); transition: background .12s, color .12s; }
.sfx-chip.auditioning .sfx-ico { background: var(--accent); color: #fff; }
.sfx-name { font-size: 13px; font-weight: 600; }
.mix-card { display: block; }
.script-card .prev-text { font-size: 13px; color: var(--text); font-weight: 500; }
.pill-play.auditioning { background: var(--accent); color: #fff; }

/* ── Audition control (replaces the snippet transport — preview, not playback) ── */
.wf-aud { display: flex; justify-content: center; margin-top: 12px; }
.wf-audbtn { display: inline-flex; align-items: center; gap: 8px; background: var(--accent); color: #fff; border: none; border-radius: 999px; padding: 9px 20px; font-size: 14px; font-weight: 700; cursor: pointer; font-family: inherit; transition: background .12s; }
.wf-audbtn:hover, .wf-audbtn.auditioning { background: var(--accent-d); }
.wf-audbtn .wf-audico { display: inline-flex; }
.wf-prevnote { display: flex; align-items: center; gap: 6px; justify-content: center; font-size: 11.5px; color: var(--text-3); margin-top: 10px; }

/* Studio segment icons + discovery card */
.studio-seg .seg-b { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.studio-seg .seg-ic { display: inline-flex; }
.lib-discover { display: flex; align-items: center; gap: 14px; background: var(--surface); border: 1px dashed var(--line-2); border-radius: var(--radius-lg); padding: 18px; }
.lib-discover > svg { color: var(--accent); flex-shrink: 0; }
.tablewrap .pill-play.auditioning { background: var(--accent); color: #fff; }

/* ── Customize Snippet editor (iOS SongSnippetView on web) ── */
.modal.modal-wide { max-width: 560px; }
/* Song picker: roomier (~35% wider) so the discovery lists breathe. */
.modal.modal-picker { max-width: 700px; }
/* Row-click-to-add rows: the whole row is the target (no per-row Assign button);
   hover reveals a subtle "Add" cue on the right. */
.sp-row { cursor: pointer; }
.sp-row .sp-add { margin-left: 4px; font-size: 12px; font-weight: 600; color: var(--accent); opacity: 0; transition: opacity .12s; white-space: nowrap; }
.sp-row:hover .sp-add { opacity: 1; }
.cs-strip { height: 160px; cursor: crosshair; --lane: 22px; overflow: visible; }
/* keep waveform + selection above the bottom handle lane */
.cs-strip .wf-bars, .cs-strip .wf-sel, .cs-strip .wf-duck, .cs-strip .wf-playhead { bottom: var(--lane); }
/* intro duck region pulses while the music is ducked during playback */
.cs-strip .wf-duck.ducking { animation: wf-duckpulse .7s ease-in-out infinite alternate; }
.cs-strip .wf-duck.ducking .wf-mic { animation: wf-micpulse .7s ease-in-out infinite alternate; }
@keyframes wf-duckpulse { from { background-color: rgba(139,92,246,.14); } to { background-color: rgba(139,92,246,.30); } }
@keyframes wf-micpulse { from { transform: scale(1); } to { transform: scale(1.25); } }
.cs-strip::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: var(--lane); background: var(--bg-2); border-top: 1px solid var(--line); border-radius: 0 0 12px 12px; }
.cs-strip .wf-marker.drag { bottom: var(--lane); width: 3px; cursor: ew-resize; z-index: 4; }
/* small flag at the TOP of the marker line */
.cs-strip .wf-marker.drag::before { content: ""; position: absolute; top: -1px; left: -3px; width: 9px; height: 9px; border-radius: 2px; background: var(--accent); }
/* grab HANDLE bar in the bottom lane */
.cs-strip .wf-marker.drag .wf-handle { position: absolute; top: 100%; left: 50%; transform: translateX(-50%); margin-top: 5px; width: 28px; height: 13px; border-radius: 5px; background: var(--accent); border: 2px solid var(--surface); box-shadow: 0 1px 3px rgba(0,0,0,.45); z-index: 5; }
.cs-strip .wf-marker.drag .wf-handle::before { content: ""; position: absolute; top: 3px; bottom: 3px; left: 8px; right: 8px; border-left: 1.5px solid rgba(255,255,255,.65); border-right: 1.5px solid rgba(255,255,255,.65); }
.cs-strip .wf-marker.drag::after { content: ""; position: absolute; top: 0; left: -11px; right: -11px; bottom: calc(-1 * var(--lane)); } /* fat hit target down through the lane */
/* magnifier loupe (iOS parity) */
/* loupe is a fixed overlay that follows the cursor (positioned in JS), clamped to
   the viewport so it's never clipped by the modal or window edges. */
.wf-loupe { position: fixed; top: 0; left: 0; width: 96px; height: 96px; border-radius: 50%; background: var(--bg-2); border: 2px solid var(--accent); box-shadow: 0 8px 22px rgba(0,0,0,.55); overflow: hidden; z-index: 80; pointer-events: none; opacity: 0; transform: scale(.8); transition: opacity .1s ease, transform .1s ease; }
.wf-loupe.on { opacity: 1; transform: scale(1); }
.wf-loupe-bars { position: absolute; left: 9px; right: 9px; top: 10px; bottom: 26px; display: flex; align-items: center; gap: 1px; }
.wf-lbar { flex: 1 1 0; min-width: 1px; border-radius: 1px; background: var(--text-3); opacity: .4; }
.wf-lbar.in { background: var(--accent); opacity: 1; }
.wf-loupe-x { position: absolute; top: 7px; bottom: 26px; left: 50%; width: 2px; background: #fff; transform: translateX(-1px); }
.wf-loupe-t { position: absolute; bottom: 7px; left: 0; right: 0; text-align: center; font-size: 10px; font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }
.cs-transport { display: flex; align-items: center; justify-content: center; gap: 30px; margin-top: 14px; }
.cs-transport .wf-skip { background: none; border: none; color: var(--text); font-size: 15px; font-weight: 700; cursor: pointer; display: inline-flex; align-items: center; gap: 1px; padding: 6px; }
.cs-transport .wf-skip:hover { color: var(--accent); }
.cs-transport .wf-skip small { font-size: 11px; }
.cs-transport .wf-playbtn { background: none; border: none; cursor: pointer; padding: 0; line-height: 0; }
.cs-transport .wf-playbtn:active { transform: scale(.95); }
.cs-balance { margin-top: 14px; }
.cs-presets { display: flex; gap: 8px; margin-top: 14px; }
.cs-preset { flex: 1; padding: 8px 0; border-radius: 8px; border: 1px solid var(--line-2); background: var(--surface); color: var(--text); font-size: 13px; font-variant-numeric: tabular-nums; cursor: pointer; font-family: inherit; }
.cs-preset.on { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 700; }
.csnip { max-height: 78vh; overflow-y: auto; }

/* League identity (logo upload) in the sidebar + named-league inheritance badge */
/* roomier block (was padding 10/min-56) with real top+bottom gaps so it never
   crowds "Team Settings" above or the CTA below (nav scrolls, so a fixed gap —
   not auto — is what actually shows). */
/* League-logo affordance — TWO forms so it never reads as a second identity
   card next to the switcher (design consult 2026-06-30). The league NAME is
   never repeated here; both forms are labeled by their ACTION. */
.lg-identity { display: flex; align-items: center; gap: 10px; cursor: pointer; }
/* No-logo NUDGE: a prominent, dismissible onboarding CTA (the real payoff). */
.lg-nudge { margin: 16px 4px 14px; padding: 14px 12px; min-height: 64px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); transition: border-color .12s; }
.lg-nudge:hover { border-color: var(--accent); }
/* Has-logo EDIT chip: JUST the logo, snug to its own size. No text label; the
   pencil affordance fades in only on hover of the logo. */
.lg-row { margin: 8px 4px 6px; padding: 5px; border-radius: 9px; width: fit-content; gap: 7px; transition: background .16s ease; }
.lg-row:hover { background: rgba(50, 90, 180, 0.12); }
.lg-row .lg-logo-img { width: 30px; height: 30px; }
.lg-edit { color: var(--text-3); display: flex; flex-shrink: 0; opacity: 0; transition: opacity .15s ease, color .15s ease; }
.lg-row:hover .lg-edit { opacity: 1; color: var(--text); }
.lg-logo-img { width: 30px; height: 30px; border-radius: 7px; object-fit: cover; flex-shrink: 0; display: block; }
.lg-logo-ph { width: 30px; height: 30px; border-radius: 7px; flex-shrink: 0; display: grid; place-items: center; color: var(--accent); background: var(--accent-bg); border: 1px dashed var(--accent); }
/* Show the league's full name proudly — wrap up to 2 lines (most leagues have
   long names); ellipsis only past 2 lines, never a one-line truncation. */
.lg-name { font-size: 13px; font-weight: 700; color: var(--text); line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.lg-sub { font-size: 11px; color: var(--text-3); }
.lg-set-logo { width: 52px; height: 52px; border-radius: 10px; flex-shrink: 0; display: grid; place-items: center; cursor: pointer; padding: 0; color: var(--accent); background: var(--accent-bg); border: 1px dashed var(--accent); transition: border-color .12s, background .12s; }
.lg-set-logo:hover { background: var(--surface); border-color: var(--accent); }
.lg-set-logo img { width: 100%; height: 100%; border-radius: 10px; object-fit: cover; display: block; }
.lg-set-logo:has(img) { border-style: solid; padding: 0; }
.sw-logo { width: 32px; height: 32px; border-radius: 8px; object-fit: cover; flex-shrink: 0; }
.sw-logo.emoji-mark { font-size: 18px; } /* emoji fills the 32px tile */
.from-league { font-size: 11.5px; color: var(--text-3); white-space: nowrap; }
.tb-group { margin-bottom: 16px; }
.tb-glabel { display: flex; align-items: center; gap: 7px; font-size: 12px; font-weight: 700; color: var(--text-2); margin: 0 2px 7px; }
.tb-glabel .ic { color: var(--text-3); }
.tb-ghint { font-weight: 400; color: var(--text-3); font-size: 11px; }
.tb-empty { font-size: 12.5px; color: var(--text-3); padding: 10px 12px; border: 1px dashed var(--line); border-radius: var(--radius); }
.badge.neutral .ic { vertical-align: -1px; margin-right: 2px; }
.lc-seg .seg-b { padding: 7px 6px; }
.lc-app { display: flex; flex-direction: column; align-items: flex-start; gap: 2px; width: 100%; text-align: left; padding: 11px 13px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); cursor: pointer; transition: border-color .12s; }
.lc-app + .lc-app { margin-top: 8px; }
.lc-app:hover { border-color: var(--accent); }
.lc-app b { color: var(--text); font-size: 13.5px; }
.lc-app span { color: var(--text-3); font-size: 11.5px; }
/* the dropzone is a <label> (inline by default) — make it a full-width centered
   column so the dashed border frames the whole panel, not a narrow column */
.lc-dz { display: flex; flex-direction: column; align-items: center; gap: 4px; width: 100%; box-sizing: border-box; }
.lc-dz .dz-ic { color: var(--accent); display: inline-flex; margin-bottom: 2px; }
.lc-dz > div { text-align: center; }
.lc-apps { display: block; }
.sched-team { font-weight: 700; color: var(--text); cursor: pointer; padding: 2px 7px; margin: 0 -3px; border-radius: 6px; display: inline-flex; align-items: center; gap: 6px; transition: background .1s, box-shadow .1s; }
.sched-team:hover { background: var(--accent-bg); box-shadow: inset 0 0 0 1.5px var(--accent); color: #ffe7dc; }
.sched-custom { font-size: 10px; font-weight: 700; color: var(--accent); background: var(--accent-bg); padding: 1px 6px; border-radius: 5px; display: inline-flex; align-items: center; gap: 3px; }
.sched-team:hover .sched-custom { background: rgba(0,0,0,.18); color: #ffe7dc; }
.lineup-banner { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 0 2px 16px; padding: 14px 16px 14px 14px; border: 1px solid var(--accent); border-left: 4px solid var(--accent); border-radius: var(--radius); background: linear-gradient(90deg, var(--accent-bg), var(--surface) 70%); box-shadow: 0 1px 0 rgba(255,87,31,.06); }
.lineup-banner.on { background: var(--accent-bg); }
.lineup-banner .ln-cal { display: grid; place-items: center; width: 42px; height: 42px; border-radius: 10px; background: var(--accent-bg); border: 1px solid var(--accent); flex-shrink: 0; }
.lineup-banner .ln-cal .ic { color: var(--accent); }
.lineup-banner .ln-body { min-width: 0; flex: 1; }
.ln-eyebrow { font-size: 10.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--accent); margin-bottom: 1px; }
.ln-date { font-size: 17px; font-weight: 800; color: var(--text); line-height: 1.15; }
.ln-date .ln-vs { font-size: 13px; font-weight: 600; color: var(--text-2); }
.ln-desc { font-size: 12px; color: var(--text-2); margin-top: 2px; }
.ln-actions { display: flex; gap: 8px; align-items: center; flex-shrink: 0; }
.lineup-banner.flash { animation: lnFlash 1.4s ease-out 1; }
@keyframes lnFlash { 0% { box-shadow: 0 0 0 0 rgba(255,87,31,.55); } 12% { box-shadow: 0 0 0 5px rgba(255,87,31,.32); } 100% { box-shadow: 0 0 0 0 rgba(255,87,31,0); } }
@media (prefers-reduced-motion: reduce) { .lineup-banner.flash { animation: none; } }

/* Team section header in the sidebar — carry the team jersey so the active team is obvious */
/* The active-team header is a CONTEXT title, not a generic section label — the base
   .navsec sizes it 10.5px/muted, which left it smaller than its own child items
   (Roster/Team Settings). Bump it: bigger jersey (set in markup), and a brighter,
   semibold, slightly larger name so the team you're working under reads as a header. */
/* The team block is a NESTED SCOPE ("you've descended into this team"), not just the
   next list section — so it gets a faint inset container that visually recedes the
   team items into their own group, marking the league-wide vs. team-scoped boundary
   without coloring the rail (the cyan→purple gradient stays unique to the pilot CTA).
   The "This team" eyebrow names the scope, parallel to the "LEAGUE" label above. */
.team-group { margin: 14px 4px 2px; padding: 2px 4px 4px; background: rgba(255,255,255,.025); border: 1px solid rgba(255,255,255,.05); border-radius: 10px; }
.team-eyebrow { padding: 8px 11px 10px 13px; }   /* tiny uppercase scope label; bottom pad = breathing room above the team row */
/* Team header row: left-aligned to the group's nav-item column (jersey-left ↔ icon-left,
   name ↔ label) so it reads as the header OF these items, not a stray misaligned row;
   sized up (bigger jersey + 15px name) so it's clearly a header, not just another option. */
.navsec-team { display: flex; align-items: flex-start; gap: 7px; padding: 2px 11px 7px 13px; }
.navsec-team .jbadge { flex-shrink: 0; margin-top: 1px; }
/* Wrap multi-word team names to a second line instead of truncating — there's
   room in the sidebar and the full name matters more than one tidy line. */
.navsec-team > span { min-width: 0; white-space: normal; overflow-wrap: anywhere; line-height: 1.25; font-size: 15px; font-weight: 600; color: var(--text); letter-spacing: 0; }

/* Dismiss "x" for the add-a-league-logo prompt — Apple placement (top-left
   corner badge), revealed only on hover so it doesn't nag. */
.lg-identity.has-dismiss { position: relative; }
.lg-dismiss { position: absolute; top: -7px; left: -7px; width: 18px; height: 18px; border-radius: 50%; border: 1px solid var(--line-2); background: var(--bg-2); color: var(--text-2); display: grid; place-items: center; cursor: pointer; padding: 0; opacity: 0; transition: opacity .12s, color .12s, border-color .12s; z-index: 2; }
.lg-identity.has-dismiss:hover .lg-dismiss { opacity: 1; }
.lg-dismiss:hover { color: var(--text); border-color: var(--text-2); }
.lg-dismiss:focus-visible { opacity: 1; outline: 2px solid var(--accent); outline-offset: 1px; }

/* ── Multiple walk-up songs (mirrors iOS PlayerEditView) ── */
.song-list { display: flex; flex-direction: column; gap: 8px; margin-bottom: 10px; }
.song-row { display: flex; align-items: center; gap: 11px; padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
.song-meta { min-width: 0; flex: 1; }
.song-title { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.song-artist { font-size: 11.5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.song-row-actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.btn.xs { padding: 5px 9px; font-size: 11.5px; display: inline-flex; align-items: center; gap: 5px; }
.icon-btn { background: none; border: 1px solid var(--line); border-radius: 7px; width: 30px; height: 30px; display: grid; place-items: center; color: var(--text-3); cursor: pointer; flex-shrink: 0; }
.icon-btn:hover { color: var(--text); }
.icon-btn.danger:hover { color: var(--red); border-color: var(--red); }
.song-empty { display: flex; align-items: center; gap: 12px; padding: 14px; border: 1px dashed var(--line); border-radius: var(--radius); margin-bottom: 10px; }
.song-empty .mw { height: 22px; opacity: .5; }
.song-actions { gap: 8px; margin-bottom: 4px; }
.pb-mode { margin: 4px 0 12px; }
.pb-desc { font-size: 11.5px; margin-top: 5px; }
/* Press Box §4c — Set List */
/* numbered, drag-reorderable set-list row (row opens Edit on click — like the
   roster row; the grip carries the grab cursor for dragging) */
.pb-setrow.dragging { opacity: .5; }
.pb-setrow.over { border-color: var(--accent); box-shadow: 0 -2px 0 var(--accent) inset; }
.pb-grip { color: var(--text-3); display: grid; place-items: center; cursor: grab; }
.pb-setrow:hover .pb-grip { color: var(--text-2); }
.pb-num { min-width: 20px; height: 20px; border-radius: 50%; background: var(--bg-2); border: 1px solid var(--line-2);
  color: var(--text-2); font-size: 11px; font-weight: 700; display: grid; place-items: center; font-variant-numeric: tabular-nums; margin-top: 1px; }
/* Announcement rows mimic the roster list: contained .listpane of .listrow rows,
   each led by a megaphone tile (the Press Box mark ≈ the jersey badge). */
.pb-pane { margin-bottom: 6px; }
/* Master-detail (roster-style): left list column + "New" button; right = editor.
   The .split grid (minmax(360,420) 1fr) is shared with the roster. */
.pb-listcol { min-width: 0; }
.pb-split .detail { min-width: 0; }   /* let the editor pane shrink so a long nowrap header title ellipsizes instead of forcing the 1fr track past the viewport */
.pb-tile-lg { width: 54px; height: 54px; border-radius: 12px; }   /* detail-header megaphone ≈ roster's 54px jersey badge */
.pb-arow { align-items: flex-start; padding: 12px 14px; gap: 11px; cursor: pointer; }   /* whole row selects into the editor; .listrow:hover gives the highlight */
.pb-tile { width: 36px; height: 36px; border-radius: 9px; background: var(--accent-bg); color: #ffb89e;
  display: grid; place-items: center; flex-shrink: 0; }
.pb-tile-empty { background: var(--bg-2); border: 1px dashed var(--line-2); color: var(--text-3); }
.pb-rowbody { min-width: 0; flex: 1; }
.pb-rowtext { font-weight: 600; color: var(--text); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.pb-rowmeta { color: var(--text-3); font-size: 12px; margin-top: 5px; display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.pb-rowsub { color: var(--text-3); }
.pb-emptyrow { cursor: default; }
.pb-emptyrow .pb-rowtext { -webkit-line-clamp: 1; }
.pb-emptyrow:hover { background: transparent; }
/* canonical waveform as CSS bars — bars=audio, orange+motion=now-auditioning */
/* gap + bar width are whole pixels so the bars snap uniformly to the device
   pixel grid — a sub-pixel 1.5px gap rounded unevenly per bar (irregular spacing). */
.mw { display: inline-flex; align-items: center; gap: 2px; height: 13px; vertical-align: -2px; }
.mw-b { width: 2px; background: var(--text-3); border-radius: 1px; transform-origin: center; }
.snip-label { display: inline-flex; align-items: center; gap: 6px; background: none; border: 0; padding: 3px 0 0; color: var(--text-3); font-size: 11px; font-variant-numeric: tabular-nums; cursor: pointer; }
.snip-label:hover { color: var(--text-2); }
.snip-label.auditioning { color: var(--accent); }
.snip-label.auditioning .mw-b { background: var(--accent); animation: mwbounce .9s ease-in-out infinite; }
.snip-label.auditioning .mw-b:nth-child(2) { animation-delay: .12s; }
.snip-label.auditioning .mw-b:nth-child(3) { animation-delay: .24s; }
.snip-label.auditioning .mw-b:nth-child(4) { animation-delay: .36s; }
.snip-label.auditioning .mw-b:nth-child(5) { animation-delay: .18s; }
.snip-label.auditioning .mw-b:nth-child(6) { animation-delay: .30s; }
@keyframes mwbounce { 0%, 100% { transform: scaleY(.55); } 50% { transform: scaleY(1.1); } }
@media (prefers-reduced-motion: reduce) { .snip-label.auditioning .mw-b { animation: none; } }
/* Pick-by-vibe card grid */
.vibe-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-top: 4px; }
.vibe-card { display: flex; flex-direction: column; align-items: flex-start; gap: 3px; text-align: left; padding: 13px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); cursor: pointer; transition: border-color .12s, background .12s; }
.vibe-card:hover { border-color: var(--accent); background: var(--accent-bg); }
.vibe-emoji { font-size: 24px; line-height: 1; }
.vibe-name { font-weight: 700; font-size: 13.5px; color: var(--text); }
.vibe-desc { font-size: 11.5px; color: var(--text-3); }
/* vibe card loading (live chart fetch) */
.vibe-card.dim { opacity: .4; pointer-events: none; }
.vibe-card.loading { pointer-events: none; border-color: var(--accent); }
/* The spinner takes the EXACT place of the vibe icon: hide the emoji glyph but
   keep its layout box (so the name stays put), then draw the ring centered on
   that box. Margin-offset (not transform: translate) so it doesn't fight
   vibespin's rotate transform. -11px = half the 22px box. */
.vibe-card.loading .vibe-emoji { visibility: hidden; position: relative; }
.vibe-card.loading .vibe-emoji::after { visibility: visible; content: ""; position: absolute; left: 50%; top: 50%; margin: -11px 0 0 -11px; width: 22px; height: 22px; border: 2px solid var(--accent-bg); border-top-color: var(--accent); border-radius: 50%; animation: vibespin .7s linear infinite; }
.vibe-card { position: relative; }
@keyframes vibespin { to { transform: rotate(360deg); } }

/* Larger, more tap/click-friendly controls inside the player detail pane
   (song management + intro studio) — ~1.3–1.4x the base sizes. Scoped to
   .detail so the rest of the console keeps its compact chrome. */
.detail .btn { padding: 10px 16px; font-size: 14px; border-radius: 9px; }
.detail .btn.sm { padding: 9px 14px; font-size: 13.5px; }
.detail .btn.xs { padding: 8px 13px; font-size: 13px; }
.detail .icon-btn { width: 40px; height: 40px; border-radius: 9px; }
.detail .tpl-chip { padding: 9px 13px; font-size: 13.5px; }
.detail .token { padding: 6px 12px; font-size: 13px; }
.detail .song-row { padding: 10px 12px; }
.detail .song-row-actions { gap: 8px; }

/* Larger, easier-to-scan player lineup rows (matches the bigger detail-pane
   buttons). Jersey/album-art glyph sizes bumped in JS (36 / 44). */
.listrow.plr { padding: 12px 12px 12px 8px; gap: 8px; }
/* grip + number read as one tight "position" cluster, then a clear break before
   the jersey/identity. Cells are CONTENT-width (no centered whitespace) so the
   digit hugs the grip dots instead of floating in a fixed cell. */
.listrow.plr .grip { width: auto; justify-content: flex-end; margin-right: -4px; } /* dots → digit ≈ 4px */
.listrow.plr .ord { width: 20px; flex-shrink: 0; font-size: 14px; text-align: center; margin-right: 2px; font-variant-numeric: tabular-nums; } /* FIXED width so 1- and 2-digit orders keep the jerseys column-aligned */
.listrow.plr .plr-name { flex: 1 1 auto; min-width: 0; } /* take the freed width so names don't truncate early */
.listrow.plr .plr-name > div:first-child { font-size: 15px; }
.listrow.plr .plr-pos { font-size: 12.5px; }
/* Claimed-parent count chip on a roster row (person glyph + N). */
.claim-chip { display: inline-flex; align-items: center; gap: 3px; flex: none; margin-right: 8px; padding: 2px 7px; border-radius: 999px; background: var(--surface-2); color: var(--text-2); font-size: 11.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* Neutral informational pill-link — e.g. "See league pricing". Rounded
   notification-pill shape (cf. .claim-chip); stays grayscale so it reads as an
   inviting info affordance, never an alert, and never competes with an orange
   (--accent) action CTA. Orange is action-only; this is informational. */
.pill-link { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px 5px 9px; border: 1px solid var(--line-2); border-radius: 999px; background: var(--surface-2); color: var(--text-2); font-size: 12px; font-weight: 600; line-height: 1; text-decoration: none; cursor: pointer; transition: background .12s ease, border-color .12s ease, color .12s ease; }
.pill-link svg { color: var(--text-3); flex-shrink: 0; }
.pill-link:hover { background: var(--surface); border-color: var(--line-hi, #3a4c6e); color: var(--text); }
.pill-link:hover svg { color: var(--text-2); }
/* Larger variant — standalone in open space (e.g. centered below a CTA row).
   A touch bigger so it owns the region, but stays a rounded neutral chip,
   never a filled CTA. */
.pill-link.lg { font-size: 13px; padding: 9px 16px 9px 14px; gap: 7px; border-color: var(--accent-cyan); }
.pill-link.lg svg { width: 15px; height: 15px; color: var(--accent-cyan); }
.pill-link.lg:hover { border-color: var(--accent-cyan); background: rgba(87, 200, 255, 0.08); }
.pill-link.lg:hover svg { color: var(--accent-cyan); }
.listrow.plr .plr-song.nosong { width: 44px; height: 44px; border-radius: 7px; }

/* Team color swatches (presets + custom picker) */
.color-row { display: flex; flex-wrap: wrap; gap: 8px; }
.swatch { width: 28px; height: 28px; border-radius: 8px; cursor: pointer; box-shadow: inset 0 0 0 1px rgba(0,0,0,.3); flex-shrink: 0; }
.swatch.on { outline: 2px solid #fff; outline-offset: 2px; }
.swatch.custom { position: relative; display: grid; place-items: center; overflow: hidden; box-shadow: inset 0 0 0 1px rgba(255,255,255,.35); }
.swatch.custom::after { content: "+"; color: #fff; font-weight: 700; font-size: 15px; mix-blend-mode: difference; }
.swatch.custom input { position: absolute; inset: -6px; opacity: 0; cursor: pointer; padding: 0; border: 0; }

/* Link-calendar: "Add to my calendar" guided steps */
.lc-app.on { border-color: var(--accent); background: var(--accent-bg); }
.lc-steps { margin-top: 14px; }
.lc-steps:empty { display: none; }
.lc-feed { background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--radius); padding: 10px 12px; margin-bottom: 12px; }
.lc-feed-row { display: flex; align-items: center; gap: 8px; margin-top: 6px; }
.lc-feed-row code { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: var(--accent); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.lc-ol { margin: 0; padding-left: 22px; display: flex; flex-direction: column; gap: 8px; }
.lc-ol li { font-size: 13px; color: var(--text-2); line-height: 1.45; }
.lc-ol li b { color: var(--text); font-weight: 600; }

/* Full-bleed dark topo/soundwave WALLPAPER — the SAME motif as the marketing
   site + app icon (cyan glow upper-left, purple/magenta lower-right), so the
   console reads as one premium brand. A fixed field behind everything; a dark
   navy scrim rides on top so content — especially the upper-left, under the
   wallpaper's brightest cyan — stays readable. The wallpaper's own center is
   dark/calm, and every panel is opaque glass, so tables/cards stay clean.
   Decorative, non-interactive. */
body::before {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  /* NO blur — it was softening/muddying the art (read as "data loss"); the
     calmer wallpaper needs none. A LIGHT scrim keeps content readable while
     letting the art's real crispness + color show through like the source. */
  background:
    linear-gradient(180deg, rgba(4,8,16,.42) 0%, rgba(4,8,16,.28) 45%, rgba(4,8,16,.44) 100%),
    url("/league-demo/assets/app-bg.webp?v=3") no-repeat center center;
  background-size: auto, cover;
  background-attachment: fixed, fixed; /* stable behind scrolling content */
}

/* Studio — populated SFX / music hooks / voices / vibes */
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.sfx-chip .sfx-emoji { width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center; font-size: 21px; background: var(--bg-2); border: 1px solid var(--line); transition: border-color .12s, transform .12s; }
.sfx-chip.auditioning .sfx-emoji { border-color: var(--accent); transform: scale(1.08); }
.hook-card { padding: 12px 13px; }
.voice-card { display: flex; align-items: center; gap: 12px; padding: 13px 14px; }
.voice-avatar { width: 42px; height: 42px; border-radius: 50%; display: grid; place-items: center; font-size: 22px; background: var(--bg-2); border: 1px solid var(--line); flex-shrink: 0; }
.voice-card .pill-play { flex-shrink: 0; }
.vibe-songs { margin-top: 16px; }
.vibe-songs:empty { display: none; }
.script-body { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; opacity: .8; }

/* Studio — hooks hero, playlists, custom-vibe card */
.hook-grid .hook-card { border-color: var(--line-2); }
.hook-grid .hook-card:hover { border-color: var(--accent); }
.pl-tracks { margin: 10px 0; padding-left: 20px; display: flex; flex-direction: column; gap: 3px; }
.pl-tracks li { font-size: 12px; color: var(--text-2); }
.pl-tracks li.muted { list-style: none; margin-left: -20px; }
.vibe-new { align-items: center; justify-content: center; text-align: center; border-style: dashed; }
.vibe-new .vibe-emoji { color: var(--accent); }

/* ── Studio Vibes tab: two-column (chooser left / songs right) ──────────────
   The base .vibe-card (above) stays a rectangle for the "Pick by vibe" modal
   (vibePickSheet); these scoped rules turn the CHOOSER tiles into the phone's
   square emoji-over-name tile and shrink them so the song list gets the room. */
.vibe-split { align-items: start; grid-template-columns: minmax(340px, 400px) 1fr; }
.vibe-chooser .vibe-grid { grid-template-columns: 1fr 1fr 1fr 1fr; gap: 7px; }
.vibe-chooser .vibe-card {
  align-items: center; justify-content: center; text-align: center; gap: 9px;
  aspect-ratio: 1 / 1; min-height: 0; padding: 6px 4px;
  transition: border-color .12s, background .12s, transform .08s;
}
.vibe-chooser .vibe-card:active { transform: scale(.96); }
.vibe-chooser .vibe-emoji { font-size: 25px; }
.vibe-chooser .vibe-name {
  font-size: 13.5px; line-height: 1.12; font-weight: 700; max-width: 100%;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.vibe-chooser .vibe-card.on { border-color: var(--accent); background: var(--accent-bg); box-shadow: 0 0 0 1px var(--accent) inset; }
.vibe-chooser .vibe-card.on .vibe-name { color: #ffd9c9; }
.vibe-chooser .vibe-new { gap: 9px; }
.vibe-chooser .vibe-new .vibe-emoji { font-size: 26px; }
/* No position override — inherit the centered base .vibe-card.loading::after.
   (These tiles center their content, so the old top-left 8px/8px was off-center.) */

/* selected vibe's song list (right detail pane) */
.vibe-detail { min-width: 0; }
.vibe-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; min-height: 280px; text-align: center; color: var(--text-2);
}
.vibe-empty .mw { height: 22px; opacity: .55; }
.vibe-detail tr.vrow { animation: vrowIn .22s ease backwards; animation-delay: calc(var(--i, 0) * 18ms); }
@keyframes vrowIn { from { opacity: 0; transform: translateX(-4px); } to { opacity: 1; transform: none; } }
.vibe-card.just-made { animation: vibeIn .28s cubic-bezier(.2,.7,.3,1); }
@keyframes vibeIn { from { opacity: 0; transform: translateY(6px) scale(.96); } to { opacity: 1; transform: none; } }

/* emoji picker — trigger tile + popover grid (replaces the bare <input>) */
.emoji-trigger {
  position: relative; width: 48px; height: 48px; flex: none; display: grid; place-items: center;
  font-size: 24px; background: var(--bg-2); border: 1px solid var(--line-2); border-radius: 10px;
  cursor: pointer; transition: border-color .12s, transform .08s;
}
.emoji-trigger:hover { border-color: var(--accent); }
.emoji-trigger:active { transform: scale(.96); }
.emoji-trigger-edit {
  position: absolute; right: -5px; bottom: -5px; width: 18px; height: 18px; display: grid; place-items: center;
  border-radius: 50%; background: var(--accent); color: #fff; border: 2px solid var(--surface);
}
.emoji-pop {
  position: absolute; z-index: 60; margin-top: 6px; left: 0;
  background: var(--surface); border: 1px solid var(--line-2); border-radius: var(--radius);
  box-shadow: 0 12px 36px rgba(0,0,0,.5); padding: 10px; width: 300px;
  display: grid; grid-template-columns: repeat(8, 1fr); gap: 2px;
}
.emoji-pop button {
  font-size: 20px; line-height: 1; aspect-ratio: 1; border: 0; background: none; border-radius: 7px;
  cursor: pointer; transition: background .1s, transform .08s;
}
.emoji-pop button:hover { background: var(--accent-bg); transform: scale(1.12); }
.emoji-pop button.on { background: var(--accent-bg); box-shadow: 0 0 0 1px var(--accent) inset; }

/* New-vibe build state — equalizer bars (reuses .mw/mwbounce) + staged label */
.vibe-building { display: flex; align-items: center; gap: 10px; margin-top: 10px; color: var(--accent); font-size: 12.5px; }
.vibe-building .mw { height: 18px; }
.vibe-building .mw-b { width: 3px; background: var(--accent); animation: mwbounce .9s ease-in-out infinite; }
.vibe-building .mw-b:nth-child(2) { animation-delay: .12s; }
.vibe-building .mw-b:nth-child(3) { animation-delay: .24s; }
.vibe-building .mw-b:nth-child(4) { animation-delay: .36s; }
.vibe-building .mw-b:nth-child(5) { animation-delay: .18s; }
.vibe-building .mw-b:nth-child(6) { animation-delay: .30s; }
.vibe-building .mw-b:nth-child(7) { animation-delay: .06s; }
.vibe-build-label { animation: vibeLabelFade .25s ease; }
@keyframes vibeLabelFade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) {
  .vibe-detail tr.vrow, .vibe-card.just-made, .vibe-chooser .vibe-card:active { animation: none; transform: none; }
  .vibe-building .mw-b { animation: none; }
}

/* Studio hook card — "Customize snippet" (waveScope) button beside Preview */
.pill-icon { width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--line-2); background: var(--bg-2); color: var(--text-2); cursor: pointer; display: grid; place-items: center; margin-right: 7px; }
.pill-icon:hover { border-color: var(--accent); color: var(--accent); }

/* League emoji mark — a squircle (sized by .sw-logo/.lg-logo-img) behind a generic
   emoji, e.g. the demo league's ⚾. Rendered in the DOM so the color glyph is reliable. */
.emoji-mark { display: inline-flex; align-items: center; justify-content: center; line-height: 1;
  background: linear-gradient(135deg, #2f5d3a, #1b3a25); }
/* personal-space "you" mark (coach cap) — slate tint, distinct from org crests.
   Flipped on X so the cap faces INTO the page, not out toward the margin. */
.emoji-mark.personal-mark { background: linear-gradient(135deg, #46566f, #2a3647); transform: scaleX(-1); }
/* Subscription league-upsell card — subtle accent rail so it reads as an offer. */
.sub-upsell { border-left: 3px solid var(--accent); }

/* Pilot-request lead modal — form + paywall-style value panel */
.modal.modal-lead { max-width: 760px; }
.lead-grid { display: grid; grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 700px) { .lead-grid { grid-template-columns: 1.32fr 1fr; gap: 28px; align-items: stretch; } }
@media (max-width: 699px) { .lead-value { order: -1; } }  /* sell before the fields on mobile */
/* Form column is a flex column that stretches to the (taller) value aside, so
   the pricing pill can center in the residual space below the button row. */
.lead-form { min-width: 0; display: flex; flex-direction: column; }
/* Residual-space slot holding the "See league pricing" pill — centers it on
   both axes in the gap the shorter form column leaves under the CTA row, so it
   reads as a deliberate pricing beat, not an orphaned link. */
.lead-price-slot { display: flex; align-items: center; justify-content: center; flex: 1 1 auto; min-height: 64px; }
.lead-value { background: rgba(255,255,255,0.03); border: 1px solid var(--line); border-radius: 12px; padding: 16px 16px 14px; }
/* Value panel coloring (per brand review): faint orange eyebrow frames it as
   "yours"; bold white group headings + a divider tell the two-audience story;
   brighter green checks read as accumulating value. Orange appears ONCE — the CTA. */
.lead-eyebrow { font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); opacity: .85; margin-bottom: 12px; }
.lead-grp { font-size: 15px; font-weight: 700; color: #fff; letter-spacing: -.01em; margin: 0 0 8px; }
.lead-grp.lead-grp-divide { margin-top: 20px; padding-top: 18px; border-top: 1px solid rgba(255,255,255,0.07); }
.lead-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.lead-list li { position: relative; padding-left: 22px; font-size: 14px; line-height: 1.5; color: rgba(255,255,255,0.82); }
.lead-list li::before { content: "✓"; position: absolute; left: 0; top: 0; color: #34D17A; font-weight: 800; font-size: 13px; }
.lead-reassure { margin: 14px 0 0; font-size: 11.5px; color: var(--text-3); line-height: 1.4; }

/* Persistent pilot CTA pinned in the sidebar (demo only) — the always-visible
   "where do I click for more info". The one SOLID-orange element in the rail. */
/* Roomier + toned-down (a #ef4f1e→#d8431a gradient reads less "hot" than flat
   full-saturation orange, while staying the one CTA). Per design review. */
/* Calmer (per design review): the glow = high chroma × large area × shadow.
   Deeper burnt-orange, NO glow shadow, smaller lit area (one-line title, tighter
   padding). Stays the one warm solid block in the rail, so still findable. */
/* Cool cyan→purple gradient pulled from the page's wave-line motif (per design
   review): calmer than orange, reads as the "brand texture" surfacing as the one
   sales CTA — distinct from the orange in-console operator buttons. No glow. */
/* margin-top:auto makes the CTA the bottom-anchor when present, so it drops to the
   foot of the rail and sits right above the account row's divider (next to the name),
   instead of stacking up top. CRITICAL: .account ALSO has margin-top:auto, and two
   auto-margin flex items in a column SPLIT the free space — which floated the CTA to
   mid-rail with a gap above the account. So when the CTA is present we cancel the
   account's own auto-margin (rule below), leaving the CTA as the sole anchor; it then
   absorbs all slack and sits flush above the account. (Account keeps its auto for the
   no-CTA case.) */
/* Glow kept just BELOW the wordmark's bloom (0 8px 34px -8px rgba(59,111,255,.28))
   so the wordmark stays the rail's brightest lit point, not this bottom CTA. */
.pilot-cta { margin: auto 4px 8px; padding: 11px 15px; background: linear-gradient(135deg, #1f9bb3 0%, #6d4bc4 100%); border: 1px solid rgba(160, 200, 255, .18); border-radius: var(--radius); color: #F2F6FF; cursor: pointer; display: flex; flex-direction: row; align-items: center; gap: 11px; text-align: left; font-family: inherit; box-shadow: 0 5px 18px -8px rgba(59,111,255,.34), inset 0 1px 0 rgba(255,255,255,.14); transition: background .16s, transform .16s, box-shadow .16s, border-color .16s; }
.pilot-cta-ic { flex: none; display: flex; opacity: .95; }
.pilot-cta-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.pilot-cta:hover .pilot-cta-ic { transform: translate(1px, -1px); transition: transform .16s; }
.nav:has(.pilot-cta) .account { margin-top: 0; }
.pilot-cta:hover { background: linear-gradient(135deg, #25abc4 0%, #7a57d4 100%); border-color: rgba(180, 215, 255, .28); box-shadow: 0 7px 22px -8px rgba(87,120,255,.44), inset 0 1px 0 rgba(255,255,255,.18); transform: translateY(-1px); }
/* Title WRAPS within the pill (the reframed copy is longer than the old text,
   and nowrap made it spill past the rounded box). The friendly paper-airplane
   glyph now leads the pill instead of a trailing chevron. */
.pilot-cta-title { font-weight: 700; font-size: 12.5px; line-height: 1.25; overflow-wrap: anywhere; }
.pilot-cta-sub { font-size: 11px; opacity: .88; }
/* Ribbon CTA — secondary outlined reinforcement (no solid fill: one solid only). */
.demobar a.ribbon-cta { border: 1px solid var(--accent); color: var(--accent); border-radius: 6px; padding: 3px 10px; font-weight: 600; text-decoration: none; }
.demobar a.ribbon-cta:hover { background: rgba(255,87,31,.12); }

/* ── Achievements (trophy case) ──────────────────────────────────────────
   Earned = GOLD (--amber status color; a badge is a status, never an orange
   action). Locked = neutral greyscale, mirroring the iOS grid. The corner
   chip marks "earn it on the other surface" (phone here ↔ monitor in the
   apps) and stays full-opacity in both states — wayfinding, not a lock.
   Earned tiles get extra flair (design-consult 2026-07-10): a warm glow +
   lift + hover sheen and a gold gradient medallion for the icon circle.
   Deliberately NOT the .card--lit masked rim-ring — that technique is
   reserved for container/identity panels ("metric tiles, teamcards...do
   NOT"), and .ach-tile is a grid item in that same category; a ring at
   148px tile width also reads as noise, not richness. The gold comes from
   the icon-circle gradient + tile glow instead — a cleaner signal at
   small size that keeps the ring scoped to its one reserved job. */
.ach-hero { display: flex; align-items: center; gap: 16px; max-width: 640px; margin-top: 14px; padding: 18px; }
.ach-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(148px, 1fr)); gap: 10px; margin: 10px 0 22px; }
.ach-tile { display: flex; flex-direction: column; align-items: center; text-align: center; padding: 16px 10px 13px; cursor: pointer; position: relative; font: inherit; color: inherit; overflow: hidden; }
.ach-tile.earned {
  border-color: rgba(240, 165, 0, .22);
  box-shadow: var(--panel-shadow), var(--panel-top-hi), 0 8px 22px -12px rgba(240, 165, 0, .35);
  transition: transform .14s ease, box-shadow .14s ease, border-color .14s ease;
}
.ach-tile.earned:hover {
  transform: translateY(-2px);
  border-color: rgba(240, 165, 0, .38);
  box-shadow: var(--panel-shadow), var(--panel-top-hi), 0 12px 28px -12px rgba(240, 165, 0, .48);
}
/* Glass sheen: a soft diagonal highlight band parked off-tile, sweeps across on hover. */
.ach-tile.earned::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(115deg, transparent 42%, rgba(255, 224, 150, .14) 50%, transparent 58%);
  background-size: 220% 100%; background-position: 120% 0;
  transition: background-position .55s ease;
}
.ach-tile.earned:hover::before { background-position: -20% 0; }
.ach-ico { width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center; background: var(--bg-2); border: 1px solid var(--line-2); color: var(--text-3); }
.ach-ico.earned {
  background: radial-gradient(circle at 34% 28%, #ffe28a 0%, #f7b400 46%, #c47f00 100%);
  border-color: rgba(255, 224, 150, .55);
  color: #241503;
  box-shadow: 0 0 0 1px rgba(255, 200, 60, .18), 0 3px 10px -3px rgba(240, 165, 0, .55), inset 0 1px 1px rgba(255, 255, 255, .35);
  transition: transform .14s ease;
}
.ach-tile.earned:hover .ach-ico.earned { transform: scale(1.05); }

/* ── Achievement MEDALS (docs/design/achievement-medals.html, Rev A) ──────────
   The medal brings its own rim, face, bevel and shadow, so this strips the flat
   gold disc `.ach-ico` would otherwise paint UNDER it — a disc behind a medal
   reads as a gold ring around a gold coin.
   ⚠️ Modifier, not an override of `.ach-ico` itself: the trophy-case HERO
   (`.ach-hero`) still uses a plain `.ach-ico.earned` gold disc for its award
   icon and is NOT a badge. Restyling `.ach-ico` globally would silently flatten
   it. Sizing stays on the inner <svg> so the existing layout + the earned-hover
   scale keep working unchanged. */
.ach-ico--medal,
.ach-ico--medal.earned {
  width: auto; height: auto; border: 0; border-radius: 0;
  background: none; box-shadow: none; overflow: visible;
}
.ach-name { font-size: 13px; font-weight: 600; margin-top: 9px; color: var(--text-2); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.ach-tile.earned .ach-name { color: var(--text); }
.ach-sub { font-size: 11.5px; margin-top: 3px; color: var(--text-3); font-variant-numeric: tabular-nums; }
.ach-sub.earned { color: var(--amber); font-weight: 700; letter-spacing: .2px; }
.ach-chip { position: absolute; top: 8px; right: 8px; width: 20px; height: 20px; border-radius: 50%; display: grid; place-items: center; background: var(--surface-2); border: 1px solid var(--line-2); color: var(--text-2); }
@media (max-width: 480px) { .ach-grid { grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); } }
