/* ===================================================================
   Carrom Scorer — styles (mobile-first, dark)
   =================================================================== */
/* Hide scrollbar chrome across every scroller — scrolling still works,
   the bar just never shows (WebKit/Blink, Firefox and legacy Edge). */
* { scrollbar-width: none; -ms-overflow-style: none; }
*::-webkit-scrollbar { width: 0; height: 0; display: none; }
:root {
  /* The notch/status-bar inset, mirrored into a custom property so script can
     read it (env() is not visible to JS any other way). Pull-to-refresh uses it
     to land its ring below the notch instead of inside it. */
  --sat: env(safe-area-inset-top, 0px);

  /* The home-indicator inset, twice over. --sab-raw is whatever the browser
     says at this instant; --sab is what the floating nav is allowed to believe.
     iOS reports the bottom inset wrongly for a moment while a view transition
     is in flight, and the bar is positioned on it — so every bad reading moved
     it. app.js latches the largest reading since the last orientation change
     into --sab, which for a given device and orientation is the true one. */
  --sab-raw: env(safe-area-inset-bottom, 0px);
  --sab: var(--sab-raw);

  /* Carrom Cup — deep indigo ground, lime Team A, cyan Team B, gold brand */
  --bg:          #0b0914;
  --bg-1:        #161226;
  /* The glass top bar and bottom nav: dark, see-through, blurred. */
  --bar:         rgba(8, 7, 12, .66);
  --bar-solid:   #0a090f;   /* where the browser can't blur */
  --bar-line:    rgba(255, 255, 255, .07);
  /* Glass is the app's one surface. Every panel is a thin fill over a blur of
     whatever is behind it, so the coins in the corner and the ground carry
     through the whole page instead of stopping at the first card. These are
     the fills; the blur belongs to the panels, since a filter can't live in a
     custom property. Composited over the page they land within a shade or two
     of the solid colours they replaced (#161226 and #221c38). */
  --surface:     rgba(30, 28, 42, .38);
  --surface-2:   rgba(46, 43, 62, .42);
  --surface-3:   #1c1635;
  /* Where a fill has to actually hide what is under it — the pinned first
     column of a table, which scrolling columns slide beneath. Blurring it
     would smear them rather than hide them, so it stays opaque, set to what
     the glass around it composites to over the page (#191628) rather than to
     the solid colour glass replaced, or the column reads as a darker stripe. */
  --surface-solid: #191628;
  --glass-blur:  blur(20px) saturate(125%);
  --border:      #2c2647;
  --text:        #ffffff;
  --muted:       #9e9aa9;
  --dim:         #625d73;

  /* Brand */
  --gold:        #ffd700;
  --gold-light:  #ffe893;
  --gold-deep:   #d49e25;
  --gold-dark:   #7a5303;
  --amber:       #ffb03a;
  /* The two sides: lime vs cyan. Gold is the brand's alone. */
  --lime:        #c6ff4d;
  --lime-deep:   #7fae1f;
  --lime-light:  #eaffc2;
  --cyan:        #33d1ff;
  --cyan-deep:   #3aa8d0;
  --cyan-light:  #d6f2ff;

  --primary:     var(--gold);
  --primary-ink: #140f00;
  --accent-soft: rgba(255, 176, 58, .10);   /* selected/active tint */
  --accent-line: rgba(255, 215, 0, .55);    /* selected/active outline */
  --win:         var(--text);
  --danger:      #e0483c;
  /* Pastel red for the game clock — reads as "running" without competing
     with the gold brand or the hard --danger red on destructive buttons. */
  --clock:       #ff9b9b;

  /* The two sides finally read apart, everywhere they appear */
  --team-a:      var(--lime);
  --team-b:      var(--cyan);

  /* Game-type pills — deliberately outside the brand and both team colours */
  --derby:       #b39dff;   /* violet */
  --lottery:     #45d6c2;   /* teal */
  --h2h:         #ff8fa8;   /* rose — one against one */
  --custom:      #78bef5;   /* sky — sides picked by hand */

  /* Radii */
  --r-sm:  8px;
  --r-md:  12px;
  --r-lg:  16px;
  --r-xl:  20px;
  --r-2xl: 24px;
  --r-3xl: 28px;
  --r-pill: 100px;
  --radius: var(--r-lg);

  /* Elevation */
  --sh-card:   0 8px 14px 0 rgba(0, 0, 0, .25);
  --sh-raised: 0 8px 14px -6px rgba(0, 0, 0, .4);
  /* The nav floats, so its shadow falls away from it on every side rather
     than only upward off a bottom edge. */
  --sh-nav:    0 10px 30px -6px rgba(0, 0, 0, .78), 0 2px 10px -3px rgba(0, 0, 0, .55);
  --sh-gold:   0 8px 17.5px 0 rgba(255, 176, 58, .3);

  /* Two families, separated by job. Bricolage Grotesque is variable on three
     axes; its weight stops at 800, so nothing here asks for 900. Optical
     sizing is left on its default (auto), so a 44px score is drawn with the
     display cut and a 13px chip with the text cut, from the one file. */
  --font-display: "Bricolage Grotesque", system-ui, sans-serif;
  --font-ui: "Geist", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; }
html {
  height: 100%;
  /* The shell is 100dvh; anything the browser paints outside it — under the
     home indicator, or during overscroll — used to be the default white. */
  background: var(--bg);
  color-scheme: dark;
}
/* App shell. The page itself never scrolls: the two bars are flex items that
   physically cannot move, and only the middle column scrolls. Fixed bars drift
   away from the edge during iOS rubber-band overscroll in a home-screen app —
   this layout has nothing to drift. */
body {
  height: 100%;
  /* 100% resolves against a viewport iOS keeps changing — the URL bar in an
     in-app browser, the home indicator in a home-screen app — which left the
     shell taller or shorter than the screen. dvh follows the real one. */
  display: flex; flex-direction: column;
  overflow: hidden;
  overscroll-behavior: none;
  position: relative;   /* the glass bars are placed against it */
  background: linear-gradient(180deg, var(--bg) 0%, var(--bg-1) 55%, var(--bg) 100%);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 15px; line-height: 1.5; font-weight: 400;
}
@supports (height: 100dvh) {
  html, body { height: 100dvh; }
}
/* Opened from the Home Screen there is no URL bar for dvh to follow, and iOS
   gets dvh wrong there instead: it opens the app ~50pt short of the screen and
   only corrects it a step per redraw, so for the first few tab switches the
   shell stopped short of the bottom — content cut off above empty ground, and
   the nav, pinned to the shell's foot, riding high and creeping down on every
   switch. The large viewport is the whole screen and never moves, which is
   what a home-screen app is. The class (set in app.js) covers iOS versions
   that only answer navigator.standalone. */
@supports (height: 100lvh) {
  @media (display-mode: standalone) {
    html, body { height: 100lvh; }
  }
  html:has(> body.standalone), body.standalone { height: 100lvh; }
}

/* In a browser wider than a phone, the app stays a phone: one column the width
   of the largest iPhones, centred on plain dark ground. The column is the
   frame for everything — the transform makes it the containing block for the
   fixed layers too (the coins in the corner, sheets, toasts, the kick-off,
   confetti), so they cover the app rather than the whole window, and anything
   drawn full-bleed (Home's rally) stops at the column's edge. Phones never
   reach this width, so nothing changes on them. */
@media (min-width: 600px) {
  /* overflow on html keeps the body's own overflow on the body: left visible,
     the browser hands the body's clip to the window instead, and the corner
     glow and the rally spill out past the column. auto, not hidden, so the
     share page — which scrolls the document — still scrolls. */
  html { background: #060509; overflow: auto; }
  body {
    width: 100%; max-width: 430px; margin-inline: auto;
    transform: translateZ(0);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, .06), 0 0 90px rgba(0, 0, 0, .65);
  }
}

/* Splash / loading screen */
.splash {
  position: fixed; inset: 0; z-index: 200;
  /* Light pooling in the app's own colours — gold above, cyan and lime off
     each shoulder — over the deep indigo ground. No photo to download, so the
     first paint is instant. */
  background:
    radial-gradient(120% 70% at 50% -12%, rgba(255, 176, 58, .10), transparent 58%),
    radial-gradient(90% 55% at 112% 18%, rgba(51, 209, 255, .075), transparent 56%),
    radial-gradient(95% 55% at -12% 78%, rgba(198, 255, 77, .055), transparent 56%),
    radial-gradient(120% 80% at 50% 118%, rgba(179, 157, 255, .05), transparent 62%),
    linear-gradient(162deg, #07060e 0%, #100c22 48%, #07060e 100%);
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 20px; padding: 24px;
  transition: opacity .35s ease;
}
.splash.hide { opacity: 0; pointer-events: none; }
.splash-logo {
  width: min(62vw, 260px); height: auto;
  filter: drop-shadow(0 10px 30px rgba(0,0,0,.5));
}
.splash-spinner {
  width: 26px; height: 26px; border-radius: 50%;
  border: 3px solid rgba(216, 168, 78, .22);
  border-top-color: var(--primary);
  animation: splash-spin .8s linear infinite;
}
@keyframes splash-spin { to { transform: rotate(360deg); } }
.splash-text { color: var(--muted); font-size: 13px; letter-spacing: .02em; }
@media (prefers-reduced-motion: reduce) {
  .splash-spinner { animation-duration: 2s; }
}

.icon-btn {
  background: transparent; border: none; color: var(--text);
  font-size: 20px; cursor: pointer; padding: 4px 8px; line-height: 1;
}
.icon-btn:active { transform: scale(.92); }

/* Carrom coins in the top corner of every screen but Home, dimmed almost to
   nothing and lit from behind in blue, so the corner has some depth without
   anything to read in it. Home has the rally playing across its whole top
   instead, and two backdrops at once would be a muddle.
   Fixed rather than scrolled: it belongs to the screen, not to the page, so it
   stays put while a list runs past it. */
.app-bg {
  position: fixed; z-index: 0; pointer-events: none;
  /* The whole top-right quarter, not a patch of it: the coins fill the corner
     and run out on the diagonal, so the shape they make is the corner itself. */
  top: 0; right: 0; width: min(100vw, 680px); height: min(62vh, 460px);
  /* Up past the safe area and any notice bar, to the true top of the screen. */
  margin-top: calc(-1 * (env(safe-area-inset-top) + var(--top-h, 0px)));
}
/* The light first, so the coins sit in it rather than on it. */
.app-bg::before {
  content: ""; position: absolute; inset: -30% -20% -45% -35%;
  background: radial-gradient(circle at 80% 14%,
    rgba(70, 134, 255, .30) 0%, rgba(52, 102, 224, .15) 38%, rgba(30, 60, 160, 0) 72%);
}
/* The coins, faded right down and cut away on the diagonal: full strength into
   the corner, gone by the middle of the screen, so there is no edge where the
   picture stops — only the page coming back.
   All the colour is taken out of them: the brass and red would fight the blue,
   and what is wanted from the picture is the rings, not the wood. The blue
   underneath is what the corner takes its colour from. */
.app-bg::after {
  content: ""; position: absolute; inset: 0;
  /* Cropped to the corner rather than fitted to the box, so the coins stay
     their own size however wide the screen gets. */
  background: url("../assets/coins.webp") no-repeat top right / auto 150%;
  filter: grayscale(1) contrast(1.1);
  opacity: .055;
  -webkit-mask-image: linear-gradient(to bottom left, #000 0%, #000 34%, rgba(0,0,0,.35) 58%, transparent 82%);
          mask-image: linear-gradient(to bottom left, #000 0%, #000 34%, rgba(0,0,0,.35) 58%, transparent 82%);
}
/* Home keeps its own backdrop while it is on screen — the rally behind the
   banner — and the coins stay out of its way. Scroll the banner off the top
   and they come up in its place, so the screen never loses its ground. */
body.on-home .app-bg { opacity: 0; transition: opacity .5s ease; }
body.on-home.past-hero .app-bg { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  body.on-home .app-bg { transition: none; }
}

/* The only scrolling element. Full width so a desktop wheel works anywhere;
   the content inside is what's capped and centred. */
.container {
  /* Above the coins. */
  position: relative; z-index: 1;
  flex: 1 1 auto; min-height: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* It runs the full height, under the glass nav; the padding keeps the content
     clear of the notch and of anything floating at either end (a notice bar at
     the top, the nav at the bottom — both measured in app.js). */
  padding: calc(env(safe-area-inset-top) + var(--top-h, 0px) + 16px) max(16px, env(safe-area-inset-right))
           calc(var(--nav-h, 64px) + 16px) max(16px, env(safe-area-inset-left));
  /* Home's backdrop reaches past this padding to both screen edges. Clipping
     sideways keeps that bleed from adding a stray horizontal scroll — clip
     rather than hidden, so this stays a one-axis scroller. */
  overflow-x: clip;
  scroll-padding-top: calc(env(safe-area-inset-top) + var(--top-h, 0px)); scroll-padding-bottom: var(--nav-h, 64px);
}
/* No blur available: the nav goes solid rather than showing text through. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav { background: var(--bar-solid); }
}
.container > * { max-width: 440px; margin-inline: auto; }

/* ---------- Installed app ----------
   On the home screen the app owns the device, so the content stops being a
   centred 440px column and fills the screen. The two bars keep their
   safe-area padding, so the system clock/battery strip at the top and the
   home indicator at the bottom stay clear on every device — the inset is
   whatever that device reports, not a fixed number. */
@media all and (display-mode: standalone) {
  .container > * { max-width: none; }
  body.lottery-draw .container > .view { max-width: none; }
}
body.standalone .container > * { max-width: none; }
body.standalone.lottery-draw .container > .view { max-width: none; }
.view { display: flex; flex-direction: column; gap: 14px; }
.view:has(.settings-card) { gap: 10px; }
/* A screen's one action, docked just above the nav: the view fills the
   column so a short screen still puts it at the bottom, and on a long one it
   sticks there while the rest scrolls under it. Sticky measures from the
   column's content edge, which its bottom padding already holds clear of the
   nav (see .container), so the inset is nothing. */
.view:has(> .dock-btn) { min-height: 100%; }
/* .btn.primary sets position: relative further down; this has to outrank it. */
.btn.primary.dock-btn { margin-top: auto; position: sticky; bottom: 0; z-index: 5; }

h1 { font-family: var(--font-display); font-size: 22px; line-height: 1.25; font-weight: 700; margin: 4px 0; }
/* Headings all sit in the display face, like h1 — they were inheriting the
   body font, which read as a different typeface beside the scores and names. */
h2 { font-family: var(--font-display); font-size: 14px; line-height: 1.4; font-weight: 700; margin: 10px 0 2px; color: var(--muted); text-transform: uppercase; letter-spacing: .06em; }
h3 { font-family: var(--font-display); font-size: 17px; line-height: 1.35; font-weight: 700; margin: 6px 0; }
.muted { color: var(--muted); }
.small { font-size: 13px; line-height: 1.4; }

/* Buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--r-md);
  padding: 11px 16px; font-size: 14px; line-height: 1.4; font-weight: 600;
  font-family: var(--font-display);
  box-shadow: var(--sh-raised);
  text-decoration: none; cursor: pointer;
}
.btn:active { transform: translateY(1px); }
/* Waiting on the database. The label already says so; this stops the
   button looking pressable, and kills the tap-down nudge. */
.btn:disabled, .seg-btn:disabled {
  opacity: .55;
  cursor: default;
  pointer-events: none;
}
.btn:disabled:active { transform: none; }
.btn.primary {
  background: linear-gradient(90deg, var(--gold-light) 0%, var(--amber) 100%);
  color: var(--primary-ink); border-color: transparent;
  box-shadow: var(--sh-gold);
}
.btn.danger { color: var(--danger); border-color: var(--danger); background: transparent; }
.btn.big { padding: 16px 20px; font-size: 18px; font-weight: 600; width: 100%; border-radius: 18px; }
/* Back link at the top of the game screen */
/* Tie / Delete: equal width, equal height */
.game-actions > .btn { flex: 1 1 0; min-width: 0; padding: 12px 10px; text-align: center; }
.row { display: flex; align-items: center; }
.row.gap { gap: 10px; }
.row.gap > * { flex: 1; }

.input {
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: 10px;
  padding: 11px 14px; font-size: 16px; width: 100%;
}

/* Hero */
/* Home banner. It carries no picture of its own: the carrom photos lie behind
   the whole top of the page (.home-bg below) and the banner is a pane of frosted
   glass over them — the photo goes soft and blurred where the card covers it,
   and sharp again everywhere else. Four things do that together: a tint thin
   enough to see through, the blur behind it, a pale hairline edge, and a light
   catching the inside of the top edge. The tint is still dark enough to hold the
   title, the line and the gold button. */
.hero {
  position: relative;
  background:
    radial-gradient(120% 100% at 50% -20%, rgba(255,176,58,.16) 0%, rgba(255,176,58,0) 62%),
    linear-gradient(180deg, rgba(10,9,16,.24) 0%, rgba(5,4,10,.44) 100%);
  /* No blur. A backdrop-filter can only composite once what is behind it has
     painted, so the banner arrived clear and then turned frosted a second later
     as the photos loaded — a visible change every time Home opened. The tint
     alone is steady from the first frame, and the photos behind are faint
     enough that nothing needs softening. */
  /* Glass takes a pale edge rather than the grey one the solid cards wear. */
  border: 1.5px solid rgba(255,255,255,.12); border-radius: var(--r-3xl);
  box-shadow: var(--sh-card), inset 0 1px 0 rgba(255,255,255,.10);
  padding: 20px 18px 18px; text-align: center;
  display: flex; flex-direction: column; gap: 10px;
}
/* A game in play gets the same banner as an idle one — glass over whatever is
   behind it. The flowing blue light belongs to the game screen now, where it
   lies behind the whole board; on Home it would make this banner the only thing
   on the page wearing a picture of its own. */

/* While a game is on, the banner's own rim runs the same light as the card
   inside it — the same gradient, the same 4s, the same direction — instead of
   the conic runner it wears when idle. Two different motions on top of each
   other read as two things happening; one motion reads as the light crossing
   the whole banner. Each band travels its own element's width, so they arrive
   at their middles together, which is what the eye follows. */
.hero.is-live.edge-glow::after { animation: none; opacity: 0; }
.hero.is-live::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1.5px; pointer-events: none; z-index: 2;
  background: linear-gradient(100deg,
    transparent 0%, transparent 38%,
    rgba(240, 214, 160, .9) 50%,
    transparent 62%, transparent 100%);
  background-size: 260% 100%;
  -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;
  animation: hero-shine 4s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .hero.is-live::before { animation: none; }
}

.hero h1 {
  margin: 0; color: #fff; font-size: 26px; line-height: 1.15;
  letter-spacing: -.5px; font-weight: 700;
  text-shadow: 0 2px 14px rgba(0,0,0,.7);
}
/* The line under the title sits tight to it (~6px); the space above the
   button stays about as it was. Without this the browser's default paragraph
   margin added ~14px on top of the banner's 10px gap. */
.hero .muted { color: #e2e8f0; margin: -4px 0 12px; }

/* Home banner logo — smaller than it used to be (148px), so the Stars of the
   week above it still leave the New Game button high on the screen. */
.hero-logo {
  align-self: center; width: min(24vw, 92px); height: auto;
  border-radius: 8px; filter: drop-shadow(0 6px 16px rgba(0,0,0,.5));
}

/* ---------- Home's backdrop ----------
   A carrom rally plays behind the top of Home — header, Stars of the week and
   banner — instead of sitting inside the banner. Dimmed to a fraction of its
   strength and masked away before the list starts, it reads as the page's own
   ground: there is no edge where the picture stops, only the app's background
   coming back. It is muted, looped and has no controls. */
.home-top {
  position: relative; display: flex; flex-direction: column; gap: 14px;
  /* How far below the banner the picture takes to disappear. Long enough to be
     a dissolve rather than a cut, short enough that it is gone before Recent
     games: the picture is the banner's backdrop, not the list's. */
  --bg-fade: 64px;
}
/* The content rides above the pictures. */
.home-top > *:not(.home-bg) { position: relative; z-index: 1; }
.home-bg {
  position: absolute; z-index: 0; pointer-events: none; overflow: hidden;
  /* Up past the column's top padding to the very top of the screen, and the
     full width of the screen — nothing should frame the picture. On a phone
     that is the column plus its padding; on a wide screen it spreads behind
     the whole window rather than ending in a seam beside the 440px column. */
  top: calc(-1 * (env(safe-area-inset-top) + var(--top-h, 0px) + 16px));
  left: 50%; transform: translateX(-50%); width: 100vw;
  /* The tail below the banner is where the picture fades out, so it has to be
     long enough to fade across — and the banner needs a picture behind it,
     otherwise its glass has nothing to blur. */
  bottom: calc(-1 * var(--bg-fade));
}
/* The opacity here is the whole effect: raise it and the footage becomes a
   video being watched rather than the ground the page stands on. */
.home-bg-video {
  position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 1.8s ease-in-out;
  /* A texture rather than a recording. */
  filter: saturate(.7) contrast(1.05) brightness(.9);
  /* The merge: full strength the whole way down the banner, then gone across
     the tail below it. Measured from the bottom rather than in percentages, so
     it lands in the same place whether or not there are stars above, and
     whether the banner holds a live score or the New Game button. */
  -webkit-mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - var(--bg-fade)), transparent 100%);
          mask-image: linear-gradient(180deg, #000 0, #000 calc(100% - var(--bg-fade)), transparent 100%);
}
.home-bg-video.on { opacity: .18; }
/* Reduced motion: no fade in (app.js also leaves the rally paused on frame one). */
@media (prefers-reduced-motion: reduce) { .home-bg-video { transition: none; } }

/* Top-bar logo + name */
/* Home's header: the logo and the signed-in player, side by side at the top of
   the page itself, so they scroll away with the content. No other screen has
   one — those start at their own first heading. */
.home-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.brand-logo { height: 44px; width: auto; display: block; }
/* The mark carries the name already — the wordmark was just repeating it. */

/* In-progress game shown inside the hero */
.hero-live {
  position: relative;
  display: flex; flex-direction: column; gap: 10px;
  text-decoration: none; color: #fff;
  /* Glass, like everything else on this screen — it used to be a near-black
     slab sitting on the banner's own glass, which read as a hole in it. A dark
     tint rather than a light one: enough ground for the score to stand on,
     little enough that the photo behind still carries through. */
  background: rgba(9,8,14,.22); border: 1px solid rgba(216,168,78,.38);
  overflow: hidden;   /* the sweep below is clipped to the card */
  border-radius: var(--radius); padding: 14px 16px; margin-top: 4px;
  -webkit-backdrop-filter: blur(6px) saturate(1.15);
          backdrop-filter: blur(6px) saturate(1.15);
}
.hero-live-top { display: flex; align-items: center; justify-content: space-between; }

/* A shine travelling through the border. Drawn as a 1px ring via mask, so the
   card's translucency over the hero photo is preserved. */
.hero-live::before {
  content: ""; position: absolute; inset: 0; border-radius: inherit;
  padding: 1px; pointer-events: none;
  background: linear-gradient(100deg,
    transparent 0%, transparent 38%,
    rgba(240, 214, 160, .95) 50%,
    transparent 62%, transparent 100%);
  background-size: 260% 100%;
  -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;
  animation: hero-shine 4s linear infinite;
}
/* The same light, crossing the whole card rather than only its rim. It has to
   sit on exactly the box the rim does — same inset, same background-size, same
   keyframes — or the two bands travel at different speeds across different
   widths and arrive out of step. The band itself is wide and soft where the
   rim's is a hard line, and it screens rather than paints, so it lifts the card
   like light passing over it instead of a grey shape sliding across. */
.hero-live::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(100deg,
    transparent 18%,
    rgba(255, 244, 214, .03) 32%,
    rgba(255, 244, 214, .09) 43%,
    rgba(255, 244, 214, .15) 50%,
    rgba(255, 244, 214, .09) 57%,
    rgba(255, 244, 214, .03) 68%,
    transparent 82%);
  background-size: 260% 100%;
  mix-blend-mode: screen;
  animation: hero-shine 4s linear infinite;
}
@keyframes hero-shine {
  from { background-position: 160% 0; }
  to   { background-position: -160% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-live::before, .hero-live::after, .live-clock .dot { animation: none; }
}
.hero-live .team-name { color: #e2e8f0; }
.hero-live .team-score { color: #fff; }
.hero-live .vs { color: #cbd5e1; }
.hero-live .tap-continue { color: var(--primary); font-size: 13px; font-weight: 600; text-align: center; }

/* Cards */
.card {
  background: var(--surface); border: 1.5px solid var(--border);
  border-radius: var(--r-xl); padding: 16px; text-decoration: none; color: var(--text);
  box-shadow: var(--sh-card);
  display: block;
}
/* --- Game card --- */
/* No outline, and a slight lift down the card: a touch brighter along the top
   edge, settling darker at the foot, so it catches light from above. Same tone
   throughout — the steps keep red, green and blue in the same proportion, so
   it never drifts purple.
   The fill is thin enough to see the page through, over a blur of whatever is
   behind: the coins in the corner and the ground beneath them carry through
   the stack of cards instead of stopping at the first one. */
.game-card {
  display: flex; flex-direction: column; gap: 14px; padding: 18px;
  background: linear-gradient(180deg,
    rgba(34, 32, 46, .40) 0%, rgba(28, 26, 38, .34) 45%, rgba(21, 20, 29, .30) 100%);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
  border: 0;
  border-radius: var(--r-2xl);
  box-shadow: var(--sh-card);
  transition: box-shadow .2s ease, transform .1s ease;
}
/* Every panel that stands on the page in its own right takes the blur. The
   small things inside one — a pill, a chip, a badge — are left to the fill
   alone: they already sit on a blurred panel, and a blur inside a blur costs
   a layer for a difference nobody can see. */
.card, .score-panel, .btn, .input, .seg-btn, .fchip-btn, .fchip-menu,
.modal, .toast, .install-steps, .draw-team, .draw-card-face, .team-preview {
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
/* No blur available: the fills go back to solid, or the page would read
   straight through every panel on it. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root { --surface: #161226; --surface-2: #221c38; --surface-solid: #161226; }
  .game-card {
    background: linear-gradient(180deg, #201a2e 0%, #1a1526 45%, #14101c 100%);
  }
  .card.profile-hero { background: linear-gradient(180deg, #201a2e 0%, #1a1526 45%, #14101c 100%); }
  .card.profile-hero.player-banner {
    background:
      radial-gradient(160px 150px at 50% var(--pb-mid), rgba(var(--tone-rgb), .24) 0%, rgba(var(--tone-rgb), .08) 50%, transparent 100%),
      linear-gradient(180deg, #201a2e 0%, #1a1526 45%, #14101c 100%);
  }
  .player-card {
    background:
      radial-gradient(110% 62% at 50% 0%, rgba(var(--tone-rgb), .17) 0%, rgba(var(--tone-rgb), .05) 42%, transparent 66%),
      linear-gradient(180deg, #201a2e 0%, #1a1526 48%, #14101c 100%);
  }
}
.game-card:active { transform: scale(.995); }
.game-card.live { box-shadow: 0 0 0 1.5px var(--primary) inset; }
/* Dim the content, but keep the card openable and the delete action legible */
.game-card.deleted { cursor: pointer; }
.game-card.deleted .game-card-top,
.game-card.deleted .gc-scores,
.game-card.deleted .gc-foot { opacity: .45; }
.game-card.deleted .gc-score { text-decoration: line-through; }
.game-card-top { position: relative; display: flex; justify-content: space-between; align-items: center; }
.gc-date { color: var(--muted); font-size: 13px; line-height: 1.4; }

.gc-scores { display: flex; align-items: center; gap: 8px; }
.gc-team {
  flex: 1; min-width: 0; display: flex; flex-direction: column;
  align-items: center; gap: 5px;
}
.gc-name {
  font-family: var(--font-display);
  font-size: 14px; line-height: 1.2; font-weight: 500; color: var(--muted); max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gc-score {
  font-family: var(--font-display); font-size: 28px; font-weight: 800;
  line-height: 1.05; color: var(--muted); font-variant-numeric: tabular-nums;
}
/* The side that took it carries the card in gold; the other steps back */
.gc-team.win .gc-name { color: var(--gold); font-weight: 600; }
.gc-team.win .gc-score { color: var(--amber); }
.gc-team.lose { opacity: 1; }
.gc-dash {
  font-family: var(--font-display); font-weight: 800;
  /* The bolt is tall: less of a drop than the old square mark needed, so it
     sits centred against the two scores rather than below them. */
  color: var(--dim); font-size: 14px; padding-top: 10px;
  flex: none; display: flex; align-items: center; justify-content: center;
}
/* The VS badge: grey whatever the image — the filter holds it even if the
   file is ever swapped for a colour one. The contrast pushes the burst behind
   the letters darker while the letters stay light. 40px tall. The opacity
   brings the letters down to the same grey as a losing or tied score. */
/* The VS mark: white, and quiet enough to read as punctuation between the two
   scores rather than a badge of its own. The bolt is far taller than it is
   wide, so the slot follows the artwork's own proportions (158 × 353). */
.gc-vs { display: block; height: 54px; width: 24px; color: #fff; opacity: .6; pointer-events: none; }
.gc-vs svg { display: block; width: 100%; height: 100%; }

.gc-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  /* No rule above the foot — the gap alone separates it from the score. */
  padding-top: 4px;
}
.gc-boards {
  font-size: 12px; line-height: 1.4; font-weight: 500; color: var(--muted);
  background: var(--surface-2); padding: 3px 9px; border-radius: 999px;
}
.gc-result { font-size: 13px; line-height: 1.4; font-weight: 500; color: var(--muted); }
.game-card.live .gc-result { color: var(--primary); }
/* Permanent-delete action on soft-deleted cards */
.gc-danger { display: flex; justify-content: flex-end; }
.gc-danger .btn-text-danger { align-self: auto; margin: 0; padding: 2px 0; font-size: 13px; }

.tag {
  font-family: var(--font-display);
  font-size: 10px; line-height: 1.5; font-weight: 600; padding: 3px 9px;
  border-radius: var(--r-sm);
  text-transform: uppercase; letter-spacing: .06em;
  border: 1px solid var(--border); background: transparent; color: var(--muted);
}
.tag.derby { color: var(--derby); background: rgba(179, 157, 255, .12); border-color: rgba(179, 157, 255, .42); }
.tag.lottery { color: var(--lottery); background: rgba(69, 214, 194, .12); border-color: rgba(69, 214, 194, .42); }
.tag.h2h { color: var(--h2h); background: rgba(255, 143, 168, .12); border-color: rgba(255, 143, 168, .42); }
.tag.custom { color: var(--custom); background: rgba(120, 190, 245, .12); border-color: rgba(120, 190, 245, .42); }

.score-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.team { display: flex; flex-direction: column; align-items: center; flex: 1; gap: 2px; }
.team .team-name { font-size: 14px; line-height: 1.4; font-weight: 500; color: var(--muted); }
.team .team-score { font-family: var(--font-display); font-size: 30px; font-weight: 700; line-height: 1.05; }
.team.win .team-name, .team.win .team-score { color: var(--win); }
.vs { color: var(--muted); font-size: 13px; }

/* Scoreboard (game view) */
.scoreboard { display: flex; align-items: stretch; gap: 8px; }
.score-panel {
  flex: 1; background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 16px 10px; text-align: center;
}
.score-panel .team-name { font-weight: 700; }
.big-score { font-family: var(--font-display); font-size: 44px; font-weight: 700; line-height: 1; }

.banner {
  border-radius: var(--r-lg); padding: 14px 16px; text-align: center;
  font-family: var(--font-display); font-weight: 800;
  box-shadow: 0 10px 18px -8px rgba(255, 176, 58, .4);
}
.tie-banner {
  background: rgba(255, 255, 255, .05); border: 1.5px solid var(--border);
  color: var(--text); box-shadow: none;
}

/* Board form */
.board-form { display: flex; flex-direction: column; gap: 10px; }
.seat-group { display: flex; flex-direction: column; gap: 6px; }
.board-row { display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
.board-no { color: var(--muted); font-weight: 700; }
.board-row span:nth-child(2) { flex: 1; font-size: 14px; }
.board-pts { color: var(--primary); font-weight: 700; white-space: nowrap; }
/* =========================================================
   A game in play — the blue light behind it, glass on top
   ========================================================= */
/* The same flowing light the Home banner wears, but behind the whole screen
   rather than inside a card. It is FIXED: the board, the form and the records
   scroll over it while the light stays put. And it is faded out well before the
   controls, exactly as Home's photos are — so there is no edge where the
   picture stops, only the app's own ground coming back. */
.game-live { position: relative; }
.game-live > * { position: relative; z-index: 1; }
/* The tiles over it are glass, so the light carries through them: the two
   scores, and the card the boards are recorded in. Same recipe as the Home
   banner — a tint thin enough to see through, a blur, and a pale edge. */
.game-live .score-panel,
.game-live .card.board-form {
  background: linear-gradient(180deg, rgba(10,9,16,.36) 0%, rgba(5,4,10,.56) 100%);
  -webkit-backdrop-filter: blur(4px) saturate(1.15);
          backdrop-filter: blur(4px) saturate(1.15);
  border-color: rgba(255,255,255,.14);
  box-shadow: var(--sh-card), inset 0 1px 0 rgba(255,255,255,.10);
}
/* The boards already recorded are a list, not a stack of slabs: each row takes
   the ground it sits on — background only, no edge, no shadow, no glow, with
   the padding, the number badge and the tap highlight all kept. That ground is
   one pane of the same glass as the cards above, so the list reads as part of
   the screen instead of lines floating loose on the light. No border on it
   either; the tint alone holds them. */
/* Each recorded board is its own pane of glass — a thin tint over the light, a
   blur, and just enough of an edge and top highlight for the pane to read as
   glass rather than a flat slab. The list itself carries nothing: glass inside
   glass only muddies both. */
.game-live .gpanel:has(.board-row) { gap: 8px; }
.game-live .board-row {
  /* One flat tint, not a gradient. Stacking a light wash over a dark ramp made
     each row read as a little ramp of its own — five of them down the screen
     and the list looked striped. The glass now comes from the blur, the edge
     and a single hairline of light along the top. */
  background: rgba(9, 8, 14, .40);
  -webkit-backdrop-filter: blur(4px) saturate(1.15);
          backdrop-filter: blur(4px) saturate(1.15);
  border: 1px solid rgba(255,255,255,.09);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07);
}
/* The row whose correction form is open below it. */
/* The row whose form is open: a touch lighter, no ring round it. */
.game-live .board-row.editing { background: rgba(9, 8, 14, .58); }

/* The correction form, opened under the board it corrects. The Record form's
   card exactly — same fields, same buttons — without the light travelling its
   rim: that belongs to the form you're filling in now, not the one fixing
   something already down. */
.board-edit-inline {
  margin-top: 2px; display: flex; flex-direction: column; gap: 12px;
  border: 0;   /* the card it sits in draws no ring of its own */
}
.game-live .board-edit-inline {
  background: rgba(9, 8, 14, .52);
  -webkit-backdrop-filter: blur(6px) saturate(1.15);
          backdrop-filter: blur(6px) saturate(1.15);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.07);
}
.board-edit-inline .board-form { gap: 12px; }

/* No blur available: a tint solid enough to keep the scores and the controls
   legible over the light. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .game-live .score-panel, .game-live .card.board-form {
    background: linear-gradient(180deg, rgba(34,28,56,.93) 0%, rgba(16,12,32,.95) 100%);
  }
}

/* =========================================================
   Game detail — modernized
   ========================================================= */
/* Scoreboard */
.scoreboard { gap: 10px; }
.score-panel {
  background: var(--surface);
  border: 1.5px solid var(--border); border-radius: var(--r-xl);
  padding: 16px 8px; display: flex; flex-direction: column; gap: 6px;
  box-shadow: var(--sh-card);
}
/* Small enough that the longest name + trophy stay on one line */
.score-panel .team-name {
  font-weight: 500; font-size: 14px; line-height: 1.4; color: var(--muted);
  white-space: nowrap;
}
.score-panel .big-score {
  font-family: var(--font-display); font-size: 44px; font-weight: 800; line-height: 1;
  color: var(--text); font-variant-numeric: tabular-nums;
}
/* The two sides read apart, then the winner lights up */
.score-panel.tA .team-name { color: var(--lime); }
.score-panel.tB .team-name { color: var(--cyan); }
.score-panel.win.tA {
  border-color: var(--lime);
  box-shadow: 0 10px 15.75px -6px rgba(198, 255, 77, .25), 0 0 14px 0 rgba(198, 255, 77, .14);
}
.score-panel.win.tB {
  border-color: var(--cyan);
  box-shadow: 0 10px 15.75px -6px rgba(51, 209, 255, .25), 0 0 14px 0 rgba(51, 209, 255, .14);
}
.score-panel.win.tA .big-score { color: var(--lime); }
.score-panel.win.tB .big-score { color: var(--cyan); }

/* Result banner — the headline of a finished game */
.banner {
  border-radius: var(--radius); padding: 15px 14px; text-align: center;
  font-family: var(--font-display); font-weight: 700; font-size: 17px; line-height: 1.3;
  text-transform: uppercase; letter-spacing: .02em;
}
.win-banner, .win-banner.tA, .win-banner.tB {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--primary);
  box-shadow: 0 0 28px rgba(216,168,78,.20), inset 0 0 22px rgba(216,168,78,.06);
  letter-spacing: .01em;
}

/* Final rosters */
.final-rosters .team-preview { background: var(--surface); padding: 14px 12px; }
.team-preview .team-name {
  font-weight: 500; font-size: 14px; line-height: 1.4; color: var(--muted);
}
.final-rosters .team-preview.win { border-color: #3d424b; }
.final-rosters .team-preview.win .team-name { color: var(--primary); }
.roster-pts { font-size: 13px; color: var(--muted); margin-top: 3px; }
.roster-list { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.roster-list li {
  display: flex; align-items: center; gap: 8px; text-align: left;
  background: var(--surface-2); border-radius: 10px; padding: 7px 9px; font-size: 13px;
}

/* Live-game section switch: Stats | Boards, one open at a time. */
.gpills { display: flex; gap: 10px; }
.gpill {
  flex: 1 1 0; min-width: 0;
  display: flex; align-items: center; justify-content: center;
  padding: 16px 12px;
  font-family: var(--font-display); font-size: 16px; font-weight: 700; line-height: 1.2;
  background: var(--surface); border: 1.5px solid var(--border);
  border-radius: var(--r-xl); color: var(--text);
  transition: border-color .18s ease, background .18s ease, color .18s ease;
}
.gpill.active {
  border-color: var(--primary);
  background: rgba(255, 215, 0, .08);
  color: var(--primary);
}
.gpill:active { transform: translateY(1px); }
/* The panels are no longer direct children of .view, so they carry its gap.
   An author `display` beats the UA's [hidden] rule, so restore it explicitly —
   without this the panel stays on screen while .hidden is true. */
.gpanel { display: flex; flex-direction: column; gap: 14px; }
.gpanel[hidden] { display: none; }

/* Section heading with an action on the right: "Boards (9)" · "Edit board". */
.section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.section-head h3 { margin: 6px 0; }

/* Board picker (editing a finished game). The rows are <button>s, so colour
   and alignment are set outright — iOS otherwise paints button text blue. */
.pick-list { display: flex; flex-direction: column; gap: 8px; }
.board-row.pick-row {
  width: 100%; font: inherit; color: var(--text); text-align: left; cursor: pointer;
  transition: box-shadow .15s ease;
}
.board-row.pick-row:active { box-shadow: 0 0 0 1.5px var(--primary) inset; }
.board-edit { gap: 12px; }
.modal-actions { display: flex; gap: 10px; }
.modal-actions .btn { flex: 1 1 0; }

/* Change log: who edited which board, each field as old -> new, and the
   knock-on effect on the score. A plain list split by 1px lines, no boxes.
   Old values are struck through and dimmed so the eye lands on what it is now. */
.changes { display: flex; flex-direction: column; }
.change { display: flex; flex-direction: column; gap: 6px; padding: 12px 0; }
.change + .change { border-top: 1px solid var(--border); }
.chg-head { font-size: 14px; line-height: 1.4; color: var(--text); }
.chg-head b { font-weight: 700; }
.chg-line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px; font-size: 13px; line-height: 1.45; }
.chg-label { color: var(--muted); min-width: 92px; }
.chg-from { color: var(--dim); text-decoration: line-through; text-decoration-color: rgba(158, 154, 169, .55); }
.chg-arrow { color: var(--dim); }
.chg-to { color: var(--text); font-weight: 600; }
.chg-effect { font-size: 12.5px; line-height: 1.45; color: var(--amber); }

/* A live board the viewer can correct: the whole row is the tap target. */
.board-row.tappable { cursor: pointer; transition: box-shadow .15s ease; -webkit-tap-highlight-color: transparent; }
.board-row.tappable:active { box-shadow: 0 0 0 1.5px var(--primary) inset; }
.board-row.tappable:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.board-hint { margin: 0; }

/* Boards list — flat, in the lighter shade the game cards start from. */
.board-row {
  display: flex; align-items: center; gap: 10px;
  background: #201a2e; border: 0;
  border-radius: var(--r-lg); padding: 13px 12px;
}
.board-no {
  width: 27px; height: 27px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); color: var(--muted); font-size: 13px; font-weight: 600;
}
.board-players { flex: 1; min-width: 0; font-size: 14px; line-height: 1.45; color: var(--text); }
.bp-line { display: block; }              /* side A + "vs" on line 1, side B on line 2 */
/* The winning pair carries the row; the other side steps back. */
.bp-side.bp-win { color: var(--text); }
.bp-side.bp-lose { color: var(--muted); }
.bp-vs { color: var(--muted); font-size: 13px; margin-left: 5px; }
.board-pts {
  font-size: 14px; line-height: 1.4; font-weight: 500; white-space: nowrap;
  padding: 0; border-radius: 0; background: none; color: var(--muted);
}
.board-pts.team-A, .board-pts.team-B { color: var(--muted); background: none; }
.board-pts b, .board-pts strong { color: var(--text); }
.board-del {
  background: transparent; border: none; color: var(--muted);
  font-size: 16px; cursor: pointer; padding: 2px 4px; line-height: 1;
}
.board-del:active { color: var(--danger); }

/* Team preview (new game confirm) */
.teams-preview { display: flex; align-items: flex-start; gap: 10px; }
.team-preview { flex: 1; text-align: center; }
.team-preview .team-name { font-weight: 600; font-size: 15px; }
.roster-list { list-style: none; margin: 10px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.roster-list li { background: var(--surface-2); border-radius: 8px; padding: 7px 10px; font-size: 14px; }
.vs.big { align-self: center; font-size: 15px; font-weight: 700; padding-top: 20px; }

/* Board form header + edit button */
.board-head { display: flex; align-items: center; justify-content: space-between; }
.board-head h3 { margin: 0; }
.btn.sm { padding: 7px 10px; font-size: 13px; font-weight: 500; }
.btn.ghost { background: transparent; border-color: var(--border); color: var(--muted); }
.btn.ghost.active { color: var(--primary); border-color: var(--primary); }

/* Modal */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 100;
  background: rgba(0,0,0,.6); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  display: flex; align-items: center; justify-content: center; padding: 16px;
}
.modal {
  background: var(--surface); border: 1.5px solid var(--border);
  border-radius: var(--r-2xl); padding: 20px; width: 100%; max-width: 420px;
  box-shadow: 0 24px 60px rgba(0, 0, 0, .65);
  max-height: 86vh; overflow-y: auto;
  /* Up and down only — with overflow-y: auto it would scroll sideways too, and a
     sideways swipe (the avatar slider) would drag the whole sheet. */
  overflow-x: hidden; overscroll-behavior: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.modal::-webkit-scrollbar { display: none; }
.team-modal .team-editor { gap: 12px; }
.player-modal { display: flex; flex-direction: column; gap: 12px; }
.player-modal h3 { margin: 0 0 2px; font-family: var(--font-display); font-size: 20px; line-height: 1.25; font-weight: 700; }
.field-label {
  font-size: 13px; line-height: 1.4; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); font-weight: 700; margin-bottom: -6px;
}
.player-modal .seg { margin-bottom: 4px; }
.btn.ghost.big { border-color: var(--border); color: var(--muted); }
.modal-sep { height: 1px; background: var(--border); margin: 4px 0; }
.btn-text-danger {
  align-self: center; margin-top: 2px;
  display: inline-flex; align-items: center; gap: 6px;
  background: none; border: none; color: var(--danger);
  font-weight: 600; font-size: 14px; cursor: pointer; padding: 6px 10px;
}
.btn-text-danger svg { display: block; }
.btn-text-danger:active { opacity: .6; }
.reset-warn {
  background: rgba(239,68,68,.12); border: 1px solid rgba(239,68,68,.5);
  border-radius: 10px; padding: 12px; display: flex; flex-direction: column; gap: 4px;
}
.reset-warn strong { color: var(--danger); }

/* Team editor */
.team-editor { display: flex; flex-direction: column; gap: 10px; }
.edit-team { display: flex; flex-direction: column; gap: 6px; }
.edit-team .team-name { font-weight: 600; color: var(--muted); font-size: 13px;
  text-transform: uppercase; letter-spacing: .04em; border-bottom: 1px solid var(--border); padding-bottom: 4px; }
.edit-row { display: flex; align-items: center; gap: 8px; }
.add-name { flex: 1; font-size: 14px; }
.btn .ic { display: block; flex: none; }
.btn.icon-only { padding: 7px 9px; }

/* New Game roster spacing */
.roster h3 { margin: 20px 0 10px; }
.roster h3:first-child { margin-top: 6px; }

/* Chips */
.chip-grid { display: flex; flex-wrap: wrap; gap: 8px; }
.chip {
  background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--r-pill);
  padding: 9px 14px; font-size: 14px; line-height: 1.4; cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}
.chip.sm { padding: 7px 12px; font-size: 14px; }
/* Selected players: soft outlined accent, deliberately unlike a solid button */
.chip.on {
  background: rgba(255, 176, 58, .1); color: var(--amber);
  border-color: var(--accent-line); font-weight: 500;
}
.chip.static { cursor: default; }
/* A player picked for a side wears that side's colour, as the winner buttons
   and the scoreboard do: lime for A (Mandirtala in a derby), cyan for B. */
.chip.on.chip-team-A {
  background: rgba(198, 255, 77, .1); color: var(--lime); border-color: var(--lime);
}
.chip.on.chip-team-B {
  background: rgba(51, 209, 255, .1); color: var(--cyan); border-color: var(--cyan);
}

/* Segmented control */
.seg { display: flex; gap: 8px; }
.seg-btn {
  flex: 1; background: var(--surface); color: var(--text);
  border: 1.5px solid var(--border); border-radius: var(--r-md);
  font-family: var(--font-display);
  padding: 12px; font-size: 14px; line-height: 1.4; font-weight: 600; cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}
/* One consistent active treatment across every segmented control:
   soft tint + outline, so it never reads as a primary button */
/* Three-up filter row needs a slightly tighter fit */
.seg-btn.seg-filter { font-size: 14px; padding: 10px 6px; }
/* Default active state for every segmented control */
.seg-btn.active {
  background: rgba(255, 176, 58, .1); color: var(--amber); border-color: var(--accent-line);
  font-weight: 500;
}
/* Each game type reads the same in its segment as on its card pill */
.seg-btn.seg-derby.active {
  background: rgba(179, 157, 255, .12); color: var(--derby); border-color: var(--derby);
}
.seg-btn.seg-lottery.active {
  background: rgba(69, 214, 194, .12); color: var(--lottery); border-color: var(--lottery);
}
.seg-btn.seg-h2h.active {
  background: rgba(255, 143, 168, .12); color: var(--h2h); border-color: var(--h2h);
}
.seg-btn.seg-custom.active {
  background: rgba(120, 190, 245, .12); color: var(--custom); border-color: var(--custom);
}
/* New Game, step one: the four kinds of game, two by two — each a picture
   filling its card with the name over its foot, in white.
   Tapping one goes to the players; step two carries the name back at the top
   with the way back. */
.mode-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.mode-card {
  position: relative; display: block; width: 100%; aspect-ratio: 1;
  padding: 0; overflow: hidden; border-radius: var(--r-2xl);
  background: #0b1238; border: 1.5px solid var(--border); color: var(--text);
  cursor: pointer; transition: border-color .2s ease, transform .1s ease;
  -webkit-tap-highlight-color: transparent;
}
.mode-card:active { transform: scale(.985); }
.mode-art {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; display: block;
}
/* A dark fade under the name so it reads over any picture. */
.mode-card::after {
  content: ""; position: absolute; inset: auto 0 0; height: 46%;
  background: linear-gradient(to top, rgba(6, 8, 28, .92), rgba(6, 8, 28, 0));
  pointer-events: none;
}
.mode-card .mode-name {
  position: absolute; left: 10px; right: 10px; bottom: 12px; z-index: 1;
  text-align: center; text-shadow: 0 1px 8px rgba(0, 0, 0, .6);
}
.mode-name {
  font-family: var(--font-display); font-size: 17px; font-weight: 700; line-height: 1.25;
  overflow-wrap: anywhere;
}
/* Each mode carries its own colour, the same one its tag wears on a game. */
.mode-derby   { --mode-c: var(--derby);   --mode-rgb: 179, 157, 255; }
.mode-lottery { --mode-c: var(--lottery); --mode-rgb: 69, 214, 194; }
.mode-h2h     { --mode-c: var(--h2h);     --mode-rgb: 255, 143, 168; }
.mode-custom  { --mode-c: var(--custom);  --mode-rgb: 120, 190, 245; }
.mode-card .mode-name { color: #fff; }
.mode-card:active { border-color: rgba(var(--mode-rgb), .6); }

/* Step two's heading: the way back, then the game's name. */
.pick-head { display: flex; align-items: center; gap: 4px; }
.pick-head .ask-back { margin-left: -9px; }
.pick-head h1 { min-width: 0; overflow-wrap: anywhere; }

.pick-hint { margin: -4px 0 0; }

/* Head-to-head and custom: two blocks either side of a VS, each holding the
   faces put up for that side and a + for the next one. The roster itself is
   behind the + (see .side-picker), so nobody is listed on the screen twice. */
.team-blocks { display: grid; grid-template-columns: 1fr auto 1fr; gap: 8px; align-items: stretch; }
.team-block {
  --tb-rgb: 198, 255, 77;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  min-width: 0; padding: 14px 10px 16px;
  background: var(--surface); border: 1.5px solid rgba(var(--tb-rgb), .35);
  border-radius: var(--r-xl);
}
.team-block.tb-B { --tb-rgb: 51, 209, 255; }
.tb-name {
  font-family: var(--font-display); font-size: 15px; font-weight: 700; line-height: 1.25;
  color: rgb(var(--tb-rgb));
}
.tb-count { font-size: 12px; line-height: 1.3; color: var(--muted); }
.tb-faces { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 8px; }
/* A face already up: tapping it takes that player out, so it carries a ✕. */
.tb-face {
  position: relative; display: flex; flex-direction: column; align-items: center; gap: 4px;
  width: 60px; padding: 0; background: none; border: 0; color: var(--text); cursor: pointer;
}
.tb-face-img { width: 52px; }
.tb-face-img.face-initial { height: 52px; font-size: 20px; }
.tb-face-name {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px; line-height: 1.3; font-weight: 600;
}
.tb-face-x {
  position: absolute; top: -2px; right: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; line-height: 1; color: var(--text);
  background: var(--surface-3); border: 1px solid var(--border);
}
.tb-face:active { transform: scale(.96); }
/* The empty seat. Dashed, so it reads as a space to fill rather than a button
   that does something on its own. */
.tb-add {
  width: 52px; height: 52px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 26px; line-height: 1; font-weight: 400;
  color: rgb(var(--tb-rgb)); background: rgba(var(--tb-rgb), .08);
  border: 1.5px dashed rgba(var(--tb-rgb), .55); cursor: pointer;
  transition: background .2s ease, transform .1s ease;
}
.tb-add:active { transform: scale(.96); background: rgba(var(--tb-rgb), .16); }
.tb-vs {
  align-self: center; flex: none;
  font-family: var(--font-display); font-size: 13px; font-weight: 800;
  letter-spacing: .1em; color: var(--dim);
}
/* Behind the +: everyone still free, one a row. */
.side-picker .sp-list { display: flex; flex-direction: column; gap: 8px; max-height: 46vh; overflow-y: auto; }
.side-picker .sp-row { width: 100%; justify-content: flex-start; }
/* The A side of any two-team control reads lime, like the draw */
.seg-btn.seg-team-A.active {
  background: rgba(198, 255, 77, .1); color: var(--lime); border-color: var(--lime);
}
/* The B side of any two-team control reads cyan, like the draw */
.seg-btn.seg-team-B.active {
  background: rgba(51, 209, 255, .1); color: var(--cyan); border-color: var(--cyan);
}
/* Guests belong to neither side, so Others stays neutral rather than borrowing
   a team's colour. Three sides don't fit at the two-up size: the type shrinks
   and a long name wraps rather than spilling out of its button. */
.seg-btn.seg-team-O.active {
  background: rgba(255, 255, 255, .07); color: var(--text); border-color: var(--muted);
}
.seg.seg-teams { gap: 6px; }
.seg-teams .seg-btn { padding: 10px 4px; font-size: 12.5px; overflow-wrap: anywhere; }

/* Table */
/* Stats table: scrolls sideways with the player/team column pinned. No side
   padding on the card, so the row dividers run from edge to edge; the first
   and last cells carry the inset instead. */
.table-card { padding: 14px 0; overflow: hidden; }
.table-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  scrollbar-width: none;        /* Firefox */
  -ms-overflow-style: none;     /* legacy Edge */
}
.table-wrap::-webkit-scrollbar { display: none; }  /* WebKit — scroll without a visible bar */

.table { border-collapse: collapse; font-size: 14px; width: max-content; min-width: 100%; }
.table th {
  text-align: right; color: var(--muted); font-weight: 600; font-size: 13px;
  padding: 6px 8px; border-bottom: 1px solid var(--border); white-space: nowrap;
}
.table td { padding: 10px 8px; border-bottom: 1px solid var(--border); text-align: right; white-space: nowrap; }
.table tr:last-child td { border-bottom: none; }

/* Pinned first column (rank · name · team) */
.table th:first-child, .table td:first-child {
  position: sticky; left: 0; z-index: 1;
  /* Solid on purpose: the other columns scroll underneath it. */
  background: var(--surface-solid);
  text-align: left; padding-left: 14px; padding-right: 10px;
  box-shadow: 1px 0 0 var(--border);
}
.table td:last-child, .table th:last-child { padding-right: 14px; }
/* Leaderboard: seven columns on a phone. Tighter gaps between the columns so
   Pts isn't cut off; the table's outer edges keep their inset. */
.table.lb-table th:not(:first-child), .table.lb-table td:not(:first-child) { padding-left: 5px; }
.table.lb-table th:not(:last-child),  .table.lb-table td:not(:last-child)  { padding-right: 5px; }
/* On a phone all seven columns have to land inside the screen: a table you have
   to drag sideways hides whichever column you were after. The numbers come down
   a point, the gaps between columns close up, and the name column gives up
   whatever is still needed — it is the only one that can lose a character
   without losing a fact. (The wrap keeps its sideways scroll as a safety net
   for a name longer than any in the league.) */
@media (max-width: 420px) {
  .table.lb-table { font-size: 13px; }
  .table.lb-table th { font-size: 11px; letter-spacing: 0; }
  .table.lb-table th:not(:first-child), .table.lb-table td:not(:first-child) { padding-left: 3px; }
  .table.lb-table th:not(:last-child),  .table.lb-table td:not(:last-child)  { padding-right: 3px; }
  .table.lb-table th:first-child, .table.lb-table td:first-child { padding-left: 10px; padding-right: 6px; }
  .table.lb-table th:last-child,  .table.lb-table td:last-child  { padding-right: 10px; }
  /* Long names ellipsize rather than push Pts off the edge. */
  .table.lb-table td:first-child { max-width: 108px; overflow: hidden; text-overflow: ellipsis; }
  .table.lb-table .rank { margin-right: -1px; }
}
}
.win-pct { color: var(--muted); }
/* The key under the leaderboard table: a quiet footnote, lighter than the table. */
.table-legend { font-weight: 300; color: var(--dim); }
.table tr:last-child td { border-bottom: none; }
.rank { color: var(--muted); }

/* The derby tally (Boards, and History filtered to Derby) is the game
   scoreboard's two score boxes — styled by .scoreboard / .score-panel. */
/* Sits directly under the tally it qualifies, so it reads as a footnote
   to the banner rather than a caption for the list below. */
.derby-note { margin: -6px 0 0; text-align: center; }

/* Bottom nav — a glass pill floating clear of the bottom edge rather than a
   bar welded to it. Held to the same 440px as the cards above, so on a wide
   window it lines up with the column instead of stretching across the screen.
   The column below is padded by the bar's height PLUS the gap under it, both
   measured into --nav-h. */
.nav {
  position: absolute; z-index: 20;
  /* Riding above the home indicator, not tucked under it: the device's own
     inset plus a margin of air. */
  bottom: calc(var(--sab) + 10px);
  /* Flush with the cards' own edges, so the pill lines up with the column
     rather than sitting a couple of pixels proud of it. */
  left: max(16px, env(safe-area-inset-left));
  right: max(16px, env(safe-area-inset-right));
  max-width: 440px; margin-inline: auto;
  display: flex; background: var(--bar);
  -webkit-backdrop-filter: blur(18px) saturate(160%); backdrop-filter: blur(18px) saturate(160%);
  /* Nothing runs into a screen edge any more, so the hairline goes the whole
     way round and the corners close into a pill. It is brighter than the top
     bar's: a shadow is no separation on a near-black page, so the rim is what
     draws the pill's shape over whatever card is passing behind it. */
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: 999px;
  box-shadow: var(--sh-nav), inset 0 1px 0 rgba(255, 255, 255, .06);
  /* Even above and below, now that neither edge is against the screen. */
  padding-block: 4px;
  transform: translateZ(0);
}
/* Icons only: the five destinations are distinct enough to name themselves,
   and dropping the captions is what lets the bar shrink to a pill. The names
   live on as aria-label/title, so a screen reader and a hover still get them. */
.nav a {
  flex: 1; display: flex; align-items: center; justify-content: center;
  padding: 12px 0;
  color: var(--muted); text-decoration: none;
  transition: color .2s ease;
}
.nav a svg {
  /* A little larger than when a caption sat under it — the icon is the whole
     signal now. */
  width: 24px; height: 24px; display: block;
  fill: none; stroke: var(--muted); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  transition: stroke .2s ease, filter .2s ease;
}
.nav a.active { color: var(--gold); }
/* The only mark of where you are, now that there is no label to embolden. */
.nav a.active svg {
  stroke: var(--gold);
  filter: drop-shadow(0 0 6px rgba(255, 215, 0, .55)) drop-shadow(0 0 12px rgba(255, 215, 0, .3));
}

/* Toast */
.toast {
  position: fixed; left: 50%; bottom: calc(var(--nav-h, 64px) + 18px); transform: translateX(-50%) translateY(20px);
  background: var(--surface-2); color: var(--text); padding: 11px 16px; border-radius: var(--r-md);
  font-size: 14px; line-height: 1.4; opacity: 0; pointer-events: none; transition: .2s;
  /* Above the modal (100): a wrong-password message is useless behind it. */
  z-index: 400;
  text-align: center; max-width: min(90vw, 340px);
  border: 1.5px solid var(--border);
  box-shadow: 0 12px 28px rgba(0, 0, 0, .55);
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Leaderboard time-range filter (5 options — needs a tighter fit) */
.seg-btn.seg-range { font-size: 13px; padding: 10px 4px; border-radius: 999px; }
.lb-count { margin: -4px 0 0; }

/* Confetti overlay — above everything, never interactive */
.confetti-canvas { position: fixed; inset: 0; z-index: 300; pointer-events: none; }

/* Add-to-Home-Screen block in Settings */
.install-box { display: flex; flex-direction: column; gap: 8px; }
.install-box strong { color: var(--text); }

/* Board form: team pickers side by side, points on a slider */
.seats { display: flex; gap: 10px; }
.seats .seat-group { flex: 1 1 0; min-width: 0; }
/* Points: the label on the left, the number big and centred on the row —
   where a finger on the slider below never covers it. */
.pts-head { position: relative; display: flex; align-items: center; min-height: 42px; }
.pts-value {
  position: absolute; left: 0; right: 0; text-align: center; pointer-events: none;
  font-family: var(--font-display); font-size: 34px; font-weight: 800; line-height: 1;
  color: var(--primary); font-variant-numeric: tabular-nums;
  text-shadow: 0 2px 14px rgba(255, 176, 58, .35);
}
/* Each step rolls the number in from the direction it moved, with a small pop,
   so dragging reads as the score counting up or down. */
.pts-value.roll-up   { animation: pts-roll-up .24s cubic-bezier(.2, .9, .3, 1.25); }
.pts-value.roll-down { animation: pts-roll-down .24s cubic-bezier(.2, .9, .3, 1.25); }
@keyframes pts-roll-up {
  from { transform: translateY(42%) scale(.86); opacity: .25; }
  60%  { transform: translateY(-5%) scale(1.14); opacity: 1; }
  to   { transform: none; opacity: 1; }
}
@keyframes pts-roll-down {
  from { transform: translateY(-42%) scale(.86); opacity: .25; }
  60%  { transform: translateY(5%) scale(1.04); opacity: 1; }
  to   { transform: none; opacity: 1; }
}

/* The slider: a thick rounded bar, gold from the left up to the handle, and a
   tall rounded-rectangle handle you can grab with a thumb. The fill width comes
   from --p (0..1, set in JS) and stops at the handle's centre. */
.slider {
  --thumb-w: 24px;
  -webkit-appearance: none; appearance: none;
  display: block; width: 100%; height: 28px; margin: 8px 0 6px; padding: 0;
  border-radius: 10px; cursor: pointer;
  background:
    linear-gradient(90deg, var(--gold-light), var(--amber)) 0 0 /
      calc(var(--thumb-w) / 2 + (100% - var(--thumb-w)) * var(--p, 0)) 100% no-repeat,
    var(--surface-2);
  box-shadow: inset 0 0 0 1.5px var(--border);
}
.slider:focus-visible { outline: 2px solid var(--primary); outline-offset: 4px; }
/* The native track is made invisible — the bar above is the track. */
.slider::-webkit-slider-runnable-track { height: 28px; background: transparent; border: 0; border-radius: 10px; }
.slider::-moz-range-track { height: 28px; background: transparent; border: 0; }
/* Handle: taller than the bar, light face, two grip lines. (WebKit and Gecko
   rules must stay separate: one unknown selector voids a shared rule.) */
.slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: var(--thumb-w); height: 38px; margin-top: -5px;   /* centred: (28 - 38) / 2 */
  border: 0; border-radius: 9px;
  background:
    linear-gradient(var(--gold-deep), var(--gold-deep)) calc(50% - 3px) 50% / 2px 14px no-repeat,
    linear-gradient(var(--gold-deep), var(--gold-deep)) calc(50% + 3px) 50% / 2px 14px no-repeat,
    linear-gradient(180deg, #fffdf4, #ffeec2);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .45), 0 0 0 1.5px rgba(255, 176, 58, .65);
  transition: transform .12s ease;
}
.slider:active::-webkit-slider-thumb { transform: scale(1.06); }
.slider::-moz-range-thumb {
  width: var(--thumb-w); height: 38px;
  border: 0; border-radius: 9px;
  background:
    linear-gradient(var(--gold-deep), var(--gold-deep)) calc(50% - 3px) 50% / 2px 14px no-repeat,
    linear-gradient(var(--gold-deep), var(--gold-deep)) calc(50% + 3px) 50% / 2px 14px no-repeat,
    linear-gradient(180deg, #fffdf4, #ffeec2);
  box-shadow: 0 6px 16px rgba(0, 0, 0, .45), 0 0 0 1.5px rgba(255, 176, 58, .65);
}
@media (prefers-reduced-motion: reduce) {
  .pts-value.roll-up, .pts-value.roll-down { animation: none; }
  .slider::-webkit-slider-thumb { transition: none; }
}

/* Nil board — played, but scored for nobody */
.board-pts.nil { color: var(--muted); font-style: italic; }
/* Player-profile boards: result from that player's own side. A win reads in
   full white — label and number both; a loss steps back in muted grey. */
.board-pts.result-won, .board-pts.result-won b { color: var(--text); }
.board-pts.result-lost { color: var(--muted); }
.board-pts.result-lost b { color: var(--muted); font-weight: 600; }
.board-pts.result-tie, .board-pts.result-tie b { color: var(--muted); }
/* Only the viewed player's own name is picked out in the line-up. */
.bp-me { color: var(--text); }

/* Hero live card: animated "vs" and a meta / action footer */
.hero-live-foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
.hl-meta { color: var(--muted); font-size: 13px; line-height: 1.4; font-weight: 300; }
.hl-view { color: var(--primary); font-size: 13px; line-height: 1.4; font-weight: 400; white-space: nowrap; }

/* Add-to-Home-Screen steps */
.install-steps {
  font-size: 14px; line-height: 1.6; color: var(--text);
  background: var(--surface-2); border-radius: 8px; padding: 10px 12px;
}
.install-steps .ic { display: inline-block; vertical-align: -3px; margin: 0 1px; }

/* ---------- Player avatars ---------- */
/* An illustrated face (assets/avatars), or the player's initial when they've
   removed their picture. */
.avatar {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  color: var(--muted); font-size: 13px; line-height: 1; font-weight: 700;
  /* Emoji shouldn't be squeezed by the display face's metrics */
  font-family: var(--font-ui), system-ui, sans-serif;
}
.avatar.has-emoji {
  background: none; border-color: transparent; font-size: 20px;
}
/* A face fills its circle, over the circle's own background. */
/* A face needs no ring or disc behind it — they only boxed it in. The round
   face files leave a margin round the head, so it's zoomed to fill the circle
   it's given; the head is round enough that nothing of it is clipped. Only
   the initial, with no picture, keeps its circle. */
.avatar.has-img { padding: 0; overflow: hidden; background: none; border-color: transparent; }
.avatar.has-img img {
  width: 100%; height: 100%; display: block; object-fit: cover; border-radius: 50%;
  transform: scale(1.4);
  user-select: none; -webkit-user-drag: none;
}
/* A face with nothing behind it: cut tight to the head, so it fills whatever
   size it's given and throws its own shadow. The initial, having no picture to
   stand on its own, keeps its circle. */
.bare-face {
  display: block; height: auto; aspect-ratio: 1; object-fit: contain; flex: none;
  filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .45));
  user-select: none; -webkit-user-drag: none;
}
.face-initial {
  flex: none; border-radius: 50%; background: var(--surface-2); border: 1px solid var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted); font-weight: 700;
}
.chip-player { display: inline-flex; align-items: center; gap: 8px; padding-left: 8px; }

/* Avatar picker inside the add/edit player modal */
.avatar-picker { display: flex; align-items: center; gap: 14px; }
.avatar-slot { position: relative; flex: none; }
/* A preview of the current pick — selection happens in the panel below. */
.avatar-slot .avatar {
  width: 64px; height: 64px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border);
  font-size: 30px;
}
.avatar-slot .avatar.has-emoji { background: var(--surface-2); border-color: var(--border); }
.avatar-slot .avatar.has-img { background: none; border-color: transparent; }
.avatar-clear {
  position: absolute; top: -2px; right: -2px;
  width: 22px; height: 22px; border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--muted); font-size: 11px; line-height: 1; cursor: pointer; padding: 0;
}
.avatar-clear[hidden] { display: none; }
.field-hint { margin: -4px 0 0; }

/* ---------- Sign in ---------- */
/* No chrome until someone is signed in — there's nowhere to go, and the
   sign-in screen carries its own logo. */
body.signed-out .nav { display: none; }
/* The sign-in screen carries no header, so the page holds the notch clear. */
body.signed-out .container { padding-top: calc(16px + env(safe-area-inset-top)); }
.signin { align-items: stretch; padding-top: 12px; }
.signin-logo { width: 84px; height: auto; align-self: center; margin-bottom: 4px; }
.signin-title { text-align: center; margin: 0; }
.signin > .muted { text-align: center; margin: 0 0 4px; }
/* Two even columns, so the names stack in a tidy grid rather than wrapping
   wherever each pill happens to end. */
.signin .chip-grid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px;
  width: 100%; max-width: 360px; align-self: center;
}
.signin .chip-player { min-width: 0; }
.signin .chip-player > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Faces are how people find themselves here, so they're bigger than in a
   picker chip. The pill trims its own padding to stay a sensible height. */
/* The New Game picker gets the same big faces: choosing who's playing is
   choosing faces too. */
.signin .chip-player, .pick-roster .chip-player { padding: 5px 16px 5px 5px; gap: 10px; font-size: 15px; }
.signin .chip-player .avatar, .pick-roster .chip-player .avatar { width: 40px; height: 40px; font-size: 16px; }
.signin-who {
  display: flex; align-items: center; gap: 12px; align-self: center;
  margin: 4px 0 8px;
}
.signin-name { font-family: var(--font-display); font-size: 20px; line-height: 1.25; font-weight: 700; }

/* 4-digit PIN box. type=text keeps the numeric keypad on mobile, so the
   digits are masked here instead of by the input type. */
.pin-input {
  text-align: center; letter-spacing: .5em; text-indent: .5em;
  font-size: 22px; padding: 12px 14px;
  -webkit-text-security: disc; text-security: disc;
}
.pin-input::placeholder { letter-spacing: .4em; font-size: 18px; color: var(--muted); }

/* ---------- Settings ---------- */
/* Settings is a stack of small utilities — keep it compact, not airy. */
.settings-card { display: flex; flex-direction: column; gap: 8px; padding: 13px 14px; }
.settings-title {
  margin: 0; font-family: var(--font-display); font-size: 14px; line-height: 1.3; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; color: var(--muted);
}
.settings-card .muted.small { font-size: 11.5px; line-height: 1.45; }
.settings-card .seg-btn { padding: 9px; font-size: 13px; }
.settings-card .btn { padding: 9px 14px; font-size: 13px; }
.settings-card .btn.big { padding: 11px 16px; font-size: 15px; }
.settings-card .notice { padding: 8px 10px; font-size: 12px; }
.settings-card .me-row { gap: 10px; }
.settings-card .avatar.avatar-lg { width: 38px; height: 38px; }
.settings-card .avatar.avatar-lg.has-emoji { font-size: 24px; }
.settings-card .me-name { font-size: 14.5px; }
.me-row { display: flex; align-items: center; gap: 12px; }
.me-id { flex: 1; min-width: 0; }
.me-name { font-size: 16px; line-height: 1.4; font-weight: 600; }
.avatar.avatar-lg { width: 48px; height: 48px; font-size: 20px; }
.avatar.avatar-lg.has-emoji { font-size: 32px; }
.notice {
  background: var(--accent-soft); border: 1px solid var(--accent-line);
  border-radius: var(--radius); padding: 10px 12px;
  color: var(--primary); font-size: 13px; line-height: 1.5;
}

/* Signed-in player, top-right of the bar. Smaller than the logo, so the ring
   never drives the bar's height. */
.me-chip {
  flex: none; width: 36px; height: 36px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  /* No ring — the avatar just sits in a well a shade lighter than the bar. */
  background: var(--surface-2); border: none;
  text-decoration: none; padding: 0;
}
.me-chip[hidden] { display: none; }
.me-chip:active { transform: scale(.94); }
/* A face stands on its own here too, so the chip keeps no well of its own —
   only someone showing an initial needs the circle behind it. */
.me-chip:has(.bare-face) { background: none; }
/* A pool of gold light behind the head, the same trick as the halo behind
   Carromdeb: the glow is the chip's, not the picture's, so it stays a round
   pool rather than tracing the outline of somebody's hair. Wider than the
   chip and gone well before its edge, so it reads as light rather than a disc.
   Only behind a real face — an initial already sits in a well of its own. */
.me-chip:has(.bare-face) { position: relative; }
.me-chip:has(.bare-face)::before {
  content: ""; position: absolute; left: 50%; top: 52%; transform: translate(-50%, -50%);
  width: 148%; height: 148%; border-radius: 50%; pointer-events: none;
  /* closest-side puts the gradient's last stop exactly on the box edge, so the
     light runs out where the box does and there is no ring to see. Faint at
     the centre and tapering the whole way — a strong middle with a short
     falloff is what made it read as a hard disc. */
  background: radial-gradient(circle closest-side,
    rgba(255, 212, 110, .17) 0%,
    rgba(255, 202, 92, .10) 42%,
    rgba(255, 196, 80, .035) 70%,
    rgba(255, 192, 72, 0) 100%);
}
/* Lifted over its own glow. */
.chip-face { width: 38px; position: relative; }
.chip-face.face-initial { width: 36px; height: 36px; font-size: 17px; }

/* "you" badge on your own row in the Players list */
.chip-me { border-color: var(--accent-line); }
.you-tag {
  font-size: 11px; line-height: 1; font-weight: 600; color: var(--primary);
  background: var(--accent-soft); border-radius: 999px; padding: 3px 7px;
}

/* ---------- Availability ---------- */
/* Players who've marked themselves out: visible, but plainly not in play. */
.chip.unavailable {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--muted); border-style: dashed; opacity: .65;
}
.chip.unavailable .avatar { opacity: .5; }
.edit-row.unavailable { opacity: .55; }
.avail-tag {
  font-size: 11px; line-height: 1; font-weight: 600; color: var(--muted);
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 7px; margin-left: 2px;
}

/* ---------- Game details: Teams ---------- */
.teams-grid { display: flex; gap: 10px; align-items: flex-start; }
.team-col { flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; gap: 10px; }
.team-col-head { display: flex; flex-direction: column; gap: 2px; }
.team-col-name {
  font-family: var(--font-display); font-size: 16px; line-height: 1.3; font-weight: 700;
  overflow-wrap: anywhere;
}
.team-col.tA .team-col-name { color: var(--lime); }
.team-col.tB .team-col-name { color: var(--cyan); }
.team-col-players { display: flex; flex-direction: column; gap: 8px; }
.team-player { display: flex; align-items: center; gap: 8px; min-width: 0; }
.tp-name { font-size: 14px; line-height: 1.4; overflow-wrap: anywhere; }

/* A deleted game reads normally; a quiet line at the top says who binned it. */
.deleted-note { margin: -4px 0 0; display: flex; align-items: center; gap: 6px; }

/* ---------- Player analytics (inside the player card) ---------- */
.analytics { display: flex; gap: 8px; }
.stat-tile {
  flex: 1 1 0; min-width: 0; text-align: center;
  background: rgba(22, 18, 38, .5); border: 1px solid var(--border);
  border-radius: var(--r-xl); padding: 12px 6px;
}
.stat-value {
  font-family: var(--font-display); font-size: 20px; line-height: 1.2; font-weight: 800;
  color: var(--gold);
}
.stat-label { font-size: 11px; line-height: 1.35; color: var(--muted); margin-top: 4px; }
/* Five ranges in a narrow modal: tighten the buttons rather than the type. */
.range-row .seg { gap: 6px; }
.range-row .seg-btn { padding: 9px 2px; }

/* ---------- Player page ---------- */
.player-head { display: flex; align-items: center; gap: 12px; }
/* Profile header (Settings and a player's page): the face stands on the same
   glass as a game card, with the name and team stacked under it, all centred. */
.profile-hero {
  position: relative;
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: 2px; padding: 20px 16px 16px;
  background: linear-gradient(180deg,
    rgba(34, 32, 46, .40) 0%, rgba(28, 26, 38, .34) 45%, rgba(21, 20, 29, .30) 100%);
  border: 0;
}
.profile-face { width: 118px; margin-bottom: 8px; }
.profile-face.face-initial { width: 96px; height: 96px; font-size: 40px; border: none; }
.profile-hero .me-name {
  font-family: var(--font-display); font-size: 20px; line-height: 1.3; font-weight: 700;
  overflow-wrap: anywhere;
}
.profile-hero .me-name .avail-tag { margin-left: 6px; vertical-align: 3px; }
.profile-team { font-size: 13.5px; line-height: 1.4; color: var(--muted); }
.profile-edit { position: absolute; top: 12px; right: 12px; }
/* Boards on a player's page link back to the game they came from. */
.board-link { text-decoration: none; color: inherit; }
.bp-date { font-size: 13px; line-height: 1.4; color: var(--muted); margin-bottom: 2px; }
/* Player-profile board rows: score on top, date below, right-aligned. */
.board-right { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; text-align: right; white-space: nowrap; }
.board-right .bp-date { margin-bottom: 0; }

/* Leaderboard rows open that player's page */
.table tbody tr.row-link { cursor: pointer; }
.table tbody tr.row-link:active { background: var(--surface-2); }
.player-link { color: inherit; text-decoration: none; }

/* ---------- No connection ---------- */
.netbar {
  position: absolute; left: 0; right: 0; top: env(safe-area-inset-top); z-index: 19;
  background: linear-gradient(rgba(201, 97, 90, .16), rgba(201, 97, 90, .16)), var(--bar-solid);
  border-bottom: 1px solid var(--danger);
  color: var(--text); font-size: 13px; line-height: 1.4; text-align: center;
  padding: 8px max(12px, env(safe-area-inset-right)) 8px max(12px, env(safe-area-inset-left));
}
.netbar[hidden] { display: none; }

/* Live sits opposite the type + date on the game header row */
/* Game details: duration then date, pushed to the right of the tag. */
.gt-meta { display: flex; align-items: center; gap: 6px; min-width: 0; margin-left: auto; }
.gt-meta .muted { font-size: 12px; line-height: 1.4; white-space: nowrap; }
.gt-sep { color: var(--dim); font-size: 12px; line-height: 1; }
/* Centred on the row itself, not on the space left over, so the tag and the
   date being different widths can't pull it off-centre. */
.gt-mid {
  position: absolute; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; pointer-events: none;
}
/* On a finished game the duration is just part of the date stamp: same colour,
   same type, so "22:50 · 8 Sep, 26" reads as one line. Tabular figures stop a
   running clock jittering as the seconds tick over. */
.clock {
  color: var(--muted);
  font-size: 12px; line-height: 1.4;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* A running game is the exception — it gets the badge, the pastel red and the
   heavier weight, so it reads as counting rather than recorded. */
.live-clock { display: inline-flex; align-items: center; gap: 6px; }
.live-clock .clock { color: var(--clock); font-size: 13px; font-weight: 600; }
.live-clock .dot {
  width: 6px; height: 6px; border-radius: 50%; flex: none;
  background: var(--clock);
  box-shadow: 0 0 0 0 rgba(255, 155, 155, .5);
  animation: dot-pulse 2.4s ease-out infinite;
}
@keyframes dot-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(255, 155, 155, .5); }
  70%  { box-shadow: 0 0 0 5px rgba(255, 155, 155, 0); }
  100% { box-shadow: 0 0 0 0 rgba(255, 155, 155, 0); }
}
/* The date is a footnote next to the type tag, not a heading */

/* ---------- Board winners so far ---------- */
.board-track { display: flex; flex-wrap: wrap; gap: 6px; }
.bt-dot {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 700; line-height: 1;
  border: 1px solid var(--border);
}
/* Two shades of the one accent, so the sides read apart without a second hue */
.bt-A { background: var(--primary); color: var(--primary-ink); border-color: var(--primary); }
.bt-B { background: var(--accent-soft); color: var(--primary); border-color: var(--accent-line); }
.bt-nil { background: var(--surface-2); color: var(--muted); }

/* ---------- Collapsible stats on a live game ---------- */
.collapse-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; background: none; border: none; padding: 6px 0; cursor: pointer;
  color: var(--text); font-size: 17px; line-height: 1.35; font-weight: 700;
  /* It's a button, so it inherits nothing by default — name the heading face
     explicitly or it sits in the body font beside the h3s next to it. */
  font-family: var(--font-display); text-align: left;
}
.collapse-head .caret {
  color: var(--muted); display: inline-flex; align-items: center;
  transition: transform .18s ease;
}
.collapse-head .caret.open { transform: rotate(90deg); }
.stats-body[hidden] { display: none; }

/* ---------- Avatar slider ---------- */
/* Change opens a ring of the 15 faces: the pick big in the middle, its
   neighbours smaller and fading out either side. Arrows, a swipe, or a tap on
   a neighbour turns it, and it wraps round. Only transform and opacity move. */
.av-slider { display: flex; flex-direction: column; align-items: center; gap: 8px; padding: 4px 0 2px; }
.av-stage {
  /* Tall enough that the pick's glow (below it most) isn't cut off. */
  position: relative; width: 100%; height: 150px; overflow: hidden; outline: none;
  touch-action: pan-y; user-select: none; -webkit-user-select: none; cursor: grab;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 16%, #000 84%, transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0, #000 16%, #000 84%, transparent 100%);
}
.av-stage.dragging { cursor: grabbing; }
.av-item {
  position: absolute; left: 50%; top: 50%; width: 92px; height: 92px; margin: -46px 0 0 -46px;
  padding: 0; cursor: pointer; background: none; border: none;
  transform: translateX(calc(var(--o, 0) * 74px + var(--drag, 0px))) scale(var(--s, 1));
  opacity: var(--a, 1);
  transition: transform .34s cubic-bezier(.2, .8, .2, 1), opacity .34s ease, filter .2s ease;
  will-change: transform;
}
.av-stage.dragging .av-item { transition: none; }
.av-item img { width: 100%; height: 100%; display: block; pointer-events: none; -webkit-user-drag: none; }
/* The slider shows the faces on their own (cut tight to the head, so each
   fills the slot's width), with a shadow round the outline. The pick glows
   gold instead of wearing a ring. */
.av-item { filter: drop-shadow(0 4px 10px rgba(0, 0, 0, .35)); }
.av-item.is-center { filter: drop-shadow(0 6px 14px rgba(255, 215, 0, .45)); }
.av-item[aria-hidden="true"] { pointer-events: none; }
.av-controls { display: flex; align-items: center; gap: 28px; }
.av-arrow {
  width: 36px; height: 36px; border-radius: 50%; padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
}
.av-arrow svg { width: 18px; height: 18px; display: block; }
.av-arrow:active { transform: scale(.92); }
@media (prefers-reduced-motion: reduce) { .av-item { transition: none; } }

/* ---------- Pull to refresh ---------- */
/* Behind the top bar (z 20) so it slides out from under it. */
.ptr {
  position: fixed; left: 50%; top: 0; z-index: 15;
  transform: translateX(-50%);
  opacity: 0; pointer-events: none; transition: opacity .15s ease;
}
.ptr.show { opacity: 1; }
.ptr-ring {
  width: 28px; height: 28px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: 0 6px 18px rgba(0,0,0,.45);
  position: relative;
}
/* The arc that becomes a spinner once it's actually refreshing */
.ptr-ring::after {
  content: ""; position: absolute; inset: 4px; border-radius: 50%;
  border: 2px solid transparent; border-top-color: var(--muted);
  transition: border-top-color .15s ease;
}
.ptr.ready .ptr-ring::after { border-top-color: var(--primary); }
.ptr.spin .ptr-ring::after {
  border-top-color: var(--primary);
  animation: splash-spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ptr.spin .ptr-ring::after { animation-duration: 2s; }
}

/* Plain text action, used for "Change / Done" on the avatar picker */
.btn-text {
  background: none; border: none; padding: 4px 2px; cursor: pointer;
  color: var(--amber); font-family: inherit; font-size: 13px; font-weight: 600;
}
.btn-text:active { opacity: .6; }
.av-slider[hidden] { display: none; }

/* ---------- Players grid ---------- */
/* Two across, so ten players land as a tidy 2x5 on a phone. */
.player-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.player-card {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 16px 10px 14px; text-align: center; min-width: 0;
  border: 0;
  transition: transform .1s ease;
}
.player-card:active { transform: scale(.985); }
.pc-face { width: 42%; margin-bottom: 4px; }
.pc-face.face-initial { width: 62px; height: 62px; font-size: 26px; margin-bottom: 6px; }
.pc-name {
  font-family: var(--font-display); font-size: 16px; line-height: 1.25; font-weight: 700;
  overflow-wrap: anywhere;
}
/* The name with its tags ("you", "out") on the same line, centred together.
   A long name with both tags wraps rather than spilling out of the card. */
.pc-name-row {
  display: inline-flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 4px 6px; max-width: 100%; min-width: 0;
}
.pc-name-row .pc-name { min-width: 0; }
.pc-name-row .you-tag, .pc-name-row .avail-tag { flex: none; margin-left: 0; }
.pc-team { font-size: 13px; line-height: 1.4; color: var(--muted); overflow-wrap: anywhere; }
/* Your own card wears the accent ring */
/* Every card looks the same; the "you" badge is what picks yours out. */
.player-card.unavailable { opacity: .6; border: 1.5px dashed var(--border); }
/* Out of the league: dimmer still, but present — their profile and record are
   one tap away, and only an admin can bring them back. */
.player-card.disabled-player { opacity: .45; border: 1.5px dashed var(--border); }

/* ---------- Manual ---------- */
.manual-card { display: flex; flex-direction: column; gap: 8px; padding: 16px; }
.manual-card h3 {
  margin: 0 0 2px; font-family: var(--font-display);
  font-size: 17px; line-height: 1.3; font-weight: 700;
}
/* Long-form reading: looser lines than the rest of the app. */
.manual-card p { margin: 0; font-size: 14px; line-height: 1.65; color: var(--text); }
.manual-card p + p { padding-top: 2px; }
.manual-footer {
  text-align: center; color: var(--muted); font-size: 13px; line-height: 1.5;
  margin: 10px 0 4px; font-style: italic;
}
/* Shared result sign-off: a small logo sitting over the line. */
.share-foot { display: flex; flex-direction: column; align-items: center; gap: 6px; margin-top: 10px; }
.share-foot-logo { height: 28px; width: auto; display: block; opacity: .9; }
.share-foot .manual-footer { margin: 0 0 4px; }

/* ---------- Shared game summary ---------- */
/* The share page brings its own header, so the app chrome steps aside. */
body.sharing .nav { display: none; }

/* And with no fixed bars to pin, it has no use for the app shell — so it opts
   out and scrolls like an ordinary web page. The shell's locked height is what
   third-party in-app browsers (a link tapped in WhatsApp) mis-measure while
   their toolbar settles, which left the header cut off the top. */
body.sharing {
  height: auto; min-height: 100%;
  display: block;
  overflow: visible;
  overscroll-behavior: auto;
}
body.sharing { min-height: 0; height: auto; }
body.sharing .container {
  overflow: visible;
  min-height: 0; flex: 0 0 auto;
  padding-top: calc(16px + env(safe-area-inset-top));
  padding-bottom: calc(24px + env(safe-area-inset-bottom));
}
.share-top {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-bottom: 4px;
}
.share-logo { height: 40px; width: auto; display: block; }
/* Share buttons carry an icon beside their label */
.game-actions .btn svg,
.settings-card .btn svg { vertical-align: -2px; margin-right: 6px; }

/* ===================================================================
   Lottery card draw — one face-down puck per player, drawn onto teams.
   Team A reads lime, Team B cyan. Takes over the scroller so it all fits
   on a single screen.
   =================================================================== */
body.lottery-draw .container { overflow: hidden; display: flex; flex-direction: column; }
body.lottery-draw .container > .view {
  flex: 1 1 auto; min-height: 0; width: 100%; max-width: 440px;
  margin-inline: auto; display: flex; flex-direction: column;
}
.draw { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; gap: 14px; }

/* ---------- Team battleboard ---------- */
.draw-teams { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; flex: none; }
.draw-team {
  background: var(--surface); border: 1.5px solid var(--border);
  border-radius: var(--r-xl); padding: 14px; min-height: 92px;
  display: flex; flex-direction: column; gap: 8px;
  transition: border-color .3s ease, box-shadow .3s ease;
}
.draw-team-A.is-turn {
  border-color: var(--lime);
  box-shadow: 0 10px 15.75px -6px rgba(198, 255, 77, .25), 0 0 14px 0 rgba(198, 255, 77, .14);
}
.draw-team-B.is-turn {
  border-color: var(--cyan);
  box-shadow: 0 10px 15.75px -6px rgba(51, 209, 255, .25), 0 0 14px 0 rgba(51, 209, 255, .14);
}
.draw-team-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.draw-team-name { font-family: var(--font-display); font-size: 15px; font-weight: 700; letter-spacing: .3px; }
.draw-team-A .draw-team-name { color: var(--lime); filter: drop-shadow(0 2px 2.6px rgba(198, 255, 77, .4)); }
.draw-team-B .draw-team-name { color: var(--cyan); filter: drop-shadow(0 2px 2.6px rgba(51, 209, 255, .4)); }
.draw-team-count {
  width: 24px; height: 24px; border-radius: var(--r-md);
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-size: 12px; font-weight: 800;
  flex-shrink: 0; font-variant-numeric: tabular-nums;
}
.draw-team-A .draw-team-count {
  color: var(--lime); background: rgba(198, 255, 77, .18);
  border: 1px solid var(--lime); box-shadow: 0 4px 8.75px -4px rgba(198, 255, 77, .2);
}
.draw-team-B .draw-team-count {
  color: var(--cyan); background: rgba(51, 209, 255, .15);
  border: 1px solid var(--cyan); box-shadow: 0 4px 8.75px -4px rgba(51, 209, 255, .2);
}

/* ---------- Drafted roster pills ---------- */
.draw-pills { display: flex; flex-direction: column; gap: 6px; align-items: flex-start; }
.draw-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 9px 3px 3px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .05); border: 1px solid var(--border);
  font-size: 11.5px; font-weight: 500; line-height: 1; color: var(--text); max-width: 100%;
  animation: chip-land .42s cubic-bezier(.18, 1.5, .45, 1) both;
}
.draw-team-A .draw-pill { border-color: rgba(198, 255, 77, .4); }
.draw-team-B .draw-pill { border-color: rgba(51, 209, 255, .4); }
.draw-pill .avatar { width: 20px; height: 20px; font-size: 11px; }
.draw-pill .avatar.has-emoji { font-size: 15px; }
.draw-pill > span:last-child { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@keyframes chip-land {
  0%   { opacity: 0; transform: scale(.4); }
  60%  { opacity: 1; transform: scale(1.12); }
  100% { opacity: 1; transform: scale(1); }
}

/* ---------- Controls ---------- */
.draw-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex: none; }
.draw-hint { font-family: var(--font-display); font-size: 18px; font-weight: 800; color: var(--text); }
.draw-shuffle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 16px; border-radius: 30px;
  font-family: var(--font-display); font-size: 13px; font-weight: 700;
  color: var(--text); background: rgba(255, 255, 255, .04);
  border: 1.5px solid var(--border); cursor: pointer;
}
.draw-shuffle .draw-shuffle-ic { display: inline-flex; }
.draw-shuffle .draw-shuffle-ic svg { width: 15px; height: 15px; display: block; stroke: var(--text); }
.draw-shuffle:active { transform: translateY(1px); }
.draw-shuffle:disabled { opacity: .35; }
/* The shuffle stays neutral — the pucks already carry the team's colour. */

/* ---------- Puck grid ---------- */
.draw-board {
  flex: 1 1 auto; min-height: 0; position: relative;
  /* A frame the cards lie in, rather than a grid they line up on. */
  border-radius: var(--r-xl);
  /* Bigger than they need to be for the frame — overlapping is the point;
     they should read as a pile of cards, not a tidy arrangement. */
  --cw: min(28vw, 116px, 11vh);
  --ch: calc(var(--cw) * 4 / 3);
  /* Inset for a card's tilted corners, so none pokes out of the frame. */
  --pad: calc(var(--cw) * .15);
}
.draw-board.is-done { display: flex; align-items: center; justify-content: center; }

/* Every card is the same size and sits where it fell. They may overlap. */
.draw-card {
  position: absolute;
  left: calc(var(--pad) + var(--x, .5) * (100% - var(--cw) - 2 * var(--pad)));
  top:  calc(var(--pad) + var(--y, .5) * (100% - var(--ch) - 2 * var(--pad)));
  width: var(--cw); height: var(--ch);
  border: 0; padding: 0; margin: 0; background: none;
  color: var(--text);   /* buttons inherit no colour; iOS would paint this blue */
  cursor: pointer; perspective: 900px;
  transform: rotate(var(--rot, 0deg));
  transition: transform .16s ease;
  /* A long press mustn't pop the phone's text-select or link menu. */
  touch-action: manipulation; -webkit-touch-callout: none; user-select: none; -webkit-user-select: none;
}
.draw-card:active { transform: rotate(var(--rot, 0deg)) scale(.94); }
.draw-card:disabled { cursor: default; }

/* Both faces exist from the start, so turning it over is a real rotation */
.dc-inner {
  position: absolute; inset: 0; border-radius: var(--r-lg);
  transform-style: preserve-3d;
  transition: transform .5s ease-in-out;
  /* Ready on its own layer before the tap, so the turn starts without a hitch. */
  will-change: transform;
}
.draw-card.is-revealed .dc-inner { transform: rotateY(180deg); }
.dc-back, .draw-card-face {
  position: absolute; inset: 0; border-radius: var(--r-lg);
  backface-visibility: hidden; -webkit-backface-visibility: hidden;
}
/* backface-visibility alone isn't enough: a face carrying overflow or a filter
   still paints through, which showed the card back mirrored behind the player.
   So the face that isn't showing is hidden outright, swapped at the halfway
   point of the turn — when the card is edge-on and neither side is readable. */
.draw-card-face { transform: rotateY(180deg); visibility: hidden; }
.draw-card.is-revealed .draw-card-face { visibility: visible; transition: visibility 0s .25s; }
.draw-card.is-revealed .dc-back { visibility: hidden; transition: visibility 0s .25s; }

/* ---------- Card back: the Carrom Scorer face-down card ---------- */
.dc-back {
  overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  border: 1.5px solid rgba(255, 255, 255, .12);
  box-shadow: 0 6px 14px -4px rgba(6, 4, 13, .85);
  background:
    linear-gradient(160deg, rgba(255,255,255,.06), rgba(255,255,255,.015)),
    #191430;
}
/* A slow highlight travelling across the card while it waits. It's its own
   layer slid by transform, which the graphics chip moves on its own. It used
   to shift background-position, which repainted every card on every frame of
   the sweep — all eight at once — and stole frames from the draw on phones. */
.dc-back::after {
  content: ''; position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: linear-gradient(115deg, rgba(255,255,255,0) 20%, rgba(255,255,255,.09) 50%, rgba(255,255,255,0) 80%);
  transform: translateX(-100%);
  animation: card-shimmer 4.2s ease-in-out infinite;
}
@keyframes card-shimmer {
  0%, 62%   { transform: translateX(-100%); }
  88%, 100% { transform: translateX(100%); }
}
/* Stagger so the light rolls across the grid rather than flashing at once */
/* No stagger: the light sweeps every card at the same moment. */

/* Inset frame */
.dc-back::before {
  content: ''; position: absolute; inset: 4px; border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, .10);
}
.dc-logo {
  /* No drop-shadow filter: at 20% opacity it couldn't be seen, but it made
     every card back far costlier to draw. */
  position: relative; z-index: 1;
  width: 64%; height: auto; opacity: .2;
}

/* Revealed face */
.draw-card-face {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  border-radius: var(--r-lg); padding: 6px;
  background: var(--surface-2);
  border: 1.5px solid var(--border);
}
/* A light inner edge, so a revealed card reads as turned over */
.draw-card-face::after {
  content: ''; position: absolute; inset: 4px; border-radius: 11px;
  border: 1px solid rgba(255, 255, 255, .22); pointer-events: none;
}
.draw-card.draw-face-A .draw-card-face {
  border: 2px solid var(--lime);
  background: linear-gradient(180deg, rgba(198, 255, 77, .18), rgba(34, 28, 56, 0) 62%), var(--surface-2);
  box-shadow: 0 12px 17.5px -6px rgba(198, 255, 77, .4);
}
.draw-card.draw-face-B .draw-card-face {
  border: 2px solid var(--cyan);
  background: linear-gradient(180deg, rgba(51, 209, 255, .18), rgba(34, 28, 56, 0) 62%), var(--surface-2);
  box-shadow: 0 12px 17.5px -6px rgba(51, 209, 255, .35);
}
.draw-card-face .avatar { width: 54%; height: auto; aspect-ratio: 1; font-size: 18px; }
.draw-card-face .avatar.has-emoji { font-size: clamp(26px, 9vw, 44px); }
.draw-card-name {
  font-family: var(--font-display);
  font-size: clamp(14px, 4.1vw, 19px); font-weight: 500; max-width: 94%;
  /* No ellipsis — the drawn card is enlarged precisely so the name fits. */
  overflow-wrap: anywhere; text-align: center; line-height: 1.15;
  color: var(--text);
}
/* The name takes the side the player just joined, like the card's edge does. */
.draw-card.draw-face-A .draw-card-name { color: var(--lime); }
.draw-card.draw-face-B .draw-card-name { color: var(--cyan); }


/* ---------- Finished ---------- */
.draw-done { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; }
.draw-done-ic { font-size: 60px; line-height: 1; }
.draw-done-text { font-size: 12.5px; font-weight: 400; color: var(--muted); max-width: 220px; }

/* ---------- Bottom actions ---------- */
.draw-controls { flex: none; display: flex; flex-direction: column; gap: 10px; }
.draw-mini { justify-content: center; gap: 12px; }
.draw-mini .btn-text { flex: none; color: var(--muted); font-size: 13px; font-weight: 400; }
.draw-mini .draw-sep { flex: none; color: var(--dim); font-size: 13px; user-select: none; padding: 0 2px; }

@media (prefers-reduced-motion: reduce) {
  .draw-pill { animation: none !important; }
  .dc-inner { transition-duration: .01ms !important; }
  .dc-back::after { animation: none !important; }
}

/* Smooth screen transition — the incoming view lifts in, but only on a real
   navigation. Same-route realtime refreshes swap silently (the router adds no
   class), so a game screen never flickers while you're watching it.
   A lift and no fade: anything with opacity under 1 walls its contents off
   from what's behind it, so every glass card on the incoming screen had
   nothing to blur until the fade ended — bare tint for the length of it, then
   the blur landing all at once (later still on an iPhone, building a layer
   per card). A transform walls nothing off. */
@keyframes route-in {
  from { transform: translateY(8px); }
  to   { transform: translateY(0); }
}
#app.route-enter { animation: route-in .24s cubic-bezier(.22, .61, .36, 1); }
@media (prefers-reduced-motion: reduce) {
  #app.route-enter { animation: none; }
}

/* Nobody has touched the screen for half a minute: the decorations stop where
   they are. It matters most during a live game, which holds a wake lock — the
   screen stays lit for the score, but the lights aren't redrawing behind it for
   an hour. Anything that means "working" (the loader, the splash, Carromdeb
   typing) keeps moving; a tap starts it all again. */
body.quiet .edge-glow::after,
body.quiet .btn.primary::after,
body.quiet .star-badge-img.badge-wink,
body.quiet .star-pic:has(.badge-wink)::after,
body.quiet .dc-back::after,
body.quiet .live-clock .dot { animation-play-state: paused; }

/* ---------- Kick-off: the countdown over a new game (playKickoff) ----------
   Over everything, the nav included, on the page's own dark ground.
   The sides come in from the top and bottom, then each beat of 3-2-1 drops in from big
   to its size, holds, and shrinks away while a soft round glow swells behind
   it. "Break!" lands and stays; then the whole thing dissolves outward onto
   the game screen, which is already rendered below.
   No lines and no filters on the numbers: a blur or a text-shadow on text
   that is animating gets drawn on its own layer on an iPhone, and the glow
   was clipped to that layer's edge — a visible box round each digit. The
   glow is a separate element, a radial gradient, which has no box to show. */
.kickoff {
  position: fixed; inset: 0; z-index: 150;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px;
  padding: calc(var(--sat) + 24px) 20px calc(var(--sab) + 24px);
  /* Opaque, not glass: whatever is underneath — the New Game screen's glowing
     button, then the game rendering — blurred through as smeared bands of
     light with hard edges. The page's own ground, solid, hides it all. */
  background: radial-gradient(120% 80% at 50% 45%, #1a1530 0%, #100c1e 45%, #0b0914 80%);
  opacity: 0; transition: opacity .25s ease;
  cursor: pointer; -webkit-user-select: none; user-select: none;
}
.kickoff.is-in { opacity: 1; }
.kickoff.is-leaving { opacity: 0; transition: opacity .42s ease; pointer-events: none; }
.kickoff.is-leaving .ko-stage { transform: scale(1.3); }
.kickoff.is-leaving .ko-top { transform: translateY(-24px); opacity: 0; }
.kickoff.is-leaving .ko-bottom { transform: translateY(24px); opacity: 0; }

/* A face-off, top and bottom: each side a full-width row of faces with its
   name on the side nearest the count. They come in from the edges they sit
   at. A soft pool of the team's colour sits behind each row. Which side is on
   top alternates game by game (kickoffTop), so position (.ko-top/.ko-bottom)
   and colour (.ko-A/.ko-B) are set separately. */
.ko-side {
  position: relative; isolation: isolate;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  width: 100%; max-width: 420px; text-align: center;
  opacity: 0; transform: translateY(-40px);
  transition: transform .6s cubic-bezier(.2, .9, .3, 1.1), opacity .45s ease;
}
.ko-side.ko-bottom { transform: translateY(40px); transition-delay: .08s; }
.kickoff.is-in .ko-side { opacity: 1; transform: none; }
.ko-side::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 10%; right: 10%; top: -10px; bottom: -10px; border-radius: 50%;
  background: radial-gradient(closest-side, rgba(var(--ko-rgb), .14), transparent);
}
.ko-A { --ko-rgb: 198, 255, 77; }
.ko-B { --ko-rgb: 51, 209, 255; }
.ko-faces { display: flex; justify-content: center; flex-wrap: wrap; gap: 6px; }
.ko-face { width: 54px; }
.ko-face.face-initial { height: 54px; font-size: 20px; }
.ko-team {
  max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-family: var(--font-display); font-size: 22px; line-height: 1.2; font-weight: 800;
  letter-spacing: -.01em;
}
.ko-A .ko-team { color: var(--lime); }
.ko-B .ko-team { color: var(--cyan); }

.ko-stage {
  position: relative; width: 230px; height: 230px;
  display: grid; place-items: center;
  transition: transform .42s ease;
}
/* A steady warm glow the beats land on. */
.ko-stage::before {
  content: ""; position: absolute; inset: -30px; border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 190, 60, .16) 0%, rgba(255, 190, 60, .06) 38%, transparent 68%);
}
.ko-stage > * { grid-area: 1 / 1; }
.ko-num {
  font-family: var(--font-display); font-size: 150px; line-height: 1; font-weight: 800;
  font-variant-numeric: tabular-nums; color: var(--gold);
  animation: ko-pop 1s cubic-bezier(.2, .9, .3, 1) both;
}
@keyframes ko-pop {
  0%   { opacity: 0; transform: scale(1.9); }
  26%  { opacity: 1; transform: scale(1); }
  72%  { opacity: 1; transform: scale(.95); }
  100% { opacity: 0; transform: scale(.62); }
}
.ko-num.ko-go {
  font-size: 72px; letter-spacing: -.02em; white-space: nowrap;
  background: linear-gradient(180deg, var(--gold-light) 0%, var(--gold) 45%, var(--amber) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: ko-go .9s cubic-bezier(.2, .9, .3, 1.25) both;
}
@keyframes ko-go {
  0%   { opacity: 0; transform: scale(.35); }
  45%  { opacity: 1; transform: scale(1.1); }
  100% { opacity: 1; transform: scale(1); }
}
/* Each beat's glow: a soft disc of gold light that swells and fades with the
   number, brighter and slower for Break!. */
.ko-glow {
  width: 100%; height: 100%; border-radius: 50%;
  background: radial-gradient(circle, rgba(255, 200, 70, .42) 0%, rgba(255, 180, 50, .16) 36%, transparent 68%);
  animation: ko-glow 1s ease-out both;
}
.ko-glow.ko-glow-go {
  background: radial-gradient(circle, rgba(255, 210, 80, .55) 0%, rgba(255, 180, 50, .2) 38%, transparent 70%);
  animation-duration: 1.3s;
}
@keyframes ko-glow {
  0%   { opacity: 0; transform: scale(.55); }
  30%  { opacity: 1; transform: scale(1); }
  100% { opacity: 0; transform: scale(1.35); }
}
.ko-glow.ko-glow-go { animation-name: ko-glow-go; }
@keyframes ko-glow-go {
  0%   { opacity: 0; transform: scale(.5); }
  35%  { opacity: 1; transform: scale(1.1); }
  100% { opacity: .75; transform: scale(1.2); }
}
@media (prefers-reduced-motion: reduce) {
  .kickoff, .kickoff.is-leaving, .ko-side, .ko-stage { transition: opacity .2s ease; }
  .ko-side, .ko-side.ko-bottom, .kickoff.is-leaving .ko-top, .kickoff.is-leaving .ko-bottom { transform: none; }
  .ko-num, .ko-num.ko-go, .ko-glow { animation: none; }
}

/* ===================================================================
   Filter chips — a badge showing the value in force, opening a menu.
   Used where a screen has more than one filter, so the options don't
   take over the page.
   =================================================================== */
.filter-bar { display: flex; flex-wrap: wrap; gap: 8px; }
.fchip { position: relative; }
.fchip-btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 12px; border-radius: var(--r-pill);
  background: var(--surface); border: 1.5px solid var(--border);
  color: var(--text); font-size: 13px; line-height: 1.3; cursor: pointer;
  transition: border-color .2s ease, color .2s ease, background .2s ease;
}
.fchip-label { color: var(--muted); font-weight: 400; }
.fchip-value { font-weight: 500; }
.fchip-caret { display: inline-flex; }
.fchip-caret svg { width: 12px; height: 12px; display: block; color: var(--muted); }
/* A filter that is actually narrowing the data announces itself */
.fchip-btn.on {
  border-color: var(--accent-line); color: var(--amber); background: rgba(255, 176, 58, .08);
}
.fchip-btn.on .fchip-label { color: var(--amber); opacity: .75; }
.fchip-btn.on .fchip-caret svg { color: var(--amber); }
.fchip.open .fchip-btn { border-color: var(--accent-line); }
.fchip.open .fchip-caret svg { transform: rotate(180deg); }

.fchip-menu {
  display: none;
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 50;
  min-width: 168px; padding: 6px;
  background: var(--surface-2); border: 1.5px solid var(--border);
  border-radius: var(--r-lg); box-shadow: 0 16px 34px rgba(0, 0, 0, .6);
}
.fchip.open .fchip-menu { display: block; }
.fchip-opt {
  display: block; width: 100%; text-align: left;
  padding: 9px 10px; border: none; border-radius: var(--r-sm);
  background: none; color: var(--text); font-size: 13.5px; cursor: pointer;
}
.fchip-opt:active { background: rgba(255, 255, 255, .06); }
.fchip-opt.sel { color: var(--amber); background: rgba(255, 176, 58, .1); font-weight: 500; }

/* ===================================================================
   Points covered — what a player put on the board, over time.
   =================================================================== */
.pc-card { display: flex; flex-direction: column; gap: 10px; }
.pc-head { display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.pc-num {
  font-family: var(--font-display); font-size: 30px; font-weight: 800;
  line-height: 1; color: var(--gold); font-variant-numeric: tabular-nums;
}
.pc-unit { font-size: 13px; color: var(--muted); }
/* Points lost sits right beside the total, in the quiet grey the units use. */
.pc-lost { font-size: 12.5px; color: var(--muted); }
.pc-chart { width: 100%; }
.pc-chart svg { width: 100%; height: 86px; display: block; overflow: visible; }
.pc-base { stroke: var(--border); stroke-width: 1; }
.pc-bar { fill: var(--amber); opacity: .75; }
/* The best day/month carries the chart */
.pc-bar-max { fill: var(--gold); opacity: 1; }
/* Played, but scored nothing — a stub keeps the timeline continuous */
.pc-bar-nil { fill: var(--border); opacity: 1; }
/* What the board cost when it went the other way, beside what it paid. */
.pc-bar-lost { fill: var(--dim); opacity: .8; }
.pc-axis {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: 11px; color: var(--muted);
  margin-top: -5px;   /* dates sit close under the baseline */
}
.pc-axis span:only-child { margin-inline: auto; }
.pc-peak { margin: 0; }
/* Group heading inside a filter menu (e.g. the twelve months) */
.fchip-group {
  padding: 8px 10px 4px; font-size: 10.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .07em; color: var(--dim);
}
.fchip-menu { max-height: 320px; overflow-y: auto; }
/* "Custom" months fold away behind a disclosure */
.fchip-group {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; padding: 9px 10px; margin-top: 2px;
  background: none; border: none; border-top: 1px solid var(--border);
  color: var(--muted); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .06em; cursor: pointer;
}
.fchip-group .fchip-caret svg { transition: transform .2s ease; }
.fchip-group.open .fchip-caret svg { transform: rotate(180deg); }
.fchip-sub { display: none; }
.fchip-sub.open { display: block; }

/* A score climbing to its new value after a board lands */
.big-score.rolling { animation: score-lift .62s cubic-bezier(.2, .9, .3, 1); }
@keyframes score-lift {
  0%   { transform: translateY(14px) scale(.94); opacity: .35; }
  55%  { transform: translateY(0) scale(1.12); opacity: 1; }
  100% { transform: translateY(0) scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .big-score.rolling { animation: none; } }

/* ===================================================================
   Travelling edge glow — a soft light that runs around a border.
   Used on a live game's panels and on the gold call-to-action buttons.
   The conic gradient is masked to the border ring only, so the fill
   underneath is untouched.
   =================================================================== */
@property --edge-ang {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}
.edge-glow { position: relative; isolation: isolate; }
.edge-glow::after {
  content: ''; position: absolute; inset: -1px; border-radius: inherit;
  padding: 2px; pointer-events: none; z-index: 2;
  background: conic-gradient(from var(--edge-ang),
    transparent 0deg, var(--edge-c, rgba(255,215,0,.9)) 26deg,
    var(--edge-c2, rgba(255,255,255,.95)) 42deg,
    var(--edge-c, rgba(255,215,0,.9)) 58deg, transparent 92deg);
  -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;
  filter: blur(.4px);
  /* One pass, then a long rest. The angle is a registered property inside a
     conic gradient, so while the light is travelling this element repaints on
     every frame — it can't be handed to the compositor. On a live game that ran
     for as long as the game was open, on a screen the wake lock is already
     holding awake. The pass itself is unchanged, about two seconds; what
     changed is how often it comes round. */
  animation: edge-run 18s linear infinite;
}
@keyframes edge-run {
  0%    { --edge-ang: 0deg;   opacity: 0; }
  1.8%  { --edge-ang: 30deg;  opacity: 1; }
  9.3%  { --edge-ang: 330deg; opacity: 1; }
  11%   { --edge-ang: 360deg; opacity: 0; }
  100%  { --edge-ang: 360deg; opacity: 0; }
}

/* Idle Home banner: the same light travels its rim, but in plain daylight
   rather than gold — gold is what a live game wears — and slower, since
   nothing is happening. */
.hero.edge-glow {
  --edge-c: rgba(214, 222, 240, .5);
  --edge-c2: rgba(255, 255, 255, .9);
}
.hero.edge-glow::after { animation-duration: 24s; }

/* A live game: each side's panel and the recording card carry the light */
.score-panel.tA.edge-glow { --edge-c: rgba(198,255,77,.85); --edge-c2: rgba(233,255,190,.95); }
.score-panel.tB.edge-glow { --edge-c: rgba(51,209,255,.85);  --edge-c2: rgba(200,242,255,.95); }
.board-form.edge-glow     { --edge-c: rgba(255,176,58,.7);   --edge-c2: rgba(255,232,147,.9); }

/* Every gold button carries the light, without needing the class at each
   call site. Slower and softer than the panels — it shouldn't nag. */
.btn.primary { position: relative; isolation: isolate; }
.btn.primary::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit;
  padding: 2px; pointer-events: none; z-index: 2;
  background: conic-gradient(from var(--edge-ang),
    transparent 0deg, rgba(255,255,255,.75) 30deg,
    rgba(255,255,255,.95) 46deg, rgba(255,255,255,.75) 62deg, transparent 96deg);
  -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;
  animation: edge-run 20s linear infinite;
}
@media (prefers-reduced-motion: reduce) { .btn.primary::after { animation: none; opacity: .4; } }

@media (prefers-reduced-motion: reduce) {
  .edge-glow::after { animation: none; opacity: .5; }
}

/* ===================================================================
   The carrom loader — five men knocked from one scatter to the other and
   back, on a loop. It stands between the two scores in the live banner,
   where the crossed swords used to: a game in play should look like a
   board in play. The paths come from tools/make-loader.py.
   =================================================================== */
/* A carrom rally, on a loop. Five men lie scattered in the middle. A striker
   comes in from the RIGHT and scatters them across to a second arrangement;
   then one comes from the LEFT and scatters them back. Right, left, right,
   left — that pair of strikes is the whole loop.

   The flights are not eyeballed. Each man's path, and the bounces two of them
   take off the board's borders on each strike, are worked out in
   tools/make-loader.py and printed into the @keyframes below: a bounce is a
   mirror, so a path with bounces is a straight line through a reflected board,
   folded back. Friction lives in the timing of the waypoints — most of the
   ground is covered early and the man creeps to a stop — which leaves the
   browser interpolating in straight lines between them, exactly as a sliding
   disc does.

   Size: set --k (1 = 120px). Speed: set --dur (one rally: two strikes). */
.carrom-loader {
  --k: 1;                /* scale: .5 for a button, 1.6 for a splash */
  --dur: 3s;             /* one rally — a strike from each side */
  --coin: 16px;          /* a carrom man */
  --striker: 22px;
  width: calc(120px * var(--k)); height: calc(120px * var(--k));
  flex: none;
}
/* The frame is a rounded square, not a circle: the strikers arrive from off the
   board, and a round mask cut them into crescents on the way in. The board
   itself is still round. */
.cl-stage {
  position: relative; width: 120px; height: 120px; overflow: hidden;
  transform: scale(var(--k)); transform-origin: top left;
  border-radius: 24px;
}
/* The board under them: the centre circle and the four pockets, just enough for
   the eye to read "carrom" rather than "dots". */
.cl-board {
  position: absolute; inset: 0; border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, rgba(255,176,58,.10) 0%, transparent 62%),
    repeating-radial-gradient(circle at 50% 50%, transparent 0 21px, rgba(255,255,255,.055) 21px 22px);
}
.cl-pocket {
  position: absolute; width: 9px; height: 9px; border-radius: 50%;
  background: radial-gradient(circle at 40% 35%, #23203a, #07060d 70%);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.05);
}
.cl-pocket.tl { left: 12px; top: 12px; }
.cl-pocket.tr { right: 12px; top: 12px; }
.cl-pocket.bl { left: 12px; bottom: 12px; }
.cl-pocket.br { right: 12px; bottom: 12px; }

/* Everything that moves is centred first and then offset by its own transform,
   so every number in the keyframes is a distance from the middle of the board. */
.cl-coin, .cl-striker, .cl-hit {
  position: absolute; left: 50%; top: 50%; border-radius: 50%;
  margin-left: calc(var(--coin) / -2); margin-top: calc(var(--coin) / -2);
  width: var(--coin); height: var(--coin);
}
/* The men are all white: two solid, one pearl a shade down from them, and two
   clear ones you can see the board through. The gold striker is the only
   colour on the board. */
.cl-coin {
  box-shadow: 0 1px 2px rgba(0,0,0,.5), inset 0 0 0 1px rgba(255,255,255,.10);
  animation: var(--dur) linear infinite;
}
.cl-coin.pure  { background: radial-gradient(circle at 34% 30%, #ffffff 0%, #f6f3ec 52%, #cdc6b8 100%); }
.cl-coin.pearl { background: radial-gradient(circle at 34% 30%, #fbfaf6 0%, #ddd8cd 55%, #a9a396 100%); }
/* Glass: the rim carries it, so it still reads as a disc where the board
   behind is dark. The blur is what makes it glass rather than a pale dot. */
.cl-coin.glass {
  background: radial-gradient(circle at 34% 30%, rgba(255,255,255,.72) 0%, rgba(255,255,255,.34) 52%, rgba(255,255,255,.17) 100%);
  box-shadow: 0 1px 2px rgba(0,0,0,.35), inset 0 0 0 1px rgba(255,255,255,.45);
  -webkit-backdrop-filter: blur(1.5px); backdrop-filter: blur(1.5px);
}
.cl-coin.c1 { animation-name: cl-c1; }
.cl-coin.c2 { animation-name: cl-c2; }
.cl-coin.c3 { animation-name: cl-c3; }
.cl-coin.c4 { animation-name: cl-c4; }
.cl-coin.c5 { animation-name: cl-c5; }

.cl-striker {
  width: var(--striker); height: var(--striker);
  margin-left: calc(var(--striker) / -2); margin-top: calc(var(--striker) / -2);
  background: radial-gradient(circle at 34% 30%, #fff6d8 0%, #ffd24d 52%, #c8901a 100%);
  box-shadow: 0 2px 6px rgba(0,0,0,.55), inset 0 0 0 1.5px rgba(255,255,255,.35);
}
.cl-striker.right { animation: cl-strike-right var(--dur) linear infinite; }
.cl-striker.left  { animation: cl-strike-left  var(--dur) linear infinite; }

/* The knock: a ring that opens where the striker lands and is gone in a blink. */
.cl-hit {
  width: 26px; height: 26px; margin-left: -13px; margin-top: -13px;
  border: 1.5px solid rgba(255,232,147,.9); opacity: 0;
}
.cl-hit.right { --hx:  40px; animation: cl-hit-right var(--dur) linear infinite; }
.cl-hit.left  { --hx: -38px; animation: cl-hit-left  var(--dur) linear infinite; }

/* Generated by tools/make-loader.py -- five flights, each a straight
   run in unfolded space folded back onto the board, so every bounce is
   a true mirror. Waypoint times carry the friction; the CSS in between
   is linear. Edit the scatters in that script, not these numbers. */
@keyframes cl-c1 {
  0% { transform: translate(-4.0px, -10.0px) rotate(0.0deg); }
  14% { transform: translate(-4.0px, -10.0px) rotate(0.0deg); }
  25.62% { transform: translate(-52.0px, -8.6px) rotate(21.6deg); }
  44% { transform: translate(-30.0px, -8.0px) rotate(31.5deg); }
  58% { transform: translate(-30.0px, -8.0px) rotate(31.5deg); }
  88% { transform: translate(-4.0px, -10.0px) rotate(0.0deg); }
  100% { transform: translate(-4.0px, -10.0px) rotate(0.0deg); }
}
@keyframes cl-c2 {
  0% { transform: translate(16.0px, -19.0px) rotate(0.0deg); }
  14% { transform: translate(16.0px, -19.0px) rotate(0.0deg); }
  44% { transform: translate(-8.0px, -16.0px) rotate(-10.9deg); }
  58% { transform: translate(-8.0px, -16.0px) rotate(-10.9deg); }
  65.61% { transform: translate(42.1px, -52.0px) rotate(-5.2deg); }
  67.97% { transform: translate(52.0px, -44.9px) rotate(-4.1deg); }
  88% { transform: translate(16.0px, -19.0px) rotate(-0.0deg); }
  100% { transform: translate(16.0px, -19.0px) rotate(0.0deg); }
}
@keyframes cl-c3 {
  0% { transform: translate(32.0px, -4.0px) rotate(0.0deg); }
  14% { transform: translate(32.0px, -4.0px) rotate(0.0deg); }
  44% { transform: translate(6.0px, 12.0px) rotate(13.7deg); }
  58% { transform: translate(6.0px, 12.0px) rotate(13.7deg); }
  69.96% { transform: translate(52.0px, 0.8px) rotate(4.2deg); }
  88% { transform: translate(32.0px, -4.0px) rotate(0.0deg); }
  100% { transform: translate(32.0px, -4.0px) rotate(0.0deg); }
}
@keyframes cl-c4 {
  0% { transform: translate(8.0px, 17.0px) rotate(0.0deg); }
  14% { transform: translate(8.0px, 17.0px) rotate(0.0deg); }
  21.63% { transform: translate(-36.9px, 52.0px) rotate(-25.6deg); }
  25.98% { transform: translate(-52.0px, 40.3px) rotate(-34.2deg); }
  44% { transform: translate(-26.0px, 20.0px) rotate(-49.1deg); }
  58% { transform: translate(-26.0px, 20.0px) rotate(-49.1deg); }
  88% { transform: translate(8.0px, 17.0px) rotate(-0.0deg); }
  100% { transform: translate(8.0px, 17.0px) rotate(0.0deg); }
}
@keyframes cl-c5 {
  0% { transform: translate(14.0px, 0.0px) rotate(0.0deg); }
  14% { transform: translate(14.0px, 0.0px) rotate(0.0deg); }
  44% { transform: translate(-12.0px, 4.0px) rotate(11.8deg); }
  58% { transform: translate(-12.0px, 4.0px) rotate(11.8deg); }
  88% { transform: translate(14.0px, 0.0px) rotate(0.0deg); }
  100% { transform: translate(14.0px, 0.0px) rotate(0.0deg); }
}
/* Off a border: men 1, 4 on the strike from the right, men 2, 3 on the strike from the left. The other three are knocked straight across each time. */

/* Strike one: in from the right, contact at 14%, then out of the way. */
@keyframes cl-strike-right {
  0%, 2%    { transform: translateX(82px); animation-timing-function: cubic-bezier(.45,0,.75,1); }
  14%       { transform: translateX(51px); animation-timing-function: cubic-bezier(.2,.8,.3,1); }
  20%       { transform: translateX(63px); animation-timing-function: cubic-bezier(.4,0,.6,1); }
  38%, 100% { transform: translateX(82px); }
}
/* Strike two: the same from the left, half a rally later. */
@keyframes cl-strike-left {
  0%, 46%   { transform: translateX(-82px); animation-timing-function: cubic-bezier(.45,0,.75,1); }
  58%       { transform: translateX(-49px); animation-timing-function: cubic-bezier(.2,.8,.3,1); }
  64%       { transform: translateX(-61px); animation-timing-function: cubic-bezier(.4,0,.6,1); }
  82%, 100% { transform: translateX(-82px); }
}
@keyframes cl-hit-right {
  0%, 13%   { opacity: 0; transform: translateX(var(--hx)) scale(.35); }
  16%       { opacity: .85; transform: translateX(var(--hx)) scale(.6); }
  28%, 100% { opacity: 0; transform: translateX(var(--hx)) scale(1.35); }
}
@keyframes cl-hit-left {
  0%, 57%   { opacity: 0; transform: translateX(var(--hx)) scale(.35); }
  60%       { opacity: .85; transform: translateX(var(--hx)) scale(.6); }
  72%, 100% { opacity: 0; transform: translateX(var(--hx)) scale(1.35); }
}

/* Anyone who asked for less motion gets the board at rest, not a blank box:
   the men where the last strike from the left left them. */
@media (prefers-reduced-motion: reduce) {
  .cl-coin, .cl-striker, .cl-hit { animation: none; }
  .cl-coin.c1 { transform: translate(-4px, -10px); }
  .cl-coin.c2 { transform: translate(16px, -19px); }
  .cl-coin.c3 { transform: translate(32px, -4px); }
  .cl-coin.c4 { transform: translate(8px, 17px); }
  .cl-coin.c5 { transform: translate(14px, 0); }
  .cl-striker.right { transform: translateX(82px); }
  .cl-striker.left  { transform: translateX(-82px); }
  .cl-hit { opacity: 0; }
}

/* In the live banner it sits between the two scores, at the size the swords
   used to be and in the quick setting the lab calls "fast". The board rings and
   the pockets are dropped here — at this size they only turn to fuzz — so the
   men and the two strikers carry it. */
.hl-loader {
  --k: .38; --dur: 2.1s;
  align-self: center;
  filter: drop-shadow(0 3px 8px rgba(0, 0, 0, .5));
}
.hl-loader .cl-board, .hl-loader .cl-pocket { display: none; }
/* A 6px disc has nothing to refract, and this one runs the whole time a game is
   live — so the glass men here are painted, not blurred. */
.hl-loader .cl-coin.glass { -webkit-backdrop-filter: none; backdrop-filter: none; }

/* ===================================================================
   Update notice — a newer build is live; restarting loads it.
   =================================================================== */
.updatebar {
  position: absolute; left: 0; right: 0; top: env(safe-area-inset-top); z-index: 19;
  display: flex; align-items: center; justify-content: center; gap: 12px;
  background: linear-gradient(rgba(255, 176, 58, .14), rgba(255, 176, 58, .14)), var(--bar-solid);
  border-bottom: 1px solid var(--gold);
  color: var(--text); font-size: 13px; line-height: 1.4;
  padding: 8px max(12px, env(safe-area-inset-right)) 8px max(12px, env(safe-area-inset-left));
}
.updatebar[hidden] { display: none; }
.updatebar-btn {
  flex: none; border: 1.5px solid var(--gold); border-radius: var(--r-pill);
  background: rgba(255, 176, 58, .12); color: var(--amber);
  font-family: var(--font-display); font-size: 12.5px; font-weight: 700;
  padding: 5px 14px; cursor: pointer;
}
.updatebar-btn:active { transform: translateY(1px); }

/* "See all" and similar inline actions. There was no rule for these, so they
   fell back to the browser's default link colour — which is what turned them
   red on tap. Plain text, no underline, a press animation instead. */
.link-action {
  font-size: 12.5px; font-weight: 500; color: var(--amber);
  text-decoration: none; -webkit-tap-highlight-color: transparent;
  transition: transform .12s ease;
}
.link-action:link, .link-action:visited, .link-action:hover, .link-action:active { color: var(--amber); }
.link-action:active { transform: scale(.94); }

/* Player stat tiles: the numbers carry the colour, the labels stay quiet */
.analytics .stat-tile:nth-child(1) .stat-value { color: var(--gold); }
.analytics .stat-tile:nth-child(2) .stat-value { color: var(--cyan); }
.analytics .stat-tile:nth-child(3) .stat-value { color: var(--lime); }

/* The "you" badge is the only thing marking your own card */
.you-tag { font-weight: 500; }

/* ===================================================================
   Date picker — presets across the top, a month calendar underneath.
   =================================================================== */
.fchip-date .fchip-menu.dp { width: 292px; max-width: min(92vw, 292px); padding: 10px; max-height: none; }
.dp-presets { display: flex; gap: 6px; margin-bottom: 10px; }
.dp-preset {
  flex: 1 1 0; padding: 7px 0; border-radius: var(--r-sm);
  background: var(--surface); border: 1.5px solid var(--border);
  color: var(--muted); font-family: var(--font-display);
  font-size: 12px; font-weight: 500; cursor: pointer;
}
.dp-preset.sel {
  background: rgba(255, 176, 58, .1); color: var(--amber); border-color: var(--accent-line);
}
.dp-preset:active { transform: translateY(1px); }

.dp-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 2px 2px 8px;
}
.dp-title { font-family: var(--font-display); font-size: 13.5px; font-weight: 600; }
.dp-nav {
  width: 28px; height: 28px; border-radius: var(--r-sm);
  background: var(--surface); border: 1.5px solid var(--border);
  color: var(--text); font-size: 15px; line-height: 1; cursor: pointer;
}
.dp-nav:disabled { opacity: .3; cursor: default; }

.dp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.dp-wd {
  text-align: center; font-size: 10px; font-weight: 600; color: var(--dim);
  padding-bottom: 4px; text-transform: uppercase;
}
.dp-day {
  aspect-ratio: 1; display: flex; align-items: center; justify-content: center;
  border: none; border-radius: var(--r-sm); background: none;
  color: var(--text); font-size: 12.5px; cursor: pointer; padding: 0;
}
.dp-day.empty { visibility: hidden; }
/* Outside the recorded span — nothing to show, so nothing to pick */
.dp-day.off { color: var(--dim); opacity: .4; cursor: default; }
.dp-day.today { box-shadow: inset 0 0 0 1px var(--border); }
.dp-day.in { background: rgba(255, 176, 58, .12); border-radius: 0; }
.dp-day.sel { background: var(--amber); color: var(--primary-ink); font-weight: 700; }
.dp-day:not(.off):not(.sel):active { background: rgba(255, 255, 255, .08); }

.dp-hint { margin-top: 8px; font-size: 11px; color: var(--muted); text-align: center; }

/* ---------- MVP ---------- */
/* Emoji keep their own face so the crown isn't squeezed by the display font. */
.mvp-crown { font-size: 18px; line-height: 1; font-family: var(--font-ui), system-ui, sans-serif; flex: none; }
.mvp-crown-sm {
  font-size: 12px; line-height: 1; margin-left: 4px; vertical-align: 1px;
  font-family: var(--font-ui), system-ui, sans-serif;
}
/* The MVP badge and the star are images: they keep their own shape, with no
   disc behind them, and a small shadow so they sit on the card. Sized per spot. */
.mvp-icon {
  display: inline-block; width: 22px; height: 22px; object-fit: contain;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
  user-select: none; -webkit-user-drag: none;
}
.mvp-crown { display: inline-flex; align-items: center; }
.mvp-crown-sm { display: inline-flex; vertical-align: -3px; }
.mvp-crown-sm .mvp-icon { width: 17px; height: 17px; }
.mvp-count .mvp-crown .mvp-icon { width: 28px; height: 28px; }
.mvp-icon.mvp-icon-title { width: 26px; height: 26px; vertical-align: -6px; margin-right: 2px; }
.star-badge-img {
  position: absolute; top: -7px; right: -8px;
  width: 28px; height: 28px; object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .55));
  user-select: none; -webkit-user-drag: none;
}
/* Stars of the week wink in turn: a bounce down small and back to normal
   size, twice, the second smaller — never past normal, never turning — with
   a flash of gold as it springs back, and a four-point glint off the tip. The
   wink itself takes about a second of a 9s cycle shared by the row, each star
   3s behind the last (--i is its place): one shine every 3s, never two at
   once, and a long rest between so it catches the eye without nagging. The
   offset brings the first wink in as Home opens rather than 8s later. */
.star-badge-img.badge-wink {
  filter: brightness(1) drop-shadow(0 2px 4px rgba(0, 0, 0, .55));
  animation: star-wink 9s cubic-bezier(.3, .7, .4, 1) infinite;
  animation-delay: calc(var(--i, 0) * 3s - 7.5s);
}
.star-pic:has(.badge-wink)::after {
  content: ""; position: absolute; top: -12px; right: -12px;
  width: 14px; height: 14px; pointer-events: none;
  background: radial-gradient(circle, #fff 0 22%, #FFE39A 45%, rgba(255, 214, 102, 0) 72%);
  clip-path: polygon(50% 0, 60% 40%, 100% 50%, 60% 60%, 50% 100%, 40% 60%, 0 50%, 40% 40%);
  opacity: 0; transform: scale(0);
  animation: star-glint 9s ease-out infinite;
  animation-delay: calc(var(--i, 0) * 3s - 7.5s);
}
@keyframes star-wink {
  0%, 86% { transform: scale(1); }
  88.4% { transform: scale(.65); }
  91.6% {
    transform: scale(1);
    filter: brightness(1.35) drop-shadow(0 0 7px rgba(255, 205, 80, .85));
  }
  93.8% { transform: scale(.82); }
  96%, 100% { transform: scale(1); }
}
@keyframes star-glint {
  0%, 89.2% { opacity: 0; transform: scale(0) rotate(0); }
  91.6% { opacity: 1; transform: scale(1.15) rotate(45deg); }
  96%, 100% { opacity: 0; transform: scale(.2) rotate(90deg); }
}
@media (prefers-reduced-motion: reduce) {
  .star-badge-img.badge-wink, .star-pic:has(.badge-wink)::after { animation: none; }
}
/* The ⓘ beside every MVP: a drawn icon (its own ring), quiet grey, with a
   28px target so it's easy to hit even though the icon is 20px. */
.info-btn {
  flex: none; width: 28px; height: 28px; padding: 0; border: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; color: var(--muted);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
}
.info-btn svg { display: block; width: 20px; height: 20px; }
.info-btn:active { color: var(--primary); }
.info-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* Game screen: the MVP card under the result — the same soft gold wash and
   edge as the profile's MVP card, with the face(s) ringed in thin gold. */
.mvp-line {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 10px 12px 14px; border-radius: var(--r-lg);
  background: linear-gradient(135deg, rgba(255, 215, 0, .10), rgba(255, 215, 0, .02) 70%), var(--surface);
  border: 1px solid rgba(255, 215, 0, .35);
  box-shadow: 0 6px 18px -8px rgba(255, 215, 0, .25);
}
/* The faces sit straight on the card — no disc, no ring — with a soft shadow
   round their outline. Shared crowns overlap, like a small group photo. */
.mvp-faces { display: flex; align-items: center; flex: none; }
.mvp-face {
  display: block; width: 50px; height: 50px; object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .35));
  user-select: none; -webkit-user-drag: none;
}
.mvp-faces .avatar { width: 50px; height: 50px; font-size: 20px; border: none; }
.mvp-faces > * + * { margin-left: -12px; }
.mvp-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
/* The score beside the MVP label, quiet against the name below it. */
.mvp-score {
  font-size: 11px; letter-spacing: 0; text-transform: none; font-weight: 600;
  color: var(--muted); font-variant-numeric: tabular-nums;
}
.mvp-line .mvp-label {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 11px; line-height: 1; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255, 215, 0, .75); font-weight: 700;
}
/* The crown follows the name(s) on the second line. */
.mvp-line .mvp-names .mvp-crown { font-size: 15px; margin-left: 6px; vertical-align: -4px; }
.mvp-line .mvp-names .mvp-icon { width: 22px; height: 22px; }
.mvp-line .mvp-names {
  font-family: var(--font-display); font-size: 17px; line-height: 1.25; font-weight: 700;
  color: var(--gold); overflow-wrap: anywhere;
}

/* The game's top partners, under the MVP. The same card, cooled off: this is
   the pair who did the work together, not the crown, so it wears the app's
   quiet edge rather than the MVP's gold. */
.pair-lines { display: flex; flex-direction: column; gap: 8px; }
.mvp-line.pair-line {
  background: linear-gradient(135deg, rgba(255, 255, 255, .05), rgba(255, 255, 255, .01) 70%), var(--surface);
  border-color: var(--border);
  box-shadow: var(--sh-card);
}
.mvp-line.pair-line .mvp-label { color: var(--muted); }
.mvp-line.pair-line .mvp-names { color: var(--text); font-size: 16px; }
/* The two faces sit shoulder to shoulder, as they do on the leaderboard. */
.mvp-line.pair-line .mvp-faces > * + * { margin-left: -10px; }
/* Slightly smaller faces than the MVP's, which also buys the line below the
   names enough width to stay one line on a phone. */
.mvp-line.pair-line .mvp-face,
.mvp-line.pair-line .mvp-faces .avatar { width: 42px; height: 42px; }
.pair-line-meta { display: block; margin-top: 2px; font-size: 11.5px; line-height: 1.45; color: var(--muted); }
.pair-line-meta .nowrap { white-space: nowrap; }

/* Leaderboard: top three, the first lit in gold. */
.mvp-card { display: flex; flex-direction: column; gap: 12px; padding: 14px; }
/* Leaderboard Top Players: the avatars sit in the middle of the card. */
.mvp-card .stars-row { justify-content: center; }
.mvp-card-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.mvp-card-title {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--font-display); font-size: 16px; line-height: 1.3; font-weight: 700;
}

/* Leaderboard: Top partners — the pairs who win most side by side. The two
   faces overlap so a pair reads as one thing, the way the MVP faces do. */
.partners-card { gap: 6px; }
.pair-row {
  display: flex; align-items: center; gap: 12px;
  /* More room on the right than the left: the points sit against that edge and
     were reading as if they'd fallen off it. */
  padding: 9px 14px 9px 6px; border-radius: var(--r-lg);
}
.pair-row.best { background: rgba(255, 215, 0, .07); }
.pair-row + .pair-row { margin-top: 2px; }
/* The two faces overlap, the way the MVP faces do, so a pair reads as one
   thing. The front one carries the medal. */
.pair-faces { flex: none; display: flex; }
.pair-faces > * + * { margin-left: -12px; }
.pair-pic { position: relative; display: inline-block; flex: none; }
.pair-face { width: 38px; height: 38px; border-radius: 50%; display: block; }
.pair-badge {
  position: absolute; top: -6px; right: -7px;
  height: 21px; width: auto; object-fit: contain;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, .55));
  user-select: none; -webkit-user-drag: none;
}
.pair-id { flex: 1; min-width: 0; }
.pair-names {
  display: flex; align-items: center; gap: 6px;
  font-family: var(--font-display); font-size: 15px; line-height: 1.3; font-weight: 700;
  overflow-wrap: anywhere;
}


/* Profile: how many games they were MVP in. */
/* Profile: Favourite partner — who they score most with (shared, if level). */
.fav-card { display: flex; flex-direction: column; padding: 0; overflow: hidden; }
.fav-partner {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  color: var(--text); text-decoration: none; -webkit-tap-highlight-color: transparent;
}
.fav-partner + .fav-partner { border-top: 1px solid var(--border); }
.fav-partner:active { background: rgba(255, 255, 255, .04); }
.fav-pts { flex: none; display: flex; flex-direction: column; align-items: flex-end; font-size: 12px; line-height: 1.3; color: var(--muted); }
.fav-pts b {
  font-family: var(--font-display); font-size: 22px; line-height: 1.1; font-weight: 800;
  color: var(--gold); font-variant-numeric: tabular-nums;
}
.mvp-count { display: flex; align-items: center; gap: 10px; padding: 12px 14px; }
/* Profile: the MVP card stands out a little from the plain cards around it —
   a soft gold wash and a gold edge, the crown's colour. */
.card.mvp-count {
  background: linear-gradient(135deg, rgba(255, 215, 0, .10), rgba(255, 215, 0, .02) 70%), var(--surface);
  border-color: rgba(255, 215, 0, .35);
  box-shadow: 0 6px 18px -8px rgba(255, 215, 0, .25);
}
.mvp-count-text { flex: 1; font-size: 14px; line-height: 1.4; font-weight: 600; }

/* The explanation behind ⓘ. */
.mvp-formula {
  align-self: center; padding: 8px 14px; border-radius: 999px;
  background: rgba(255, 215, 0, .08); border: 1.5px solid rgba(255, 215, 0, .35);
  font-family: var(--font-display); font-size: 17px; font-weight: 800; color: var(--gold);
}
.mvp-rules { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; font-size: 14px; line-height: 1.45; }
.mvp-rules b { color: var(--text); }

/* Home: Stars of the week — round avatars with names under them, in order,
   the leader ringed in gold. */
.stars { display: flex; flex-direction: column; gap: 12px; }
.stars-title {
  margin: 0; font-family: var(--font-display); font-size: 20px; line-height: 1.25;
  font-weight: 700; color: var(--text); text-transform: none; letter-spacing: 0;
}
/* Tight row: players read as one group (Stars of the week and Top Players). */
.stars-row { display: flex; gap: 8px; }
.star {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  width: 64px; min-width: 0; color: var(--text); text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.star:active { transform: scale(.96); }
.star-face { width: 58px; }
.star-face.face-initial { height: 58px; font-size: 22px; }
.star .avatar.has-emoji { font-size: 30px; }
.star-name { font-size: 13px; line-height: 1.3; font-weight: 600; text-align: center; overflow-wrap: anywhere; }

/* The badge on an avatar's rim (⭐️ on every Star of the week, 🥇🥈🥉 on Top
   Players): a small filled circle with the same outline as the avatar. */
.star-pic { position: relative; display: inline-flex; }
.star-badge {
  position: absolute; top: -3px; right: -5px;
  width: 24px; height: 24px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--surface-2); border: 1.5px solid var(--border);
  font-size: 13px; line-height: 1; font-family: var(--font-ui), system-ui, sans-serif;
  box-shadow: 0 2px 6px rgba(0, 0, 0, .45);
}

/* ===================================================================
   Ask — questions about the rules and the numbers (answered by bot.js)
   =================================================================== */

/* The chat fills the column: the log scrolls, the question bar stays put. */
body.asking .container { overflow: hidden; display: flex; flex-direction: column; }
body.asking .container > .view { flex: 1 1 auto; min-height: 0; width: 100%; }
/* The chat half takes the whole screen. A conversation wants the room, and the
   floating nav would sit on top of the question bar — so it steps out and the
   chevron beside the title is the way back to the front page. Nothing left to
   clear at the foot but the home indicator. The front page keeps its nav: it
   is a tab like any other. */
body.ask-chat .nav { display: none; }
body.ask-chat .container { padding-bottom: calc(var(--sab) + 10px); }

/* The Ask tab's front page: Carromdeb, the greeting and the starters sitting
   in the middle of the screen, with Ask more under them. No question bar —
   picking a starter or pressing Ask more is what opens the chat. */
.ask-front { overflow-y: auto; overscroll-behavior: contain; }
.ask-front-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
/* Auto margins rather than justify-content: centred the same way, but on a
   short screen the top stays reachable instead of being clipped away. */
.ask-front-body {
  margin-block: auto; display: flex; flex-direction: column; align-items: center; gap: 20px;
  padding-bottom: 8px;
}
.ask-front .ask-intro { gap: 14px; }
.ask-more { align-self: stretch; }
.ask { display: flex; flex-direction: column; gap: 10px; position: relative; }
/* The title floats over the top of the log, so a message scrolling past it
   fades out instead of being cut off. The log is padded to clear it (measured
   in app.js). What it floats on is frosted glass rather than a fill: a flat
   colour stood out as a dark box against the page's swirl, and stopped short
   of the screen's edges. The glass is a layer behind the title, reaching up
   past the column's top padding and out through its side padding to the
   screen, and masked to fade away at the foot — a mask on the head itself
   would fade the title too. */
.ask-head {
  position: absolute; top: 0; left: 0; right: 0; z-index: 4;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 6px 0 24px;
  isolation: isolate;
}
.ask-head::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  top: calc(-1 * (env(safe-area-inset-top) + var(--top-h, 0px) + 16px));
  bottom: 0;
  left: calc(-1 * max(16px, env(safe-area-inset-left)));
  right: calc(-1 * max(16px, env(safe-area-inset-right)));
  background: rgba(11, 9, 20, .28);
  -webkit-backdrop-filter: blur(14px) saturate(120%); backdrop-filter: blur(14px) saturate(120%);
  -webkit-mask: linear-gradient(180deg, #000 0, #000 62%, transparent 100%);
          mask: linear-gradient(180deg, #000 0, #000 62%, transparent 100%);
}
.ask-head-left { display: flex; align-items: center; gap: 2px; min-width: 0; }
/* Pulled left by its own padding so the chevron's glyph, not its tap target,
   lines up with the column edge. */
.ask-back {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; margin-left: -9px; padding: 0;
  background: none; border: 0; border-radius: 999px;
  color: var(--muted); cursor: pointer;
  transition: color .2s ease;
}
.ask-back svg { width: 23px; height: 23px; display: block; }
.ask-back:active { color: var(--text); }
/* Clear is a quiet action beside the title, not a heading of its own. */
.ask-head .btn-text { font-weight: 500; }
.ask-log {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
  display: flex; flex-direction: column; gap: 10px; padding: 2px 0 4px;
}
.ask-msg { display: flex; flex-direction: column; gap: 8px; max-width: 90%; }
.ask-bot { align-self: stretch; max-width: 100%; }
.ask-me { align-self: flex-end; align-items: flex-end; }
.ask-text {
  padding: 10px 13px; border-radius: 16px; font-size: 14px; line-height: 1.55;
  white-space: pre-line; overflow-wrap: anywhere; color: var(--text);
}
/* Carromdeb's answers sit straight on the page, like a reply in a chat app;
   your questions are solid bubbles a shade lighter than the page, with no
   outline — the fill alone is what separates them. */
.ask-bot .ask-text { background: none; border: 0; padding: 2px 4px; }
.ask-me .ask-text { background: var(--surface-2); border: 0; border-top-right-radius: 6px; }
.ask-chips { display: flex; flex-wrap: wrap; gap: 6px; }
/* The opening screen: art, greeting and starter questions stacked and centred,
   sitting in the middle of the empty chat rather than at the top of it. */
.ask-log.is-empty { justify-content: center; align-items: center; padding-bottom: var(--ask-head-h, 0px); }
.ask-intro { align-self: stretch; max-width: 100%; align-items: center; text-align: center; gap: 12px; }
.ask-guru-wrap { position: relative; display: inline-flex; margin-top: 6px; }
/* A soft golden halo behind him — wider than the picture, fading to nothing
   well before the edge, so it reads as light rather than a disc. */
.ask-guru-wrap::before {
  content: ""; position: absolute; left: 50%; top: 54%; transform: translate(-50%, -50%);
  width: 165%; height: 145%; border-radius: 50%; pointer-events: none;
  background: radial-gradient(circle,
    rgba(255, 215, 0, .11) 0%, rgba(255, 194, 71, .05) 42%, rgba(255, 215, 0, 0) 68%);
}
.ask-intro .ask-guru {
  position: relative; width: min(40vw, 132px); height: auto; display: block;
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, .5));
}
.ask-intro .ask-chips { justify-content: center; }
/* Held back while an answer is still being revealed. Both of these carry a
   display of their own, so the browser's own [hidden] rule can't win. */
.ask-chips[hidden], .ask-ai-foot[hidden] { display: none; }
/* The figures an answer turns on: numbers, player names, team names. */
.ask-bot .ask-text b { font-weight: 700; color: var(--text); }
.ask-chip {
  background: var(--surface-2); color: var(--text); font: inherit; font-size: 13px; line-height: 1.3;
  border: 1px solid var(--border); border-radius: var(--r-pill); padding: 7px 12px;
  cursor: pointer; text-align: left;
}
.ask-chip:active { transform: scale(.97); }
.ask-bar { flex: none; display: flex; gap: 8px; align-items: stretch; }
.ask-input { flex: 1 1 auto; min-width: 0; font-size: 16px; }   /* 16px: iOS won't zoom on focus */
.ask-send { flex: none; width: 48px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.ask-send svg { width: 20px; height: 20px; display: block; }
/* The box keeps its 16px (smaller would make iOS zoom on focus); only the
   prompt inside it is set smaller. */
.ask-input::placeholder { font-size: 13.5px; }
/* Where an answer came from: quiet, tappable, and it stays with the answer. */
.ask-source {
  display: inline-flex; align-items: center; gap: 6px; align-self: flex-start;
  font-size: 11.5px; line-height: 1.3; color: var(--muted);
  text-decoration: none; padding: 4px 8px 4px 6px;
  background: var(--surface-2); border-radius: var(--r-pill);
}
.ask-source:active { opacity: .7; }
.ask-source-icon { font-family: var(--font-ui), system-ui, sans-serif; font-size: 12px; }
/* An answer written by the AI helper, and the wait for one. */
.ask-ai-tag { font-size: 11.5px; line-height: 1.3; color: var(--dim); padding-left: 4px; }
.ask-pending .ask-text { color: var(--muted); font-style: italic; display: flex; align-items: center; gap: 8px; }
/* The wait itself: three dots dancing to the left of "Thinking…". */
.ask-dots { flex: none; display: inline-flex; align-items: center; gap: 3px; }
.ask-dots i {
  width: 4.5px; height: 4.5px; border-radius: 50%; background: var(--muted);
  animation: ask-dot 1.05s ease-in-out infinite;
}
.ask-dots i:nth-child(2) { animation-delay: .15s; }
.ask-dots i:nth-child(3) { animation-delay: .3s; }
@keyframes ask-dot {
  0%, 60%, 100% { transform: translateY(0); opacity: .45; }
  30% { transform: translateY(-3px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .ask-dots i { animation-duration: 2.6s; } }
/* The shared AI allowance ring beside Clear, like a usage meter: gold while
   there's plenty, amber from 70%, red from 90%. */
.ask-head-actions { display: flex; align-items: center; gap: 12px; }
.ask-ring {
  flex: none; width: 28px; height: 28px; padding: 0; border: 0; background: none;
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
}
.ask-ring[hidden] { display: none; }
.ask-ring:active { transform: scale(.92); }
.ask-ring svg { width: 22px; height: 22px; display: block; }
.ask-ring-track { fill: none; stroke: var(--border); stroke-width: 3; }
.ask-ring-fill { fill: none; stroke: var(--gold); stroke-width: 3; stroke-linecap: round; }
.ask-ring.warn .ask-ring-fill { stroke: var(--amber); }
.ask-ring.full .ask-ring-fill { stroke: var(--danger); }
/* Under an AI answer: "✦ AI can make mistakes." and, on the latest one, the
   thumbs. A vote leaves just the chosen thumb, in gold. */
.ask-ai-foot { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 8px; padding-left: 4px; }
.ask-ai-foot .ask-ai-tag { padding-left: 0; }
.ask-votes { display: inline-flex; gap: 2px; }
.ask-vote {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 26px; padding: 0; border: 0; border-radius: 8px;
  background: none; color: var(--muted); cursor: pointer;
}
.ask-vote:active { transform: scale(.9); background: rgba(255, 255, 255, .06); }
.ask-vote svg, .ask-voted svg { width: 15px; height: 15px; display: block; }
.ask-voted { display: inline-flex; align-items: center; padding: 0 6px; color: var(--gold); }
/* Share, beside the thumbs on the newest answer. Older ones are shared by
   holding them, so the callout menu stays out of the way there. */
.ask-share {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 26px; padding: 0; border: 0; border-radius: 8px;
  background: none; color: var(--muted); cursor: pointer;
}
.ask-share:active { transform: scale(.9); background: rgba(255, 255, 255, .06); }
.ask-share svg { width: 15px; height: 15px; display: block; }
.ask-bot .ask-text { -webkit-touch-callout: none; }
.ask-voted svg path { fill: currentColor; fill-opacity: .2; }
/* Which model answers: small text above the question box when a chat begins. */
.ask-model { flex: none; font-size: 11.5px; line-height: 1.3; color: var(--dim); padding: 0 4px; text-align: center; }
.ask-model[hidden] { display: none; }
/* Tap the ring: today's shared AI usage as a bar, and the model answering. */
.usage-card { display: flex; flex-direction: column; gap: 10px; }
.usage-card h3 { margin: 0; }
.usage-count { font-size: 15px; color: var(--muted); }
.usage-count b {
  font-family: var(--font-display); font-size: 30px; line-height: 1; font-weight: 800;
  color: var(--text); margin-right: 3px; font-variant-numeric: tabular-nums;
}
.usage-bar { height: 8px; border-radius: 999px; background: var(--border); overflow: hidden; }
.usage-bar > span { display: block; height: 100%; border-radius: inherit; background: var(--gold); }
.usage-bar.warn > span { background: var(--amber); }
.usage-bar.full > span { background: var(--danger); }
.usage-note { margin: 0; }
.usage-model { font-size: 13px; color: var(--muted); }


/* ===================================================================
   Guest mode — looking round without an account.
   The bar follows .netbar/.updatebar exactly so the layout maths that
   reserves --top-h keeps working; only the tint differs. Cyan, not gold
   or red: this is neither the brand speaking nor anything wrong.
   =================================================================== */
.guestbar {
  position: absolute; left: 0; right: 0; top: env(safe-area-inset-top); z-index: 19;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: linear-gradient(rgba(51, 209, 255, .13), rgba(51, 209, 255, .13)), var(--bar-solid);
  border-bottom: 1px solid rgba(51, 209, 255, .55);
  color: var(--cyan-light); font-size: 12.5px; line-height: 1.4;
  padding: 7px max(12px, env(safe-area-inset-right)) 7px max(12px, env(safe-area-inset-left));
}
.guestbar[hidden] { display: none; }

/* Sign-in: the way in for someone with no name on the roster. The rule keeps
   it clearly apart from the player chips above it. */
.signin-guest { width: 100%; margin-top: 22px; }
.signin-or {
  display: flex; align-items: center; gap: 12px;
  color: var(--dim); font-size: 12px; margin-bottom: 14px;
}
.signin-or::before, .signin-or::after {
  content: ""; flex: 1; height: 1px; background: var(--border);
}
.btn.guest-enter { width: 100%; }

/* A guest has no face to show, so the suitcase stands in for one — bare, like
   every other avatar in the app, with the same drop shadow under it. */
.guest-face { width: 56px; }
.guest-face.guest-face-md { width: 64px; }
.guest-face.profile-face { width: 104px; }
.guest-face.chip-face { width: 34px; }
.signin-who.guest-who { align-items: center; }

/* The terms of the visit — on the way in, and again in Settings. */
.guest-terms {
  list-style: none; margin: 18px 0 24px; padding: 0;
  display: grid; gap: 12px; text-align: left; width: 100%;
}
.guest-terms.flush { margin: 4px 0 12px; }
.guest-terms li {
  position: relative; padding-left: 22px;
  color: var(--muted); font-size: 13.5px; line-height: 1.5;
}
.guest-terms li::before {
  content: ""; position: absolute; left: 4px; top: 8px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--cyan);
}
.guest-terms li b { color: var(--text); font-weight: 600; }

/* A game the guest started: real to them, invisible to everyone else. */
.tag-temp {
  display: inline-flex; align-items: center; padding: 2px 8px;
  border-radius: var(--r-pill);
  background: rgba(51, 209, 255, .12); border: 1px solid rgba(51, 209, 255, .45);
  color: var(--cyan-light); font-size: 10.5px; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase;
}

/* ===================================================================
   Player banners — Players tab cards and a player's own page.
   Each wears their side's colour: a soft glow from the top and a thin
   lit edge along it. The top three all time carry their medal. Kept to
   light, not movement: nothing animates, so a grid of ten stays calm.
   =================================================================== */
.player-card, .player-banner { --tone-rgb: 255, 176, 58; }
.tone-man  { --tone-rgb: 198, 255, 77; }
.tone-chat { --tone-rgb: 51, 209, 255; }

.player-card {
  position: relative; overflow: hidden; isolation: isolate;
  gap: 3px; padding: 18px 10px 12px;
  background:
    radial-gradient(110% 62% at 50% 0%, rgba(var(--tone-rgb), .17) 0%, rgba(var(--tone-rgb), .05) 42%, transparent 66%),
    linear-gradient(180deg, rgba(34, 32, 46, .40) 0%, rgba(28, 26, 38, .34) 48%, rgba(21, 20, 29, .30) 100%);
  -webkit-backdrop-filter: var(--glass-blur); backdrop-filter: var(--glass-blur);
}
/* The lit edge: brightest at the centre, fading out before the corners. */
.player-card::before, .player-banner::before {
  content: ""; position: absolute; top: 0; left: 14%; right: 14%; height: 2px;
  background: linear-gradient(90deg, transparent, rgba(var(--tone-rgb), .9), transparent);
  border-radius: 0 0 2px 2px; pointer-events: none;
}
.player-card .pc-face {
  filter: drop-shadow(0 6px 12px rgba(0, 0, 0, .5)) drop-shadow(0 0 14px rgba(var(--tone-rgb), .18));
}
.player-card .pc-team { color: rgba(var(--tone-rgb), .78); font-size: 12.5px; }
.pc-medal {
  position: absolute; top: 8px; left: 9px; z-index: 1;
  font-size: 24px; line-height: 1;
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, .55));
}
/* The drawn medals (assets/medal-1…3.webp). They are taller than they are wide
   — the ribbon — so height leads and the width follows. */
.medal-img { display: block; width: auto; object-fit: contain; user-select: none; -webkit-user-drag: none; }
.pc-medal-img { height: 26px; }
/* The all-time top three: a hairline of gold round the card, so the podium
   reads across the grid before the medals do. */
.player-card.podium {
  box-shadow: var(--sh-card), inset 0 0 0 1px rgba(255, 215, 0, .26);
}
/* Rating and win rate, under a hairline so they read as the card's footer. */
.pc-stats {
  display: inline-flex; align-items: baseline; justify-content: center; gap: 5px;
  margin-top: 8px; padding-top: 8px; width: 82%;
  border-top: 1px solid rgba(255, 255, 255, .06);
  font-size: 11.5px; line-height: 1.3; color: var(--muted); white-space: nowrap;
}
.pc-pts b { font-family: var(--font-display); font-size: 14px; font-weight: 700; color: var(--gold-light); }
.pc-dot { color: var(--dim); }
.pc-stats-none { color: var(--dim); }

/* A player's page: the same idea at full size, with faint carrom-board rings
   behind the face. */
/* --pb-pad and --pb-face place the face; the glow and the rings are centred on
   the middle of it from the same two numbers, so they can't drift apart. */
.profile-hero.player-banner {
  --pb-pad: 26px; --pb-face: 108px;
  --pb-mid: calc(var(--pb-pad) + var(--pb-face) / 2);
  overflow: hidden; isolation: isolate;
  padding: var(--pb-pad) 16px 18px;
  /* The same glass as every other card (Settings' profile card, a game card),
     with the team's glow pooled behind the face. A whole card rather than a
     banner dissolving into the page: a fade would still end in a hard edge of
     blur where the backdrop filter stops. */
  background:
    radial-gradient(160px 150px at 50% var(--pb-mid), rgba(var(--tone-rgb), .24) 0%, rgba(var(--tone-rgb), .08) 50%, transparent 100%),
    linear-gradient(180deg, rgba(34, 32, 46, .40) 0%, rgba(28, 26, 38, .34) 45%, rgba(21, 20, 29, .30) 100%);
}
.player-banner::before { left: 10%; right: 10%; height: 3px; }
.player-banner::after {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: var(--pb-mid); width: 250px; height: 250px; transform: translate(-50%, -50%);
  border-radius: 50%;
  background: repeating-radial-gradient(circle, transparent 0 21px, rgba(var(--tone-rgb), .15) 21px 22.5px);
  -webkit-mask: radial-gradient(circle, #000 18%, transparent 68%);
          mask: radial-gradient(circle, #000 18%, transparent 68%);
}
.player-banner .profile-face {
  width: var(--pb-face); margin-bottom: 10px;
  filter: drop-shadow(0 12px 22px rgba(0, 0, 0, .55)) drop-shadow(0 0 22px rgba(var(--tone-rgb), .28));
}
.player-banner .me-name { font-size: 23px; letter-spacing: -.01em; }
.player-banner .profile-face.face-initial { height: var(--pb-face); font-size: 40px; }
.pb-team {
  margin-top: 6px; padding: 3px 11px; border-radius: var(--r-pill);
  background: rgba(var(--tone-rgb), .12); border: 1px solid rgba(var(--tone-rgb), .38);
  color: rgba(var(--tone-rgb), 1); font-size: 12px; font-weight: 600; line-height: 1.4;
}
/* Where they stand all time. Gold only for the podium. */
.pb-standing {
  display: inline-flex; align-items: center; gap: 8px;
  margin-top: 14px; padding: 7px 14px 7px 8px; border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .05); border: 1px solid rgba(255, 255, 255, .08);
  font-size: 12.5px; color: var(--muted); line-height: 1.2;
}
.pb-standing b { color: var(--text); font-weight: 700; }
.pb-place {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; height: 24px; padding: 0 5px; border-radius: 12px;
  background: rgba(255, 255, 255, .08);
  font-family: var(--font-display); font-size: 12px; font-weight: 700; color: var(--text);
}
/* With a medal in it the pill is just a mount for the picture. */
.pb-place:has(.medal-img) { background: none; min-width: 0; padding: 0; }
.pb-place-img { height: 26px; }
/* Every player's chip looks the same — the top three simply have a medal in
   front of the rating. Nothing gilded for them, nothing plainer for the rest. */
.pb-standing:not(:has(.pb-place)) { padding-left: 14px; }

/* The winner's trophy — an image now, not the 🏆 emoji. It sits after a
   winning name at the size of that text, leads the win banner, and ends
   the idle hero line. */
.trophy-icon {
  display: inline-block; width: 1.2em; height: 1.2em; object-fit: contain;
  margin-left: .3em; vertical-align: -.24em;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .45));
  user-select: none; -webkit-user-drag: none;
}
.trophy-icon.trophy-banner { width: 26px; height: 26px; margin: 0 .4em 0 0; vertical-align: -6px; }
.trophy-icon.trophy-hero { width: 20px; height: 20px; vertical-align: -4px; }

/* Leaderboard month picker: Jan–Dec in a grid, Custom opens the calendar. */
.dp-pane[hidden] { display: none; }
.dp-months { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; }
.dp-month, .dp-custom {
  padding: 9px 0; border-radius: var(--r-sm);
  background: var(--surface); border: 1.5px solid var(--border);
  color: var(--text); font-family: var(--font-display);
  font-size: 12.5px; font-weight: 500; cursor: pointer;
}
.dp-month.now { border-color: var(--accent-line); }
.dp-month:disabled { color: var(--dim); opacity: .4; cursor: default; }
.dp-month.sel, .dp-custom.sel {
  background: rgba(255, 176, 58, .1); color: var(--amber); border-color: var(--accent-line); font-weight: 600;
}
.dp-month:not(:disabled):active, .dp-custom:active { transform: translateY(1px); }
.dp-custom { display: block; width: 100%; margin-top: 8px; }
.dp-back {
  border: none; background: none; padding: 2px 2px 8px; cursor: pointer;
  color: var(--amber); font-family: var(--font-display); font-size: 12.5px; font-weight: 500;
}
