:root{
  /* paper */
  --paper:#f7f0dc; --paper2:#fdf8ea; --paper3:#efe6cd;
  --ink:#16130d;   --ink2:#241f16;   --ink3:#332c20;
  /* accents */
  --turf:#37703c;  --turf-dk:#255029; --turf-lt:#5d9a55;
  --gold:#d99a2b;  --brick:#a94f2c;  --violet:#5b3f8f;
  --teal:#2d7a72;  --rose:#b6456b;
  --silver:#82868f; --bronze:#a06a3a;
  /* type */
  --txt:#2c2820; --dim:#6d6454; --faint:#9a8f7c;
  --line:#ddd2b3; --line2:#c6b993;
  --r:3px;
  --shadow:6px 6px 0 var(--ink);
  --shadow-sm:4px 4px 0 var(--ink);
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  --serif:'Playfair Display',Georgia,'Times New Roman',serif;
  --sans:'Barlow',system-ui,-apple-system,sans-serif;
  /* field texture -- swapped for a light-on-dark pair in night mode */
  --field-numbers:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjkyIiBoZWlnaHQ9IjQ2Ij48dGV4dCB4PSIxMDQiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMTYxMzBkIiBmaWxsLW9wYWNpdHk9IjAuMDkiIHRleHQtYW5jaG9yPSJzdGFydCI+MTA8L3RleHQ+PHRleHQgeD0iMjkyIiB5PSIzNCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iIzE2MTMwZCIgZmlsbC1vcGFjaXR5PSIwLjA5IiB0ZXh0LWFuY2hvcj0ic3RhcnQiPjIwPC90ZXh0Pjx0ZXh0IHg9IjQ4MCIgeT0iMzQiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMxNjEzMGQiIGZpbGwtb3BhY2l0eT0iMC4wOSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4zMDwvdGV4dD48dGV4dCB4PSI2NjgiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMTYxMzBkIiBmaWxsLW9wYWNpdHk9IjAuMDkiIHRleHQtYW5jaG9yPSJzdGFydCI+NDA8L3RleHQ+PHRleHQgeD0iODU2IiB5PSIzNCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iIzE2MTMwZCIgZmlsbC1vcGFjaXR5PSIwLjA5IiB0ZXh0LWFuY2hvcj0ic3RhcnQiPjUwPC90ZXh0Pjx0ZXh0IHg9IjEwNDQiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMTYxMzBkIiBmaWxsLW9wYWNpdHk9IjAuMDkiIHRleHQtYW5jaG9yPSJzdGFydCI+NDA8L3RleHQ+PHRleHQgeD0iMTIzMiIgeT0iMzQiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiMxNjEzMGQiIGZpbGwtb3BhY2l0eT0iMC4wOSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4zMDwvdGV4dD48dGV4dCB4PSIxNDIwIiB5PSIzNCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iIzE2MTMwZCIgZmlsbC1vcGFjaXR5PSIwLjA5IiB0ZXh0LWFuY2hvcj0ic3RhcnQiPjIwPC90ZXh0Pjx0ZXh0IHg9IjE2MDgiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjMTYxMzBkIiBmaWxsLW9wYWNpdHk9IjAuMDkiIHRleHQtYW5jaG9yPSJzdGFydCI+MTA8L3RleHQ+PC9zdmc+");
  --field-lines:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NCIgaGVpZ2h0PSI0MCI+PGxpbmUgeDE9IjAuNzUiIHkxPSIwIiB4Mj0iMC43NSIgeTI9IjQwIiBzdHJva2U9IiMxNjEzMGQiIHN0cm9rZS1vcGFjaXR5PSIwLjA1IiBzdHJva2Utd2lkdGg9IjEuNSIvPjxsaW5lIHgxPSI0Ny4wIiB5MT0iNCIgeDI9IjQ3LjAiIHkyPSIxMSIgc3Ryb2tlPSIjMTYxMzBkIiBzdHJva2Utb3BhY2l0eT0iMC4wNiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iNDcuMCIgeTE9IjI5IiB4Mj0iNDcuMCIgeTI9IjM2IiBzdHJva2U9IiMxNjEzMGQiIHN0cm9rZS1vcGFjaXR5PSIwLjA2IiBzdHJva2Utd2lkdGg9IjEuNSIvPjwvc3ZnPg==");
  /* A faint fractal-noise fleck, tiled small -- the paper metaphor
     (--paper/--paper2/--paper3, the field texture above) stops short of
     actually looking like paper up close; this pushes it there without
     reading as a texture at all unless you're looking for it. Same
     light-on-dark swap in night mode as the field texture. */
  --paper-grain:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxODAnIGhlaWdodD0nMTgwJz4KPGZpbHRlciBpZD0nbic+CjxmZVR1cmJ1bGVuY2UgdHlwZT0nZnJhY3RhbE5vaXNlJyBiYXNlRnJlcXVlbmN5PScwLjg1JyBudW1PY3RhdmVzPScyJyBzdGl0Y2hUaWxlcz0nc3RpdGNoJyByZXN1bHQ9J3QnLz4KPGZlQ29sb3JNYXRyaXggaW49J3QnIHR5cGU9J21hdHJpeCcgdmFsdWVzPScwIDAgMCAwIDAuMDg2ICAwIDAgMCAwIDAuMDc1ICAwIDAgMCAwIDAuMDUxICAwIDAgMCAwLjA1IDAnLz4KPC9maWx0ZXI+CjxyZWN0IHdpZHRoPScxODAnIGhlaWdodD0nMTgwJyBmaWx0ZXI9J3VybCgjbiknLz4KPC9zdmc+");
  /* position accents */
  --pos-qb:#3f5d7d; --pos-rb:#5f7a3d; --pos-wr:#8a4a7a;
  --pos-te:#9c6b2e; --pos-k:#4a8a8a;  --pos-def:#5a4f8a;
  /* Fixed (never redefined in night mode, unlike --pos-qb etc. above,
     which get re-tuned lighter there since they're meant as colored TEXT
     against the paper background). These back a solid badge FILL instead
     -- a light-mode-dark color that stays dark/saturated enough for light
     text on top in both themes, the same --ink-fixed/--paper-fixed idea
     applied to the position palette. */
  --pos-qb-bg:#3f5d7d; --pos-rb-bg:#5f7a3d; --pos-wr-bg:#8a4a7a;
  --pos-te-bg:#9c6b2e; --pos-k-bg:#4a8a8a;  --pos-def-bg:#5a4f8a;
  /* Always the light theme's near-black, even in night mode -- for the
     handful of gold-branded chips (Premium pill, daily challenge button,
     grade stamp, etc.) whose background stays gold in both themes rather
     than inverting with the page. Pairing them with --ink instead would
     go light-on-light once night mode flips --ink to cream. */
  --ink-fixed:#16130d;
  /* The companion to --ink-fixed -- always the light theme's near-white,
     even in night mode. For the "black chip" components (Blind mode's
     card/flag/tab, the topbar's Total pill) that use --ink as a literal
     black background rather than as the page's text color: --ink itself
     flips to cream in night mode (it's tuned for text, not backgrounds),
     which turns those into glaring light chips on an otherwise dark page.
     Pairing --ink-fixed with --paper-fixed keeps them a true black chip
     with light text in both themes, same fix as --ink-fixed above. */
  --paper-fixed:#fdf8ea;
}
/* Night mode -- an explicit toggle (persisted, not just system dark mode),
   so a paper-and-ink daytime look and a dark "night game" look share every
   rule below through the same tokens instead of needing two stylesheets. */
:root[data-theme="dark"]{
  --paper:#1b1812; --paper2:#221e17; --paper3:#2a251c;
  --ink:#f3ead2;   --ink2:#e9ddc0;   --ink3:#dcccaa;
  --turf:#4a9a4f;  --turf-dk:#6bbf67; --turf-lt:#8fd488;
  --gold:#e8c157;  --brick:#e08a5c;  --violet:#9b7fd4;
  --teal:#4fb3a6;  --rose:#e07a9c;
  --silver:#b7bcc4; --bronze:#c98f56;
  /* --faint carried over the light theme's own value here at first, but
     that reads as dark-text-tuned: only 3.2-3.7:1 against this theme's
     three paper tones, under the 4.5:1 floor for the real body text it
     backs everywhere (slot-empty, pick.inelig, the empty-state icon
     added alongside this fix, activity timestamps) -- not just a
     decorative accent. Brightened to clear 4.5:1 against paper3, the
     darkest of the three it appears on, while staying visibly dimmer
     than --dim so the two-tier hierarchy (faint < dim) still reads. */
  --txt:#e9ddc0; --dim:#a89a7e; --faint:#998d6d;
  --line:#3a3324; --line2:#4a4130;
  /* Brightened for night mode -- the light-mode values are tuned for dark
     text on the paper background and go too muddy to read once that
     background turns dark (see .pos-head/.pick-pos, used on every
     position label across the board, records, and roster screens). */
  --pos-qb:#7fa8d9; --pos-rb:#9bc46a; --pos-wr:#d488c0;
  --pos-te:#c99a4a; --pos-k:#7ec4c4;  --pos-def:#9b9bdf;
  --field-numbers:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNjkyIiBoZWlnaHQ9IjQ2Ij48dGV4dCB4PSIxMDQiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4xMDwvdGV4dD48dGV4dCB4PSIyOTIiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4yMDwvdGV4dD48dGV4dCB4PSI0ODAiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4zMDwvdGV4dD48dGV4dCB4PSI2NjgiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij40MDwvdGV4dD48dGV4dCB4PSI4NTYiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij41MDwvdGV4dD48dGV4dCB4PSIxMDQ0IiB5PSIzNCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iI2YzZWFkMiIgZmlsbC1vcGFjaXR5PSIwLjEiIHRleHQtYW5jaG9yPSJzdGFydCI+NDA8L3RleHQ+PHRleHQgeD0iMTIzMiIgeT0iMzQiIGZvbnQtZmFtaWx5PSJtb25vc3BhY2UiIGZvbnQtc2l6ZT0iMjQiIGZvbnQtd2VpZ2h0PSI3MDAiIGZpbGw9IiNmM2VhZDIiIGZpbGwtb3BhY2l0eT0iMC4xIiB0ZXh0LWFuY2hvcj0ic3RhcnQiPjMwPC90ZXh0Pjx0ZXh0IHg9IjE0MjAiIHk9IjM0IiBmb250LWZhbWlseT0ibW9ub3NwYWNlIiBmb250LXNpemU9IjI0IiBmb250LXdlaWdodD0iNzAwIiBmaWxsPSIjZjNlYWQyIiBmaWxsLW9wYWNpdHk9IjAuMSIgdGV4dC1hbmNob3I9InN0YXJ0Ij4yMDwvdGV4dD48dGV4dCB4PSIxNjA4IiB5PSIzNCIgZm9udC1mYW1pbHk9Im1vbm9zcGFjZSIgZm9udC1zaXplPSIyNCIgZm9udC13ZWlnaHQ9IjcwMCIgZmlsbD0iI2YzZWFkMiIgZmlsbC1vcGFjaXR5PSIwLjEiIHRleHQtYW5jaG9yPSJzdGFydCI+MTA8L3RleHQ+PC9zdmc+");
  --field-lines:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5NCIgaGVpZ2h0PSI0MCI+PGxpbmUgeDE9IjAuNzUiIHkxPSIwIiB4Mj0iMC43NSIgeTI9IjQwIiBzdHJva2U9IiNmM2VhZDIiIHN0cm9rZS1vcGFjaXR5PSIwLjA3IiBzdHJva2Utd2lkdGg9IjEuNSIvPjxsaW5lIHgxPSI0Ny4wIiB5MT0iNCIgeDI9IjQ3LjAiIHkyPSIxMSIgc3Ryb2tlPSIjZjNlYWQyIiBzdHJva2Utb3BhY2l0eT0iMC4wOCIgc3Ryb2tlLXdpZHRoPSIxLjUiLz48bGluZSB4MT0iNDcuMCIgeTE9IjI5IiB4Mj0iNDcuMCIgeTI9IjM2IiBzdHJva2U9IiNmM2VhZDIiIHN0cm9rZS1vcGFjaXR5PSIwLjA4IiBzdHJva2Utd2lkdGg9IjEuNSIvPjwvc3ZnPg==");
  --paper-grain:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxODAnIGhlaWdodD0nMTgwJz4KPGZpbHRlciBpZD0nbic+CjxmZVR1cmJ1bGVuY2UgdHlwZT0nZnJhY3RhbE5vaXNlJyBiYXNlRnJlcXVlbmN5PScwLjg1JyBudW1PY3RhdmVzPScyJyBzdGl0Y2hUaWxlcz0nc3RpdGNoJyByZXN1bHQ9J3QnLz4KPGZlQ29sb3JNYXRyaXggaW49J3QnIHR5cGU9J21hdHJpeCcgdmFsdWVzPScwIDAgMCAwIDAuOTUzICAwIDAgMCAwIDAuOTE4ICAwIDAgMCAwIDAuNzkyICAwIDAgMCAwLjA2IDAnLz4KPC9maWx0ZXI+CjxyZWN0IHdpZHRoPScxODAnIGhlaWdodD0nMTgwJyBmaWx0ZXI9J3VybCgjbiknLz4KPC9zdmc+");
}
*{box-sizing:border-box;margin:0;padding:0}
html,body{height:100%}
body{
  background-color:var(--paper);
  background-image: var(--paper-grain), var(--field-numbers), var(--field-lines);
  background-repeat:repeat, repeat-x, repeat;
  background-position:top left, top left, top left;
  /* 17px, not the usual 16px default -- every size on the page is set in
     rem, so this one change scales the whole site (including every
     clamp() range, which stays proportionally correct since both ends
     of each clamp are rem-relative to this same root) rather than
     needing to individually bump the dozens of small mono UI labels the
     retro theme leans on for almost everything. */
  color:var(--txt); font-family:var(--sans); font-size:17px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--turf-dk);text-underline-offset:3px}
/* One themed keyboard-focus ring for the whole site -- every button,
   link and input here was leaning on the browser's own bare default
   outline, which is still genuinely visible (nothing anywhere resets
   outline:none), just visually inconsistent with everything else on
   the page. :focus-visible rather than :focus specifically so this
   only shows for keyboard/programmatic focus, not a mouse click --
   every button already has its own ink-shadow press feedback for
   pointer interaction, and a ring flashing on every click would fight
   with that instead of adding anything. Outline automatically follows
   each element's own border-radius (a pill button gets a rounded
   ring, a card gets a square one) with no extra rule needed. */
:focus-visible{outline:3px solid var(--turf);outline-offset:2px}
/* Everything else on the page is fully custom -- an OS-default scrollbar
   was the one remaining element that never matched. Firefox's two-value
   shorthand first, since it has no equivalent of the finer-grained
   ::-webkit- pseudo-elements below. */
*{scrollbar-color:var(--line2) var(--paper2); scrollbar-width:thin}
::-webkit-scrollbar{width:12px;height:12px}
::-webkit-scrollbar-track{background:var(--paper2)}
::-webkit-scrollbar-thumb{
  background:var(--line2);border:3px solid var(--paper2);border-radius:var(--r);
}
::-webkit-scrollbar-thumb:hover{background:var(--dim)}
::-webkit-scrollbar-corner{background:var(--paper2)}
.screen{display:none;min-height:100vh}
/* display:none -> block re-enters the element into the render tree, which
   restarts a CSS animation from scratch -- so this one small rule gives
   every screen switch (home, a round, results, the hub, a profile...) the
   same gentle settle-in, one shared touch of polish instead of one-off
   animations scattered per screen. */
.screen.active{display:block;animation:screen-in .32s ease-out both}
@keyframes screen-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){
  .screen.active{animation:none}
}

/* ---------- skeleton loading placeholders ---------- */
/* Used wherever a card used to just stay hidden (or show plain "Loading…"
   text) while its first fetch is in flight -- a shimmering placeholder
   shape reads as faster and more finished than either blank space or a
   line of text, and gives an early sense of the content's actual layout. */
@keyframes skeleton-shimmer{0%{background-position:-135% 0}100%{background-position:135% 0}}
.skeleton{
  background-image:linear-gradient(90deg, var(--paper3) 25%, var(--line) 50%, var(--paper3) 75%);
  background-size:250% 100%;animation:skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius:var(--r);
}
@media (prefers-reduced-motion:reduce){
  .skeleton{animation:none;background-image:none;background-color:var(--paper3)}
}
.skeleton-line{height:.8em}
.skeleton-line + .skeleton-line{margin-top:.55em}
.skeleton-line.medium{width:70%}
.skeleton-line.full{width:100%}
/* The draft board's own skeleton (see boardSkeletonHtml) -- .pick-pos
   is normally just plain text with no box shape of its own, so its
   skeleton stand-in needs an explicit size the real element doesn't. */
.pick-skeleton{cursor:default}
.pick-pos-skeleton{width:34px;height:1.4rem;flex:none;border-radius:4px}
/* A brief highlight for a moment that just happened -- a friend landing
   on the list, a score actually posting -- rather than only a change in
   text that's easy to miss, especially on a small screen. Plays once on
   class-add and settles back to nothing; never re-added just to redraw
   the same state (see submitScore's own comment on why). */
@keyframes flash-confirm{
  0%{background-color:color-mix(in srgb, var(--turf) 30%, transparent)}
  100%{background-color:transparent}
}
.flash-confirm{animation:flash-confirm 1.1s ease-out}
@media (prefers-reduced-motion:reduce){ .flash-confirm{animation:none} }
.confirm-check{width:.8em;height:.8em;color:var(--turf-dk);margin-right:.3em;vertical-align:-.08em}

/* shared type helpers */
.on-clock,.dial-lbl,.pos-head,.hero-badge,.final-badge,.topbar-score .lbl,.slot-tag,.fr-tag,.pick-pos{
  font-family:var(--mono);text-transform:uppercase;letter-spacing:.16em;
}

/* ---------- buttons ---------- */
.btn{
  font-family:var(--mono); font-size:.8rem; font-weight:700; letter-spacing:.14em;
  text-transform:uppercase; border:2px solid var(--ink); border-radius:var(--r);
  padding:.8rem 1.5rem; cursor:pointer; box-shadow:var(--shadow-sm);
  transition:transform .1s, box-shadow .1s, background .12s, color .12s;
}
.btn:hover{transform:translate(2px,2px);box-shadow:2px 2px 0 var(--ink)}
.btn:active{transform:translate(4px,4px);box-shadow:0 0 0 var(--ink)}
.btn-primary{background:var(--turf);color:var(--paper2)}
.btn-primary:hover{background:var(--turf-dk)}
.btn-ghost{background:var(--paper2);color:var(--ink)}
.btn-ghost:hover{background:var(--paper3)}
.btn-danger{background:var(--brick);color:var(--paper2)}
.btn-danger:hover{background:color-mix(in srgb, var(--brick) 80%, black)}
.btn-lg{
  padding:2.4rem 2rem;font-size:clamp(1.4rem,5vw,2rem);letter-spacing:.16em;
  border-width:4px;box-shadow:12px 12px 0 var(--ink);width:100%;
}
/* The soft blurred shadow layered alongside the existing hard offset one
   is additive, not a replacement -- every button on the page already
   presses down on hover (a smaller shift than :active, same direction),
   and reversing that just for the mode cards would read as an
   inconsistent, contradictory affordance. The glow adds tactility on
   top of that established language instead of fighting it. */
.btn-lg:hover{transform:translate(4px,4px);box-shadow:8px 8px 0 var(--ink), 0 10px 26px rgba(0,0,0,.3)}
.btn-lg:active{transform:translate(8px,8px);box-shadow:4px 4px 0 var(--ink)}

/* ---------- ads ---------- */
/* Ezoic's own destroyAll() is requested for a Premium account that
   turns out to have already had ads requested (see app.js's
   maybeShowAds()), but it doesn't reliably tear down an ad Google Ad
   Manager has already rendered into one of these id-less placeholder
   divs -- confirmed live: the underlying iframe stays in the DOM and
   visible even after destroyAll() runs. Hiding the placeholder itself
   is the actual guarantee, independent of whether Ezoic's API call
   worked: every one of these divs starts hidden, and only the
   html.ez-show-ads state (set the moment a session is known, or
   guessed to be, non-Premium -- see the inline script in <head> and
   maybeShowAds()) reveals it. A Premium account that guessed wrong for
   a moment before its real status resolved goes back to hidden here,
   this time for real. */
[id^="ezoic-pub-ad-placeholder"]{display:none !important}
html.ez-show-ads [id^="ezoic-pub-ad-placeholder"]{display:block !important}
/* The bottom sticky "Adhesion" anchor ad is a second, separate ad format
   -- confirmed live on the real Premium account: it's injected by Google
   Ad Manager as a direct child of <html> (position:fixed, z-index at the
   max 32-bit int), entirely outside the ezoic-pub-ad-placeholder divs
   above, so hiding those never touched it. data-anchor-status is
   Google's own marker attribute for this ad format, present regardless
   of the ad unit's own generated id/path -- a stable selector where the
   id itself (it embeds a per-page hash) isn't. Inline styles set the
   iframe chain's own display without !important, so this still wins
   even though Google's anchor-ad script re-applies its inline style on
   scroll/resize. */
[data-anchor-status]{display:none !important}
html.ez-show-ads [data-anchor-status]{display:block !important}
/* A third, separate ad format again found live on the real Premium
   account: Ezoic's own sticky outstream video unit, injected straight
   into <body> as .ez-video-outstream-wrap > ... > .ez-video-container
   (an "ez-stuck ez-outstream-container ez-multiformat-ad" div), not a
   Google anchor ad and not one of the ezoic-pub-ad-placeholder divs
   either. Matched on class substrings rather than the exact combination
   Ezoic happened to apply, so a future variant with an extra class or
   two still gets caught. */
[class*="ez-video"],[class*="outstream"]{display:none !important}
html.ez-show-ads [class*="ez-video"],html.ez-show-ads [class*="outstream"]{display:block !important}

/* ---------- account ---------- */
.link-btn{
  background:none;border:0;padding:0;cursor:pointer;font-family:var(--mono);
  font-size:.68rem;font-weight:700;letter-spacing:.05em;color:var(--turf-dk);
  text-decoration:underline;text-underline-offset:2px;
}
.link-btn:hover{color:var(--turf)}
/* The banner pill has its own color/shape already -- an underline meant
   for plain text links would just fight it. */
.banner-edit-btn{text-decoration:none;vertical-align:middle;display:inline-flex;align-items:center;gap:.35rem}
/* Only present until a real banner is saved (see renderAccountBar) --
   the underline .banner-edit-btn just turned off above is exactly what
   the rest of this row's links use, so this still reads as clickable. */
.banner-edit-cta{text-decoration:underline;text-underline-offset:2px}
/* 1440px, not 1280 -- the home screen's four stacked containers
   (this, .whats-new, .start-layout, .hero-modes) all share the same
   max-width so their edges line up as one column; bumped together from
   1280 since on a wide monitor the old value left a very wide, plain
   strip of background on either side doing nothing. .start-layout's own
   grid-template-columns is widened by the matching amount below --
   .hero-modes' mode-quad grid already uses fr units, so it just
   stretches to fill the new width on its own. */
/* Sticky (not static) so the nav/account row stays reachable while the
   rest of the home screen scrolls, without the footprint of a full
   side rail (tried and reverted; see git history).
   Split into two elements now: .account-bar-row (the actual flex
   content) kept its own max-width:1440px;margin:0 auto for centering,
   which meant putting the sticky positioning/background/shadow
   directly on it left bare page-background gutters on either side on
   any screen wider than 1440px -- a full-bleed sticky bar with visible
   seams on a wide monitor doesn't read as "part of the page." Those
   properties live on this new unconstrained wrapper instead, so the
   background/shadow/stripe genuinely span edge to edge while the
   content inside still centers at the same 1440px it always has.
   Background matches the page's own --paper (not the lighter --paper2
   the pills themselves use) so there's no visible color step where the
   sticky bar meets the content scrolling underneath it. z-index:30 --
   audited against every overlay on the page (modals @80, badge/elite/
   luck toasts @94-96, the loading screen and confetti layer @60/@70):
   all of them sit comfortably above this. */
.account-bar-outer{
  position:sticky;top:0;z-index:30;background:var(--paper);
  transition:box-shadow .2s ease;
}
.account-bar-row{
  max-width:1440px;margin:0 auto;padding:.65rem 1.25rem .6rem;
  display:flex;flex-wrap:wrap;justify-content:flex-end;align-items:center;gap:.6rem .9rem;
}
/* A scaled-down copy of the page's own .endzone-stripe motif, opacity-
   faded in only once .is-scrolled (below) -- at the very top of the
   page, .endzone-stripe itself already sits directly above this bar,
   so showing this copy at the same time read as the same diagonal
   stripe appearing twice in quick succession. Fading it in exactly
   when .endzone-stripe would have scrolled out of view means there's
   only ever one stripe visible at once: the real one at rest, this
   one once scrolled. */
.account-bar-outer::after{
  content:'';position:absolute;left:0;right:0;bottom:0;height:4px;
  background:repeating-linear-gradient(-45deg,var(--turf) 0 8px,var(--gold) 8px 16px);
  border-bottom:2px solid var(--ink);
  opacity:0;transition:opacity .2s ease;
}
/* Toggled by app.js once the page has scrolled past y=0. A flat,
   no-blur shadow straight down -- not the soft blurred elevation
   shadows a typical app sticky-header would use -- so it reads as the
   same hard-offset shadow language every button/card on this page
   already uses, rather than a one-off effect imported from elsewhere.
   Only appears once there's actually content to sit above; flush with
   no shadow at the top of the page like it always was. */
.account-bar-outer.is-scrolled{
  box-shadow:0 4px 0 var(--ink);
}
.account-bar-outer.is-scrolled::after{opacity:1}
/* Desktop only -- below 600px these six already get the exact same pill
   treatment from the grid layout further down, just full-width and
   stacked instead of inline. Above that breakpoint they used to be bare
   underlined text sitting next to GOAT Lab/Go Premium's colored pills
   and the theme toggle's own circular chip, so the row read as three
   unrelated visual languages mashed onto one line instead of one
   deliberate header. A neutral pill (paper background, ink border) reads
   as "informational nav" at a glance, distinct from the colored CTAs
   (brick, gold) and the sentence-style account-bar text that follows.
   ID selectors, not the shared .link-btn class -- "Log in / Sign up" and
   the rest of #account-bar's signed-in sentence reuse that same class
   there, and pill-ing those too would read as inconsistent with the
   middle-dot-separated text around them. #btn-history-nav belongs in
   this list -- it's the same kind of plain nav link as the other six,
   just added later. */
@media(min-width:601px){
  #btn-founders, #btn-records, #btn-shame, #btn-stats, #btn-badges, #btn-leagues, #btn-history-nav{
    background:var(--paper2);border:2px solid var(--ink);border-radius:999px;color:var(--ink);
    padding:.45rem .9rem;text-decoration:none;box-shadow:2px 2px 0 var(--ink);
    font-size:.7rem;transition:.12s;
  }
  #btn-founders:hover, #btn-records:hover, #btn-shame:hover, #btn-stats:hover, #btn-badges:hover, #btn-leagues:hover, #btn-history-nav:hover{
    transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink);background:var(--paper3);color:var(--ink);
  }
  #btn-founders:active, #btn-records:active, #btn-shame:active, #btn-stats:active, #btn-badges:active, #btn-leagues:active, #btn-history-nav:active{
    transform:translate(2px,2px);box-shadow:0 0 0 var(--ink);
  }
  /* One accent color per pill, reusing the exact colors the hero-modes
     buttons just below use for Classic/Auction/Playoff/Team/Daily/Blind
     -- lets the eye tell these six-plus-one pills apart at a glance
     instead of one undifferentiated row of identical paper chips, drawn
     from the same palette that's already visible on this screen rather
     than inventing new colors just for this row. */
  .topbar-icon{
    display:inline-block;width:.8rem;height:.8rem;vertical-align:-1px;
    margin-right:.4rem;flex:none;transition:transform .12s ease;
  }
  #btn-founders .topbar-icon{background-color:var(--gold)}
  #btn-records .topbar-icon{background-color:var(--turf-dk)}
  #btn-shame .topbar-icon{background-color:var(--brick)}
  #btn-stats .topbar-icon{background-color:var(--teal)}
  #btn-badges .topbar-icon{background-color:var(--violet)}
  #btn-leagues .topbar-icon{background-color:var(--turf)}
  #btn-history-nav .topbar-icon{background-color:var(--ink-fixed)}
  /* GOAT Lab's own icon takes its color from the pill's text (paper2 on
     a brick background) rather than joining the accent-per-item system
     above -- it's the one pill in this row that isn't part of that
     informational-nav set. */
  .goat-lab-pill .topbar-icon{background-color:var(--paper2)}
  /* A small scale-up on the icon itself, on top of whatever the pill's
     own hover state already does -- one shared rule via .link-btn
     (every plain nav pill's class) rather than repeating all seven IDs
     again, plus GOAT Lab's own class since it isn't one of those. Same
     tactile language the site's buttons already use elsewhere (a
     press/hover nudge), just applied to the icon instead of the whole
     pill, which already has its own hover treatment. */
  .account-bar-row .link-btn:hover .topbar-icon,
  .goat-lab-pill:hover .topbar-icon{transform:scale(1.15)}
  /* A bit of daylight between the plain nav pills and the colored CTA
     pills that follow -- margin-left on top of the row's own gap, not a
     replacement for it, so the two clusters read as separate groups
     instead of one long undifferentiated run of pills. A plain gap on
     its own can read as an accidental wrapping gap rather than a
     deliberate one, so ::before adds a real vertical rule centered in
     that space -- position:relative on the pill itself just gives that
     pseudo-element something to anchor to. */
  .goat-lab-pill{margin-left:.5rem;position:relative}
  .goat-lab-pill::before{
    content:'';position:absolute;left:-.7rem;top:50%;transform:translateY(-50%);
    width:2px;height:1.3rem;background:var(--ink);opacity:.2;
  }
  /* Desktop has room for all seven secondary nav links inline, so the
     wrapper the mobile collapse below needs (#nav-secondary) and its
     own toggle button stay fully out of the way here: display:contents
     unwraps the div entirely, so its children lay out exactly as if
     they were direct children of .account-bar-row, identically to
     before this wrapper existed. */
  .nav-toggle-btn{display:none}
  .nav-secondary{display:contents}
}
/* margin-right:auto (on the button now, not the bare image -- see
   #btn-logo-top below) eats the row's free space right after this item,
   pinning it to the left edge while everything else -- already
   justify-content:flex-end -- stays clustered at the right, same trick
   used to split a flex row into two groups without restructuring it. */
.site-logo{width:56px;height:auto;display:block}
/* Wraps the logo so it's an actual control (scrolls to top -- see
   app.js) rather than decoration; reset to plain and undecorated so it
   doesn't look or behave like the pill buttons around it, just holds
   the image and eats the row's free space per the comment above. */
.logo-btn{background:none;border:0;padding:0;cursor:pointer;margin-right:auto;line-height:0}
/* Below 600px the desktop trick above (logo pinned left, everything else
   clustered right) just wraps into a ragged, mismatched pile -- plain
   underlined text links jammed next to pill buttons with no grouping.
   Centering the logo on its own row and giving the plain nav links the
   same pill treatment as GOAT Lab / Go Premium makes the whole row read
   as one consistent button group instead. */
/* A real grid instead of flex-wrap's natural (and uneven) wrapping --
   flex-wrap here left "Founding Members" alone on its own line, then
   a ragged 3/3/1 pile below it, since wrapping only ever depends on
   each label's own text width. A named-area grid lays every button
   out on purpose: the four nav links as an even 2-column block, Go
   Premium promoted to its own full-width row since it's the one
   button here actually worth someone tapping, GOAT Lab/theme paired
   underneath, sign-in state last. No HTML changes needed -- every
   item just gets assigned to a named area by its existing id/class. */
@media(max-width:600px){
  /* Cancels the sticky positioning .account-bar-outer picks up
     unconditionally above -- that's tuned for the compact single-row
     desktop bar; this multi-row stacked grid is tall enough that
     pinning it would eat a big share of a phone screen permanently.
     relative, not static -- ::after (the bottom stripe accent) is
     absolutely positioned against the outer wrapper, and static would
     push that positioning out to the next positioned ancestor up the
     tree instead. */
  .account-bar-outer{position:relative}
  .account-bar-row{
    display:grid;grid-template-columns:1fr 1fr;gap:.55rem;padding-top:.9rem;
    grid-template-areas:
      "logo     logo"
      "toggle   toggle"
      "nav      nav"
      "premium  premium"
      "goat     theme"
      "account  account";
  }
  .logo-btn{grid-area:logo;justify-self:center;margin:0 0 .3rem}
  .site-logo{width:56px;max-width:56px}
  /* The seven secondary nav destinations (Founding Members through
     Draft History) used to all sit here, always visible, every one of
     them competing with the actual game-mode buttons and hero content
     for the first screen of a phone visit. Collapsed behind this
     toggle instead -- everyone still reaches every page in one tap,
     just not automatically pushed past all of it before they've seen
     what the site actually does. Desktop has room to show all seven
     inline and never renders this button at all (see the
     min-width:601px block above). */
  .nav-toggle-btn{
    grid-area:toggle;display:block;width:100%;
    background:var(--paper2);border:2px solid var(--ink);border-radius:999px;color:var(--ink);
    font-family:var(--mono);font-weight:700;font-size:.72rem;letter-spacing:.04em;
    padding:.5rem .6rem;box-shadow:2px 2px 0 var(--ink);cursor:pointer;text-align:center;
  }
  .nav-toggle-btn:active{transform:translate(2px,2px);box-shadow:0 0 0 var(--ink)}
  /* Collapsed to nothing by default (grid-area:nav then contributes no
     height at all) -- .expanded (toggled in app.js) switches it to the
     same 2-up pill grid these seven used to occupy unconditionally. */
  .nav-secondary{grid-area:nav;display:none;grid-template-columns:1fr 1fr;gap:.55rem}
  .nav-secondary.expanded{display:grid}
  #btn-founders, #btn-records, #btn-shame, #btn-stats, #btn-badges, #btn-leagues, #btn-history-nav{
    background:var(--paper2);border:2px solid var(--ink);border-radius:999px;color:var(--ink);
    padding:.5rem .6rem;text-decoration:none;box-shadow:2px 2px 0 var(--ink);
    text-align:center;font-size:.72rem;
  }
  /* Seven items don't split evenly into two columns -- the odd one out
     spans both, same full-width treatment it already had before this
     was collapsible. */
  #btn-history-nav{grid-column:1 / -1}
  .goat-lab-pill{grid-area:goat;text-align:center}
  #btn-theme{grid-area:theme;justify-self:center}
  .account-bar{grid-area:account;margin-top:.2rem}
  /* ID selector -- the unconditional .account-bar{text-align:right} a
     little further down in this file would otherwise win this specific
     property at equal class-only specificity regardless of viewport,
     same trap hit twice already this session for other mobile overrides. */
  #account-bar{text-align:center}
  .go-premium-pill{grid-area:premium}
  /* Bigger than its desktop size, and now its own full-width row
     instead of one pill lost among six others -- the single button in
     this row actually worth a tap gets to look like it. ID selector,
     not just the class -- the unconditional .go-premium-pill definition
     is declared later in this file and would otherwise win the
     font-size/padding/box-shadow tug-of-war at equal class-only
     specificity regardless of viewport (confirmed this was silently
     losing even before this rewrite -- same trap hit repeatedly this
     session, worth double-checking for every mobile override from
     here on rather than trusting a screenshot alone). */
  #btn-premium-hero{
    font-size:.95rem;padding:.8rem 1rem;border-width:3px;
    text-align:center;box-shadow:3px 3px 0 var(--ink);
  }
  #btn-premium-hero:hover{box-shadow:1.5px 1.5px 0 var(--ink)}
}
.whats-new{
  max-width:1440px;margin:.8rem auto 0;padding:.65rem 1.1rem;
  background:var(--paper2);border:2px solid var(--gold);border-radius:var(--r);
  display:flex;align-items:center;justify-content:space-between;gap:.8rem;
  font-family:var(--mono);font-size:.75rem;color:var(--ink);
}
.whats-new[hidden]{display:none}
.whats-new-close{background:none;border:0;cursor:pointer;font-size:1.1rem;line-height:1;color:var(--dim);flex:none}
.whats-new-close:hover{color:var(--ink)}
.whats-new-actions{display:flex;align-items:center;gap:.7rem;flex:none}
/* This Week / Today's Highest Scores -- two more .lb-panel cards, sitting
   at the bottom of the home screen, below "How it works" and just above
   the legal footer, rather than competing with the trivia/wordmark/
   leaderboard row for attention up top. margin-bottom:0 overrides
   .lb-panel's own 2rem (same specificity, this rule just comes later)
   since .disclaimer's own margin-top handles the gap before the footer
   instead. */
.home-highlights{
  max-width:1440px;margin:1.6rem auto;padding:0 1.25rem;
  display:grid;grid-template-columns:1fr 1fr;gap:1.4rem;
}
.home-highlight-card{margin-bottom:0}
/* Small colored accent -- top border + a title icon -- on the two
   home-highlights cards, matching the treatment #home-lb/#home-trivia
   already carry in the sidebar (see below). Before this, these two were
   the only plain, uncolored panels on an otherwise icon- and color-heavy
   home screen. */
#weekly-digest{border-top-width:5px;border-top-color:var(--turf-dk)}
#community-spotlight{border-top-width:5px;border-top-color:var(--gold)}
.highlight-title-icon{width:.8rem;height:.8rem;margin-right:.4rem;vertical-align:-.1em;flex:none}
#weekly-digest-content .lb-board-title .icon-glyph{background-color:var(--turf-dk)}
#spotlight-content .lb-board-title .icon-glyph{background-color:var(--gold)}
/* Same trophy the Leaderboards hub heading itself now wears (see
   .lbhub-heading-icon) -- a small visual callback from this home-screen
   widget to the full hub it links out to. turf-dk to match #home-lb's
   own top border, already that color. */
#home-lb-content .lb-board-title .icon-glyph{background-color:var(--turf-dk)}
.highlight-line{font-family:var(--mono);font-size:.82rem;line-height:1.6;color:var(--ink2);margin:0 0 .4rem}
.highlight-line:last-child{margin-bottom:0}
.highlight-line b{color:var(--ink)}
.spotlight-dots{display:flex;gap:.4rem;margin-top:.9rem}
.spotlight-dot{width:.4rem;height:.4rem;border-radius:50%;background:var(--line2)}
.spotlight-dot.active{background:var(--turf-dk)}
@media(max-width:760px){
  .home-highlights{grid-template-columns:1fr}
}
/* A half-step up in size/tracking from the base .7rem/normal-spacing
   this used to run at, now that it sits in a sticky, always-visible
   bar rather than one that scrolls out of view -- text that's on
   screen the whole time earns slightly more presence than text that
   was only ever glanced at once per visit. */
.account-bar{
  text-align:right;font-family:var(--mono);font-size:.72rem;letter-spacing:.01em;color:var(--dim);
}
.account-bar .sep{margin:0 .4rem;color:var(--faint)}
.account-bar b{color:var(--ink)}
/* Sets "Signed in as X" apart from the sep-separated run of action
   links that follows it -- who you are and what you can do next are
   two different kinds of information, and a small chip (rather than
   just more plain sentence text) makes that split visible instead of
   something the reader has to parse out of one long line. */
.account-identity{
  display:inline-block;background:var(--paper3);border:1px solid var(--line2);
  border-radius:999px;padding:.25rem .75rem;margin-right:.5rem;
}
.premium-badge{
  font-family:var(--mono);font-weight:700;font-size:.64rem;letter-spacing:.04em;color:var(--gold);
}
.theme-toggle{
  background:var(--paper2);border:2px solid var(--ink);border-radius:999px;color:var(--ink);
  font-size:.85rem;line-height:1;width:2rem;height:2rem;cursor:pointer;
  display:grid;place-items:center;box-shadow:2px 2px 0 var(--ink);transition:.12s;flex:none;
}
.theme-toggle:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink);background:var(--paper3)}
.theme-toggle:active{transform:translate(2px,2px);box-shadow:0 0 0 var(--ink)}
/* Slightly bigger than the theme toggle/plain nav pills around it, plus
   a soft gold glow outside its own ink border -- this is the one button
   in the row that actually makes money, so it earns a touch more visual
   weight than GOAT Lab (a plain colored pill) or the six informational
   nav pills, without going as far as a hero-sized banner that would be
   too loud for something shown on every single visit. Hidden outright
   once the visitor already has Premium (see renderAccountBar in app.js),
   same as the account bar's own link. */
.go-premium-pill{
  position:relative;overflow:hidden;
  display:flex;flex-direction:column;align-items:center;gap:.05rem;
  font-family:var(--mono);font-weight:700;font-size:.72rem;letter-spacing:.05em;
  background:var(--gold);border:2px solid var(--ink);border-radius:999px;color:var(--ink-fixed);
  padding:.4rem .95rem;cursor:pointer;transition:.12s;flex:none;
  white-space:nowrap;
  box-shadow:2px 2px 0 var(--ink), 0 0 0 3px color-mix(in srgb, var(--gold) 30%, transparent);
}
/* The main line is the existing pill label unchanged; this just adds a
   second, smaller line under it -- "no ads" already lived in the title
   tooltip, but a tooltip only reaches someone who happens to hover, and
   removing ads is exactly the perk a free (ad-seeing) visitor is best
   positioned to want the moment they notice it. */
.go-premium-sub{font-size:.56rem;font-weight:600;letter-spacing:.03em;opacity:.8}
.go-premium-pill:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink), 0 0 0 3px color-mix(in srgb, var(--gold) 30%, transparent);background:#c98a20}
.go-premium-pill:active{transform:translate(2px,2px);box-shadow:0 0 0 var(--ink)}
.go-premium-pill[hidden]{display:none}
/* A gold-foil shimmer sweep on hover -- a fixed white streak rather
   than a themed color, since this is meant to read as light catching
   foil, not a UI accent that should flip with the page's own theme. */
.go-premium-pill::after{
  content:'';position:absolute;top:0;left:-75%;width:45%;height:100%;
  background:linear-gradient(115deg, transparent, rgba(255,255,255,.7), transparent);
  transform:skewX(-20deg);pointer-events:none;
  transition:left .55s ease;
}
.go-premium-pill:hover::after{left:125%}
@media (prefers-reduced-motion:reduce){ .go-premium-pill::after{transition:none;display:none} }
.goat-lab-pill{
  font-family:var(--mono);font-weight:700;font-size:.66rem;letter-spacing:.05em;
  background:var(--brick);border:2px solid var(--ink);border-radius:999px;color:var(--paper2);
  padding:.4rem .8rem;cursor:pointer;box-shadow:2px 2px 0 var(--ink);transition:.12s;flex:none;
  white-space:nowrap;text-decoration:none;display:inline-block;
}
.goat-lab-pill:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink);filter:brightness(.85)}
.goat-lab-pill:active{transform:translate(2px,2px);box-shadow:0 0 0 var(--ink)}

.auth-modal[hidden]{display:none}
.auth-modal{
  position:fixed;inset:0;background:rgba(22,19,13,.55);z-index:80;
  display:flex;align-items:center;justify-content:center;padding:1.25rem;
}
.auth-card{
  position:relative;width:100%;max-width:360px;background:var(--paper2);
  border:3px solid var(--ink);border-radius:var(--r);box-shadow:var(--shadow);
  padding:2rem 1.6rem 1.6rem;text-align:left;
}
.auth-close{
  position:absolute;top:.7rem;right:.8rem;background:none;border:0;cursor:pointer;
  font-size:1.4rem;line-height:1;color:var(--dim);font-family:var(--sans);
  transition:color .12s ease,transform .1s ease;
}
.auth-close:hover{color:var(--ink);transform:scale(1.15)}
.auth-close:active{transform:scale(.9)}
.auth-title{font-family:var(--serif);font-weight:800;font-size:1.5rem;margin-bottom:1.2rem;color:var(--ink)}
.confirm-modal-actions{display:flex;gap:.7rem;margin-top:1rem}
.confirm-modal-actions .btn{flex:1}
/* Reuses the same .auth-card shell every other modal on the page uses
   (auth, roster, etc.) rather than a one-off dialog just for this --
   max-height + scroll is the only addition, since this list is the one
   modal content here with no natural upper bound on length as more
   entries get added over time. */
.changelog-list{
  list-style:none;margin:0;padding:0;max-height:min(60vh,26rem);overflow-y:auto;
  font-family:var(--mono);font-size:.78rem;line-height:1.55;color:var(--ink2);
}
.changelog-list li{padding:.7rem 0;border-bottom:1px solid var(--line)}
.changelog-list li:last-child{border-bottom:0}
.changelog-list b{color:var(--ink)}
/* Monday's weekly recap popup (see maybeShowWeeklyRecap) -- a big
   number for the one stat worth leading with (last week's best score),
   plain mono lines underneath for rank/streak context. */
.weekly-recap-body{text-align:left;margin:.3rem 0 1.3rem}
.weekly-recap-stat{font-family:var(--serif);font-weight:900;font-size:2.4rem;color:var(--gold);line-height:1;letter-spacing:-.02em;margin-bottom:.2rem}
.weekly-recap-stat small{font-family:var(--mono);font-weight:600;font-size:.62rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);margin-left:.5rem}
.weekly-recap-line{font-family:var(--mono);font-size:.82rem;color:var(--ink2);line-height:1.6}
.weekly-recap-line b{color:var(--ink)}
.auth-card .lb-name-input{width:100%;display:block;margin-bottom:.7rem}
/* display:block above outranks the plain [hidden] UA rule (two classes
   beat one attribute selector), so a field this modal hides -- display
   name outside signup, or the whole form during a Google/guest name
   prompt -- would otherwise still render. */
.auth-card .lb-name-input[hidden]{display:none}
/* Right-aligned, pulled up into the gap .auth-password's own
   margin-bottom already leaves -- the conventional spot for this exact
   link on just about every login form, so it reads as familiar rather
   than needing its own explanation. Only ever visible in login mode
   (see openAuthModal) -- signup/claim/guest have no existing password
   to reset. */
.auth-forgot-link{display:block;text-align:right;margin:-.3rem 0 .9rem}
.auth-submit{width:100%;margin-top:.3rem}
.btn-google{
  display:flex;align-items:center;justify-content:center;gap:.6rem;
  background:#fff;color:#3c4043;border-color:var(--ink);
}
.btn-google:hover{background:#f7f7f7}
.google-g{flex:none}
.auth-divider{
  display:flex;align-items:center;gap:.7rem;margin:1rem 0;
  font-family:var(--mono);font-size:.68rem;color:var(--dim);text-transform:uppercase;letter-spacing:.1em;
}
.auth-divider::before,.auth-divider::after{content:'';flex:1;height:1px;background:var(--line)}
.auth-card .btn-google[hidden],.auth-card .auth-divider[hidden]{display:none}
.challenge-modal-copy{font-family:var(--mono);font-size:.78rem;color:var(--dim);line-height:1.5;margin-bottom:1.1rem}
.gift-toggle-btn{display:block;margin-top:.6rem;margin-bottom:.8rem;font-size:.74rem}
.gift-row{margin-bottom:.8rem}
.gift-row-hint{font-family:var(--mono);font-size:.68rem;color:var(--dim);margin-top:-.3rem}
/* Sits right under the purchase button -- the one moment someone's
   deciding whether to trust the site with a card, so the reassurance
   belongs at the point of doubt, not buried in Terms/Privacy. Wording
   mirrors privacy.html's own claim exactly, not a separate promise. */
.premium-trust{
  display:flex;align-items:center;justify-content:center;gap:.4rem;
  font-family:var(--mono);font-size:.66rem;color:var(--faint);margin-top:.7rem;text-align:center;
}
.premium-trust .icon-glyph{width:.75rem;height:.75rem;background-color:var(--faint);flex:none}
/* One-line contextual hook, shown only when openPremiumModal was given a
   reason (see PREMIUM_MODAL_REASONS in app.js) -- hidden entirely for the
   generic hero-pill entry point, which still gets just the plain compare
   table below. Bold serif to read as a headline, not another line of the
   modal's own mono body copy. */
.premium-modal-reason{
  font-family:var(--serif);font-weight:800;font-size:1.05rem;color:var(--turf-dk);
  margin-bottom:1rem;line-height:1.35;
}
.premium-modal-reason[hidden]{display:none}
/* Free-vs-Premium comparison grid, replacing what used to be a plain
   starred perks list -- a side-by-side makes the actual gap concrete
   instead of asking a reader to hold nine bullet points in their head
   and guess how many of them they're currently missing. */
.premium-compare{
  display:grid;grid-template-columns:1fr 3.6rem 4.2rem;
  font-family:var(--mono);font-size:.7rem;margin-bottom:1.1rem;
  border:2px solid var(--ink);border-radius:var(--r);overflow:hidden;
  box-shadow:3px 3px 0 var(--ink);
}
.premium-compare-row{display:contents}
.premium-compare-row > span{
  padding:.6rem .5rem;border-bottom:1px solid var(--line);color:var(--ink);
  display:flex;align-items:center;justify-content:center;text-align:center;
  transition:background .12s ease;
}
.premium-compare-row > span:first-child{justify-content:flex-start;text-align:left;color:var(--ink2);font-weight:600}
.premium-compare-row:last-child > span{border-bottom:0}
.premium-compare-head > span{
  font-weight:700;letter-spacing:.06em;text-transform:uppercase;font-size:.6rem;
  background:var(--paper3);color:var(--dim);border-bottom:2px solid var(--ink);
}
/* Faint zebra striping on the two comparison columns only -- the Premium
   column keeps its own solid gold tint the whole way down, so striping it
   too would just muddy the one column meant to read as a single block. */
.premium-compare-row:nth-of-type(even):not(.premium-compare-head) > span:not(:last-child){
  background:var(--paper3);
}
/* Third column (Premium) tinted gold the whole way down -- the eye should
   land on this column, not have to hunt for it row by row. */
.premium-compare-row > span:last-child{background:color-mix(in srgb, var(--gold) 16%, transparent)}
.premium-compare-head > span:last-child{background:var(--gold);color:var(--ink-fixed)}
.premium-compare-row:not(.premium-compare-head):hover > span{background:var(--line)}
.premium-compare-row:not(.premium-compare-head):hover > span:last-child{background:color-mix(in srgb, var(--gold) 30%, transparent)}
/* The two numeric columns are fixed-width so the label column can flex,
   but on a narrow phone that still only leaves it ~140-160px -- not
   enough for longer rows ("Custom banner & name color", "Daily archive
   replays") at .7rem mono, so they wrap to two lines and the modal grows
   taller than it needs to. Narrower numeric columns plus a touch smaller
   type claws that space back for exactly the screen width most players
   actually see this on. */
@media(max-width:400px){
  .premium-compare{grid-template-columns:1fr 2.6rem 3.2rem;font-size:.64rem}
  .premium-compare-row > span{padding:.5rem .3rem}
}
/* pc-yes/pc-no live on an inner <b>, not the grid-cell <span> itself -- the
   span has to stay full cell size for its border-bottom to line up with the
   rest of the row, so the pill's own compact size lives one level in. */
.pc-yes,.pc-no{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:1.4rem;padding:.15rem .5rem;border-radius:999px;font-weight:800;
}
.pc-yes{background:color-mix(in srgb, var(--turf-dk) 20%, transparent);color:var(--turf-dk)}
.pc-no{background:var(--paper3);color:var(--faint)}
.premium-compare-row:nth-of-type(even) .pc-no{background:var(--line)}
/* Invite-a-friend link: a plain readonly field rather than .lb-name-input,
   which .auth-card forces to width:100%/display:block -- fighting that
   just to lay this out in a row isn't worth it when a dedicated class
   sidesteps the conflict entirely. */
.invite-link-row{display:flex;gap:.5rem;margin-bottom:.7rem}
.invite-link-input{
  flex:1;min-width:0;font-family:var(--mono);font-size:.72rem;padding:.6rem .7rem;
  border:2px solid var(--ink);border-radius:var(--r);background:var(--paper);color:var(--ink2);
}
.invite-link-row .btn{flex:none}
/* Player banner -- a Premium perk, next to a name in the account bar and
   on leaderboards. One of ten fixed vector icons on one of five fixed
   background colors, both resolved in JS (BANNER_COLORS in app.js) from
   a picker rather than free text or emoji -- emoji render as a fixed
   full-color glyph outside this site's control (different per OS/
   browser), where these mask-image icons stay a plain currentColor
   silhouette like every other icon on the page, so they can be tinted
   per swatch the same way. */
.icon-glyph{
  display:inline-block;background-color:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
.icon-star{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDE0LjM1LDguNzYgTDIxLjUxLDguOTEgTDE1LjgsMTMuMjQgTDE3Ljg4LDIwLjA5IEwxMiwxNiBMNi4xMiwyMC4wOSBMOC4yLDEzLjI0IEwyLjQ5LDguOTEgTDkuNjUsOC43NiBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDE0LjM1LDguNzYgTDIxLjUxLDguOTEgTDE1LjgsMTMuMjQgTDE3Ljg4LDIwLjA5IEwxMiwxNiBMNi4xMiwyMC4wOSBMOC4yLDEzLjI0IEwyLjQ5LDguOTEgTDkuNjUsOC43NiBaJy8+PC9zdmc+")}
.icon-football{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMSwxMiBRMTIsMyAyMywxMiBRMTIsMjEgMSwxMiBaIE0xMS4yLDYuNSBIMTIuOCBWMTcuNSBIMTEuMiBaIE04LjMsOS4zIEwxMC4zLDkuMyBWMTAuNyBMOC4zLDEwLjcgWiBNMTMuNyw5LjMgTDE1LjcsOS4zIFYxMC43IEwxMy43LDEwLjcgWiBNOC4zLDEzLjMgTDEwLjMsMTMuMyBWMTQuNyBMOC4zLDE0LjcgWiBNMTMuNywxMy4zIEwxNS43LDEzLjMgVjE0LjcgTDEzLjcsMTQuNyBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMSwxMiBRMTIsMyAyMywxMiBRMTIsMjEgMSwxMiBaIE0xMS4yLDYuNSBIMTIuOCBWMTcuNSBIMTEuMiBaIE04LjMsOS4zIEwxMC4zLDkuMyBWMTAuNyBMOC4zLDEwLjcgWiBNMTMuNyw5LjMgTDE1LjcsOS4zIFYxMC43IEwxMy43LDEwLjcgWiBNOC4zLDEzLjMgTDEwLjMsMTMuMyBWMTQuNyBMOC4zLDE0LjcgWiBNMTMuNywxMy4zIEwxNS43LDEzLjMgVjE0LjcgTDEzLjcsMTQuNyBaJy8+PC9zdmc+")}
.icon-fire{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgQzksNyA1LDkgNSwxNCBDNSwxOC40IDguNiwyMiAxMywyMiBDMTcsMjIgMjAsMTkgMjAsMTUgQzIwLDEyIDE4LDEwIDE3LDkgQzE3LjUsMTEgMTYuNSwxMi41IDE1LDEyLjUgQzEzLDEyLjUgMTIuNSwxMC41IDEzLjUsOC41IEMxNC41LDYuNSAxMy41LDMuNSAxMiwyIFonLz48L3N2Zz4=");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgQzksNyA1LDkgNSwxNCBDNSwxOC40IDguNiwyMiAxMywyMiBDMTcsMjIgMjAsMTkgMjAsMTUgQzIwLDEyIDE4LDEwIDE3LDkgQzE3LjUsMTEgMTYuNSwxMi41IDE1LDEyLjUgQzEzLDEyLjUgMTIuNSwxMC41IDEzLjUsOC41IEMxNC41LDYuNSAxMy41LDMuNSAxMiwyIFonLz48L3N2Zz4=")}
.icon-trophy{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNNywzIEgxNyBWMTAgQzE3LDE0IDE0LjUsMTYgMTIsMTYgQzkuNSwxNiA3LDE0IDcsMTAgWiBNMTEsMTYgSDEzIFYxOCBIMTEgWiBNOCwxOSBIMTYgVjIxIEg4IFogTTUsNCBDMiw0IDIsOSA1LDkuNSBDNC42LDggNC42LDYgNSw0LjUgWiBNMTksNCBDMjIsNCAyMiw5IDE5LDkuNSBDMTkuNCw4IDE5LjQsNiAxOSw0LjUgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNNywzIEgxNyBWMTAgQzE3LDE0IDE0LjUsMTYgMTIsMTYgQzkuNSwxNiA3LDE0IDcsMTAgWiBNMTEsMTYgSDEzIFYxOCBIMTEgWiBNOCwxOSBIMTYgVjIxIEg4IFogTTUsNCBDMiw0IDIsOSA1LDkuNSBDNC42LDggNC42LDYgNSw0LjUgWiBNMTksNCBDMjIsNCAyMiw5IDE5LDkuNSBDMTkuNCw4IDE5LjQsNiAxOSw0LjUgWicvPjwvc3ZnPg==")}
.icon-crown{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTMsOSBMNywxMyBMMTIsNCBMMTcsMTMgTDIxLDkgTDIxLDE5IEwzLDE5IFonLz48L3N2Zz4=");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTMsOSBMNywxMyBMMTIsNCBMMTcsMTMgTDIxLDkgTDIxLDE5IEwzLDE5IFonLz48L3N2Zz4=")}
.icon-lightning{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEzLDIgTDcsMTMgTDExLDEzIEw5LDIyIEwxNywxMCBMMTIsMTAgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEzLDIgTDcsMTMgTDExLDEzIEw5LDIyIEwxNywxMCBMMTIsMTAgWicvPjwvc3ZnPg==")}
.icon-skull{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBDNywyIDQsNiA0LDEwIEM0LDE0IDYsMTYgNiwxOCBMNiwxOSBDNiwyMCA3LDIxIDgsMjEgTDE2LDIxIEMxNywyMSAxOCwyMCAxOCwxOSBMMTgsMTggQzE4LDE2IDIwLDE0IDIwLDEwIEMyMCw2IDE3LDIgMTIsMiBaIE04LjUsOSBBMiwyLjQgMCAxLDAgOC41LDEzIEEyLDIuNCAwIDEsMCA4LjUsOSBaIE0xNS41LDkgQTIsMi40IDAgMSwwIDE1LjUsMTMgQTIsMi40IDAgMSwwIDE1LjUsOSBaIE0xMiwxMy41IEwxMC4zLDE3IEwxMy43LDE3IFonLz48L3N2Zz4=");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBDNywyIDQsNiA0LDEwIEM0LDE0IDYsMTYgNiwxOCBMNiwxOSBDNiwyMCA3LDIxIDgsMjEgTDE2LDIxIEMxNywyMSAxOCwyMCAxOCwxOSBMMTgsMTggQzE4LDE2IDIwLDE0IDIwLDEwIEMyMCw2IDE3LDIgMTIsMiBaIE04LjUsOSBBMiwyLjQgMCAxLDAgOC41LDEzIEEyLDIuNCAwIDEsMCA4LjUsOSBaIE0xNS41LDkgQTIsMi40IDAgMSwwIDE1LjUsMTMgQTIsMi40IDAgMSwwIDE1LjUsOSBaIE0xMiwxMy41IEwxMC4zLDE3IEwxMy43LDE3IFonLz48L3N2Zz4=")}
.icon-diamond{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTIsOSBMNywzIEwxNywzIEwyMiw5IEwxMiwyMiBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTIsOSBMNywzIEwxNywzIEwyMiw5IEwxMiwyMiBaJy8+PC9zdmc+")}
.icon-target{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBhMTAsMTAgMCAxLDAgMC4wMSwwIFogTTEyLDYuNSBhNS41LDUuNSAwIDEsMCAwLjAxLDAgWiBNMTIsMTAuMyBhMS43LDEuNyAwIDEsMCAwLjAxLDAgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBhMTAsMTAgMCAxLDAgMC4wMSwwIFogTTEyLDYuNSBhNS41LDUuNSAwIDEsMCAwLjAxLDAgWiBNMTIsMTAuMyBhMS43LDEuNyAwIDEsMCAwLjAxLDAgWicvPjwvc3ZnPg==")}
.icon-rocket{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBDOSw1IDgsOSA4LDEzIEw4LDE3IEw2LDIxIEw5LDE5LjUgTDksMjEuNSBMMTIsMjMgTDE1LDIxLjUgTDE1LDE5LjUgTDE4LDIxIEwxNiwxNyBMMTYsMTMgQzE2LDkgMTUsNSAxMiwyIFogTTEyLDcuMyBhMiwyIDAgMSwwIDAuMDEsMCBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBDOSw1IDgsOSA4LDEzIEw4LDE3IEw2LDIxIEw5LDE5LjUgTDksMjEuNSBMMTIsMjMgTDE1LDIxLjUgTDE1LDE5LjUgTDE4LDIxIEwxNiwxNyBMMTYsMTMgQzE2LDkgMTUsNSAxMiwyIFogTTEyLDcuMyBhMiwyIDAgMSwwIDAuMDEsMCBaJy8+PC9zdmc+")}
.icon-heart{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIxIEMxMiwyMSAzLDE0LjUgMyw4LjUgQzMsNS41IDUuNSwzIDguNSwzIEMxMCwzIDExLjMsMy44IDEyLDUgQzEyLjcsMy44IDE0LDMgMTUuNSwzIEMxOC41LDMgMjEsNS41IDIxLDguNSBDMjEsMTQuNSAxMiwyMSAxMiwyMSBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIxIEMxMiwyMSAzLDE0LjUgMyw4LjUgQzMsNS41IDUuNSwzIDguNSwzIEMxMCwzIDExLjMsMy44IDEyLDUgQzEyLjcsMy44IDE0LDMgMTUuNSwzIEMxOC41LDMgMjEsNS41IDIxLDguNSBDMjEsMTQuNSAxMiwyMSAxMiwyMSBaJy8+PC9zdmc+")}
.icon-shield{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDIwLDUgTDIwLDExIEMyMCwxNiAxNi41LDIwIDEyLDIyIEM3LjUsMjAgNCwxNiA0LDExIEw0LDUgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDIwLDUgTDIwLDExIEMyMCwxNiAxNi41LDIwIDEyLDIyIEM3LjUsMjAgNCwxNiA0LDExIEw0LDUgWicvPjwvc3ZnPg==")}
.icon-flag{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMiBINS41IFYyMiBINCBaIE01LjUsMyBMMTksMyBMMTUuNSw3IEwxOSwxMSBMNS41LDExIFonLz48L3N2Zz4=");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMiBINS41IFYyMiBINCBaIE01LjUsMyBMMTksMyBMMTUuNSw3IEwxOSwxMSBMNS41LDExIFonLz48L3N2Zz4=")}
.icon-check{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQuNSwxMiBMOSwxNi41IEwxOS41LDYgTDIxLDcuNSBMOSwxOS41IEwzLDEzLjUgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQuNSwxMiBMOSwxNi41IEwxOS41LDYgTDIxLDcuNSBMOSwxOS41IEwzLDEzLjUgWicvPjwvc3ZnPg==")}
/* Added to differentiate the badge grid -- 76 badges shared just the 10
   icons above, so most looked identical to unrelated achievements. These
   8 give the biggest badge "families" (streaks, luck coincidences,
   games-played milestones, auction economics) their own unifying shape
   instead of clashing with everything else. */
.icon-medal{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDEgTDIxLDYuNSBMMjEsMTcuNSBMMTIsMjMgTDMsMTcuNSBMMyw2LjUgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDEgTDIxLDYuNSBMMjEsMTcuNSBMMTIsMjMgTDMsMTcuNSBMMyw2LjUgWicvPjwvc3ZnPg==")}
.icon-hourglass{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTUsMiBIMTkgVjUgTDEzLDEyIEwxOSwxOSBWMjIgSDUgVjE5IEwxMSwxMiBMNSw1IFonLz48L3N2Zz4=");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTUsMiBIMTkgVjUgTDEzLDEyIEwxOSwxOSBWMjIgSDUgVjE5IEwxMSwxMiBMNSw1IFonLz48L3N2Zz4=")}
.icon-dice{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNNCw0IEgyMCBWMjAgSDQgWiBNNi41LDYuNSBhMS41LDEuNSAwIDEsMCAwLjAxLDAgWiBNMTcuNSw2LjUgYTEuNSwxLjUgMCAxLDAgMC4wMSwwIFogTTEyLDEyIGExLjUsMS41IDAgMSwwIDAuMDEsMCBaIE02LjUsMTcuNSBhMS41LDEuNSAwIDEsMCAwLjAxLDAgWiBNMTcuNSwxNy41IGExLjUsMS41IDAgMSwwIDAuMDEsMCBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNNCw0IEgyMCBWMjAgSDQgWiBNNi41LDYuNSBhMS41LDEuNSAwIDEsMCAwLjAxLDAgWiBNMTcuNSw2LjUgYTEuNSwxLjUgMCAxLDAgMC4wMSwwIFogTTEyLDEyIGExLjUsMS41IDAgMSwwIDAuMDEsMCBaIE02LjUsMTcuNSBhMS41LDEuNSAwIDEsMCAwLjAxLDAgWiBNMTcuNSwxNy41IGExLjUsMS41IDAgMSwwIDAuMDEsMCBaJy8+PC9zdmc+")}
.icon-coin{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMTYgYTgsMyAwLDAsMCAxNiwwIGE4LDMgMCwwLDAgLTE2LDAgWiBNNCwxMiBhOCwzIDAsMCwwIDE2LDAgYTgsMyAwLDAsMCAtMTYsMCBaIE00LDggYTgsMyAwLDAsMCAxNiwwIGE4LDMgMCwwLDAgLTE2LDAgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMTYgYTgsMyAwLDAsMCAxNiwwIGE4LDMgMCwwLDAgLTE2LDAgWiBNNCwxMiBhOCwzIDAsMCwwIDE2LDAgYTgsMyAwLDAsMCAtMTYsMCBaIE00LDggYTgsMyAwLDAsMCAxNiwwIGE4LDMgMCwwLDAgLTE2LDAgWicvPjwvc3ZnPg==")}
.icon-helmet{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMTMgQzQsNiA4LDIgMTIsMiBDMTYsMiAyMCw2IDIwLDEzIEwyMCwxNSBINCBaIE05LDE1IFYxOSBINyBWMTUgWiBNMTcsMTUgVjE5IEgxNSBWMTUgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMTMgQzQsNiA4LDIgMTIsMiBDMTYsMiAyMCw2IDIwLDEzIEwyMCwxNSBINCBaIE05LDE1IFYxOSBINyBWMTUgWiBNMTcsMTUgVjE5IEgxNSBWMTUgWicvPjwvc3ZnPg==")}
.icon-chart{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMjAgSDggVjEzIEg0IFogTTEwLDIwIEgxNCBWOCBIMTAgWiBNMTYsMjAgSDIwIFYxNiBIMTYgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMjAgSDggVjEzIEg0IFogTTEwLDIwIEgxNCBWOCBIMTAgWiBNMTYsMjAgSDIwIFYxNiBIMTYgWicvPjwvc3ZnPg==")}
.icon-compass{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBhMTAsMTAgMCAxLDAgMC4wMSwwIFogTTEyLDQuNCBhNy42LDcuNiAwIDEsMCAwLjAxLDAgWiBNMTIsNiBMMTQsMTIgTDEyLDE4IEwxMCwxMiBaJy8+PC9zdmc+");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMTIsMiBhMTAsMTAgMCAxLDAgMC4wMSwwIFogTTEyLDQuNCBhNy42LDcuNiAwIDEsMCAwLjAxLDAgWiBNMTIsNiBMMTQsMTIgTDEyLDE4IEwxMCwxMiBaJy8+PC9zdmc+")}
.icon-gem{-webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDE4LDcgTDE1LDIxIEw5LDIxIEw2LDcgWicvPjwvc3ZnPg==");mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDE4LDcgTDE1LDIxIEw5LDIxIEw2LDcgWicvPjwvc3ZnPg==")}
.player-banner{
  display:inline-flex;align-items:center;justify-content:center;
  width:1.5rem;height:1.5rem;
  background:var(--swatch);border:2px solid var(--ink);border-radius:50%;
  vertical-align:middle;
}
.player-banner .icon-glyph{width:.85rem;height:.85rem}
/* Brick, not gold -- already proven safe against paper2 in both themes
   (5.15:1 light, 6.29:1 dark) without needing a backing circle the way
   gold does, so the flame can sit bare next to a name instead of
   needing its own badge chrome like a banner does. */
.streak-flair{display:inline-flex;align-items:center;gap:.2rem;font-family:var(--mono);font-weight:700;font-size:.7rem;color:var(--brick)}
.streak-flair .icon-glyph{width:.8rem;height:.8rem}
/* Violet, same bare-icon treatment as the streak flame -- also already
   proven safe against paper2 in both themes (7.75:1 light, 5.04:1 dark)
   -- kept distinct from brick (streak) and from any banner color a
   player could actually pick, so the three flairs never get confused
   for one another at a glance. */
.founder-flair{display:inline-flex;align-items:center;color:var(--violet)}
.founder-flair .icon-glyph{width:.8rem;height:.8rem}
.premium-flair{display:inline-flex;align-items:center;color:var(--gold)}
.premium-flair .icon-glyph{width:.8rem;height:.8rem}
/* Turf-dk, the same green already proven as this site's default link
   color in both themes -- distinct from brick (streak), violet
   (founder), and gold (premium) so all four flairs stay tellable apart
   at a glance. */
.recruiter-flair{display:inline-flex;align-items:center;color:var(--turf-dk)}
.recruiter-flair .icon-glyph{width:.8rem;height:.8rem}
/* Badges -- a filled pill, deliberately unlike the bare streak/founder
   flair icons and unlike a banner's own colored circle, so a badge never
   reads as a fourth kind of thing to confuse with either. Gold icon on a
   neutral paper2 chip rather than a gold-filled chip, since a solid-gold
   pill would look exactly like picking gold as a banner color. */
.profile-badges-row{display:flex;flex-wrap:wrap;gap:.5rem}
.profile-badge-group{margin-bottom:.9rem}
.profile-badge-group:last-child{margin-bottom:0}
.profile-badge-group-label{
  font-family:var(--mono);font-weight:700;font-size:.62rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--dim);margin-bottom:.4rem;
}
.badge-chip{
  display:inline-flex;align-items:center;gap:.35rem;
  background:var(--paper2);border:2px solid var(--ink);border-radius:999px;
  padding:.35rem .7rem;font-family:var(--mono);font-weight:700;font-size:.7rem;
  color:var(--ink);box-shadow:2px 2px 0 var(--ink);
}
.badge-chip .icon-glyph{width:.85rem;height:.85rem;background-color:var(--gold)}

/* ---------- End Zone Bingo ---------- */
/* 7 columns x 7 rows -- one row per position (QB/RB/WR/TE/K/DEF) plus a
   7th of cross-position oddities, matching BINGO_CONDITIONS' own grouping
   in api/leaderboard.js. Every cell, filled or not, prints its own real
   condition (app.js's BINGO_SHORT) over a light tint of its row's color,
   plus a tiny corner tag -- a square says what it actually needs without
   being tapped first, and the tint still groups all seven rows at a
   glance. Not forced to a strict square aspect-ratio like the very first
   version -- real short sentences need more room than a bare letter did,
   so height is left to whatever two or three lines of tiny text actually
   takes. Filled flips the tint to full-strength color, flips the text to
   paper2 for contrast, and adds a checkmark. */
.bingo-count{font-family:var(--mono);font-weight:400;font-size:.68rem;color:var(--dim)}
.bingo-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:.3rem;margin:.7rem 0}
.bingo-cell{
  position:relative;min-height:3.4rem;border-radius:6px;border:2px solid var(--line2);
  cursor:pointer;padding:.75rem .15rem .2rem;transition:.12s;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
}
.bingo-cell:hover{transform:translateY(-1px);border-color:var(--ink)}
.bingo-cell-row-tag{position:absolute;top:2px;left:3px;font-family:var(--mono);font-weight:700;font-size:.42rem;letter-spacing:.02em;opacity:.8}
.bingo-cell-text{font-family:var(--mono);font-weight:700;font-size:.48rem;line-height:1.2;text-align:center;color:var(--ink);word-break:break-word}
.bingo-cell-check{position:absolute;top:1px;right:3px;font-size:.6rem;line-height:1;color:var(--paper2);font-weight:700}
.bingo-cell.filled{border-color:var(--ink);box-shadow:1px 1px 0 var(--ink)}
.bingo-cell.filled .bingo-cell-text{color:var(--paper2)}
/* Unfilled: a light tint of the row's color, corner tag in that same
   color; filled: that same color at full strength. Paired so each row
   only has to name its color once. */
.bingo-cell.row-qb{background:color-mix(in srgb, var(--pos-qb) 16%, var(--paper2))}
.bingo-cell.row-qb .bingo-cell-row-tag{color:var(--pos-qb)}
.bingo-cell.filled.row-qb{background:var(--pos-qb)}
.bingo-cell.row-rb{background:color-mix(in srgb, var(--pos-rb) 16%, var(--paper2))}
.bingo-cell.row-rb .bingo-cell-row-tag{color:var(--pos-rb)}
.bingo-cell.filled.row-rb{background:var(--pos-rb)}
.bingo-cell.row-wr{background:color-mix(in srgb, var(--pos-wr) 16%, var(--paper2))}
.bingo-cell.row-wr .bingo-cell-row-tag{color:var(--pos-wr)}
.bingo-cell.filled.row-wr{background:var(--pos-wr)}
.bingo-cell.row-te{background:color-mix(in srgb, var(--pos-te) 16%, var(--paper2))}
.bingo-cell.row-te .bingo-cell-row-tag{color:var(--pos-te)}
.bingo-cell.filled.row-te{background:var(--pos-te)}
.bingo-cell.row-k{background:color-mix(in srgb, var(--pos-k) 16%, var(--paper2))}
.bingo-cell.row-k .bingo-cell-row-tag{color:var(--pos-k)}
.bingo-cell.filled.row-k{background:var(--pos-k)}
.bingo-cell.row-def{background:color-mix(in srgb, var(--pos-def) 16%, var(--paper2))}
.bingo-cell.row-def .bingo-cell-row-tag{color:var(--pos-def)}
.bingo-cell.filled.row-def{background:var(--pos-def)}
.bingo-cell.row-any{background:color-mix(in srgb, var(--gold) 22%, var(--paper2))}
.bingo-cell.row-any .bingo-cell-row-tag{color:color-mix(in srgb, var(--gold) 65%, var(--ink))}
.bingo-cell.filled.row-any{background:var(--gold)}
.bingo-detail{font-family:var(--mono);font-size:.72rem;color:var(--dim);min-height:2.2em;line-height:1.4;margin:0}
/* Fixed, centered, floats above everything (including the auth/premium
   modals' z-index:80) since it can fire the instant a score posts, which
   on the result screen is nothing else is open over -- but staying above
   confirms it never gets visually trapped behind one regardless. Starts
   translated up and transparent; .show (added a frame after unhiding, so
   the transition actually has something to animate from) slides it down
   into place. */
.badge-toast{
  position:fixed;top:1.2rem;left:50%;z-index:95;
  display:flex;align-items:center;gap:.7rem;
  background:var(--paper2);border:3px solid var(--ink);border-radius:var(--r);
  padding:.8rem 1.2rem;box-shadow:var(--shadow);
  opacity:0;transform:translate(-50%,-18px);transition:opacity .3s,transform .3s;
  pointer-events:none;max-width:calc(100vw - 2rem);
}
.badge-toast[hidden]{display:none}
.badge-toast.show{opacity:1;transform:translate(-50%,0)}
/* The unlock moment itself -- the slide-fade above just announces the
   toast; this makes earning a badge feel distinct from any other message
   on the page. The card's ink-shadow punches out further than its resting
   --shadow on impact, and the icon slams down like a rubber stamp hitting
   paper, oversized and slightly rotated, before settling square -- the
   same hard-edged ink-shadow language used everywhere else on the page
   rather than a generic bounce or glow. Runs alongside (not instead of)
   the opacity/transform transition above: this only touches box-shadow
   on the card and transform on the icon, so the two never fight over the
   same property. */
.badge-toast.show{animation:badge-toast-impact .45s ease-out .08s both}
.badge-toast.show #badge-toast-icon{animation:badge-stamp .5s cubic-bezier(.32,1.7,.5,1) .08s both}
@keyframes badge-toast-impact{
  0%,40%{box-shadow:var(--shadow)}
  55%{box-shadow:10px 10px 0 var(--ink)}
  100%{box-shadow:var(--shadow)}
}
@keyframes badge-stamp{
  0%{transform:scale(2.4) rotate(-16deg);opacity:0}
  50%{transform:scale(.85) rotate(5deg);opacity:1}
  72%{transform:scale(1.14) rotate(-2deg)}
  100%{transform:scale(1) rotate(0)}
}
@media (prefers-reduced-motion:reduce){
  .badge-toast.show, .badge-toast.show #badge-toast-icon{animation:none}
}
/* #badge-toast-icon, not the .badge-toast-icon class this looked like it
   should be -- the element only ever carries id="badge-toast-icon" (see
   index.html and showBadgeToastQueue's className assignment in app.js),
   so this rule was dead: the icon rendered at 0x0 and was invisible on
   every single badge unlock until this fix. */
#badge-toast-icon{width:1.8rem;height:1.8rem;background-color:var(--gold);flex:none}
.badge-toast-label{font-family:var(--mono);font-size:.6rem;letter-spacing:.12em;text-transform:uppercase;color:var(--dim)}
.badge-toast-name{font-family:var(--serif);font-weight:800;font-size:1.1rem;color:var(--ink);white-space:nowrap}
/* Same shape as .badge-toast, but fixed near-black/gold rather than
   theme-adaptive -- see --paper-fixed's own comment on why: this is
   meant to read as a genuine rare event both in light and dark mode,
   not blend into whichever paper tone is currently active. */
.elite-toast{
  position:fixed;top:1.2rem;left:50%;z-index:96;
  display:flex;align-items:center;gap:.7rem;
  background:var(--ink-fixed);border:3px solid var(--gold);border-radius:var(--r);
  padding:.8rem 1.3rem;box-shadow:var(--shadow);
  opacity:0;transform:translate(-50%,-18px) scale(.92);transition:opacity .3s,transform .3s;
  pointer-events:none;max-width:calc(100vw - 2rem);
}
.elite-toast[hidden]{display:none}
.elite-toast.show{opacity:1;transform:translate(-50%,0) scale(1)}
.elite-toast-icon{width:1.8rem;height:1.8rem;background-color:var(--gold);flex:none}
.elite-toast-label{font-family:var(--mono);font-size:.6rem;letter-spacing:.12em;text-transform:uppercase;color:var(--gold)}
.elite-toast-name{font-family:var(--serif);font-weight:800;font-size:1.1rem;color:var(--paper-fixed);white-space:nowrap}
/* Sits lower than .badge-toast/.elite-toast (top:1.2rem) rather than
   sharing their exact spot -- a pick can be both a 99th-percentile
   celebration and a lucky-history callout at once, and stacking here
   instead of overlapping is simpler than picking one to suppress. */
.luck-toast{
  position:fixed;top:5.6rem;left:50%;z-index:94;
  display:flex;align-items:center;gap:.6rem;
  background:var(--paper2);border:2px solid var(--gold);border-radius:var(--r);
  padding:.6rem 1rem;box-shadow:var(--shadow-sm);
  opacity:0;transform:translate(-50%,-12px);transition:opacity .3s,transform .3s;
  pointer-events:none;max-width:calc(100vw - 2rem);
  font-family:var(--mono);font-size:.78rem;color:var(--ink);
}
.luck-toast[hidden]{display:none}
.luck-toast.show{opacity:1;transform:translate(-50%,0)}
.luck-toast .icon-glyph{width:1.1rem;height:1.1rem;background-color:var(--gold);flex:none}
/* Same shape as the good-luck toast, just recolored to the site's usual
   "warning/bad" accent (see .pick-price.tier-expensive, the rebuilding_
   year badge) instead of restyling the whole thing separately. */
.luck-toast.luck-toast-bad{border-color:var(--brick)}
.luck-toast.luck-toast-bad .icon-glyph{background-color:var(--brick)}
/* Same slide-fade shape as .luck-toast above, its own element rather
   than a shared class -- fires at a completely different moment (right
   after a submission, not mid-draft) so the two never actually overlap
   in time, but keeping them distinct avoids a misleading class name on
   whichever one came second. Reuses .luck-toast's top offset (5.6rem)
   rather than .badge-toast's (1.2rem) so a round that both earns a
   badge and beats a personal best doesn't stack the two on top of each
   other. */
.pb-toast{
  position:fixed;top:5.6rem;left:50%;z-index:94;
  display:flex;align-items:center;gap:.6rem;font-weight:700;
  background:var(--paper2);border:2px solid var(--turf-dk);border-radius:var(--r);
  padding:.6rem 1rem;box-shadow:var(--shadow-sm);
  opacity:0;transform:translate(-50%,-12px);transition:opacity .3s,transform .3s;
  pointer-events:none;max-width:calc(100vw - 2rem);
  font-family:var(--mono);font-size:.78rem;color:var(--ink);
}
.pb-toast[hidden]{display:none}
.pb-toast.show{opacity:1;transform:translate(-50%,0)}
.pb-toast .icon-glyph{width:1.1rem;height:1.1rem;background-color:var(--turf-dk);flex:none}
.banner-icon-row{display:grid;grid-template-columns:repeat(5,1fr);gap:.5rem;margin-bottom:1.1rem}
.banner-icon-btn{
  width:100%;aspect-ratio:1;color:var(--ink);
  background:var(--paper2);border:2px solid var(--ink);border-radius:50%;cursor:pointer;
  display:grid;place-items:center;box-shadow:2px 2px 0 var(--ink);transition:.12s;
}
.banner-icon-btn .icon-glyph{width:1.15rem;height:1.15rem}
.banner-icon-btn:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink)}
/* Gold-on-ink (the site's usual "selected" look) loses contrast in dark
   mode -- both land light -- so this picker's own selected state uses
   the turf/paper2 pairing instead, proven to hold up in both themes. */
.banner-icon-btn.active{background:var(--turf);color:var(--paper2);outline:3px solid var(--ink);outline-offset:1px}
.banner-swatch-row{display:flex;flex-wrap:wrap;gap:.6rem;margin-bottom:1.1rem}
.banner-swatch{
  width:1.9rem;height:1.9rem;border-radius:50%;border:2px solid var(--ink);cursor:pointer;padding:0;
  background:var(--swatch);box-shadow:2px 2px 0 var(--ink);transition:.12s;
}
.banner-swatch:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink)}
.banner-swatch.active{outline:3px solid var(--gold);outline-offset:2px}
/* The name-color row's "reset to default" option -- unlike every other
   swatch it has no --swatch of its own to show as a fill, so it needs its
   own look (a plain paper circle with a slash) rather than rendering as a
   visually broken, colorless button. */
.banner-swatch-none{background:var(--paper2);position:relative}
.banner-swatch-none::after{
  content:'';position:absolute;inset:0;margin:auto;width:65%;height:2px;
  background:var(--dim);transform:rotate(-45deg);
}
.banner-preview-lbl{font-family:var(--mono);font-size:.68rem;color:var(--dim)}
/* A single real .lb-row instead of two disconnected preview widgets --
   see updateBannerLivePreview in app.js -- so this is genuinely what
   both choices together will look like on an actual board, in the
   modal's own paper2 background rather than a real board's. */
.banner-live-preview{margin:.9rem 0 1.3rem;padding:.9rem;background:var(--paper2);border:2px solid var(--line2);border-radius:var(--r)}
.banner-live-preview .banner-preview-lbl{display:block;margin-bottom:.5rem}
.banner-live-preview .lb-row{border-bottom:0;cursor:default}
.namecolor-title{margin-top:1.3rem}
#namecolor-preview{font-weight:700}
.auth-switch{font-family:var(--mono);font-size:.72rem;color:var(--dim);margin-top:1.1rem;text-align:center}

/* ---------- start ---------- */
/* The pylon/goal-line stripe every end zone actually has -- the one purely
   decorative touch that ties the page to its name before a word of copy
   loads. Sits above the account bar, full-bleed, independent of the
   max-width content column below it. */
.endzone-stripe{
  height:9px;width:100%;
  background:repeating-linear-gradient(-45deg,
    var(--turf) 0 16px, var(--gold) 16px 32px);
  border-bottom:2px solid var(--ink);
}
.hero-badge{
  display:inline-block;font-size:.62rem;color:var(--ink);
  border:2px solid var(--ink);background:var(--paper2);
  padding:.4rem .8rem;border-radius:var(--r);margin-bottom:1rem;font-weight:700;
}
.hero-badge strong{font-weight:900;color:var(--turf-dk)}
.wordmark{
  font-family:var(--serif);font-weight:900;text-transform:uppercase;
  font-size:clamp(2.8rem,11vw,5.2rem);line-height:.92;letter-spacing:-.02em;color:var(--ink);
  /* A crisp gold step plus a soft blurred lift behind it, the same "hard
     offset + soft blur" pairing already used for button hovers elsewhere
     in this file (see .btn-lg:hover). Used to also have a second hard
     step in --ink for a jersey-number look, but --ink is also this
     element's own text color -- a same-color shadow sitting just behind
     same-color letters reads as a smudged double-exposure rather than a
     crisp outline, actively hurting legibility rather than adding depth. */
  text-shadow:4px 4px 0 var(--gold), 0 14px 22px rgba(0,0,0,.28);
  transition:transform .15s ease-out;
  transform-style:preserve-3d;
}
/* display:inline-block, not inline -- a transform (the settle-in
   animation below, and the hover tilt applied to the parent .wordmark
   itself) never applies to a plain non-replaced inline box regardless of
   what's requested; this is what actually lets either one render. */
.wordmark span{display:inline-block}
.wordmark .accent{color:var(--turf);margin-left:.14em}
/* Settles into place once on load, like a rolled die coming to rest --
   ZONE lands a beat after END rather than both landing together, so it
   reads as two separate rolls, not one static image fading in. both
   keeps each span at its animation's final state after it finishes
   instead of snapping back to the pre-animation styles. */
@keyframes wordmark-settle{
  0%{transform:translateY(-18px) rotate(-6deg);opacity:0}
  65%{transform:translateY(3px) rotate(1.5deg);opacity:1}
  100%{transform:translateY(0) rotate(0deg);opacity:1}
}
.wordmark span{animation:wordmark-settle .5s cubic-bezier(.34,1.56,.64,1) both}
.wordmark .accent{animation-delay:.12s}
@media(prefers-reduced-motion:reduce){
  .wordmark span{animation:none}
}
@media(max-width:480px){.wordmark{text-shadow:3px 3px 0 var(--gold)}}
/* Past 11vw's own growth curve, the wordmark saturates its old 5.2rem
   cap by ~756px viewport and then just sits flat while its actual
   container -- the middle column of .start-layout, minmax(0,780px) --
   keeps growing all the way out to that 780px on any wide desktop
   screen, leaving a big dead gap on either side of the title once the
   sidebars' own content (trivia/leaderboard cards) is what's actually
   setting the row's height. Gated to 1300px, past the 3-column layout's
   own tight-fit zone right at its 1101px breakpoint (where the middle
   column can be as narrow as ~440px) -- 780px is only reliably available
   once .start-layout is well clear of that edge. 7.8rem's own content
   width still lands comfortably under 780px even at the widest ordinary
   desktop viewports, so it grows to fill the column without risking
   overflow into the sidebars on either side. */
@media(min-width:1300px){
  .wordmark{font-size:clamp(5.2rem,6.5vw,7.8rem)}
  .tagline{font-size:clamp(1.3rem,1.6vw,1.7rem);max-width:38ch}
}
.tagline{
  font-family:var(--serif);font-style:italic;font-weight:600;color:var(--ink2);
  margin:.7rem auto 1.6rem;font-size:clamp(1.15rem,3.4vw,1.55rem);line-height:1.35;
  max-width:34ch;padding-bottom:1rem;border-bottom:3px solid var(--ink);
}
.tagline em{color:var(--turf-dk);font-style:italic}

.howto{margin-top:2rem}
.howto summary{
  display:inline-flex;align-items:center;gap:.5rem;list-style:none;cursor:pointer;
  font-family:var(--mono);font-weight:700;font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);color:var(--ink);
  padding:.55rem 1rem;box-shadow:var(--shadow-sm);transition:transform .1s,box-shadow .1s,background .12s;
}
.howto summary::-webkit-details-marker{display:none}
.howto summary::after{content:'+';font-size:1rem;line-height:1;font-weight:700}
.howto[open] summary::after{content:'\2212'}
.howto summary:hover{background:var(--turf);color:var(--paper2)}
.howto summary:active{transform:translate(3px,3px);box-shadow:0 0 0 var(--ink)}
.howto-body{
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  box-shadow:var(--shadow);padding:1.6rem;text-align:left;margin-top:1.1rem;
  animation:rv .22s ease-out;
}
.rules{list-style:none;counter-reset:r}
.rules li{
  counter-increment:r;position:relative;padding-left:2.5rem;margin-bottom:.95rem;
  color:var(--ink2);font-size:.97rem;
}
.rules li::before{
  content:counter(r);position:absolute;left:0;top:.02rem;
  width:1.7rem;height:1.7rem;border-radius:var(--r);
  background:var(--turf);border:2px solid var(--ink);color:var(--paper2);
  font-family:var(--mono);font-weight:700;font-size:.72rem;display:grid;place-items:center;
}
.rules b{color:var(--ink);font-weight:700}
.roster-preview{display:flex;flex-wrap:wrap;gap:.4rem;margin-top:1.4rem;padding-top:1.4rem;border-top:2px dashed var(--line2)}
.roster-preview span{
  font-family:var(--mono);font-weight:700;font-size:.7rem;letter-spacing:.1em;
  background:var(--paper);border:2px solid var(--ink);color:var(--ink);
  padding:.34rem .62rem;border-radius:var(--r);
}
.roster-preview .flex{background:var(--gold);color:var(--ink-fixed)}

/* ---------- daily challenge ---------- */
.daily-card{
  background:var(--paper2);border:3px solid var(--ink);border-radius:var(--r);
  box-shadow:10px 10px 0 var(--ink);padding:1.3rem 1.4rem 1.5rem;margin:0 auto 1.2rem;
  max-width:560px;text-align:center;
}
.daily-top{display:flex;align-items:center;justify-content:center;gap:.6rem;margin-bottom:.8rem}
.daily-kicker{
  font-family:var(--mono);font-weight:700;font-size:.68rem;letter-spacing:.18em;
  text-transform:uppercase;color:var(--dim);
}
.daily-date{
  font-family:var(--mono);font-weight:700;font-size:.68rem;letter-spacing:.08em;
  color:var(--turf-dk);padding:.1rem .4rem;border:2px solid var(--line2);border-radius:var(--r);
}
.daily-streak-row[hidden]{display:none}
.daily-streak-row{
  display:flex;align-items:center;justify-content:center;gap:.5rem;margin:-.4rem 0 1.1rem;
  font-family:var(--mono);font-size:.7rem;color:var(--dim);
}
.daily-streak-row b{color:var(--gold);font-size:.85rem}
.daily-streak-sep{color:var(--faint)}
.streak-flame-group{display:inline-flex;align-items:center;gap:.28rem}
/* A dim, static flame is the "0 today, but you have a best" state --
   the row itself only hides when there's never been a streak at all
   (streak.best === 0), so a broken current streak still needs to render
   as *something* rather than a blank gap where the icon usually sits. */
.streak-flame{width:.7rem;height:.7rem;background-color:var(--faint);flex:none;transition:width .25s,height .25s}
/* Three tiers, not a continuous scale -- a streak is a day-count a
   player checks once a day at most, so discrete "it leveled up" jumps
   read as milestones worth noticing, where smooth interpolation would
   just look like noisy sizing between visits. */
.streak-flame.streak-flame-lit{background-color:var(--brick)}
.streak-flame.streak-flame-hot{
  width:.9rem;height:.9rem;background-color:var(--brick);
  animation:streak-flame-flicker 1.6s ease-in-out infinite;
}
.streak-flame.streak-flame-blaze{
  width:1.1rem;height:1.1rem;background-color:var(--gold);
  animation:streak-flame-flicker 1.1s ease-in-out infinite;
  filter:drop-shadow(0 0 3px var(--gold));
}
@keyframes streak-flame-flicker{
  0%,100%{transform:scale(1) rotate(-3deg)}
  50%{transform:scale(1.14) rotate(3deg)}
}
@media (prefers-reduced-motion:reduce){
  .streak-flame.streak-flame-hot,.streak-flame.streak-flame-blaze{animation:none}
}
.btn-xl{
  display:block;width:100%;padding:1.3rem 1.2rem;font-size:clamp(1.05rem,3.6vw,1.4rem);
  letter-spacing:.12em;border-width:3px;box-shadow:9px 9px 0 var(--ink);
  background:var(--gold);color:var(--ink-fixed);
}
.btn-xl:hover{transform:translate(3px,3px);box-shadow:6px 6px 0 var(--ink);background:#e2a83c}
.btn-xl:active{transform:translate(6px,6px);box-shadow:3px 3px 0 var(--ink)}
.daily-status{font-family:var(--mono);font-size:.72rem;color:var(--dim);margin-top:.8rem;line-height:1.5}
.daily-archive-link{display:inline-block;margin-top:.9rem}
.daily-archive-link .premium-badge{margin-left:.3rem}
/* Once today's round is done, the score itself is the headline -- bigger and
   bolder than any of the surrounding start-screen copy, gold to match the
   result screen's plate. */
.daily-score{
  display:flex;align-items:baseline;justify-content:center;gap:.55rem;
  background:var(--paper3);border:3px solid var(--ink);border-radius:var(--r);
  box-shadow:6px 6px 0 var(--ink);padding:.85rem 1.4rem;margin-bottom:1.4rem;
}
.daily-score[hidden]{display:none}
.daily-score-num{
  font-family:var(--serif);font-weight:900;font-size:clamp(2.4rem,8vw,3.2rem);
  color:var(--gold);line-height:1;letter-spacing:-.02em;
}
.daily-score-lbl{
  font-family:var(--mono);font-size:.64rem;letter-spacing:.14em;text-transform:uppercase;color:var(--dim);
}
.start-layout{
  max-width:1440px;margin:0 auto;padding:0 1.25rem;
  display:grid;grid-template-columns:280px minmax(0,780px) 280px;justify-content:center;
  gap:0 2rem;grid-template-areas:"trivia top side";align-items:start;
}
/* align-self:center rather than the grid's own default align-items:start --
   this column's content (wordmark, tagline) is much shorter than the
   trivia/leaderboard cards beside it, so pinned to the top it left a big
   dead gap between the tagline and the mode buttons below. Centered
   within the row's full height (set by the taller side columns) instead,
   so it reads as balanced against them rather than parked at the top of
   empty space. */
.hero-top{grid-area:top;align-self:center;text-align:center;padding-top:clamp(.5rem,1.5vh,1rem)}
/* Sits outside start-layout entirely -- rather than being confined to the
   grid's ~780px middle column, it gets the site's full 1440px content
   width, same as the topbar stripe, so the mode buttons actually use the
   space the trivia/leaderboard columns leave empty once they end. */
.hero-bottom{text-align:center;max-width:680px;margin:0 auto;padding:0 1.25rem 2.2rem}
.home-lb,.home-trivia{max-width:280px;margin:0;margin-top:clamp(.5rem,2vh,1.5rem)}
.home-lb{grid-area:side}
.trivia-col{grid-area:trivia;max-width:280px}
.home-trivia{text-align:left}
.home-rank-digest{max-width:280px;margin:0 0 clamp(.5rem,2vh,1.5rem);text-align:left}
.home-rank-digest-text{margin:0 0 .5rem;font-size:.85rem;line-height:1.45;color:var(--ink2)}
/* The watermark's containing block is #trivia-content, not #home-trivia --
   #home-trivia already carries its own position value (sticky on desktop,
   forced to static below 1100px, both via #id selectors elsewhere in this
   file), so a class-only position:relative here would never have won that
   fight consistently, and did lose it on the mobile/static side: the
   absolutely-positioned watermark escaped all the way out to the page's
   own containing block instead of staying inside the card. #trivia-content
   is new and untouched by either of those rules, so it can just always be
   its own clean, uncontested positioning context. */
#trivia-content{position:relative;overflow:hidden}
/* Purely decorative -- the one bit of imagery on an otherwise all-type,
   all-color-block page. Tinted per that day's actual featured team (see
   refreshHomeTrivia in app.js) rather than a fixed color, so the card
   feels tied to today's specific legend instead of generic. Low opacity
   and behind everything else in the card (z-index isn't needed since it's
   the first child and the text content that follows naturally paints
   over it), so it never fights with the real text for attention. */
.trivia-watermark{
  position:absolute;right:-1.1rem;bottom:-1.4rem;width:6.5rem;height:6.5rem;
  opacity:.14;pointer-events:none;color:var(--ink);
}
#home-lb,#home-trivia{position:sticky;top:1.5rem;border-top-width:5px}
#home-lb{border-top-color:var(--turf-dk)}
#home-trivia{border-top-color:var(--gold)}
.lb-row-you{border-top:2px dashed var(--line2);margin-top:.3rem;padding-top:.6rem;font-weight:700}
.lb-row-you .lb-name{color:var(--turf-dk)}
/* Ranks 1-5 left, 6-10 right, rather than one ten-row column -- at full
   strength this list would grow taller than the wordmark/trivia content
   next to it and push the mode buttons (which come right after
   start-layout in the page flow) further down. Two columns keeps it
   about as tall as the trivia card beside it instead. Explicit
   grid-row per item rather than relying on grid auto-placement, since
   auto-placement's row cursor doesn't reliably wrap column 2 back to
   row 1 once column 1 is several rows deep. */
/* Trims .lb-panel's shared 1.4rem padding down a bit just for this one
   card -- the 2-column list below is tight on width, and this claws back
   a few extra px for it without touching the trivia/full-board panels
   that share the same .lb-panel class. */
#home-lb{padding:1.1rem}
#home-lb-list .lb-empty{grid-column:1/-1}
/* min-width:0 because a flex row placed in a 1fr grid track otherwise
   keeps the track from shrinking below the un-truncated name's full
   width -- .lb-name's own min-width:0 only lets *it* shrink once .lb-row
   already has a constrained width to divide up. */
#home-lb-list .lb-row{gap:.3rem;font-size:.8rem;padding:.4rem 0;min-width:0}
/* Same desktop-only tightening as the hub's own .lb-row, just kept
   separate since this widget's rows only apply their compact sizing at
   the wider single-column sidebar layout (see the max-width:1100px
   block below for the narrower two-column state, which already has
   plenty of room and is left alone). */
@media (min-width:1101px){
  /* A little shorter still -- every row/rank/score stays, just packed
     tighter (row padding, panel padding, and the title/link margins
     hugging them a bit closer) so the card reads more compact without
     dropping anything from the board. */
  #home-lb-list .lb-row{padding:.1rem 0;font-size:.72rem}
  #home-lb{padding:.85rem}
  #home-lb .lb-board-title{margin-bottom:.3rem}
  #home-lb .lbhub-link{margin-top:.45rem}
}
/* Today's top 3, inside the daily-challenge card itself (see
   dailyCardPodiumHtml) -- right where someone's deciding whether to
   play, not off on the separate leaderboard widget beside it. Each row
   tinted with its own tier's color at low opacity, same color-mix
   technique as the hub's own top-3 name highlight, just as a full row
   background here since there's no separate "list" context to keep it
   scoped to just the name. text-align:left overrides .daily-card's own
   centered text -- flex rows don't care either way, but the plain name/
   score text inside each one would otherwise center along with
   everything else in the card. Gold's row is a touch taller/bolder
   than silver/bronze's -- first place earns slightly more visual
   weight than a same-size row would give every tier equally. */
.daily-card-podium{display:flex;flex-direction:column;gap:.3rem;margin:1rem 0 .2rem;text-align:left}
.daily-podium-row{
  display:flex;align-items:center;gap:.5rem;border-radius:var(--r);
  padding:.4rem .6rem;font-family:var(--mono);font-size:.78rem;
}
.daily-podium-row .podium-medal{width:1.1rem;height:1.1rem;margin-right:0;flex:none}
.daily-podium-name{font-weight:700;color:var(--ink);flex:1 1 auto;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.daily-podium-score{font-weight:700;color:var(--ink2);flex:none}
.daily-podium-rank-1{background:color-mix(in srgb, var(--gold) 22%, transparent);padding:.55rem .65rem;font-size:.86rem}
.daily-podium-rank-1 .podium-medal{width:1.25rem;height:1.25rem}
.daily-podium-rank-2{background:color-mix(in srgb, var(--silver) 22%, transparent)}
.daily-podium-rank-3{background:color-mix(in srgb, var(--bronze) 22%, transparent)}
#home-lb-list .lb-rank{width:1rem}
#home-lb-list .lb-name-group{gap:.25rem}
/* Each row here only has ~100px total to split between rank, name and
   score, so the full circular badge (24px incl. border) would crowd the
   name out almost entirely -- shrink it here so the name keeps real
   width. Keeps the circle (not just the bare glyph): each swatch's
   `fg` icon color is only guaranteed readable against its own `bg`
   circle -- e.g. gold's icon color is only 2.3:1 against the card
   background on its own, well under the 3:1 floor for graphics, so
   dropping the backing circle would make gold banners nearly invisible
   in light mode. */
#home-lb-list .player-banner{width:.95rem;height:.95rem;border:none;flex:none}
#home-lb-list .player-banner .icon-glyph{width:.55rem;height:.55rem}
#home-lb-list .lb-row:nth-child(1){grid-column:1;grid-row:1}
#home-lb-list .lb-row:nth-child(2){grid-column:1;grid-row:2}
#home-lb-list .lb-row:nth-child(3){grid-column:1;grid-row:3}
#home-lb-list .lb-row:nth-child(4){grid-column:1;grid-row:4}
#home-lb-list .lb-row:nth-child(5){grid-column:1;grid-row:5}
#home-lb-list .lb-row:nth-child(6){grid-column:2;grid-row:1}
#home-lb-list .lb-row:nth-child(7){grid-column:2;grid-row:2}
#home-lb-list .lb-row:nth-child(8){grid-column:2;grid-row:3}
#home-lb-list .lb-row:nth-child(9){grid-column:2;grid-row:4}
#home-lb-list .lb-row:nth-child(10){grid-column:2;grid-row:5}
#home-lb-list .lb-row-you{grid-column:1/-1;grid-row:6}
.trivia-pts-row{display:flex;align-items:baseline;gap:.4rem;margin:.3rem 0 .6rem}
.trivia-pts{font-family:var(--serif);font-weight:900;font-size:2.1rem;color:var(--gold);line-height:1;letter-spacing:-.02em}
.trivia-pts-lbl{font-family:var(--mono);font-size:.62rem;letter-spacing:.12em;text-transform:uppercase;color:var(--dim)}
.total-points-row{display:flex;align-items:baseline;gap:.7rem;flex-wrap:wrap}
.total-points-value{font-family:var(--serif);font-weight:900;font-size:2.1rem;color:var(--gold);line-height:1;letter-spacing:-.02em;margin:0}
.total-points-rank{font-family:var(--mono);font-size:.78rem;color:var(--dim);margin:0}
.total-points-compare{font-family:var(--mono);font-size:.72rem;color:var(--dim);margin:.3rem 0 0}
.total-points-compare b{color:var(--ink)}
.total-points-compare[hidden]{display:none}
.trivia-name{font-family:var(--mono);font-weight:700;font-size:.78rem;color:var(--ink);margin-bottom:.6rem;line-height:1.4}
.trivia-blurb{font-family:var(--serif);font-style:italic;font-size:.86rem;color:var(--ink2);line-height:1.55}
@media (max-width:1100px){
  .start-layout{grid-template-columns:minmax(0,560px);grid-template-areas:"top" "trivia" "side"}
  .home-lb,.home-trivia{max-width:560px;width:100%;margin:1rem 0}
  /* .trivia-col's own max-width is the 280px desktop sidebar's -- left
     uncapped here, it kept the card pinned to that narrow width and
     flush against the left edge of the wider single-column track
     instead of centered, most visible now that the collapsed mobile
     button is so much narrower than the card around it. */
  .trivia-col{max-width:none}
  #home-lb,#home-trivia{position:static}
  /* Left-aligned text reads fine in the narrow 280px desktop sidebar,
     but once the card goes full-width and stacks with everything else
     centered (the wordmark, tagline, mode buttons), left-aligned text
     in a wide box looks off-balance -- center it to match. ID selector,
     not just the class -- .lb-panel's own text-align:left is declared
     later in this file at equal class-only specificity and would
     otherwise silently win regardless of viewport, the same trap
     #home-trivia's own border-top already hit. */
  #home-trivia{text-align:center}
  .trivia-pts-row{justify-content:center}
  /* The 280px desktop sidebar is too narrow to split into two columns
     without truncating names -- at this width the widget is full-width
     (560px) instead, where two columns still leave real room per name. */
  #home-lb-list{display:grid;grid-template-columns:1fr 1fr;column-gap:.4rem}
}
/* That same 560px cap keeps shrinking right along with the viewport
   below it, though -- a true phone screen (as opposed to the 1100px
   breakpoint's own tablet-width assumption) leaves each column well
   under half of "real room per name", which is exactly what was
   truncating names like "Golden T…" here. Below 600px, back to one
   column stacked in real rank order -- display:block is enough on its
   own; the nth-child grid-column/grid-row rules above only do anything
   under display:grid, so they're simply inert here rather than needing
   their own reset. */
@media(max-width:600px){
  #home-lb-list{display:block}
}
/* Collapsed to one line by default on phone-sized screens -- the full
   card (title, a big point number, a name, and an italic blurb) ran
   long enough to push the mode buttons a full scroll below the fold.
   Same spot in the layout either way, just hidden until tapped open;
   see app.js's own refreshHomeTrivia (adds .trivia-loaded once there's
   real content to show) and #btn-trivia-toggle's click handler (adds
   .trivia-expanded). Untouched above 600px, where the card was never
   collapsed to begin with. */
.trivia-toggle-btn{display:none}
@media(max-width:600px){
  #home-trivia.trivia-loaded:not(.trivia-expanded) #trivia-content{display:none}
  #home-trivia.trivia-loaded:not(.trivia-expanded) .trivia-toggle-btn{
    display:flex;align-items:center;justify-content:center;gap:.5rem;width:100%;
    font-family:var(--serif);font-weight:800;font-size:.95rem;color:var(--ink);
    background:none;border:0;padding:.3rem 0;cursor:pointer;
  }
  #home-trivia.trivia-loaded:not(.trivia-expanded) .trivia-toggle-btn .icon-glyph{
    width:1rem;height:1rem;background-color:var(--gold);flex:none;
  }
}
/* Icons live as ::before content rather than markup so they can't be wiped
   out by the JS that rewrites #btn-daily's textContent on every render.
   Each is a plain white silhouette used as a mask -- background-color:
   currentColor means it always matches that button's own text color, so
   it tracks night mode automatically with no per-theme overrides. */
#btn-start::before,
#btn-start-blind::before,
#btn-start-auction::before,
#btn-start-playoff::before,
#btn-start-team::before,
#btn-start-seasonsim::before,
#btn-daily::before{
  content:'';display:inline-block;width:1.15em;height:1.15em;
  margin-right:.55rem;vertical-align:-.18em;background-color:currentColor;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
#btn-start::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMiwyIEgyMiBWMjIgSDIgWiBNNyw3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE0xNyw3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE03LDE3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE0xNywxNyBtLTIuMiwwIGEyLjIsMi4yIDAgMSwwIDQuNCwwIGEyLjIsMi4yIDAgMSwwIC00LjQsMCBNMTIsMTIgbS0yLjIsMCBhMi4yLDIuMiAwIDEsMCA0LjQsMCBhMi4yLDIuMiAwIDEsMCAtNC40LDAnLz48L3N2Zz4=");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMiwyIEgyMiBWMjIgSDIgWiBNNyw3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE0xNyw3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE03LDE3IG0tMi4yLDAgYTIuMiwyLjIgMCAxLDAgNC40LDAgYTIuMiwyLjIgMCAxLDAgLTQuNCwwIE0xNywxNyBtLTIuMiwwIGEyLjIsMi4yIDAgMSwwIDQuNCwwIGEyLjIsMi4yIDAgMSwwIC00LjQsMCBNMTIsMTIgbS0yLjIsMCBhMi4yLDIuMiAwIDEsMCA0LjQsMCBhMi4yLDIuMiAwIDEsMCAtNC40LDAnLz48L3N2Zz4=");
}
#btn-start-blind::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHJlY3QgeD0nMScgeT0nOCcgd2lkdGg9JzknIGhlaWdodD0nOCcgcng9JzIuNScgZmlsbD0nI2ZmZicvPjxyZWN0IHg9JzE0JyB5PSc4JyB3aWR0aD0nOScgaGVpZ2h0PSc4JyByeD0nMi41JyBmaWxsPScjZmZmJy8+PHJlY3QgeD0nMTAnIHk9JzEwJyB3aWR0aD0nNCcgaGVpZ2h0PScyJyBmaWxsPScjZmZmJy8+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHJlY3QgeD0nMScgeT0nOCcgd2lkdGg9JzknIGhlaWdodD0nOCcgcng9JzIuNScgZmlsbD0nI2ZmZicvPjxyZWN0IHg9JzE0JyB5PSc4JyB3aWR0aD0nOScgaGVpZ2h0PSc4JyByeD0nMi41JyBmaWxsPScjZmZmJy8+PHJlY3QgeD0nMTAnIHk9JzEwJyB3aWR0aD0nNCcgaGVpZ2h0PScyJyBmaWxsPScjZmZmJy8+PC9zdmc+");
}
#btn-start-auction::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMyw2IEwxNSw2IEwyMiwxMiBMMTUsMTggTDMsMTggWiBNOCwxMiBtLTIuMiwwIGEyLjIsMi4yIDAgMSwwIDQuNCwwIGEyLjIsMi4yIDAgMSwwIC00LjQsMCcvPjwvc3ZnPg==");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMyw2IEwxNSw2IEwyMiwxMiBMMTUsMTggTDMsMTggWiBNOCwxMiBtLTIuMiwwIGEyLjIsMi4yIDAgMSwwIDQuNCwwIGEyLjIsMi4yIDAgMSwwIC00LjQsMCcvPjwvc3ZnPg==");
}
#btn-start-playoff::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nbm9uZScgc3Ryb2tlPScjZmZmJyBzdHJva2Utd2lkdGg9JzInIHN0cm9rZS1saW5lY2FwPSdzcXVhcmUnIGQ9J00yLDUgTDgsNSBNMiwxMCBMOCwxMCBNOCw1IEw4LDEwIE04LDcuNSBMMTIsNy41IE0yLDE0IEw4LDE0IE0yLDE5IEw4LDE5IE04LDE0IEw4LDE5IE04LDE2LjUgTDEyLDE2LjUgTTEyLDcuNSBMMTIsMTYuNSBNMTIsMTIgTDIwLDEyJy8+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nbm9uZScgc3Ryb2tlPScjZmZmJyBzdHJva2Utd2lkdGg9JzInIHN0cm9rZS1saW5lY2FwPSdzcXVhcmUnIGQ9J00yLDUgTDgsNSBNMiwxMCBMOCwxMCBNOCw1IEw4LDEwIE04LDcuNSBMMTIsNy41IE0yLDE0IEw4LDE0IE0yLDE5IEw4LDE5IE04LDE0IEw4LDE5IE04LDE2LjUgTDEyLDE2LjUgTTEyLDcuNSBMMTIsMTYuNSBNMTIsMTIgTDIwLDEyJy8+PC9zdmc+");
}
#btn-daily::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTIsMTIgUTEyLDIgMjIsMTIgUTEyLDIyIDIsMTIgWicvPjwvc3ZnPg==");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTIsMTIgUTEyLDIgMjIsMTIgUTEyLDIyIDIsMTIgWicvPjwvc3ZnPg==");
}
#btn-start-team::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDIwLDUgTDIwLDExIEMyMCwxNyAxNiwyMSAxMiwyMiBDOCwyMSA0LDE3IDQsMTEgTDQsNSBaJy8+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIgTDIwLDUgTDIwLDExIEMyMCwxNyAxNiwyMSAxMiwyMiBDOCwyMSA0LDE3IDQsMTEgTDQsNSBaJy8+PC9zdmc+");
}
/* An ascending bar chart -- the same "stats add up across a season"
   idea as the site's own .icon-chart glyph elsewhere, not a new visual
   concept invented just for this one button. */
#btn-start-seasonsim::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMjAgSDggVjEzIEg0IFogTTEwLDIwIEgxNCBWOCBIMTAgWiBNMTYsMjAgSDIwIFY0IEgxNiBaJy8+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTQsMjAgSDggVjEzIEg0IFogTTEwLDIwIEgxNCBWOCBIMTAgWiBNMTYsMjAgSDIwIFY0IEgxNiBaJy8+PC9zdmc+");
}
.btn-blind{background:var(--ink-fixed);color:var(--paper-fixed);position:relative;overflow:visible}
.btn-blind:hover{background:color-mix(in srgb, var(--ink-fixed) 85%, white)}
.btn-playoff{background:var(--violet);color:var(--paper2);position:relative;overflow:visible}
.btn-playoff:hover{background:color-mix(in srgb, var(--violet) 85%, black)}
.fan-fav-badge{
  position:absolute;top:-.75rem;right:-.7rem;
  background:var(--gold);color:var(--ink-fixed);
  font-family:var(--mono);font-size:.56rem;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;padding:.3rem .55rem;border:2px solid var(--ink);
  border-radius:999px;box-shadow:3px 3px 0 var(--ink);
  transform:rotate(7deg);pointer-events:none;
}

/* ---------- hero modes: auction | daily challenge | blind, classic below ---------- */
/* Lives outside start-layout on purpose -- see the comment on .hero-bottom
   above. Same 1440px content width as the topbar stripe and account bar,
   so the buttons actually use the page's full width instead of being
   boxed into the ~680px column between the trivia and leaderboard cards. */
.hero-modes{max-width:1440px;margin:0 auto 1.6rem;padding:0 1.25rem}
/* Classic and blind flank the daily card as tall side panels, with
   auction as its own full-width banner underneath -- the daily challenge
   is the one thing worth returning for every day, so it stays the visual
   center instead of getting buried among four equal-weight buttons. All
   four are one flex/grid container (.mode-quad) rather than a panel-trio
   plus a separate banner sibling, specifically so mobile's stack order
   and desktop's panels-vs-banner arrangement can be set independently --
   `order` controls the former, `grid-area` the latter, and neither
   depends on these elements' actual DOM order. Below 760px there isn't
   room for a card and two panels side by side without squeezing the
   daily card's own content, so it collapses to a single stacked column,
   daily first since it's the one thing worth checking every day. */
.mode-quad{
  display:flex;flex-direction:column;gap:.9rem;margin-bottom:.9rem;
}
@media(max-width:759px){
  .mode-trio-daily{order:1}
  #btn-start-blind{order:2}
  .mode-classic{order:3}
  .mode-tier2{order:4}
}
.mode-trio-daily{margin:0;max-width:none}
/* Auction/Playoff/Team/Season Sim used to each be their own full-width
   row in .mode-quad's grid -- four consecutive colored stripes with
   nothing but a corner badge telling them apart. Grouped into one
   container instead so desktop can lay them out 2x2 (see the min-width
   rule below) rather than stacking every one of them full-width; on
   mobile this is just a plain vertical stack, same as before, since
   there's no room for two across down here. */
.mode-tier2{display:flex;flex-direction:column;gap:.9rem}
.btn-panel{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.8rem;
  width:100%;min-height:130px;padding:1.2rem 1rem;
  font-size:clamp(1.05rem,2vw,1.6rem);letter-spacing:.1em;
  border-width:4px;box-shadow:9px 9px 0 var(--ink);position:relative;overflow:visible;
}
.btn-panel:hover{transform:translate(3px,3px);box-shadow:6px 6px 0 var(--ink), 0 10px 26px rgba(0,0,0,.3)}
.btn-panel:active{transform:translate(6px,6px);box-shadow:3px 3px 0 var(--ink)}
/* Same specificity as the shared icon rule above (one id + one pseudo) --
   declared later, so these two win the tie: a bigger glyph stacked above
   the label instead of inline before it, sized for a tile rather than a
   line of text. Targets whichever two buttons are the panels right now
   (classic + blind) rather than a role-neutral selector, since which
   button is a panel vs. the banner is exactly what this layout swaps. */
#btn-start::before,
#btn-start-blind::before{
  width:2.6em;height:2.6em;margin:0;vertical-align:0;
}
.btn-lg{margin-bottom:.4rem}
@media(min-width:760px){
  .mode-quad{
    display:grid;grid-template-columns:1fr 1.6fr 1fr;align-items:stretch;gap:1.4rem;
    grid-template-areas:"classic daily blind" "tier2 tier2 tier2";
  }
  .mode-classic{grid-area:classic}
  .mode-trio-daily{grid-area:daily}
  #btn-start-blind{grid-area:blind}
  /* 2x2 instead of one row per mode -- auction/playoff fill the first
     row, team/seasonsim the second, in plain DOM order (see the
     .mode-tier2 markup in index.html). align-items:stretch (the grid
     default) matches each row's own two cards to the taller one's
     height, same idea .mode-quad's own top row already relies on. */
  .mode-tier2{grid-area:tier2;display:grid;grid-template-columns:1fr 1fr;gap:1.4rem}
}
/* Same btn-{color} / btn-lg / mode-{name} split as Playoff and Team
   Mode right above -- btn-seasonsim carries only the color (the site's
   own "new feature" gold, see the fan-fav-badge default, rather than a
   dedicated palette entry for a mode that isn't permanent yet), mode-
   seasonsim carries only grid placement (see the desktop/mobile rules
   below). Mixing the two into one class is what made this button end
   up on .btn-panel's small-card sizing instead of .btn-lg's full-width
   banner treatment every other mode button here actually uses. */
.btn-seasonsim{background:var(--gold);color:var(--ink-fixed);position:relative;overflow:visible}
.btn-seasonsim:hover{background:color-mix(in srgb, var(--gold) 85%, black)}
.btn-team{background:var(--teal);color:var(--paper2);position:relative;overflow:visible}
.btn-team:hover{background:color-mix(in srgb, var(--teal) 80%, black)}
/* Same teal as the Team Mode button itself, so the ad popup's heading
   reads as the same feature at a glance. */
.team-mode-accent{color:var(--teal)}
.seasonsim-accent{color:var(--gold)}
/* Budget lives on the auction card itself now -- one bordered/shadowed
   block (the wrapper carries the card chrome that used to live on the
   button alone) instead of a button plus a separate strip of chips below
   it, so picking a budget reads as part of the auction card rather than a
   control floating loose on the main page. The trigger button and the
   preset chips are siblings inside that wrapper, not nested -- a <button>
   can't contain the chips' own <button> elements (invalid HTML) -- so
   each keeps its own click handler with no bubbling to guard against. */
.mode-auction{
  background:var(--brick);border:4px solid var(--ink);border-radius:var(--r);
  box-shadow:12px 12px 0 var(--ink);transition:transform .1s,box-shadow .1s;
}
.mode-auction:hover{transform:translate(4px,4px);box-shadow:8px 8px 0 var(--ink), 0 10px 26px rgba(0,0,0,.3)}
.mode-auction:active{transform:translate(8px,8px);box-shadow:4px 4px 0 var(--ink)}
.btn-auction-main{
  display:block;width:100%;background:none;border:0;color:var(--paper2);cursor:pointer;
  font-family:var(--mono);font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  font-size:clamp(1.4rem,5vw,2rem);padding:2rem 2rem 1.3rem;
}
.auction-presets{
  display:flex;align-items:center;justify-content:center;gap:.5rem;flex-wrap:wrap;
  padding:1.1rem 1rem 1.6rem;margin:0 1.5rem;border-top:2px dashed rgba(255,255,255,.3);
}
.auction-presets-lbl{font-family:var(--mono);font-size:.64rem;letter-spacing:.1em;text-transform:uppercase;color:var(--paper3)}
.preset-chip{
  font-family:var(--mono);font-weight:700;font-size:.68rem;letter-spacing:.04em;
  background:var(--paper2);border:2px solid var(--ink);color:var(--dim);
  padding:.4rem .75rem;border-radius:999px;cursor:pointer;transition:.12s;
}
.preset-chip:hover{border-color:var(--ink3);color:var(--ink)}
/* Brick-on-brick would vanish against the auction card's own background
   now that the presets live on it, so the selected chip reads with a
   neutral gray instead of the card's own color -- and not green, gold or
   violet, all already spoken for elsewhere on the page. */
.preset-chip.active{background:var(--ink3);border-color:var(--ink3);color:var(--paper2)}
.src-note{color:var(--dim);font-size:.8rem;margin-top:1.4rem;font-family:var(--mono)}
/* More top margin/padding and a fainter, smaller legal paragraph than
   before -- it used to sit right under the mode buttons at nearly the
   same visual weight as everything above it, flattening the page's own
   momentum right at the point it should be winding down instead. The
   legal text itself also gets a narrower reading width so it doesn't
   read as a wall spanning the full page. */
.disclaimer{
  /* --dim, not --faint -- quieted a bit too far in an earlier pass; this
     is still real legal text someone might actually need to read, not a
     purely decorative label, so it needs to stay legible, not just quiet. */
  color:var(--dim);font-size:.72rem;line-height:1.7;font-family:var(--mono);
  width:100%;box-sizing:border-box;margin:2.6rem 0 0;padding:2.2rem 1.25rem 2.4rem;
  border-top:2px solid var(--line2);text-align:center;
}
.disclaimer > p:first-child{max-width:620px;margin-left:auto;margin-right:auto}
.footer-links{display:flex;gap:.6rem;justify-content:center;flex-wrap:wrap;margin:1.2rem 0}
.footer-link{
  font-family:var(--mono);font-weight:700;font-size:.64rem;letter-spacing:.06em;text-transform:uppercase;
  background:var(--paper2);border:2px solid var(--line2);color:var(--dim);
  padding:.4rem .9rem;border-radius:999px;text-decoration:none;transition:.12s;display:inline-block;
}
.footer-link:hover{border-color:var(--turf-dk);color:var(--ink)}
/* .footer-link's own rule already sets every visual property a <button>
   needs (background, border, font, padding) -- this just clears the
   couple of browser button defaults that would otherwise slip through
   underneath all that (default cursor, any native appearance chrome). */
.footer-link-btn{cursor:pointer;appearance:none;-webkit-appearance:none}
.footer-copyright{font-size:.68rem;margin-top:.6rem}
.footer-copyright a{color:var(--dim);text-decoration:underline;text-underline-offset:2px}
.footer-copyright a:hover{color:var(--turf-dk)}

/* ---------- static pages: terms / privacy / faq ---------- */
.static-page{max-width:720px;margin:0 auto;padding:clamp(2rem,6vh,3.5rem) 1.25rem 4rem}
.static-back{
  font-family:var(--mono);font-size:.68rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--dim);text-decoration:none;display:inline-block;margin-bottom:2rem;
}
.static-back:hover{color:var(--turf-dk)}
.static-title{
  font-family:var(--serif);font-weight:900;font-size:clamp(2rem,6vw,2.8rem);
  color:var(--ink);letter-spacing:-.01em;
}
.static-updated{font-family:var(--mono);font-size:.66rem;color:var(--faint);letter-spacing:.04em;margin:.7rem 0 2.6rem}
.static-body h2{font-family:var(--serif);font-weight:800;font-size:1.25rem;color:var(--ink);margin:2.1rem 0 .8rem}
.static-body h2:first-child{margin-top:0}
.static-body p{color:var(--ink2);line-height:1.7;margin-bottom:1rem;font-size:.95rem}
.static-body ul{margin:0 0 1.1rem 1.3rem;color:var(--ink2);line-height:1.7;font-size:.95rem}
.static-body li{margin-bottom:.4rem}
.static-body a{color:var(--turf-dk)}
.static-faq-item{margin-bottom:1.8rem}
.static-faq-q{
  font-family:var(--mono);font-weight:700;font-size:.85rem;color:var(--ink);
  margin-bottom:.5rem;display:flex;gap:.5rem;
}
.static-faq-q::before{content:'Q.';color:var(--turf-dk);flex:none}
.static-faq-a{color:var(--ink2);line-height:1.7;font-size:.95rem;padding-left:1.4rem}

/* A mode landing page's above-the-fold preview: three real rolls (see
   the page's own HTML for which), reusing the actual in-game roll
   card/board/pick classes verbatim so it's pixel-identical to the real
   thing rather than a screenshot -- no extra image bytes, and it
   automatically follows the site's light/dark theme. pick-static only
   strips the pointer cursor .pick otherwise implies; no .elig/.inelig
   class means none of the hover/dim states apply, since this isn't
   actually clickable. */
.mode-preview-meta{font-family:var(--mono);font-size:.7rem;color:var(--dim);padding:0 .7rem;margin:-.1rem 0 .35rem}
.pick-static{cursor:default}
.mode-preview-caption{font-family:var(--mono);font-size:.68rem;color:var(--dim);text-align:center;padding:.8rem;border-top:2px dashed var(--line2)}

/* Links to the other four mode pages, sitting below a page's own CTA(s)
   so someone who isn't ready to play this particular mode has
   somewhere else on the site to go instead of just leaving. */
.static-modes-nav{font-size:.85rem;margin-top:.6rem}
.static-modes-nav a{margin-right:.3rem}


/* CTA buttons on a mode's own landing page (daily.html, classic.html, etc.)
   -- these send `?play=<mode>` back to the app (see app.js's
   handlePlayDeepLink) rather than reusing the in-game mode buttons'
   classes directly, since those are tuned for the start screen's grid
   layout, not a standalone link on a static page. Color per mode
   mirrors that mode's own accent everywhere else it shows up (the
   leaderboard hub's tabs, the mode buttons themselves). */
.static-cta{
  display:inline-block;margin:.5rem .6rem 1rem 0;padding:1rem 1.8rem;
  font-family:var(--mono);font-weight:700;font-size:.8rem;letter-spacing:.12em;text-transform:uppercase;
  border:3px solid var(--ink);border-radius:var(--r);text-decoration:none;
  box-shadow:6px 6px 0 var(--ink);transition:transform .1s,box-shadow .1s;
}
.static-cta:hover{transform:translate(3px,3px);box-shadow:3px 3px 0 var(--ink)}
.static-cta:active{transform:translate(6px,6px);box-shadow:0 0 0 var(--ink)}
/* Chained with the shared .static-cta class (every usage already
   carries both, e.g. class="static-cta static-cta-blind") rather than
   left as a bare single-class selector -- .static-body a's own color
   rule is (0,0,1,1) specificity, which otherwise beat a bare
   .static-cta-*'s (0,0,1,0) and left every one of these buttons
   showing green link text instead of its intended color. */
.static-cta.static-cta-daily{background:var(--gold);color:var(--ink-fixed)}
.static-cta.static-cta-classic{background:var(--turf);color:var(--paper2)}
.static-cta.static-cta-blind{background:var(--ink-fixed);color:var(--paper-fixed)}
.static-cta.static-cta-playoff{background:var(--violet);color:var(--paper2)}
.static-cta.static-cta-auction{background:var(--brick);color:var(--paper2)}
.static-cta.static-cta-team{background:var(--teal);color:var(--paper2)}
.static-cta.static-cta-seasonsim{background:var(--gold);color:var(--ink-fixed)}

/* ---------- topbar ---------- */
.topbar{
  position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;
  gap:1rem;padding:.7rem 1.25rem;background:var(--paper2);border-bottom:3px solid var(--ink);
}
/* Thin bar tracking picks-completed-of-total, pinned to the topbar's own
   bottom edge so it scrolls/sticks as one unit with it rather than
   needing its own sticky offset. */
.draft-progress{position:absolute;left:0;right:0;bottom:-3px;height:4px;background:var(--line2)}
.draft-progress-fill{height:100%;width:0%;background:var(--gold);transition:width .35s ease}
/* Mobile only -- #slot-banner's own "Pick 4 of 9" scrolls out of view
   once the player pool is long, same reason .pick-toast exists (see its
   own comment). Wraps onto its own line inside the topbar rather than
   needing a second sticky element with its own offset math, the same
   trick .lb-rival already uses inside .lb-row. */
.mobile-pick-strip{display:none}
@media(max-width:860px){
  .topbar{flex-wrap:wrap}
  .mobile-pick-strip{
    display:block;flex-basis:100%;text-align:center;
    font-family:var(--mono);font-weight:700;font-size:.68rem;letter-spacing:.04em;
    color:var(--dim);padding-top:.35rem;margin-top:.25rem;border-top:1px solid var(--line);
  }
}
.topbar-left{display:flex;align-items:center;gap:.85rem;min-width:0}
.btn-menu{
  font-family:var(--mono);font-weight:700;font-size:.66rem;letter-spacing:.12em;text-transform:uppercase;
  background:var(--paper);border:2px solid var(--ink);border-radius:var(--r);color:var(--ink);
  padding:.42rem .7rem;cursor:pointer;box-shadow:3px 3px 0 var(--ink);transition:.1s;white-space:nowrap;
}
.btn-menu:hover{background:var(--turf);color:var(--paper2);transform:translate(1px,1px);box-shadow:2px 2px 0 var(--ink)}
.btn-menu:active{transform:translate(3px,3px);box-shadow:0 0 0 var(--ink)}
.blind-flag{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;
  /* Fixed near-black background, not the theme-adaptive --ink -- --ink
     flips to cream in night mode, which would take gold text down to an
     unreadable gold-on-cream. See --paper-fixed's own comment. */
  background:var(--ink-fixed);color:var(--gold);border:2px solid var(--ink-fixed);border-radius:var(--r);padding:.24rem .55rem;
}
.blind-flag[hidden]{display:none}
.daily-flag{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;
  background:var(--gold);color:var(--ink-fixed);border:2px solid var(--ink);border-radius:var(--r);padding:.24rem .55rem;
}
.daily-flag[hidden]{display:none}
.auction-flag{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;
  background:var(--brick);color:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);padding:.24rem .55rem;
}
.auction-flag[hidden]{display:none}
.playoff-flag{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;
  background:var(--gold);color:var(--ink-fixed);border:2px solid var(--ink);border-radius:var(--r);padding:.24rem .55rem;
}
.playoff-flag[hidden]{display:none}
.team-flag{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.16em;text-transform:uppercase;
  background:var(--teal);color:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);padding:.24rem .55rem;
}
.team-flag[hidden]{display:none}
.topbar-brand{
  font-family:var(--serif);font-weight:900;text-transform:uppercase;
  letter-spacing:-.01em;font-size:1.5rem;color:var(--ink);display:flex;gap:.3rem;
}
.topbar-brand .accent{color:var(--turf)}
@media(max-width:560px){.topbar-brand{display:none}}
.topbar-scores{display:flex;gap:.6rem}
.topbar-score{
  display:flex;flex-direction:column-reverse;align-items:center;gap:.05rem;
  /* Fixed near-black, not the theme-adaptive --ink -- see .blind-flag's
     comment. This is the running Total/Budget pill shown on every draft
     screen, so a gold-on-cream flip in night mode would be a real
     legibility problem, not just an aesthetic one. */
  background:var(--ink-fixed);border:2px solid var(--ink-fixed);border-radius:var(--r);
  padding:.3rem 1.1rem .38rem;box-shadow:var(--shadow-sm);
}
.topbar-score[hidden]{display:none}
.topbar-score .lbl{font-size:.55rem;color:var(--paper3);letter-spacing:.2em}
.topbar-score .val{font-family:var(--mono);font-weight:700;font-size:1.45rem;color:var(--gold);line-height:1.05}
#topbar-budget{background:var(--brick);border-color:var(--brick)}
#topbar-budget .val{color:var(--paper2)}
.per-slot{font-family:var(--mono);font-size:.55rem;color:var(--paper3);opacity:.85;letter-spacing:.04em}
/* How much you could actually spend on your very next pick, above the
   bare $1-per-remaining-slot floor canAfford() already enforces --
   green means real room to chase an expensive player, red means
   you're picking from the board's cheap end for the rest of the round.
   Sits on the dark topbar-budget pill regardless of theme, so the
   track itself stays a fixed semi-transparent black rather than a
   theme color, letting any fill color read clearly against it. */
.pressure-meter{width:100%;height:4px;background:rgba(0,0,0,.3);border-radius:2px;overflow:hidden;margin-top:.32rem}
.pressure-fill{height:100%;border-radius:2px;width:100%;background:var(--turf-lt);transition:width .2s,background-color .2s}
.pressure-fill.pressure-tight{background:var(--gold)}
.pressure-fill.pressure-critical{background:#ff5a45}

.layout{display:grid;grid-template-columns:262px 1fr;gap:1.4rem;max-width:1180px;margin:0 auto;padding:1.4rem 1.25rem 3rem}
.layout>*{min-width:0}
@media(max-width:860px){.layout{grid-template-columns:1fr}}

/* ---------- pick toast (mobile only) ---------- */
/* Below 860px the roster stacks above the board (see .layout), so by the
   time someone taps a pick further down a long board, the roster's own
   reveal-on-pick score is scrolled out of view. This surfaces the same
   reveal as a toast so a mobile drafter still gets the moment without
   needing to scroll back up. Desktop already sees it live in the roster
   panel, so the toast stays hidden there entirely.
   Pinned to the TOP, not the bottom -- it used to sit at the bottom,
   which (once its own separate position:fixed containing-block bug was
   fixed -- it was nested inside #screen-game, same issue as the mobile
   nav bar, see that commit) turned out to still be the exact strip most
   mobile ad networks anchor their own sticky ad to, so it stayed
   invisible behind it. Sits below the mobile quick nav specifically at
   its own breakpoint so the two never overlap; above that width (681-
   860px, where the toast still shows but the nav doesn't) it sits near
   the very top instead. */
.pick-toast{
  position:fixed;left:50%;top:1.1rem;z-index:60;
  display:flex;align-items:center;gap:.55rem;
  background:var(--ink-fixed);color:var(--paper-fixed);border:2px solid var(--paper-fixed);border-radius:999px;
  padding:.55rem 1.1rem;font-family:var(--mono);font-weight:700;font-size:.78rem;letter-spacing:.02em;
  box-shadow:2px 2px 0 var(--ink-fixed);pointer-events:none;
  transform:translate(-50%,-150%);transition:transform .25s ease;
  max-width:calc(100vw - 2rem);
}
.pick-toast.show{transform:translate(-50%,0)}
.pick-toast-pts{opacity:.75}
@media(min-width:861px){.pick-toast{display:none}}
@media(max-width:680px){.pick-toast{top:4.1rem}}

/* ---------- mobile quick nav ---------- */
/* Hidden entirely above the 680px mobile breakpoint -- desktop already
   has the full topbar/menu for this. Fixed to the TOP of the viewport,
   not the bottom -- it started bottom-fixed, but that's the exact strip
   most mobile ad networks anchor their own sticky/anchor ad to, which
   just covered it. Not nested in .account-bar-outer either: that bar
   deliberately drops its own sticky positioning below 600px (its own
   comment: a permanently pinned copy of its tall multi-row mobile
   layout would eat too much of a phone screen), so nesting this inside
   it would mean this scrolls away too. z-index above .account-bar-
   outer's own 30 so it stays on top of it at the very top of the page,
   comfortably below every modal/toast (80+). body's padding-top makes
   room for it so the account bar's own content doesn't start out
   hidden underneath it. Still hidden during an active round (body.
   in-game-screen, toggled in show()) to keep a mobile board's already-
   tight vertical space from losing more of it to a bar that mode
   doesn't need. */
.mobile-bottom-nav{display:none}
@media(max-width:680px){
  body{padding-top:3.5rem}
  body.in-game-screen{padding-top:0}
  .mobile-bottom-nav{
    display:flex;position:fixed;left:0;right:0;top:0;z-index:70;
    border-bottom:2px solid var(--ink);background:var(--paper2);
  }
  body.in-game-screen .mobile-bottom-nav{display:none}
}
.mobile-nav-btn{
  flex:1;display:flex;flex-direction:column;align-items:center;gap:.2rem;
  background:none;border:0;padding:.55rem 0 .6rem;cursor:pointer;
  font-family:var(--mono);font-size:.62rem;font-weight:700;letter-spacing:.03em;
  color:var(--dim);
}
.mobile-nav-btn .icon-glyph{width:1.2rem;height:1.2rem;background-color:var(--dim)}
.mobile-nav-btn.active{color:var(--turf-dk)}
.mobile-nav-btn.active .icon-glyph{background-color:var(--turf-dk)}

/* ---------- roster ---------- */
.roster-col{display:flex;flex-direction:column;min-width:0}
.roster{display:flex;flex-direction:column;gap:.45rem;align-content:start}
@media(max-width:860px){.roster{flex-direction:row;flex-wrap:wrap}}
.roster-note{
  font-family:var(--mono);font-size:.66rem;color:var(--dim);line-height:1.5;margin-top:.5rem;
}
.roster-note:first-of-type{margin-top:.8rem;padding-top:.7rem;border-top:2px dashed var(--line2)}
/* Only a veiled board hands out the star -- and auction's means something
   different (best value vs. average, not most raw points), so it gets its
   own explanation rather than sharing blind mode's. */
.roster-note-star{display:none}
#screen-game.blind .roster-note-star{display:block}
.roster-note-star-value{display:none}
#screen-game.auction .roster-note-star-value{display:block}
.roster-note-auction{display:none}
#screen-game.auction .roster-note-auction{display:block}
/* A faint per-mode motif behind the whole draft screen, so which mode
   you're in reads at a glance from the screen itself, not just the small
   text flag in the topbar -- the board otherwise looks identical across
   every mode. Blind's "?" mirrors the exact placeholder every hidden
   pick-pts span already shows (see hidePts in app.js); Playoff shares it
   since it's the same veiled-board mechanic under the .blind class, not
   a separate one. Fixed, not absolute, so it stays anchored to one
   corner of the viewport regardless of how far the board scrolls, and
   z-index:0 keeps it behind every real element without needing to touch
   this screen's own stacking elsewhere. Opacity is intentionally low
   enough in both themes that it reads as texture, not a second thing
   competing with the board for attention. */
#screen-game.auction::after,
#screen-game.blind::after{
  content:'';position:fixed;top:12vh;right:-2rem;width:16rem;height:16rem;
  background-color:var(--ink);opacity:.05;pointer-events:none;z-index:0;
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
}
#screen-game.auction::after{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMDAgMTAwJz48dGV4dCB4PSc1MCcgeT0nNzUnIGZvbnQtZmFtaWx5PSdtb25vc3BhY2UnIGZvbnQtc2l6ZT0nOTInIGZvbnQtd2VpZ2h0PSc3MDAnIGZpbGw9JyNmZmYnIHRleHQtYW5jaG9yPSdtaWRkbGUnPiQ8L3RleHQ+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMDAgMTAwJz48dGV4dCB4PSc1MCcgeT0nNzUnIGZvbnQtZmFtaWx5PSdtb25vc3BhY2UnIGZvbnQtc2l6ZT0nOTInIGZvbnQtd2VpZ2h0PSc3MDAnIGZpbGw9JyNmZmYnIHRleHQtYW5jaG9yPSdtaWRkbGUnPiQ8L3RleHQ+PC9zdmc+");
}
#screen-game.blind::after{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMDAgMTAwJz48dGV4dCB4PSc1MCcgeT0nNzUnIGZvbnQtZmFtaWx5PSdtb25vc3BhY2UnIGZvbnQtc2l6ZT0nOTInIGZvbnQtd2VpZ2h0PSc3MDAnIGZpbGw9JyNmZmYnIHRleHQtYW5jaG9yPSdtaWRkbGUnPj88L3RleHQ+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMDAgMTAwJz48dGV4dCB4PSc1MCcgeT0nNzUnIGZvbnQtZmFtaWx5PSdtb25vc3BhY2UnIGZvbnQtc2l6ZT0nOTInIGZvbnQtd2VpZ2h0PSc3MDAnIGZpbGw9JyNmZmYnIHRleHQtYW5jaG9yPSdtaWRkbGUnPj88L3RleHQ+PC9zdmc+");
}
@media(max-width:700px){
  #screen-game.auction::after,
  #screen-game.blind::after{width:10rem;height:10rem;top:8vh;right:-1.5rem}
}
/* A faint whole-screen tint alongside the corner watermark above --
   auction leans gold (money), blind/playoff/team leans violet
   (mystery), both applied as a translucent overlay rather than a flat
   background-color, so the paper grain and yard-line texture underneath
   still show through, just tinted. */
#screen-game.auction{background-image:linear-gradient(color-mix(in srgb, var(--gold) 6%, transparent), color-mix(in srgb, var(--gold) 6%, transparent));}
#screen-game.blind{background-image:linear-gradient(color-mix(in srgb, var(--violet) 6%, transparent), color-mix(in srgb, var(--violet) 6%, transparent));}
.slot{
  position:relative;display:flex;align-items:center;gap:.6rem;padding:.5rem .65rem;border-radius:var(--r);
  background:var(--paper2);border:2px solid var(--line2);min-height:48px;transition:.13s;
}
.slot.done{padding-bottom:.62rem}
@media(max-width:860px){.slot{flex:1 1 calc(33.333% - .4rem);min-width:0}}
/* Three across leaves a filled slot no room for the name next to the score. */
@media(max-width:600px){.slot{flex:1 1 calc(50% - .4rem)}}
.slot.on{border-color:var(--ink);background:var(--paper2);box-shadow:var(--shadow-sm)}
.slot.done{border-color:var(--ink);background:var(--paper3);border-left:5px solid var(--team-color, var(--ink))}
/* A bigger payoff than the plain count-up every pick already gets (see
   renderRoster() in app.js), reserved for a pick whose number really was
   hidden a moment ago -- the card actually swings face-up like a real
   card being turned over (rotateY, never past 90deg so the backface --
   which this single-faced card doesn't have content for -- never comes
   into view), with the same gold flash as before riding along at the
   midpoint instead of a flat color sweep. Settles back to .slot.done's
   own background and a flat 0deg exactly, so nothing looks different
   once the animation ends. */
@keyframes reveal-flash{
  0%{transform:perspective(600px) rotateY(-90deg);background-color:var(--gold);box-shadow:0 0 0 2px var(--gold)}
  60%{transform:perspective(600px) rotateY(12deg);background-color:var(--gold);box-shadow:0 0 0 2px var(--gold)}
  100%{transform:perspective(600px) rotateY(0deg);background-color:var(--paper3);box-shadow:none}
}
.slot.reveal-flash{animation:reveal-flash .55s ease-out}
@media(prefers-reduced-motion:reduce){.slot.reveal-flash{animation:none}}
.slot-tag{font-weight:700;font-size:.66rem;color:var(--dim);width:40px;flex:none}
.slot-tag.pos-flex{color:var(--gold)}
.slot.on .slot-tag{color:var(--turf-dk)}
.slot.done .slot-tag{color:var(--ink)}
/* A filled slot's position used to be just a small colored letter --
   easy to miss, and the one thing on the row NOT already carrying a
   color (the border there is the drafted TEAM's color, see
   --team-color). A solid chip makes the position itself legible at a
   glance instead of requiring a squint at two-letter text. Same
   specificity as the two rules above (three classes each) -- placed
   after them, so this wins the tie once a slot is filled. */
.slot .slot-tag.pos-qb,.slot .slot-tag.pos-rb,.slot .slot-tag.pos-wr,
.slot .slot-tag.pos-te,.slot .slot-tag.pos-k,.slot .slot-tag.pos-def{
  color:var(--paper-fixed);text-align:center;padding:.22rem 0;border-radius:var(--r);
}
.slot .slot-tag.pos-qb{background:var(--pos-qb-bg)}
.slot .slot-tag.pos-rb{background:var(--pos-rb-bg)}
.slot .slot-tag.pos-wr{background:var(--pos-wr-bg)}
.slot .slot-tag.pos-te{background:var(--pos-te-bg)}
.slot .slot-tag.pos-k{background:var(--pos-k-bg)}
.slot .slot-tag.pos-def{background:var(--pos-def-bg)}
.slot-fill{flex:1;min-width:0}
.slot-empty{color:var(--faint);font-size:.82rem;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.slot.on .slot-empty{color:var(--turf)}
.slot-player{font-size:.85rem;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.slot-meta{font-family:var(--mono);font-size:.63rem;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.slot-star{color:var(--gold);-webkit-text-stroke:.4px var(--ink);margin-right:.24rem;font-size:.8rem}
.slot-score{flex:none;text-align:right;line-height:1.1}
.slot-pts{font-family:var(--mono);font-weight:700;font-size:.92rem;color:var(--turf-dk)}
/* Percentile of that week among every performance the game can roll at the
   position -- the rail is the same number, readable without stopping to read. */
.slot-pctl{font-family:var(--mono);font-size:.58rem;letter-spacing:.06em;color:var(--dim);margin-top:.1rem}
.slot-wrank{font-family:var(--mono);font-size:.58rem;letter-spacing:.03em;font-weight:700;color:var(--turf-dk);margin-top:.15rem;white-space:nowrap}
.slot-price{font-family:var(--mono);font-size:.58rem;letter-spacing:.03em;font-weight:700;color:var(--brick);margin-top:.15rem;white-space:nowrap}
.slot-vsavg{font-family:var(--mono);font-size:.58rem;letter-spacing:.03em;font-weight:700;color:var(--dim);margin-top:.15rem;white-space:nowrap}
.slot-vsavg.above{color:var(--turf-dk)}
.slot-vsavg.below{color:var(--brick)}
.slot-rail{position:absolute;left:0;right:0;bottom:0;height:4px;background:var(--line);border-radius:0 0 1px 1px;overflow:hidden}
.slot-rail i{display:block;height:100%;background:var(--turf)}
.pick-pts.veiled{color:var(--faint)}

/* ---------- stage ---------- */
.slot-banner{display:flex;align-items:baseline;gap:.7rem;margin-bottom:1rem;flex-wrap:wrap}
.on-clock{font-size:.6rem;color:var(--dim)}
#slot-name{
  font-family:var(--serif);font-weight:900;font-size:clamp(1.8rem,4.4vw,2.5rem);
  text-transform:uppercase;letter-spacing:-.015em;line-height:1;color:var(--ink);
}
.slot-hint{font-family:var(--mono);color:var(--dim);font-size:.78rem}

.roll-card{background:var(--paper2);border:3px solid var(--ink);border-radius:var(--r);box-shadow:var(--shadow);overflow:hidden}

/* A full-width callout above the dials for a stinker/shootout/game
   breaker (see isSpecialRoll() in app.js) -- deliberately bigger and
   louder than a small pill next to the matchup line, since the reroll
   buttons are locked the moment this shows (a player who doesn't
   notice why their rerolls stopped working is a worse experience than
   one who does). Rebuilt with a fresh .special-roll-in class on every
   render so a new special roll always replays the entrance below, even
   if the previous pick was also special. */
.special-roll-banner{
  display:flex;align-items:center;gap:.7rem;padding:.85rem 1rem;
  border-bottom:3px solid var(--ink);
}
.special-roll-banner[hidden]{display:none}
.special-roll-banner .icon-glyph{width:1.9rem;height:1.9rem;flex:none}
.special-roll-text{display:flex;flex-direction:column;gap:.1rem;min-width:0}
.special-roll-label{
  font-family:var(--serif);font-weight:900;font-size:1.05rem;
  letter-spacing:.01em;text-transform:uppercase;
}
.special-roll-desc{font-family:var(--mono);font-size:.68rem;opacity:.85}
/* Gold for a huge individual game, a true black chip for a dead one
   (paired --ink-fixed/--paper-fixed rather than --ink, which flips to
   cream in night mode -- same fix as every other black-chip component
   on the page), brick for a whole game that got out of hand. */
.special-roll-breaker{background:var(--gold);color:var(--ink-fixed)}
.special-roll-defwreck{background:var(--pos-def-bg);color:var(--paper2)}
.special-roll-stinker{background:var(--ink-fixed);color:var(--paper-fixed)}
.special-roll-shootout{background:var(--brick);color:var(--paper2)}
/* Three times rarer than the other three (0.1% vs 0.3% each), so it
   gets a treatment none of them do -- a continuous light sweep, not
   just the shared one-shot entrance pop below, so it keeps reading as
   the rarest one for as long as it's on screen, not just for the
   first 0.4s. A separate ::after (its own `animation`, not the
   banner's own background) rather than animating the banner's
   background directly, so it never fights the entrance pop for the
   same property. */
.special-roll-miracle{background:var(--violet);color:var(--paper2);position:relative;overflow:hidden}
.special-roll-miracle::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(120deg, transparent 35%, rgba(255,255,255,.4) 50%, transparent 65%);
  background-size:260% 100%;
  animation:special-roll-shimmer 2.6s ease-in-out infinite;
}
@keyframes special-roll-shimmer{
  0%{background-position:180% 0}
  100%{background-position:-80% 0}
}
@keyframes special-roll-in{
  0%{transform:scale(.9) translateY(-6px);opacity:0}
  60%{transform:scale(1.02) translateY(0);opacity:1}
  100%{transform:scale(1)}
}
.special-roll-banner.special-roll-in{animation:special-roll-in .4s cubic-bezier(.32,1.7,.5,1) both}
@media (prefers-reduced-motion:reduce){
  .special-roll-banner.special-roll-in{animation:none}
  .special-roll-miracle::after{animation:none;display:none}
}

/* First-round walkthrough (see startCoachmark in app.js) -- a moving
   spotlight cutout (a transparent box whose oversized box-shadow dims
   everything else) plus a small tooltip bubble, both repositioned in
   JS against whatever real element the current step targets, rather
   than a fixed tour baked into the layout. The wrapper itself ignores
   clicks (pointer-events:none) so only the spotlight's own dimmed
   backdrop and the bubble's buttons are actually interactive. */
.coachmark{position:fixed;inset:0;z-index:70;pointer-events:none}
.coachmark[hidden]{display:none}
.coachmark-spotlight{
  position:absolute;border-radius:var(--r);
  box-shadow:0 0 0 9999px color-mix(in srgb, var(--ink-fixed) 65%, transparent);
  transition:all .3s ease;pointer-events:auto;
}
.coachmark-bubble{
  position:absolute;max-width:280px;background:var(--paper2);color:var(--ink);
  border:2px solid var(--ink);border-radius:var(--r);padding:.9rem 1rem;
  box-shadow:var(--shadow-sm);pointer-events:auto;transition:all .3s ease;
}
.coachmark-text{margin:0 0 .7rem;font-size:.85rem;line-height:1.4}
.coachmark-actions{display:flex;align-items:center;justify-content:space-between;gap:.8rem}
.coachmark-actions .btn{padding:.5rem 1rem;font-size:.68rem}
@media (prefers-reduced-motion:reduce){
  .coachmark-spotlight,.coachmark-bubble{transition:none}
}

.rollbar{display:grid;grid-template-columns:repeat(3,1fr);border-bottom:3px solid var(--ink);background:var(--paper3)}
/* Season Sim has no week axis at all (see renderSeasonSimBoard in
   app.js, which hides the whole week dial rather than just its reroll
   button) -- down to two dials, so they split the row down the middle
   instead of leaving a third empty track. */
#screen-game.seasonsim .rollbar{grid-template-columns:repeat(2,1fr)}
.dial{position:relative;padding:.8rem .8rem 3.4rem;text-align:center;border-right:2px solid var(--line2)}
.dial:last-child{border-right:0}
.dial-lbl{display:block;font-size:.56rem;color:var(--dim);margin-bottom:.15rem}
.dial-val{display:block;font-family:var(--serif);font-weight:900;font-size:clamp(1.25rem,4.4vw,1.75rem);line-height:1.1;text-transform:uppercase;color:var(--ink);white-space:nowrap}
.dial-val.spin{animation:spin .42s ease-out}
@keyframes rv{from{opacity:0;transform:translateY(-7px)}to{opacity:1;transform:none}}
@keyframes spin{0%{opacity:0;transform:translateY(-9px) scale(.94)}100%{opacity:1;transform:none}}
.reroll{
  position:absolute;bottom:.6rem;left:50%;transform:translateX(-50%);
  background:var(--gold);border:3px solid var(--ink);color:var(--ink-fixed);
  font-family:var(--mono);font-weight:700;font-size:clamp(.8rem,3.2vw,1rem);
  padding:.55rem 1rem;border-radius:var(--r);cursor:pointer;box-shadow:4px 4px 0 var(--ink);
  display:inline-flex;gap:.4rem;align-items:center;transition:.13s;white-space:nowrap;
}
/* Team Mode hides the team reroll button via the plain `hidden` attribute
   (see renderRoll in app.js) -- author CSS always wins over the browser's
   own [hidden]{display:none} default regardless of specificity, so the
   unconditional display:inline-flex above would otherwise keep it
   visible (and clickable) despite being "hidden". */
.reroll[hidden]{display:none}
.reroll:hover:not(:disabled){background:var(--turf);color:var(--paper2);transform:translateX(-50%) translate(2px,2px);box-shadow:2px 2px 0 var(--ink)}
.reroll:active:not(:disabled){transform:translateX(-50%) translate(4px,4px);box-shadow:0 0 0 var(--ink)}
.reroll:disabled{opacity:.34;cursor:not-allowed;border-color:var(--line2);background:var(--paper2);box-shadow:none}
.reroll em{font-style:normal;font-weight:700;color:var(--ink-fixed)}
.reroll:hover:not(:disabled) em{color:var(--paper2)}
/* The rewarded-ad bonus reroll (see canWatchAdForReroll in app.js) --
   distinct violet instead of the normal gold, so "this one costs an ad"
   reads as a different kind of button at a glance, not just another
   reroll that happens to say AD on it. */
.reroll.reroll-ad{background:var(--violet);color:var(--paper2)}
.reroll.reroll-ad em{color:var(--paper2)}
.reroll.reroll-ad:hover:not(:disabled){background:var(--violet);filter:brightness(1.12)}
.reroll:disabled em{color:var(--faint)}

.matchup{
  padding:.65rem 1rem;font-family:var(--mono);font-size:.73rem;color:var(--ink2);
  border-bottom:2px solid var(--line2);border-left:5px solid var(--team-color, var(--ink));
  display:flex;gap:.55rem;align-items:center;flex-wrap:wrap;
}
.matchup .res{font-weight:700;padding:.1rem .42rem;border-radius:var(--r);font-size:.66rem;border:2px solid var(--ink);color:var(--paper2)}
.matchup .W{background:var(--turf-dk)}
.matchup .L{background:var(--brick)}
.matchup .T{background:var(--gold);color:var(--ink-fixed)}

.btn-reveal{
  display:flex;align-items:center;justify-content:center;gap:.4rem;width:100%;
  border-radius:0;border:none;border-bottom:2px solid var(--line2);box-shadow:none;
  padding:.55rem 1rem;font-size:.78rem;
}
.btn-reveal:hover{transform:none;box-shadow:none;background:var(--paper3)}
.btn-reveal:active{transform:none;box-shadow:none}
.btn-reveal:disabled{opacity:.55;cursor:default;background:var(--paper2)}
.btn-reveal[hidden]{display:none}

.board{padding:.45rem}
.pos-group{margin-bottom:.3rem}
.pos-head{font-size:.58rem;color:var(--dim);padding:.6rem .7rem .32rem;border-bottom:1px solid var(--line);margin-bottom:.25rem}
.pos-head.pos-qb{color:var(--pos-qb);border-bottom-color:var(--pos-qb)}
.pos-head.pos-rb{color:var(--pos-rb);border-bottom-color:var(--pos-rb)}
.pos-head.pos-wr{color:var(--pos-wr);border-bottom-color:var(--pos-wr)}
.pos-head.pos-te{color:var(--pos-te);border-bottom-color:var(--pos-te)}
.pos-head.pos-k{color:var(--pos-k);border-bottom-color:var(--pos-k)}
.pos-head.pos-def{color:var(--pos-def);border-bottom-color:var(--pos-def)}
.pick{
  display:flex;align-items:center;gap:.75rem;width:100%;text-align:left;
  background:transparent;border:2px solid transparent;border-radius:var(--r);
  padding:.5rem .65rem;cursor:pointer;transition:.12s;color:inherit;font:inherit;
}
.pick.elig:hover{background:var(--turf);border-color:var(--ink);box-shadow:var(--shadow-sm)}
.pick.inelig{opacity:.4;cursor:not-allowed}
.pick-pos{font-weight:700;font-size:.62rem;width:34px;flex:none;color:var(--dim)}
.pick.elig .pick-pos{color:var(--turf-dk)}
/* Same specificity as the eligible rule above (three classes each) --
   placed after it, so the tie goes to the position color, win or lose. */
.pick .pick-pos.pos-qb{color:var(--pos-qb)}
.pick .pick-pos.pos-rb{color:var(--pos-rb)}
.pick .pick-pos.pos-wr{color:var(--pos-wr)}
.pick .pick-pos.pos-te{color:var(--pos-te)}
.pick .pick-pos.pos-k{color:var(--pos-k)}
.pick .pick-pos.pos-def{color:var(--pos-def)}
.pick.elig:hover .pick-pos{color:var(--paper2)}
.pick-body{flex:1;min-width:0}
.pick-name{display:block;font-weight:600;font-size:.95rem;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.pick-line{display:block;font-family:var(--mono);font-size:.66rem;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:.05rem}
/* The stat the price is actually based on -- shown in place of the (hidden)
   line, since it's the one number an auction shopper needs to judge a price. */
.pick-avg{display:block;font-family:var(--mono);font-size:.66rem;color:var(--turf-dk);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:.05rem}
.pick.elig:hover .pick-name,.pick.elig:hover .pick-line,.pick.elig:hover .pick-avg,.pick.elig:hover .pick-pts{color:var(--paper2)}
/* Season Sim's own board has no single-game score to show -- season
   high and low stand in for it, so the drafter judges a boom/bust range
   instead of just reading off one already-decided number. */
.pick-highlow{display:block;font-family:var(--mono);font-size:.66rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:.05rem}
.pick-hi{color:var(--turf-dk)}
.pick-lo{color:var(--dim)}
.pick.elig:hover .pick-highlow,.pick.elig:hover .pick-hi,.pick.elig:hover .pick-lo{color:var(--paper2)}
.pick-pts{font-family:var(--mono);font-weight:700;font-size:1.15rem;flex:none;min-width:62px;text-align:right;color:var(--ink)}
.pick.elig .pick-pts{color:var(--turf-dk)}
.pick-price{
  font-family:var(--mono);font-weight:700;font-size:.72rem;flex:none;white-space:nowrap;
  background:var(--paper3);border:2px solid var(--line2);border-radius:var(--r);
  padding:.12rem .4rem;color:var(--turf-dk);
}
.pick-price.tier-cheap{color:var(--turf-dk);border-color:var(--turf-lt)}
.pick-price.tier-mid{color:#96701c;border-color:var(--gold)}
:root[data-theme="dark"] .pick-price.tier-mid{color:#d9a53c}
.pick-price.tier-expensive{color:var(--brick);border-color:var(--brick)}
.pick-price.price-over{color:var(--brick);border-color:var(--brick)}
.pick.elig:hover .pick-price{background:var(--paper2);border-color:var(--paper2)}
.pick-take{
  font-family:var(--mono);font-weight:700;font-size:.6rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--turf-dk);opacity:0;flex:none;transition:.12s;white-space:nowrap;
}
.pick.elig:hover .pick-take{opacity:1;color:var(--paper2)}
.pick.inelig .pick-take{opacity:1;color:var(--faint)}
@media(max-width:520px){.pick-take{display:none}}

.no-elig{padding:1.5rem 1rem;text-align:center;border-top:3px solid var(--ink);background:var(--paper3)}
.no-elig p{color:var(--ink2);margin-bottom:.9rem;font-family:var(--mono);font-size:.82rem}
.no-elig b{color:var(--turf-dk)}

/* ---------- result ---------- */
.result-wrap{max-width:660px;margin:0 auto;padding:clamp(2rem,6vh,4rem) 1.25rem 4rem;text-align:center}

/* ---------- leaderboard hub ---------- */
.lbhub-wrap{max-width:820px;margin:0 auto;padding:2.4rem 1.25rem 4rem}
.lbhub-heading{
  font-family:var(--serif);font-weight:900;text-transform:uppercase;color:var(--ink);
  font-size:clamp(1.8rem,5vw,2.6rem);margin-bottom:1.2rem;
}
.lbhub-panel{margin-bottom:0}
/* Flex row + tighter bottom margin for just the hub's own heading (see
   index.html) -- .lbhub-heading itself stays untouched since it's
   shared by a dozen other plain-text screens (Hall of Shame, Compare
   Players, Draft History...) that have no icon badge or subtitle to
   make room for. */
#screen-leaderboards .lbhub-heading{display:flex;align-items:center;gap:.55rem;margin-bottom:.5rem}
/* Circular gold badge around the trophy icon -- same "chip" treatment
   as .badge-card-icon, just sized off the heading's own font-size (em)
   so it scales with the heading's clamp() instead of needing its own
   breakpoint. */
.lbhub-heading-icon-wrap{
  display:flex;align-items:center;justify-content:center;flex:none;
  width:1.5em;height:1.5em;border-radius:50%;
  background:var(--gold);border:2px solid var(--ink);box-shadow:2px 2px 0 var(--ink);
}
/* Scoped to this one heading (see index.html), not .lbhub-heading
   itself -- that class is shared by a dozen other screens (Hall of
   Shame, Compare Players, Draft History...) a trophy has no business
   sitting next to. ink-fixed, not gold, now that the glyph sits on a
   gold-filled badge instead of directly on the page background. */
.lbhub-heading-icon{width:.78em;height:.78em;background-color:var(--ink-fixed)}
/* One-line explainer under the heading, above the mode tabs -- gives
   the hub a beat of orientation before the activity ticker and nine
   mode tabs throw a wall of options at a first-time visitor. */
.lbhub-heading-sub{
  font-family:var(--mono);font-size:.78rem;color:var(--dim);line-height:1.5;
  margin:0 0 1.4rem;max-width:48ch;
}
/* Per-mode top accent on the board panel itself (see loadLbHubBoard's
   own class toggle) -- same color each mode's own tab already wears
   when active (.lb-tab-normal.active etc.), just carried down onto the
   board underneath. ID + class, not the class alone: .lb-panel's own
   border shorthand is declared later in this file at equal
   specificity otherwise and would silently win back to plain ink, the
   same fix every other per-mode/per-board accent bar on this page
   already needed. */
#lbhub-board-panel{border-top-width:5px;border-top-color:var(--ink)}
#lbhub-board-panel.lbhub-mode-daily{border-top-color:var(--gold)}
#lbhub-board-panel.lbhub-mode-normal{border-top-color:var(--turf)}
#lbhub-board-panel.lbhub-mode-blind{border-top-color:var(--ink-fixed)}
#lbhub-board-panel.lbhub-mode-playoff{border-top-color:var(--violet)}
#lbhub-board-panel.lbhub-mode-auction70,
#lbhub-board-panel.lbhub-mode-auction100,
#lbhub-board-panel.lbhub-mode-auction150{border-top-color:var(--brick)}
#lbhub-board-panel.lbhub-mode-career{border-top-color:var(--turf-dk)}
#lbhub-board-panel.lbhub-mode-underdog{border-top-color:var(--rose)}
#lbhub-board-panel.lbhub-mode-team{border-top-color:var(--teal)}
#lbhub-board-panel.lbhub-mode-premium{border-top-color:var(--gold)}
/* Same gold as the Season Sim mode button on the home screen -- one
   consistent color identity for the mode, not a second unrelated hue
   just because daily/premium already claimed gold elsewhere. */
#lbhub-board-panel.lbhub-mode-seasonsim{border-top-color:var(--gold)}
.lbhub-link{display:inline-block;margin-top:.9rem;font-size:.75rem}
.lbhub-xlinks{margin-top:2.2rem;padding-top:1.2rem;border-top:1px dashed var(--line);font-family:var(--mono);font-size:.72rem;color:var(--dim)}
.lbhub-xlinks .link-btn{margin:0 .3rem}
.founders-sub{font-family:var(--mono);font-size:.78rem;color:var(--dim);line-height:1.55;margin:-.6rem 0 1.4rem}
.profile-search{display:flex;gap:.6rem;margin-bottom:1.2rem}
/* Sits between #profile-meta and .compare-prompt -- overrides the
   negative top margin #profile-meta's own .founders-sub class normally
   relies on to snug up against the heading, since that assumes whatever
   follows it is more text, not a button. */
.profile-share-btn{display:block;margin:0 0 1.2rem}
/* Same input+button row as the search box, just sitting inline on a
   profile page instead of at the top of the hub. */
.compare-prompt{display:flex;gap:.6rem;margin:-.6rem 0 1.4rem}
.compare-grid{display:grid;grid-template-columns:1fr 1fr;gap:1.2rem}
.compare-col{margin-bottom:0}
@media(max-width:680px){.compare-grid{grid-template-columns:1fr}}
/* Only applied when both sides actually have a comparable score (see
   loadCompare) -- turf, the same "this is the good outcome" color used
   for a primary button, rather than anything that could read as an
   error on the losing side. */
.compare-win{background:color-mix(in srgb, var(--turf) 12%, transparent)}
.compare-win .lb-score{color:var(--turf-dk)}

/* ---------- private leagues ---------- */
.league-forms{display:grid;grid-template-columns:1fr 1fr;gap:1.2rem;margin-top:1.2rem}
@media(max-width:680px){.league-forms{grid-template-columns:1fr}}
.league-leave-link{display:inline-block;margin-top:1.2rem;color:var(--brick)}

/* ---------- badges ---------- */
.badges-section-title{
  font-family:var(--mono);font-weight:700;font-size:.7rem;letter-spacing:.14em;text-transform:uppercase;
  color:var(--dim);margin:2rem 0 .8rem;
}
.badges-section-title:first-child{margin-top:0}
.badges-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:1rem}
.badge-card{
  display:flex;gap:.8rem;align-items:flex-start;
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  padding:1rem;box-shadow:3px 3px 0 var(--ink);
}
.badge-card-icon{
  display:flex;align-items:center;justify-content:center;flex:none;
  width:2.4rem;height:2.4rem;border-radius:50%;
  background:var(--gold);border:2px solid var(--ink);
}
.badge-card-icon .icon-glyph{width:1.3rem;height:1.3rem;background-color:var(--ink-fixed)}
.badge-card-label{font-family:var(--mono);font-weight:700;font-size:.78rem;color:var(--ink);margin-bottom:.3rem}
.badge-card-desc{font-family:var(--serif);font-style:italic;font-size:.82rem;color:var(--ink2);line-height:1.4;margin:0}
.badge-card-premium{border-color:var(--gold);border-width:3px}
.badge-card-label .premium-badge{margin-left:.4rem;font-size:.6rem}
.badge-card{cursor:pointer}
.badge-card:hover{box-shadow:1px 1px 0 var(--ink);transform:translate(2px,2px)}
.badge-chip{cursor:pointer}
/* Same circular gold badge as .badge-card-icon, just bigger and centered
   for the detail modal's own header. */
.badge-detail-icon-wrap{
  display:flex;align-items:center;justify-content:center;margin:0 auto 1rem;
  width:4.4rem;height:4.4rem;border-radius:50%;
  background:var(--gold);border:3px solid var(--ink);
}
.badge-detail-icon-wrap .icon-glyph{width:2.2rem;height:2.2rem;background-color:var(--ink-fixed)}
#badge-detail-modal .auth-title{text-align:center}
#badge-detail-modal .challenge-modal-copy{text-align:center}
.badge-detail-stat{
  font-family:var(--mono);font-size:.78rem;font-weight:700;color:var(--turf-dk);
  text-align:center;background:var(--paper3);border-radius:var(--r);padding:.6rem;margin:0;
}

/* ---------- site stats ---------- */
.stats-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:1rem}
.stat-card{
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  padding:1.2rem;text-align:center;box-shadow:3px 3px 0 var(--ink);
}
/* Sized down from the original 2.2rem -- fine for a 3-5 digit count,
   but a real running total (Career Points Scored) spilled clean out
   of a ~140px card once it reached seven digits. CSS has no clean way
   to size a font off its own text length rather than its container's
   width, so this is picked to comfortably fit an 8-9 digit number at
   this grid's minimum card width instead. */
.stat-num{font-family:var(--serif);font-weight:900;font-size:1.7rem;color:var(--turf-dk);line-height:1.15;overflow-wrap:break-word}
/* Applied by loadStats() off each card's own final formatted length --
   short counts (1-3 digits) get to be bigger than the 1.7rem default
   above since they have the room, long ones (7+, namely Career Points
   Scored) step down so a growing running total never has to crowd or
   wrap inside a card sized for a 5-6 digit number. */
.stat-num-lg{font-size:2.1rem}
.stat-num-md{font-size:1.9rem}
.stat-num-sm{font-size:1.4rem}
.stat-num-xs{font-size:1.15rem}
.stat-label{font-family:var(--mono);font-size:.66rem;letter-spacing:.08em;text-transform:uppercase;color:var(--dim);margin-top:.4rem}
/* One flowing line rather than a stacked list -- reads like a ticker
   without needing an actual marquee animation, and never grows taller
   than a couple of wrapped lines even with 8 items on a narrow phone. */
.activity-ticker{margin-bottom:1.2rem;padding:.7rem .9rem;background:var(--paper2);border:2px solid var(--line2);border-radius:var(--r);font-family:var(--mono);font-size:.72rem;color:var(--dim)}
.activity-ticker[hidden]{display:none}
.activity-ticker-lbl{display:block;font-weight:700;letter-spacing:.1em;text-transform:uppercase;font-size:.6rem;color:var(--turf-dk);margin-bottom:.45rem}
.activity-ticker-list{list-style:none;display:flex;flex-direction:column;padding:0;margin:0}
.activity-ticker-list li{display:flex;align-items:baseline;gap:.5rem;cursor:pointer;padding:.28rem 0;border-top:1px solid var(--line2)}
.activity-ticker-list li:first-child{border-top:none;padding-top:0}
.activity-ticker-list li:last-child{padding-bottom:0}
.activity-ticker-list li:hover .activity-ticker-name{text-decoration:underline}
.activity-ticker-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:700;color:var(--ink)}
.activity-ticker-detail{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.activity-ticker-time{flex:none;color:var(--faint);font-size:.66rem}
/* Player Records -- a colored position tag like .fr-tag's, just under
   its own name so it doesn't inherit .fr-tag's fixed 40px width sized
   for a 2-3 letter roster-card context. */
.records-pos-tag{font-weight:700;font-size:.7rem;width:2.4rem;flex:none;font-family:var(--mono)}
.records-pos-tag.pos-qb{color:var(--pos-qb)} .records-pos-tag.pos-rb{color:var(--pos-rb)} .records-pos-tag.pos-wr{color:var(--pos-wr)}
.records-pos-tag.pos-te{color:var(--pos-te)} .records-pos-tag.pos-k{color:var(--pos-k)}  .records-pos-tag.pos-def{color:var(--pos-def)}
/* A nested flex-column, not a change to .lb-name-group itself -- that
   class is shared with every ordinary leaderboard row, which never needs
   a name to wrap onto two lines the way a "drafted by" credit does here. */
.lb-name-stack{display:flex;flex-direction:column;min-width:0;flex:1}
.records-by{font-family:var(--mono);font-size:.65rem;color:var(--dim)}
.records-by b{cursor:pointer;font-weight:700;color:var(--turf-dk)}
.records-by b:hover{text-decoration:underline}

/* The whole result -- badge, score, roster -- reads as one stamped card, so
   a screenshot of it (which is exactly what Share result invites) is
   self-contained and self-identifying rather than just a number floating
   on the page background. */
.result-card{
  position:relative;
  background:var(--paper2);border:4px solid var(--ink);border-radius:var(--r);
  box-shadow:14px 14px 0 var(--turf-dk);padding:2rem 1.6rem 1.6rem;margin-bottom:2rem;
}
/* A graded-paper stamp -- average percentile across all 9 picks (see
   draftGrade in app.js), mapped onto a familiar A+-to-F school scale
   rather than a raw number, since "B+" reads instantly where "78th
   percentile" needs a beat of translating. Absent whenever percentiles
   themselves didn't load (draftGrade returns null), same as every other
   percentile-dependent readout on the site. */
.grade-stamp{
  position:absolute;top:-1.1rem;right:-1.1rem;z-index:1;
  width:4.4rem;height:4.4rem;border-radius:50%;
  background:var(--gold);border:3px solid var(--ink);
  display:flex;align-items:center;justify-content:center;
  font-family:var(--serif);font-weight:900;font-size:1.55rem;color:var(--ink-fixed);
  transform:rotate(9deg);box-shadow:3px 3px 0 var(--ink);
}
.grade-stamp[hidden]{display:none}
@media(max-width:480px){.grade-stamp{width:3.5rem;height:3.5rem;font-size:1.25rem;top:-.85rem;right:-.85rem}}
.result-card.card-daily{box-shadow:14px 14px 0 var(--gold)}
.result-card.card-auction{box-shadow:14px 14px 0 var(--brick)}
/* Ties the card's own visual weight to how the round actually went, not
   just which mode it was played in -- placed after (so it wins over)
   the mode coloring above. VERDICT_TIERS' five mid-range bands (75-175)
   intentionally get no class at all and keep today's default/mode
   treatment -- only a score notably bad or notably great earns a
   different one. See verdictTierClass() in app.js for the thresholds,
   kept in sync with VERDICT_TIERS itself rather than reinvented here. */
.result-card.verdict-rough{box-shadow:14px 14px 0 var(--line2);border-color:var(--dim)}
.result-card.verdict-strong{box-shadow:14px 14px 0 var(--turf)}
.result-card.verdict-elite{
  box-shadow:14px 14px 0 var(--gold);border-color:var(--gold);
  background:linear-gradient(160deg, color-mix(in srgb, var(--gold) 12%, var(--paper2)), var(--paper2) 55%);
}
@media(max-width:480px){
  .result-card{padding:1.5rem 1.1rem 1.2rem;box-shadow:8px 8px 0 var(--turf-dk)}
  .result-card.card-daily{box-shadow:8px 8px 0 var(--gold)}
  .result-card.card-auction{box-shadow:8px 8px 0 var(--brick)}
  .result-card.verdict-rough{box-shadow:8px 8px 0 var(--line2)}
  .result-card.verdict-strong{box-shadow:8px 8px 0 var(--turf)}
  .result-card.verdict-elite{box-shadow:8px 8px 0 var(--gold)}
}
.result-card-brand{
  font-family:var(--serif);font-weight:900;text-transform:uppercase;letter-spacing:-.01em;
  font-size:1.15rem;color:var(--ink);margin-bottom:1.2rem;
}
.result-card-brand .accent{color:var(--turf)}
.result-card-tag{
  font-family:var(--mono);font-size:.6rem;letter-spacing:.1em;color:var(--faint);
  margin-top:1.6rem;padding-top:1rem;border-top:2px dashed var(--line2);
}
.final-badge{
  display:inline-block;font-size:.6rem;font-weight:700;color:var(--paper2);
  border:2px solid var(--ink);background:var(--ink);padding:.35rem .9rem;border-radius:var(--r);
}
/* The auto-generated roster name (see rosterNickname() in app.js) --
   sits between the plain "Final" badge and the giant score, so it reads
   as this specific roster's own headline rather than competing with
   either the badge or the number for attention. */
.final-nickname{
  font-family:var(--serif);font-weight:800;font-style:italic;color:var(--turf-dk);
  font-size:1.05rem;letter-spacing:.01em;margin:.7rem 0 0;
}
.final-total{
  font-family:var(--serif);font-weight:900;font-size:clamp(4rem,15vw,6.5rem);
  color:var(--turf);line-height:.95;margin:1.1rem 0 .2rem;letter-spacing:-.03em;
}
.final-total small{
  display:block;font-family:var(--mono);font-weight:500;font-size:.65rem;letter-spacing:.24em;
  text-transform:uppercase;color:var(--dim);margin-top:.7rem;
}
/* A brief pop right as the count-up above lands, rather than the number
   just quietly stopping -- the actual payoff moment of a round. */
@keyframes final-pop{0%{transform:scale(1)}40%{transform:scale(1.12)}100%{transform:scale(1)}}
#final-total.final-pop{display:inline-block;animation:final-pop .45s ease-out}
@media(prefers-reduced-motion:reduce){#final-total.final-pop{animation:none}}

/* The daily score is the one number worth bragging about -- everyone played
   the exact same box scores today, so it earns a louder color and a bigger
   size than an unlimited-practice round's total. */
.daily-result .final-badge{background:var(--gold);color:var(--ink-fixed);border-color:var(--ink)}
.daily-result .final-total{color:var(--gold);font-size:clamp(4.5rem,17vw,7.5rem)}
/* Same rough/strong/elite grouping as .result-card above, applied to the
   number itself -- see verdictTierClass() in app.js. Placed after (so it
   wins over) the daily-mode gold color right above, same reasoning as
   .result-card's own verdict rules winning over its mode coloring. */
.final-total.verdict-rough{color:var(--dim)}
.final-total.verdict-strong{color:var(--turf-dk)}
.final-total.verdict-elite{
  color:var(--gold);
  text-shadow:0 0 18px color-mix(in srgb, var(--gold) 55%, transparent), 0 0 40px color-mix(in srgb, var(--gold) 25%, transparent);
}
.final-verdict{
  font-family:var(--serif);font-style:italic;font-weight:600;color:var(--ink2);
  font-size:1.15rem;margin:0 auto 1.6rem;max-width:32ch;padding-bottom:1.4rem;border-bottom:3px solid var(--ink);
}
.final-budget{font-family:var(--mono);font-size:.78rem;color:var(--dim);margin:-1rem auto 1.6rem;max-width:32ch}
.final-budget[hidden]{display:none}
.challenge-result{
  font-family:var(--mono);font-size:.8rem;color:var(--ink2);line-height:1.5;
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  padding:.6rem .9rem;margin:-1rem auto 1.6rem;max-width:34ch;
}
.challenge-result[hidden]{display:none}
.challenge-result .challenge-verdict{font-weight:700;color:var(--turf-dk)}
/* ---------- leaderboard ---------- */
.lb-panel[hidden]{display:none}
.lb-panel{
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  box-shadow:var(--shadow-sm);padding:1.15rem;text-align:left;margin-bottom:1.6rem;
}
.lb-board[hidden]{display:none}
.lb-period-tabs{display:flex;gap:.5rem;margin-bottom:.9rem}
.lb-period-tabs[hidden]{display:none}
/* Same tab look as .lb-period-tabs, just allowed to wrap -- seven mode
   tabs on the leaderboard hub won't fit one row on anything but a wide
   desktop viewport, where .lb-period-tabs only ever holds two. */
.lb-mode-tabs{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:.9rem}
/* The leaderboard hub's own mode tabs specifically -- 9 of them in one
   flat row (daily/classic/blind/3 auction budgets/playoff/team/career)
   read as an undifferentiated pile, especially once it wraps on a
   narrower screen. Grouped into labeled clusters instead: the three
   auction budgets read as one family rather than three separate modes,
   and Playoff/Team Mode's shared Premium gate is named rather than left
   for a player to notice on their own. Only applies here -- the Hall of
   Shame and private-league mode tabs keep the plain flat .lb-mode-tabs
   layout above, since neither has enough tabs to need this. */
/* flex-direction:column, not the wrapping row above -- each group needs
   to occupy its own full-width band so the This Week/All-Time period
   tabs (moved here in JS, see setHubMode) land directly under whichever
   group the active mode actually belongs to, instead of always sitting
   at the very end regardless of which mode -- and often several of
   these need it (Classic/Blind in Modes, all three Auction budgets,
   Playoff in Premium), not just one fixed spot. */
.lb-mode-tabs-grouped{flex-direction:column;align-items:stretch;gap:1rem}
.lb-tab-group{display:flex;flex-direction:column;gap:.4rem}
/* Card treatment for each cluster, hub only -- Modes/Auction/Premium/
   Lifetime now read as distinct sections instead of a label floating
   over a plain row of pills. Scoped to #lbhub-mode-tabs specifically so
   Hall of Shame and Private Leagues' identical .lb-tab-group markup
   keeps its current plain look. */
#lbhub-mode-tabs .lb-tab-group{
  background:var(--paper2);border:2px solid var(--line2);border-radius:var(--r);
  padding:.6rem .7rem .65rem;
}
.lb-tab-group-label{
  font-family:var(--mono);font-size:.58rem;font-weight:700;letter-spacing:.12em;
  text-transform:uppercase;color:var(--dim);
}
.lb-tab-group-row{display:flex;flex-wrap:wrap;gap:.5rem}
.lb-period-tab{
  font-family:var(--mono);font-weight:700;font-size:.66rem;letter-spacing:.1em;text-transform:uppercase;
  padding:.35rem .8rem;border:2px solid var(--ink);border-radius:999px;
  background:var(--paper2);color:var(--ink);cursor:pointer;transition:.12s;
}
.lb-period-tab:hover{border-color:var(--ink3)}
/* turf/paper2, not the site's usual gold+ink "selected" look -- gold on
   this card's paper2 background fails contrast in dark mode (see the
   banner-icon-btn.active comment above for the numbers behind this). */
.lb-period-tab.active{background:var(--turf);border-color:var(--turf);color:var(--paper2)}
/* The profile screen's own tab bar (see setProfileTab in app.js) reuses
   this same pill style; profile-tab-count is just a dimmer inline number
   riding along on the Badges/Bingo tabs so their counts are visible
   without switching to them -- inherits the tab's own color (dimmed via
   opacity, not a fixed color) so it still reads correctly once a tab
   goes active and its background flips to turf. */
.profile-tab-count{opacity:.7;font-weight:400}
/* Leaderboard hub mode tabs: each one fills with that mode's own color
   when selected, matching the real mode buttons on the home page
   (turf/ink/violet/brick, gold+ink for Daily) instead of the generic
   turf/paper2 "selected" look above -- same specificity as
   .lb-period-tab.active, so source order (below it) is what wins. */
.lb-tab-daily.active{background:var(--gold);border-color:var(--gold);color:var(--ink-fixed)}
.lb-tab-normal.active{background:var(--turf);border-color:var(--turf);color:var(--paper2)}
.lb-tab-blind.active{background:var(--ink-fixed);border-color:var(--ink-fixed);color:var(--paper-fixed)}
.lb-tab-playoff.active{background:var(--violet);border-color:var(--violet);color:var(--paper2)}
.lb-tab-auction.active{background:var(--brick);border-color:var(--brick);color:var(--paper2)}
.lb-tab-career.active{background:var(--turf-dk);border-color:var(--turf-dk);color:var(--paper2)}
.lb-tab-underdog.active{background:var(--rose);border-color:var(--rose);color:var(--paper2)}
.lb-tab-team.active{background:var(--teal);border-color:var(--teal);color:var(--paper2)}
.lb-tab-premium.active{background:var(--gold);border-color:var(--gold);color:var(--ink-fixed)}
.lb-tab-seasonsim.active{background:var(--gold);border-color:var(--gold);color:var(--ink-fixed)}
.team-picker-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.5rem;margin-top:1rem}
.team-picker-btn{
  display:flex;align-items:center;gap:.5rem;text-align:left;cursor:pointer;
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  padding:.5rem .6rem;font-family:var(--mono);font-size:.72rem;color:var(--ink);
  box-shadow:2px 2px 0 var(--ink);transition:.1s;
}
.team-picker-btn:hover{transform:translate(1px,1px);box-shadow:1px 1px 0 var(--ink)}
.team-picker-btn:active{transform:translate(2px,2px);box-shadow:0 0 0 var(--ink)}
.team-picker-swatch{width:.6rem;height:.6rem;border-radius:50%;flex:none;background:var(--team-color,var(--ink))}
#btn-lbhub-team-change[hidden]{display:none}
/* The modal's own .auth-card is a narrow 360px column (see .auth-card),
   much tighter than the hub panel's full-width version of this same
   grid -- two columns and a scrollable cap keep all 32 teams reachable
   without blowing out the modal's height. */
#team-modal .team-picker-grid{grid-template-columns:1fr 1fr;max-height:45vh;overflow-y:auto;margin-top:.8rem}
.lb-board-title{
  font-family:var(--mono);font-weight:700;font-size:.66rem;letter-spacing:.16em;text-transform:uppercase;
  color:var(--dim);margin-bottom:.8rem;
}
.lb-board-list{list-style:none}
.lb-row{
  display:flex;flex-wrap:wrap;align-items:center;gap:.7rem;padding:.4rem 0;
  border-bottom:1px solid var(--line);font-family:var(--mono);font-size:.85rem;
}
.lb-row:last-child{border-bottom:0}
/* Tighter rows and panel padding on desktop only -- a 25-row board (the
   hub's own TOP_N) was running well past a screen's height there; phones
   already show far fewer rows at once, so mobile is left at its own
   roomier, easier-to-tap size. */
@media (min-width:681px){
  .lb-row{padding:.16rem 0;font-size:.74rem;gap:.6rem}
  .lb-panel{padding:1rem 1.3rem}
  .lb-board-title{margin-bottom:.4rem}
}
.lb-rank{color:var(--dim);min-width:1.6rem;flex:none;display:inline-flex;align-items:baseline;gap:.2rem}
/* Podium rank badges -- hub board only (#lbhub-list), so the plain-text
   .lb-rank used on every other board sitewide (home widget, profile,
   compare, league standings, records, founders) keeps its current
   look. A pill, not a fixed-size circle, so it still has room to grow
   for the viewer's own rank-change arrow (rankDeltaHtml) without
   breaking out of shape. */
#lbhub-list .lb-rank{
  color:var(--ink);align-items:center;gap:.2rem;
  min-width:1.7rem;height:1.7rem;padding:0 .35rem;
  border:2px solid var(--line2);border-radius:999px;background:var(--paper);
}
#lbhub-list .lb-row:nth-child(1) .lb-rank{background:var(--gold);border-color:var(--ink);color:var(--ink-fixed);box-shadow:1px 1px 0 var(--ink)}
#lbhub-list .lb-row:nth-child(2) .lb-rank{background:var(--silver);border-color:var(--ink);color:var(--ink-fixed);box-shadow:1px 1px 0 var(--ink)}
#lbhub-list .lb-row:nth-child(3) .lb-rank{background:var(--bronze);border-color:var(--ink);color:var(--ink-fixed);box-shadow:1px 1px 0 var(--ink)}
/* A light highlight on hover for plain rows -- excludes premium/
   bannered rows, which already carry their own gradient tint (see
   .lb-row-premium/.lb-row-bannered below) that a flat hover fill would
   just mute. */
#lbhub-list .lb-row[data-profile-name]:not(.lb-row-premium):not(.lb-row-bannered):hover{background:var(--paper3)}
/* Rank-change indicator (see rankDeltaHtml) -- in normal flex flow
   next to the number, not absolutely positioned over what comes after
   it. It used to float past the rank column's fixed width so it would
   never nudge other rows' numbers out of alignment, but a two-digit
   rank paired with a two-digit swing ("38" and a "-29") needed more
   room than the gap to the name column had, and the floating badge
   just overlapped the name text underneath it. This one row (already
   visually distinct -- dashed border, bold, tinted background) getting
   a slightly wider rank column than its neighbors is a smaller cost
   than that. */
.lb-rank-delta{font-size:.62rem;font-weight:700;white-space:nowrap}
.lb-rank-up{color:var(--turf-dk)}
.lb-rank-down{color:var(--brick)}
/* Top-3 name highlight (see topTier in app.js) -- the same gold/silver/
   bronze the post-submission podium message already uses for #1-3,
   just always-on for every row that reaches that rank rather than only
   the moment a score first posts. A colored circle around the rank
   number itself was tried first (and around a two-digit rank paired
   with a rank-change arrow, crowded fast) -- a soft tint behind the
   name reads as the same "you placed" cue without needing its own
   footprint. Negative margin cancels the padding's own width so the
   highlight doesn't nudge the name column's layout. */
.lb-name-gold, .lb-name-silver, .lb-name-bronze{
  border-radius:.25em;padding:.05em .3em;margin:-.05em -.3em;
}
.lb-name-gold{background:color-mix(in srgb, var(--gold) 35%, transparent)}
.lb-name-silver{background:color-mix(in srgb, var(--silver) 35%, transparent)}
.lb-name-bronze{background:color-mix(in srgb, var(--bronze) 35%, transparent)}
/* A row's own name+banner+score sit in one line at their natural widths
   (nothing here grows to fill it); .lb-rival forces itself onto a second
   line regardless of how much space is left on the first, via flex-basis
   spanning the whole row -- .lb-row only needed flex-wrap for this one
   child to have somewhere to wrap to. */
.lb-rival{flex-basis:100%;padding-left:2.3rem;font-size:.72rem;color:var(--dim)}
/* Same "second line under the name" trick as .lb-rival above, just on
   every Underdog Index row instead of only the signed-in visitor's own
   -- see rarestPickHtml in app.js. */
.lb-rarest-pick{flex-basis:100%;padding-left:2.3rem;font-size:.72rem;color:var(--dim)}
.lb-rarest-pick b{color:var(--ink)}
/* Same "second line under the name" trick again -- see
   rankShareButtonHtml in app.js. Only ever on your own row, right
   below whichever other detail lines (rarest pick, rival gap) that row
   already has, so it reads as one more line in the same stack rather
   than competing with them for space. */
.lb-rank-share{flex-basis:100%;padding-left:2.3rem;font-size:.72rem;text-align:left}
/* League standings only -- a filled pill next to the name rather than
   a whole extra line under it (unlike .lb-rarest-pick/.lb-rank-share
   above), since it's a one-word-ish callout on a single row, not a
   detail with its own numbers to lay out. Turf-dk/paper, the same
   "positive movement" color .lb-rank-up already uses for the arrows,
   just filled instead of bare text so it reads as a badge, not a stat. */
.lb-biggest-mover{
  display:inline-flex;align-items:center;flex:none;
  background:var(--turf-dk);color:var(--paper-fixed);border-radius:999px;
  padding:.15rem .55rem;font-family:var(--mono);font-weight:700;font-size:.62rem;
  letter-spacing:.02em;white-space:nowrap;
}
.lb-row-mine .lb-name{color:var(--turf-dk)}
.friend-stat{font-size:.72rem;color:var(--dim);white-space:nowrap;margin-left:auto}
.friend-remove{background:none;border:0;cursor:pointer;font-size:1rem;line-height:1;color:var(--dim);flex:none}
.friend-remove:hover{color:var(--brick)}
/* Premium flex on every board a name appears on, not just its own
   profile (see premiumFlairHtml) -- a left gold accent bar plus a
   faint tint, matching the team-color left-border treatment .matchup
   already uses elsewhere, rather than a full box border that would
   look heavy in a dense, variable-height row list. Negative margin
   compensates for the padding so the bar sits flush at the row's own
   edge instead of shifting every row's content inward. */
.lb-row-premium{
  background:linear-gradient(90deg, rgba(217,154,43,.14), transparent 70%);
  border-left:3px solid var(--gold);
  padding-left:.5rem;margin-left:-.5rem;
}
/* A player's own customized banner takes priority over the generic
   Premium tint above -- this is THEIR chosen color, not just "some
   Premium account", so it gets the same left-accent treatment,
   personalized via --banner-accent (set inline per row, see
   bannerRowStyle in app.js). Deliberately leaves .player-banner at its
   normal 1.5rem size here rather than growing it -- a bigger badge
   made bannered rows visibly taller than every other row on the same
   board, which read as broken, not prominent. The accent bar/tint
   alone already carries the "this is someone's own pick" signal
   without changing row height. */
.lb-row-bannered{
  background:linear-gradient(90deg, color-mix(in srgb, var(--banner-accent) 16%, transparent), transparent 70%);
  border-left:3px solid var(--banner-accent);
  padding-left:.5rem;margin-left:-.5rem;
}
/* Any row tagged with a name links through to that player's public
   profile (see openProfile in app.js) -- the underline-on-hover lands on
   just the name, not the whole row, so the row itself doesn't read like
   a button the rank/score are also clickable parts of. */
.lb-row[data-profile-name]{cursor:pointer}
.lb-row[data-profile-name]:hover .lb-name{text-decoration:underline}
/* Persistent underline, not hover-only like .lb-row's name links --
   this is the one and only name shown here, not a long repeated list
   where a permanent underline on every row would read as noisy, and
   hover alone is invisible on touch, which is most visitors. Matches
   the same always-underlined convention every other account-bar action
   (.link-btn: Invite a Friend, Friends, Log out, ...) already uses, so
   "Signed in as X" doesn't stand out as inert plain text next to links
   that all visibly look clickable. */
.account-name-link{cursor:pointer;text-decoration:underline;text-underline-offset:2px}
.account-name-link:hover{color:var(--turf-dk)}
/* Wider than the auth card's default 360px -- .fr-row's meta line
   (season, round/team, box-score line) needs the extra room to not
   wrap on every row. */
.roster-modal-card{max-width:480px}
.roster-modal-card .final-roster{margin-top:.2rem}
/* Deliberately its own class rather than reusing .fr-pts -- finish()
   queries .fr-pts globally (document-wide, not scoped to the result
   screen) to count each score up, and this modal's markup can still be
   sitting in the DOM (just hidden) from an earlier open when a new round
   finishes. */
.roster-modal-pts-col{flex:none;text-align:right}
.roster-modal-pts{font-family:var(--mono);font-weight:700;font-size:1.05rem;color:var(--ink)}
/* Groups the name with its banner so the banner sits right against the
   name it belongs to, rather than reading as attached to the score at
   the far end of the row -- .lb-name still does its own truncation
   first, so a long name never pushes the banner (or the score) off. */
.lb-name-group{display:flex;align-items:center;gap:.35rem;flex:1;min-width:0}
/* flex:0 1 auto, not flex:1 -- the old flex:1 grew .lb-name to fill
   the whole name-group regardless of how short the name actually was,
   which (despite this block's own comment above) pushed the banner/
   flair icons that follow it out to the group's far edge instead of
   sitting right against the name. Still shrinks (and still ellipses)
   under a max-width for a genuinely long name, just doesn't grow past
   its own content for a short one. */
.lb-name{flex:0 1 auto;max-width:12rem;min-width:0;color:var(--ink);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.lb-score{color:var(--turf-dk);font-weight:700;flex:none}
/* "14 picks" on the Underdog Index, not a bare "14" -- every other
   board's score is unambiguously points, but a raw count reads as an
   unlabeled number without this. Dimmer and smaller than the number
   itself so it reads as a unit, not a second value competing with it. */
.lb-score-unit{font-weight:500;font-size:.68em;color:var(--dim)}
/* Scoped to rows that actually open a roster on click (see the
   document.body delegated listener) -- records/most-drafted/leagues/
   profile/compare all reuse .lb-score for plain, non-clickable values,
   so this can't just target the class everywhere without implying a
   click that isn't there. */
.lb-row[data-roster-name] .lb-score{
  cursor:pointer;background:var(--paper);border:2px solid var(--ink);border-radius:999px;
  padding:.15rem .65rem;box-shadow:1px 1px 0 var(--ink);transition:.12s;
}
.lb-row[data-roster-name] .lb-score:hover{transform:translate(1px,1px);box-shadow:0 0 0 var(--ink);background:var(--paper3)}
.lb-row[data-roster-name] .lb-score:active{transform:translate(1px,1px);box-shadow:none}
/* One rule decorates every empty state site-wide (leaderboards, records,
   Hall of Shame, leagues, badge progress, the stats dashboard) with the
   same small football glyph already used as the Fantasy Legend card's
   own watermark -- a ::before pseudo-element rather than touching the
   many call sites that set this class's text, so nothing in app.js
   needs to change to pick it up. display:flex works for both the <li>
   and <p> elements this class is applied to. */
.lb-empty{
  display:flex;align-items:center;gap:.5rem;
  color:var(--faint);font-size:.8rem;font-style:italic;padding:.3rem 0;
}
.lb-empty::before{
  content:'';flex:none;display:inline-block;width:.85rem;height:.85rem;
  background-color:var(--faint);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-position:center;mask-position:center;
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMSwxMiBRMTIsMyAyMywxMiBRMTIsMjEgMSwxMiBaIE0xMS4yLDYuNSBIMTIuOCBWMTcuNSBIMTEuMiBaIE04LjMsOS4zIEwxMC4zLDkuMyBWMTAuNyBMOC4zLDEwLjcgWiBNMTMuNyw5LjMgTDE1LjcsOS4zIFYxMC43IEwxMy43LDEwLjcgWiBNOC4zLDEzLjMgTDEwLjMsMTMuMyBWMTQuNyBMOC4zLDE0LjcgWiBNMTMuNywxMy4zIEwxNS43LDEzLjMgVjE0LjcgWicvPjwvc3ZnPg==");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbC1ydWxlPSdldmVub2RkJyBmaWxsPScjZmZmJyBkPSdNMSwxMiBRMTIsMyAyMywxMiBRMTIsMjEgMSwxMiBaIE0xMS4yLDYuNSBIMTIuOCBWMTcuNSBIMTEuMiBaIE04LjMsOS4zIEwxMC4zLDkuMyBWMTAuNyBMOC4zLDEwLjcgWiBNMTMuNyw5LjMgTDE1LjcsOS4zIFYxMC43IEwxMy43LDEwLjcgWiBNOC4zLDEzLjMgTDEwLjMsMTMuMyBWMTQuNyBMOC4zLDE0LjcgWiBNMTMuNywxMy4zIEwxNS43LDEzLjMgVjE0LjcgWicvPjwvc3ZnPg==");
}
/* A couple of contexts get their own icon instead of the default
   football above -- "no badges" and "no friends" aren't score-related
   emptiness, and the same generic glyph everywhere started repeating
   the exact problem the badge icon set itself had before this session
   (see the badge-icon-differentiation work). */
.lb-empty-badges::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDEgTDIxLDYuNSBMMjEsMTcuNSBMMTIsMjMgTDMsMTcuNSBMMyw2LjUgWicvPjwvc3ZnPg==");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDEgTDIxLDYuNSBMMjEsMTcuNSBMMTIsMjMgTDMsMTcuNSBMMyw2LjUgWicvPjwvc3ZnPg==");
}
.lb-empty-friends::before{
  -webkit-mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIxIEMxMiwyMSAzLDE0LjUgMyw4LjUgQzMsNS41IDUuNSwzIDguNSwzIEMxMCwzIDExLjMsMy44IDEyLDUgQzEyLjcsMy44IDE0LDMgMTUuNSwzIEMxOC41LDMgMjEsNS41IDIxLDguNSBDMjEsMTQuNSAxMiwyMSAxMiwyMSBaJy8+PC9zdmc+");
  mask-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAyNCAyNCc+PHBhdGggZmlsbD0nI2ZmZicgZD0nTTEyLDIxIEMxMiwyMSAzLDE0LjUgMyw4LjUgQzMsNS41IDUuNSwzIDguNSwzIEMxMCwzIDExLjMsMy44IDEyLDUgQzEyLjcsMy44IDE0LDMgMTUuNSwzIEMxOC41LDMgMjEsNS41IDIxLDguNSBDMjEsMTQuNSAxMiwyMSAxMiwyMSBaJy8+PC9zdmc+");
}
.lb-submit[hidden]{display:none}
.lb-submit{display:flex;flex-wrap:wrap;gap:.6rem;margin-top:1.2rem;padding-top:1.2rem;border-top:2px dashed var(--line2)}
.lb-name-input{
  flex:1;min-width:140px;font-family:var(--mono);font-size:.85rem;padding:.6rem .8rem;
  border:2px solid var(--ink);border-radius:var(--r);background:var(--paper);color:var(--ink);
}
.lb-name-input:focus{outline:2px solid var(--turf);outline-offset:1px}
.lb-submit-status{
  width:100%;font-family:var(--mono);font-size:.7rem;color:var(--dim);margin-top:.2rem;
  transition:color .12s ease;
}
/* Toggled by setStatus() in app.js whenever a status line is a genuine
   failure (a caught error, a blocked validation) rather than a transient
   "Saving…"/success message -- same --brick red as .btn-danger, so a real
   problem actually reads differently instead of blending into the rest of
   the mono body copy. */
.lb-submit-status.is-error{color:var(--brick);font-weight:700}
.lb-submitted[hidden]{display:none}
.lb-submitted{
  font-family:var(--mono);font-size:.78rem;color:var(--turf-dk);font-weight:700;
  margin-top:1.2rem;padding-top:1.2rem;border-top:2px dashed var(--line2);
}
/* Daily's "beat X% of players" line (see lbSubmittedMsg) -- a second,
   dimmer fact under the bold rank line above it, not part of the same
   sentence, and not bold itself so it doesn't compete with the rank for
   attention. */
.lb-beat-pct{display:block;font-weight:400;color:var(--dim);margin-top:.3rem}
/* Top-3 finish -- the medal glyph replaces the plain checkmark (see
   lbSubmittedMsg), and just the rank number itself breaks from the
   line's own turf-dk color to gold/silver/bronze, so "#2" actually
   reads as a podium finish instead of matching every other rank. */
.podium-medal{width:.85em;height:.85em;margin-right:.3em;vertical-align:-.08em}
.podium-rank-1{color:var(--gold)}
.podium-rank-2{color:var(--silver)}
.podium-rank-3{color:var(--bronze)}
/* A podium finish is the moment someone's most likely to actually want
   Premium -- shown right where their eyes already are (just under the
   rank they're proud of), not a modal barging in on top of it. Never
   shown to an account that already has Premium (see the rank <= 3
   check in app.js). */
.lb-podium-upsell[hidden]{display:none}
.lb-podium-upsell{
  font-family:var(--mono);font-size:.76rem;color:var(--ink);
  margin-top:.7rem;padding:.6rem .8rem;background:var(--paper2);
  border:2px solid var(--gold);border-radius:var(--r);
}
.lb-podium-upsell .link-btn{color:var(--gold);font-weight:700;text-decoration:underline}
.final-roster{display:flex;flex-direction:column;gap:.4rem;text-align:left}
.fr-row{
  display:flex;align-items:center;gap:.7rem;padding:.6rem .8rem;
  background:var(--paper2);border:2px solid var(--ink);border-left:5px solid var(--team-color, var(--ink));
  border-radius:var(--r);box-shadow:var(--shadow-sm);
}
/* The result screen already cascades each pick's own point count-up down
   the roster (see finish() in app.js, which sets each row's own
   animation-delay to match that same stagger) -- this makes the row
   itself arrive on the same beat instead of just its number, so the
   whole card reads as a checklist completing rather than static rows
   with numbers quietly filling in one at a time. `both` keeps every row
   at its settled, fully-visible state once the animation ends, since the
   fill-mode default would otherwise snap it back to the 0% keyframe. */
@keyframes fr-row-in{
  from{opacity:0;transform:translateY(6px)}
  to{opacity:1;transform:translateY(0)}
}
.fr-row{animation:fr-row-in .35s ease-out both}
@media(prefers-reduced-motion:reduce){.fr-row{animation:none}}
/* Same solid-chip treatment as .slot-tag above -- a filled pick's own
   position used to be just a small colored letter here too, easy to
   miss next to the row's team-colored border. */
.fr-tag{
  font-weight:700;font-size:.64rem;width:40px;flex:none;
  color:var(--paper-fixed);text-align:center;padding:.22rem 0;border-radius:var(--r);
  background:var(--dim);
}
.fr-tag.pos-qb{background:var(--pos-qb-bg)} .fr-tag.pos-rb{background:var(--pos-rb-bg)} .fr-tag.pos-wr{background:var(--pos-wr-bg)}
.fr-tag.pos-te{background:var(--pos-te-bg)} .fr-tag.pos-k{background:var(--pos-k-bg)}  .fr-tag.pos-def{background:var(--pos-def-bg)}
.fr-body{flex:1;min-width:0}
.fr-name{font-weight:600;font-size:.93rem;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fr-meta{font-family:var(--mono);font-size:.63rem;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fr-pts{font-family:var(--mono);font-weight:700;font-size:1.05rem;color:var(--ink);flex:none}
/* Expandable per-pick recap -- "what the best pick on that exact roll
   actually was," captured at draft() time (see bestName/bestPos/bestPts
   in app.js) rather than recomputed after the fact, since the board
   itself no longer exists once play has moved on to the next roll. */
.fr-row.fr-expandable{cursor:pointer}
.fr-chevron{
  display:inline-block;margin-left:.4rem;font-size:.7em;color:var(--dim);
  transition:transform .15s;
}
.fr-row.fr-open .fr-chevron{transform:rotate(180deg)}
.fr-recap{
  font-family:var(--mono);font-size:.72rem;color:var(--dim);line-height:1.5;
  background:var(--paper);border:2px dashed var(--line2);border-radius:var(--r);
  padding:.55rem .8rem;margin-top:-.15rem;
}
.fr-recap[hidden]{display:none}

/* ---------- draft history ---------- */
.history-entry:last-child .history-row{border-bottom:0}
.history-row{cursor:pointer}
.history-row:hover .lb-name{text-decoration:underline}
.history-when{font-family:var(--mono);font-size:.68rem;color:var(--dim)}
.history-roster{margin:.3rem 0 1rem;padding:.7rem;background:var(--paper);border:2px dashed var(--line2);border-radius:var(--r)}
.history-roster[hidden]{display:none}

/* ---------- stats dashboard (premium) ---------- */
.dashboard-chart{padding:.5rem 0}
.dashboard-chart svg{display:block;overflow:visible}
.dashboard-chart-inner{position:relative}
.dashboard-chart-summary{display:flex;gap:1.4rem;margin-bottom:.3rem}
.dashboard-chart-stat{display:flex;flex-direction:column;align-items:flex-start;font-family:var(--mono)}
.dashboard-chart-stat b{font-size:1.05rem;color:var(--ink);line-height:1.15}
.dashboard-chart-stat small{font-size:.6rem;color:var(--dim);text-transform:uppercase;letter-spacing:.05em}
.dashboard-grid-line{stroke:var(--line2);stroke-width:1;shape-rendering:crispEdges}
.dashboard-grid-label{font-family:var(--mono);font-size:8px;fill:var(--faint)}
.dashboard-area-path{fill:url(#dashFade);stroke:none}
.dashboard-fade-start{stop-color:var(--turf);stop-opacity:.3}
.dashboard-fade-end{stop-color:var(--turf);stop-opacity:0}
.dashboard-line-path{stroke:var(--turf);stroke-width:2.5;stroke-linejoin:round;stroke-linecap:round}
.dashboard-dot{fill:var(--paper2);stroke:var(--turf-dk);stroke-width:2;cursor:pointer;transition:.1s}
.dashboard-dot:hover{fill:var(--gold);stroke:var(--ink)}
/* The tooltip is a sibling HTML element positioned over the SVG using the
   exact same pixel coordinates the dot itself was drawn at (dashboard-
   chart-inner has no padding of its own, so nothing needs correcting for
   between the two coordinate spaces). Pointer-events off so hovering the
   tooltip itself can never steal the mouseleave that's meant to dismiss
   it. */
.dashboard-tooltip{
  position:absolute;transform:translate(-50%,calc(-100% - 10px));
  background:var(--paper2);border:2px solid var(--ink);border-radius:var(--r);
  padding:.35rem .55rem;font-family:var(--mono);font-size:.68rem;white-space:nowrap;
  box-shadow:2px 2px 0 var(--ink);pointer-events:none;z-index:5;
  display:flex;flex-direction:column;gap:.05rem;line-height:1.3;
}
.dashboard-tooltip[hidden]{display:none}
.dashboard-tooltip b{color:var(--ink)}
.dashboard-tooltip span{color:var(--dim)}
.dashboard-tooltip.tip-below{transform:translate(-50%,10px)}
.dashboard-panel-sub{font-family:var(--mono);font-size:.72rem;color:var(--dim);margin-bottom:.6rem}
.dashboard-modes{display:flex;flex-direction:column;gap:.2rem}
.dashboard-mode-row{display:flex;flex-wrap:wrap;align-items:baseline;gap:.3rem 1rem;font-family:var(--mono);font-size:.82rem;padding:.5rem 0;border-bottom:1px solid var(--line)}
.dashboard-mode-row:last-child{border-bottom:0}
.dashboard-mode-name{font-weight:600;color:var(--ink)}
.dashboard-mode-stats{display:flex;gap:1rem;margin-left:auto}
.dashboard-mode-stat{color:var(--dim);white-space:nowrap;text-align:right}
/* Position Breakdown's "Most picked" stat carries a real player name,
   unlike every other stat here (a short number), so it needs to wrap
   instead of forcing the row wide on mobile. */
.dashboard-mode-stat-wrap{white-space:normal}
.dashboard-mode-name.pos-qb{color:var(--pos-qb)} .dashboard-mode-name.pos-rb{color:var(--pos-rb)} .dashboard-mode-name.pos-wr{color:var(--pos-wr)}
.dashboard-mode-name.pos-te{color:var(--pos-te)} .dashboard-mode-name.pos-k{color:var(--pos-k)}  .dashboard-mode-name.pos-def{color:var(--pos-def)}

/* ---------- badge progress ---------- */
.progress-list{list-style:none;display:flex;flex-direction:column;gap:.8rem}
.progress-item{display:flex;flex-direction:column;gap:.35rem}
.progress-label{
  display:flex;align-items:center;gap:.4rem;font-family:var(--mono);font-size:.76rem;color:var(--ink2);
}
.progress-label .icon-glyph{width:.85rem;height:.85rem;background-color:var(--gold);flex:none}
.progress-label b{color:var(--ink)}
.progress-track{height:.5rem;background:var(--paper);border:2px solid var(--ink);border-radius:999px;overflow:hidden}
.progress-fill{height:100%;background:var(--turf);border-radius:999px}
.fr-recap b{color:var(--ink)}
.fr-recap-good{color:var(--turf-dk);font-weight:700}
.fr-recap-diff{color:var(--brick);font-weight:700}
/* margin-top, not a bottom margin on whatever precedes it -- the result
   screen stacks a different combination above this row depending on
   state (result card alone, +leaderboard panel, +daily-challenge nudge,
   any mix), and margin collapsing means this alone guarantees a proper
   gap no matter which one is last. Without it, #daily-nudge (a <p>
   sibling with no margin-bottom of its own) sat flush against these
   buttons with a literal 0px gap whenever it was the last thing shown --
   1.6rem here collapses with .result-card's larger 2rem margin-bottom in
   the no-nudge case, so that gap is unchanged. */
.final-actions{display:flex;gap:.8rem;justify-content:center;flex-wrap:wrap;margin-top:1.6rem}

/* ---------- loading ---------- */
.loading[hidden]{display:none}
.loading{position:fixed;inset:0;background:color-mix(in srgb, var(--paper) 82%, transparent);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;z-index:60}
/* The site's own flat vector icon, not the OS's own emoji glyph -- every
   other icon on the page renders identically everywhere; this one used
   to look different per platform (a different illustration style per
   OS/browser) and clash with the rest. */
.ball{width:2.6rem;height:2.6rem;color:var(--turf-dk);animation:tumble .9s linear infinite}
@media(prefers-reduced-motion:reduce){.ball{animation:none}}
@keyframes tumble{0%{transform:rotate(0) translateY(0)}50%{transform:rotate(180deg) translateY(-14px)}100%{transform:rotate(360deg) translateY(0)}}
/* A real record-book fact (see randomLoadingFact in app.js), shown for as
   long as the spinner above it is -- empty (and hidden via :empty) until
   the fact itself actually resolves, so there's never a blank reserved
   gap flashing before it fills in. */
.loading-fact{max-width:min(90vw,26rem);text-align:center;font-family:var(--mono);font-size:.72rem;line-height:1.5;color:var(--dim);padding:0 1rem}
.loading-fact:empty{display:none}

/* ---------- confetti ---------- */
/* A one-shot celebratory pop for a legendary pick, right where its name was
   clicked -- built as plain divs rather than a canvas or a library,
   consistent with the rest of the project having zero dependencies. */
.confetti-layer{position:fixed;inset:0;pointer-events:none;z-index:70;overflow:hidden}
.confetti-piece{
  position:absolute;width:8px;height:14px;opacity:.95;border-radius:1px;
  transform:translate(-50%,-50%);
  animation:confetti-burst ease-out forwards;
}
@keyframes confetti-burst{
  0%{transform:translate(-50%,-50%) translate(0,0) rotate(0deg);opacity:1}
  55%{opacity:1}
  100%{transform:translate(-50%,-50%) translate(var(--dx), calc(var(--dy) + var(--fall))) rotate(var(--rot));opacity:0}
}

/* ---------- Season Sim result screen ---------- */
.seasonsim-callouts{display:flex;flex-wrap:wrap;gap:.4rem 1.2rem;margin:.6rem 0 0;font-family:var(--mono);font-size:.78rem;color:var(--ink)}
.seasonsim-callout-icon{width:.9em;height:.9em;vertical-align:-.1em;margin-right:.3em;color:var(--gold)}
.seasonsim-roster-hint{font-weight:400;text-transform:none;letter-spacing:0;color:var(--faint)}
.seasonsim-roster-row{cursor:pointer}
.seasonsim-roster-row:hover .lb-name{text-decoration:underline}
.seasonsim-roster-row .fr-chevron{margin-left:.3em}
.seasonsim-roster-row.fr-open .fr-chevron{transform:rotate(180deg)}
.seasonsim-week-detail-wrap[hidden]{display:none}
.seasonsim-week-detail-list{
  list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(90px,1fr));gap:.3rem .6rem;
  font-family:var(--mono);font-size:.72rem;color:var(--dim);
  background:var(--paper);border:2px dashed var(--line2);border-radius:var(--r);
  padding:.55rem .8rem;margin:-.15rem 0 .3rem;
}
.seasonsim-week-detail-row{display:flex;justify-content:space-between;gap:.4rem}
.seasonsim-bye{color:var(--faint);font-style:italic}
