/* NBA Doomscroll — styles
 *
 * Deliberately matches NBA Content Stream, which in turn inherits the
 * HoopsMatic / Media Vote Tracker visual system: light surface, DM Sans body
 * + JetBrains Mono accents, blue accent #3b82f6, small rounded white cards on
 * a soft gray background, boxed nav tabs, monospace meta rows.
 *
 * Tokens below are copied from nba-content-stream/assets/styles.css so the
 * two features read as one product. The only intentional divergence is the
 * column width: Content Stream is a 1100px browsing surface, this is a
 * centered reading column, because it is a scroll feed.
 */

:root{
  --bg:#f5f5f7; --surface:#fff; --surface-hover:#f0f0f2; --border:#d1d1d6;
  --text:#1d1d1f; --text-secondary:#6e6e73;
  --accent:#3b82f6; --accent-dim:rgba(59,130,246,.15);
  --green:#1d8a40; --green-dim:rgba(52,199,89,.16);
  --red:#d12c2c; --red-dim:rgba(239,68,68,.13);
  --orange:#b26b00; --orange-dim:rgba(245,158,11,.16);

  /* per-card-type colors, same understated treatment Content Stream gives
     its per-source badges */
  --t-trade:#1d8a40;
  --t-rumor:#b26b00;
  --t-vs:#3b82f6;
  --t-quiz:#7c3aed;
  --t-vault:#0f766e;
  --t-buzz:#c2255c;

  --col:46rem;
}

*{margin:0; padding:0; box-sizing:border-box}
body{
  font-family:'DM Sans',-apple-system,BlinkMacSystemFont,sans-serif;
  background:var(--bg); color:var(--text); min-height:100vh;
  line-height:1.5; -webkit-font-smoothing:antialiased;
}
body.modal-open{ overflow:hidden }
a{ color:var(--accent); text-decoration:none }
a:hover{ text-decoration:underline }
.mono{ font-family:'JetBrains Mono',monospace; font-size:.92em }

.container{ max-width:var(--col); margin:0 auto; padding:1.1rem 1rem 4rem }

/* -------- nav tabs (matches Content Stream / Media Vote Tracker) -------- */
.tabs{display:flex; gap:.3rem; margin-bottom:.7rem; flex-wrap:wrap}
.tab{
  font-family:'JetBrains Mono',monospace; font-size:.72rem; font-weight:600;
  padding:.4rem .8rem; border:1px solid var(--border); border-radius:8px;
  background:var(--surface); color:var(--text-secondary);
  cursor:pointer; transition:.15s; white-space:nowrap;
}
.tab:hover{ border-color:var(--accent); color:var(--accent) }
.tab.active{ background:var(--accent); border-color:var(--accent); color:#fff }
/* Six tabs wrap to two rows on a phone, which is a lot of vertical space to
   give a sticky header on a scroll feed. Same single-row horizontal scroll
   Content Stream uses for its source pills. */
@media(max-width:560px){
  .tabs{ flex-wrap:nowrap; overflow-x:auto; -webkit-overflow-scrolling:touch;
         padding-bottom:.15rem; margin-bottom:.5rem }
  .tabs::-webkit-scrollbar{ height:4px }
  .tabs::-webkit-scrollbar-thumb{ background:var(--border); border-radius:2px }
}

/* -------- header -------- */
.hdr{display:flex; align-items:baseline; justify-content:space-between;
     gap:1rem; margin-bottom:.25rem; flex-wrap:wrap}
.hdr h1{ font-size:1.3rem; font-weight:700; letter-spacing:-.03em }
.hdr .brand{
  font-family:'JetBrains Mono',monospace; font-size:.7rem;
  color:var(--text-secondary); text-transform:uppercase; letter-spacing:.08em;
}
.subtitle{ color:var(--text-secondary); font-size:.82rem; line-height:1.4;
           margin-bottom:.6rem; max-width:46rem }

/* -------- sticky controls -------- */
.controls{
  position:sticky; top:0; z-index:100; background:var(--bg);
  padding:.5rem 0 .55rem; margin-bottom:.4rem;
  border-bottom:1px solid var(--border);
}
.controls-row{ display:flex; align-items:center; gap:.5rem; flex-wrap:wrap }

/* Deliberately quiet. This used to be a pulsing green pill sitting first in
   the control row, which made "we are not tracking you" the loudest thing on a
   page about basketball. It is now a plain text button after the primary
   control; the popover it opens is unchanged. */
.privacy-badge{
  margin-left:auto;
  display:inline-flex; align-items:center;
  padding:.2rem .3rem; border-radius:6px;
  font-family:'JetBrains Mono',monospace; font-size:.58rem; font-weight:500;
  letter-spacing:.04em;
  border:0; background:none; text-decoration:underline;
  text-decoration-color:var(--border); text-underline-offset:3px;
  color:var(--text-secondary); cursor:pointer; transition:.15s;
}
.privacy-badge:hover{ color:var(--text); text-decoration-color:var(--text-secondary) }

.icon-btn{
  font-family:'JetBrains Mono',monospace; font-size:.62rem; font-weight:600;
  padding:.25rem .6rem; border:1px solid var(--border); border-radius:999px;
  background:var(--surface); color:var(--text-secondary); cursor:pointer;
  text-transform:uppercase; letter-spacing:.05em; transition:.15s;
}
.icon-btn:hover{ border-color:var(--accent); color:var(--accent) }

.tab-action{
  display:inline-flex; align-items:center; gap:.35rem;
  font-family:'JetBrains Mono',monospace; font-size:.62rem; font-weight:600;
  padding:.25rem .6rem; border-radius:999px;
  border:1px solid var(--accent); background:var(--accent); color:#fff;
  cursor:pointer; text-transform:uppercase; letter-spacing:.05em; transition:.15s;
}
.tab-action:hover{ filter:brightness(1.08) }
.tab-action:disabled{ opacity:.55; cursor:default }
.tab-note{ font-family:'JetBrains Mono',monospace; font-size:.6rem; color:var(--text-secondary) }
@media(max-width:520px){ .tab-note{ display:none } }

.privacy-pop{
  margin-top:.5rem; padding:.55rem .7rem;
  font-size:.78rem; color:var(--text-secondary); line-height:1.45;
  background:var(--surface); border:1px solid var(--border); border-radius:8px;
}
.privacy-pop b{ color:var(--green) }

.summary{
  font-size:.7rem; color:var(--text-secondary); margin:.25rem 0 .55rem;
  font-family:'JetBrains Mono',monospace;
}
.summary strong{ color:var(--text); font-weight:600 }

/* You against the feed. Quiet by design and hidden entirely until there is a
   score: a running tally is a reward for playing, not a scoreboard staring at
   someone who has not. */
.score-line{
  font-family:'JetBrains Mono',monospace; font-size:.66rem; font-weight:600;
  letter-spacing:.04em; text-transform:uppercase;
  color:var(--t-vault); margin:-.3rem 0 .55rem;
}

/* -------- feed cards -------- */
.feed{ display:grid; gap:.6rem; grid-template-columns:minmax(0,1fr) }
.feed-msg{
  text-align:center; padding:3rem 1rem; color:var(--text-secondary);
  font-family:'JetBrains Mono',monospace; font-size:.85rem;
}
/* A divider inside a feed that keeps going, not a full stop. Quieter and far
   shorter than .feed-msg, which owns three rems of padding because it is the
   only thing on screen when it shows. */
.feed-note{
  padding:1.1rem 1rem .9rem; font-size:.7rem; opacity:.75;
  border-top:1px solid var(--border);
}
.card{
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  padding:.8rem .9rem; transition:.15s; position:relative;
  min-width:0; overflow-wrap:anywhere;
}
.card:hover{ border-color:var(--accent) }
.card.pinned{ border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-dim) }
.pinned-note{
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.06em; color:var(--accent);
  margin-bottom:-.2rem;
}

/* meta row — quiet, monospace, low contrast (Content Stream .top) */
.card-head{
  display:flex; align-items:center; gap:.45rem; flex-wrap:wrap;
  font-size:.6rem; color:var(--text-secondary); margin-bottom:.5rem;
  font-family:'JetBrains Mono',monospace; text-transform:uppercase; opacity:.9;
}
.chip{
  display:inline-flex; align-items:center; gap:.25rem;
  padding:.08rem .35rem; border-radius:3px; font-weight:600;
  letter-spacing:.04em; font-size:.58rem;
  font-family:'JetBrains Mono',monospace; text-transform:uppercase;
  background:var(--surface-hover); color:var(--text-secondary);
}
.chip::before{
  content:""; width:.45rem; height:.45rem; border-radius:50%;
  background:currentColor;
}
.t-trade .card-head .chip:first-child{ color:var(--t-trade); background:rgba(29,138,64,.10) }
.t-rumor .card-head .chip:first-child{ color:var(--t-rumor); background:rgba(178,107,0,.10) }
.t-vs    .card-head .chip:first-child{ color:var(--t-vs);    background:rgba(59,130,246,.10) }
.t-quiz  .card-head .chip:first-child{ color:var(--t-quiz);  background:rgba(124,58,237,.10) }
.t-vault .card-head .chip:first-child{ color:var(--t-vault); background:rgba(15,118,110,.10) }
.t-buzz  .card-head .chip:first-child{ color:var(--t-buzz);  background:rgba(194,37,92,.10) }
.chip.live{ background:var(--green-dim); color:var(--green) }
.chip.live::before{ background:currentColor }
.chip.dummy{ background:transparent; border:1px dashed var(--border); color:var(--text-secondary) }
.chip.dummy::before{ display:none }

.card-sub{
  color:var(--text-secondary); font-size:.7rem; margin-top:.45rem;
  font-family:'JetBrains Mono',monospace;
}

/* faces + logos */
.face{
  width:2.1rem; height:2.1rem; border-radius:50%;
  background:var(--surface-hover); object-fit:cover; flex:none;
  border:1px solid var(--border); overflow:hidden; color:transparent;
}
.face.lg{ width:3.2rem; height:3.2rem }
/* .face sets color:transparent so a broken <img> does not print its alt text
   across the card. An initials circle is a <span>, not an image, so it has to
   put the colour back — and scale with the circle it is in. */
.face.mt-ini{ color:var(--text-secondary); font-size:.72rem }
.face.lg.mt-ini{ font-size:1.05rem }
.team-logo{ width:1.4rem; height:1.4rem; object-fit:contain; flex:none }
.team-logo.lg{ width:2.1rem; height:2.1rem }

/* -------- trade cards -------- */
.trade-grid{ display:flex; gap:.7rem; align-items:stretch }
.trade-side{ flex:1 1 0; min-width:0 }
.trade-side-head{ display:flex; align-items:center; gap:.4rem; margin-bottom:.45rem }
.trade-team{ font-weight:650; font-size:.85rem }
.trade-arrows{ align-self:center; color:var(--text-secondary); font-size:1.1rem }
.trade-player{ display:flex; align-items:center; gap:.5rem; margin-bottom:.4rem; min-width:0 }
.tp-text{ display:flex; flex-direction:column; min-width:0 }
.tp-name{ font-size:.85rem; font-weight:600; white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.tp-sal{ color:var(--text-secondary); font-size:.68rem }
.trade-verdict{
  margin-top:.5rem; padding-top:.5rem; font-size:.8rem; color:var(--text-secondary);
  border-top:1px solid var(--border);
}
.trade-verdict .mono{ font-weight:600 }
.trade-verdict.ok .mono{ color:var(--green) }
.trade-verdict.warn .mono{ color:var(--orange) }
.trade-verdict.bad .mono{ color:var(--red) }

/* -------- rumor cards -------- */
.rumor-otd{
  color:var(--orange); font-size:.6rem; letter-spacing:.06em;
  text-transform:uppercase; font-weight:600; margin-bottom:.35rem;
  font-family:'JetBrains Mono',monospace;
}
.rumor-text{
  font-size:1.05rem; font-weight:650; line-height:1.38; letter-spacing:-.005em;
  margin:0 0 .15rem;
}
/* An entry printed in full is a paragraph, not a headline. At 1.05rem and 650
   weight a whole quotation reads as shouting, so the type steps down when the
   excerpt and the passage around it are printed together. */
.rumor-text.has-span{
  font-size:.95rem; font-weight:450; line-height:1.5; letter-spacing:0;
  color:var(--text);
}

/* ONE SPAN OF THE ENTRY IS THE LINK. WHAT SURROUNDS IT IS NOT.
   This is the one thing the rumors page does that the feed was not: a stretch
   somewhere inside the entry is underlined and coloured - often starting
   partway in, with plain text on both sides - and the passage carries on around
   it. It used to be the whole excerpt inside one anchor, which made every card
   a wall of underline. */
a.rumor-link{
  color:var(--accent); text-decoration:underline;
  text-decoration-color:var(--accent); text-underline-offset:3px;
  text-decoration-thickness:from-font; transition:.15s;
}
a.rumor-link:hover{ text-decoration-thickness:2px }
a.rumor-link:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:3px }

/* The outlet at the foot of the entry, italic and underlined the way the rumors
   page prints it. Colour stays inherited so the line still reads as metadata
   rather than as a second call to action competing with the lead. */
a.rumor-src{
  color:inherit; font-style:italic; text-decoration:underline;
  text-decoration-style:dotted; text-decoration-color:var(--border);
  text-underline-offset:3px; transition:.15s;
}
a.rumor-src:hover{ text-decoration-style:solid; text-decoration-color:var(--accent); color:var(--accent) }
a.rumor-src:focus-visible{ outline:2px solid var(--accent); outline-offset:3px; border-radius:3px }
.rumor-meta{ margin-top:.5rem }

/* The tagged player sits to the RIGHT of the entry, which is where the rumors
   page puts it, and beside rather than above it - a face on its own line would
   push the text below the fold on a phone. The column is only emitted when a
   tile actually resolved, so nothing here ever has to style an empty slot. */
.rumor-row{ display:flex; gap:.7rem; align-items:flex-start }
.rumor-body{ flex:1 1 auto; min-width:0 }
.rumor-who{
  flex:0 0 3.1rem; display:flex; flex-direction:column;
  align-items:center; gap:.2rem; text-align:center;
}
.rumor-face{ width:3.1rem; height:3.1rem }
.rumor-who-name .ent{
  font-size:.58rem; line-height:1.15; letter-spacing:.01em;
  font-family:'JetBrains Mono',monospace; color:var(--text-secondary);
}
@media (max-width:380px){
  .rumor-who{ flex-basis:2.4rem }
  .rumor-face{ width:2.4rem; height:2.4rem }
  .rumor-who-name{ display:none }
}
.rumor-quote{
  margin:.5rem 0 .1rem; padding:.4rem .65rem;
  border-left:2px solid var(--orange);
  color:var(--text-secondary); font-size:.85rem; font-style:italic;
  background:var(--orange-dim); border-radius:0 6px 6px 0;
}

/* -------- teammates score -------- */
.mt-head{ display:flex; align-items:flex-start; gap:.5rem; margin:.4rem 0 .2rem }
.mt-side{ flex:1 1 0; display:flex; flex-direction:column; align-items:center; gap:.15rem;
          text-align:center; min-width:0 }
.mt-ini{
  display:flex; align-items:center; justify-content:center;
  font-family:'JetBrains Mono',monospace; font-size:.85rem; font-weight:600;
  color:var(--text-secondary);
}
.mt-name{ font-size:.82rem; font-weight:600; line-height:1.2 }
.mt-score{ font-size:1.4rem; font-weight:700 }
.mt-side.a .mt-score{ color:var(--t-vs) }
.mt-side.b .mt-score{ color:#c2410c }
.mt-sub{ font-size:.58rem; color:var(--text-secondary) }
.mt-verdict{ text-align:center }
/* The withheld score keeps its own width so the head row does not jump when
   the number arrives. A tabular figure is the widest thing that can land
   there, so two of them is the right reservation. */
.mt-score[data-score]{ min-width:3.2ch; display:inline-block; opacity:.45 }
.mt-score:not([data-score]){ opacity:1 }
.card[data-spoiled] .mt-score{ opacity:1 }
.mt-spoil-btn{
  border:0; background:none; padding:0; cursor:pointer;
  font:inherit; color:var(--text-secondary);
  text-decoration:underline; text-underline-offset:3px;
  text-decoration-color:var(--border);
}
.mt-spoil-btn:hover{ color:var(--text) }

/* -------- trade trends -------- */
.tt-list{ margin-top:.5rem; border-top:1px solid var(--border) }
.tt-row{
  display:flex; align-items:center; gap:.55rem; padding:.45rem 0;
  border-bottom:1px solid var(--border); text-decoration:none; color:inherit;
}
.tt-row:last-child{ border-bottom:0 }
.tt-row:hover .tt-name{ color:var(--accent) }
.tt-rank{ width:1rem; color:var(--text-secondary); font-size:.7rem; flex:none; text-align:right }
.tt-main{ flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:.1rem }
.tt-name{ font-weight:650; font-size:.92rem; letter-spacing:-.005em }
.tt-dests{ display:flex; flex-wrap:wrap; gap:.1rem .5rem; align-items:baseline }
.tt-dest{ display:inline-flex; align-items:baseline; gap:.15rem }
.tt-dest-n{ font-size:.6rem; color:var(--text-secondary) }
/* A percentage needs a hair more room than "6" did, and a space before it so
   "DEN46%" does not read as one token. */
.tt-dest-n{ margin-left:.18rem }
.tt-dest-none{ font-size:.7rem; color:var(--text-secondary) }
.tt-builds{ flex:none; text-align:right; line-height:1.1 }
.tt-builds b{ display:block; font-size:1.05rem; color:var(--t-trade) }
.tt-builds span{ font-size:.55rem; color:var(--text-secondary); text-transform:uppercase; letter-spacing:.06em;
                 white-space:nowrap }

/* -------- trade daily digest -------- */
.td-rank{
  font-size:.58rem; text-transform:uppercase; letter-spacing:.06em;
  color:var(--t-trade); font-weight:600; margin-bottom:.3rem;
}
.td-hero{ display:flex; align-items:center; gap:.6rem; margin-bottom:.15rem }
.td-hero-text{ min-width:0 }
.td-name-big{ font-size:1.15rem; font-weight:700; letter-spacing:-.015em; line-height:1.2 }
.td-share{ font-size:.82rem; color:var(--text-secondary); line-height:1.3 }
.td-share b{ color:var(--t-trade); font-size:1rem }

/* A weekly top-25 card. The rank numeral is the card's identity, so it leads
   at a size that reads before the name does. */
.tr-row{ display:flex; align-items:center; gap:.65rem; margin-top:.35rem }
.tr-num{
  flex:none; min-width:2.1ch; text-align:right;
  font-size:1.6rem; font-weight:700; line-height:1;
  color:var(--t-trade); opacity:.55;
}
.tr-text{ min-width:0 }
.tr-count{ color:var(--text-secondary); opacity:.8; font-size:.76rem; white-space:nowrap }

/* Rank-card detail: where he was sent, who came back. Only present on cards
 * with enough builds in the loaded slice to support it, so this block is
 * often absent and must not reserve space when it is. */
.trd{ margin-top:.55rem; padding-top:.5rem; border-top:1px solid var(--hairline, rgba(0,0,0,.08)) }
.trd-row{ display:flex; align-items:baseline; flex-wrap:wrap; gap:.3rem; margin-top:.2rem; font-size:.8rem }
.trd-key{
  flex:none; font-size:.56rem; letter-spacing:.07em; text-transform:uppercase;
  color:var(--text-secondary); min-width:5.6rem;
}
.trd-v{ white-space:nowrap }
.trd-n{ color:var(--text-secondary); opacity:.75; font-size:.72rem }
.trd-sep{ color:var(--text-secondary); opacity:.45 }
.trd-note{ margin-top:.3rem; font-size:.62rem; color:var(--text-secondary); opacity:.7 }
.td-label{
  margin-top:.6rem; font-size:.58rem; text-transform:uppercase; letter-spacing:.06em;
  color:var(--text-secondary);
}
.td-list{ margin-top:.25rem }
.td-row{
  position:relative; display:flex; align-items:center; gap:.45rem;
  padding:.3rem .45rem; border-radius:6px; overflow:hidden; margin-bottom:.2rem;
  background:var(--surface-hover); text-decoration:none; color:inherit;
}
.td-row:hover{ text-decoration:none; box-shadow:inset 0 0 0 1px var(--t-trade) }
.td-row:hover .td-name{ color:var(--t-trade) }
.td-fill{
  position:absolute; left:0; top:0; bottom:0; border-radius:6px;
  background:rgba(29,138,64,.16);
}
.td-row > :not(.td-fill){ position:relative }
.td-name{ flex:1 1 auto; min-width:0; font-size:.85rem; font-weight:600 }
.td-pct{ font-size:.75rem; color:var(--t-trade); font-weight:600 }

/* -------- buzz cards -------- */
.buzz-meta{
  display:flex; align-items:center; gap:.45rem; margin-bottom:.4rem;
  font-size:.6rem; letter-spacing:.05em; text-transform:uppercase;
  color:var(--text-secondary);
}
.buzz-src{ color:var(--t-buzz); font-weight:600 }
.buzz-hot{
  color:var(--t-buzz); background:rgba(194,37,92,.10);
  padding:.05rem .3rem; border-radius:3px; font-weight:600;
}
.buzz-time{ margin-left:auto }
/* 16:9 so a missing or slow image does not reflow the card under the reader's
   thumb mid-scroll. */
.buzz-thumb{
  position:relative; margin:0 0 .55rem; border-radius:8px; overflow:hidden;
  background:var(--surface-hover); aspect-ratio:16/9;
}
.buzz-thumb img{ width:100%; height:100%; object-fit:cover; display:block }
.buzz-play{
  position:absolute; inset:0; margin:auto; width:2.6rem; height:2.6rem;
  display:flex; align-items:center; justify-content:center;
  border-radius:50%; background:rgba(0,0,0,.55); color:#fff; font-size:.9rem;
  padding-left:.15rem;
}
/* A YouTube card that can play in place. The box keeps the thumbnail's 16/9
   so swapping the iframe in does not move the page under the reader's thumb. */
.yt-embed{ cursor:pointer }
.yt-embed:focus-visible{ outline:2px solid var(--accent); outline-offset:2px }
.yt-embed .yt-frame{
  position:absolute; inset:0; width:100%; height:100%; border:0; display:block;
  background:#000;
}
/* Once the player is in, its own controls own the surface. */
.yt-embed.playing .buzz-play{ display:none }
/* A dead thumbnail must not take the player down with it: the box keeps its
   shape and stays pressable, rather than removing itself the way a plain
   thumbnail does. */
.buzz-thumb.no-thumb img{ visibility:hidden }
.buzz-thumb.no-thumb{ background:var(--surface-hover) }

.buzz-title{ font-size:1.05rem; font-weight:650; line-height:1.34; letter-spacing:-.01em }
.buzz-excerpt{
  margin-top:.4rem; color:var(--text-secondary); font-size:.85rem; line-height:1.45;
}
.buzz-ents{ display:flex; flex-wrap:wrap; gap:.15rem .55rem; margin-top:.5rem }

/* Bluesky posts, rendered the way nba-content-stream renders them: avatar on
   the left, "Author Name: post text" flowing beside it, then whatever the
   author attached. Selectors and metrics are ported from that repo's
   assets/styles.css so the two features read as one product. The avatar is
   initials rather than a face — Content Stream pulls avatars live from the
   Bluesky AppView, and this app makes no such call. */
.bsky-body{ display:flex; align-items:flex-start; gap:.6rem; margin-bottom:.3rem }
.bsky-avatar-link{ flex:0 0 auto; text-decoration:none }
.bsky-avatar{
  width:2.4rem; height:2.4rem; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--surface-hover); border:1px solid var(--border);
  color:var(--text-secondary); font-family:'JetBrains Mono',monospace;
  font-size:.7rem; font-weight:600; letter-spacing:.02em;
}
.bsky-body-text{ flex:1 1 auto; min-width:0; font-size:1rem; line-height:1.45 }
.bsky-author{ font-weight:650; color:var(--text); text-decoration:none }
.bsky-author:hover{ color:var(--accent) }
.bsky-author-sep{ color:var(--text); margin-right:.25rem }
.bsky-text{
  color:var(--text); overflow-wrap:anywhere; word-break:break-word;
  white-space:pre-wrap;
}
.inline-url, .inline-mention{ color:var(--accent); text-decoration:none; word-break:break-all }
.inline-url:hover, .inline-mention:hover{ text-decoration:underline }

.bsky-images{ display:block; margin:.5rem 0 }
.bsky-images a{ display:block; width:100% }
/* A single image is shown WHOLE, at its own aspect ratio.
   It used to be max-height:22rem with object-fit:cover, which crops. Most of
   what HoopsHype posts to Bluesky is a chart, a table or a bar-race still -
   images whose whole point is the rows at the top and the bottom - and cover
   was cutting both off. height:auto means the element takes the image's own
   shape, so there is nothing to crop and no letterboxing either. */
.bsky-images img{
  display:block; width:100%; height:auto;
  border-radius:8px; background:var(--surface-hover);
}
/* A multi-image post is still a grid of thumbnails: four full-height images
   stacked would be a page each. Those are crops by design, and tapping one
   opens the post. */
.bsky-images.grid{ display:grid; grid-template-columns:1fr 1fr; gap:.25rem }
.bsky-images.grid img{ height:8.5rem; object-fit:cover }

.bsky-video{ position:relative; display:block; margin:.5rem 0; border-radius:8px; overflow:hidden }
/* Same reasoning as the still above: the poster is shown whole. min-height
   keeps the box from collapsing to nothing if the poster fails to load, since
   the clip is absolutely positioned inside it and would have nothing to size
   against. */
.bsky-video img{ display:block; width:100%; height:auto; min-height:6rem }
/* The clip is laid over its own poster rather than replacing it, so the card
   never changes height when playback starts, stops or fails. */
.bsky-video video{
  position:absolute; inset:0; width:100%; height:100%;
  /* contain, not cover: the box is now the poster's own shape, so the clip
     already matches it, and contain guarantees nothing is cropped if the two
     disagree by a pixel. */
  object-fit:contain; background:#000;
}
.bsky-video.playing .play-hint{ opacity:0 }
.bsky-video .play-hint{ transition:opacity .2s }
.bsky-video .play-hint{
  position:absolute; left:.5rem; bottom:.5rem; padding:.25rem .55rem;
  border-radius:4px; background:rgba(0,0,0,.72); color:#fff;
  font-family:'JetBrains Mono',monospace; font-size:.7rem; font-weight:600;
}

.bsky-extcard{
  display:flex; gap:.6rem; align-items:stretch; margin:.5rem 0;
  border:1px solid var(--border); border-radius:8px; overflow:hidden;
  text-decoration:none; color:var(--text); background:var(--bg);
}
.bsky-extcard:hover{ border-color:var(--accent) }
.bsky-extcard img{ flex:0 0 auto; width:6.5rem; height:5rem; object-fit:cover }
.bsky-extcard .ext-meta{ padding:.5rem .65rem; min-width:0; flex:1 1 auto }
.bsky-extcard .ext-title{
  font-size:.86rem; font-weight:600; line-height:1.35;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.bsky-extcard .ext-desc{
  font-size:.74rem; color:var(--text-secondary); margin-top:.15rem;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.bsky-extcard .ext-host{
  font-size:.62rem; color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; margin-top:.3rem;
}
@media(max-width:600px){
  .bsky-extcard{ flex-direction:column }
  .bsky-extcard img{ width:100%; height:auto; aspect-ratio:1.91/1 }
}

/* Quoted post: bordered, inset, subordinate to the post quoting it. The whole
   box opens the quoted post on Bluesky. Ported from Content Stream. */
.bsky-quoted{
  display:block; margin:.5rem 0; padding:.6rem .7rem;
  border:1px solid var(--border); border-radius:10px; background:var(--bg);
  text-decoration:none; color:var(--text);
}
.bsky-quoted:hover{ border-color:var(--accent) }
.bsky-quoted-missing{
  font-family:'JetBrains Mono',monospace; font-size:.7rem;
  color:var(--text-secondary); font-style:italic;
}
.bsky-quoted-head{ display:flex; align-items:center; gap:.4rem; margin-bottom:.3rem }
.bsky-quoted-avatar{
  width:1.4rem; height:1.4rem; border-radius:50%; object-fit:cover; display:block;
  background:var(--surface-hover); flex:0 0 auto;
}
.bsky-quoted-author{ font-weight:650; font-size:.8rem }
.bsky-quoted-handle{ font-size:.68rem; color:var(--text-secondary) }
.bsky-quoted-text{
  font-size:.85rem; line-height:1.4; white-space:pre-wrap;
  overflow-wrap:anywhere; word-break:break-word;
}
.bsky-quoted-images{ margin:.4rem 0 0 }
.bsky-quoted-images img{
  display:block; width:100%; max-height:14rem; object-fit:cover;
  border-radius:6px; background:var(--surface-hover);
}
.bsky-quoted-images.grid{ display:grid; grid-template-columns:1fr 1fr; gap:.25rem }
.bsky-quoted-images.grid img{ height:7rem; max-height:none }
.bsky-quoted-video{ position:relative; margin:.4rem 0 0; border-radius:6px; overflow:hidden }
.bsky-quoted-video img{ display:block; width:100%; max-height:14rem; object-fit:cover }
.bsky-quoted-video-hint{
  position:absolute; left:.4rem; bottom:.4rem; padding:.18rem .45rem;
  border-radius:4px; background:rgba(0,0,0,.72); color:#fff;
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
}
.bsky-quoted-linkcard{
  display:flex; flex-direction:column; gap:.15rem; margin-top:.4rem;
  padding:.4rem .5rem; border:1px solid var(--border); border-radius:6px;
}
.bsky-quoted-linkcard .qlc-title{ font-size:.78rem; font-weight:600; line-height:1.3 }
.bsky-quoted-linkcard .qlc-host{ font-size:.6rem; color:var(--text-secondary) }

/* Reddit: the poster's handle sits in the meta row, and the body keeps the
   line breaks it was written with (a stat table is unreadable reflowed). */
.buzz-poster{ color:var(--text-secondary) }
.buzz-excerpt.as-written{ white-space:pre-wrap; overflow-wrap:anywhere }

/* -------- vs cards -------- */
.vs-head{ display:flex; align-items:flex-start; gap:.5rem }
.vs-player{
  flex:1 1 0; display:flex; flex-direction:column; align-items:center;
  gap:.2rem; text-align:center; font-size:.82rem; font-weight:600; min-width:0;
}
.vs-score{ font-size:1.5rem; color:var(--text); font-weight:700 }
.vs-mid{
  align-self:center; font-family:'JetBrains Mono',monospace;
  color:var(--text-secondary); font-size:.65rem; letter-spacing:.08em; padding-top:1.1rem;
}
.vs-headline{ text-align:center; font-weight:650; font-size:1.05rem; margin:.5rem 0 .2rem; letter-spacing:-.005em }
.vs-rows{ margin-top:.4rem; border-top:1px solid var(--border) }
.vs-row{
  display:flex; justify-content:space-between; align-items:center;
  padding:.3rem 0; border-bottom:1px solid var(--border); font-size:.8rem;
}
.vs-cat{ color:var(--text-secondary) }
.vs-nums{ font-family:'JetBrains Mono',monospace }
.vs-nums b{ font-weight:500; color:var(--text-secondary) }
.vs-nums b.win{ color:var(--text); font-weight:700 }
.vs-dash{ color:var(--text-secondary); margin:0 .35rem }
.win{ color:var(--text) }
.vs-bigwin{ font-size:.76rem; color:var(--text-secondary); margin-top:.35rem }
.vs-bigwin-name{ color:var(--text); font-weight:600 }
.vs-bigwin .mono{ color:var(--accent) }

/* -------- trivia + quiz -------- */
.trivia-q{ font-size:1.05rem; font-weight:650; line-height:1.38; margin-bottom:.55rem }
.trivia-opts{ display:flex; gap:.5rem }
.trivia-opt{
  flex:1 1 0; display:flex; flex-direction:column; align-items:center; gap:.3rem;
  background:var(--surface); border:1px solid var(--border); border-radius:10px;
  padding:.6rem .4rem; cursor:pointer; transition:.15s;
  color:var(--text); font-family:inherit; font-size:.82rem; font-weight:600;
}
.trivia-opt:hover{ border-color:var(--accent); color:var(--accent) }
.trivia-val{
  visibility:hidden; color:var(--text-secondary); font-size:.7rem;
  font-family:'JetBrains Mono',monospace;
}
.trivia-opts.revealed .trivia-val{ visibility:visible }
/* Cap Call: the salary sits under the name from the start, the scoring
   average appears with the answer. The unit is small so the number reads
   as the number. */
.trivia-sub{ font-size:.72rem; color:var(--text-secondary); margin-top:-.1rem }
.trivia-unit{ font-style:normal; font-size:.65em; opacity:.7; margin-left:.2em }

.quiz-diff{
  display:inline-block; font-family:'JetBrains Mono',monospace;
  font-size:.6rem; letter-spacing:.06em; text-transform:uppercase; font-weight:600;
  color:var(--text-secondary); margin-bottom:.45rem;
}
.quiz-diff.easy{ color:var(--green) }
.quiz-diff.medium{ color:var(--orange) }
.quiz-diff.hard{ color:var(--red) }

.quiz-sil-wrap{
  display:flex; flex-direction:column; align-items:center; gap:.35rem;
  margin-bottom:.6rem; cursor:pointer;
}
/* The photograph is shown in full, unobscured - Jorge's call, and the right
   one. Blurring a famous face is a puzzle about the blur: the reader squints
   at a smudge and either recognises the shape or shrugs. Showing a clear
   photograph of a player who genuinely is hard to place is a puzzle about
   basketball, which is the game this is meant to be. The difficulty now comes
   from WHO is on screen, not from what has been done to the picture, so the
   pool is weighted toward obscure players instead (see build_data.mjs).

   Square, not circular: data/faces tiles are 96x96 with the head centred, and
   a circle clipped the hair and the shoulders - the two things a reader
   actually reasons from when the face alone is not enough. */
.quiz-sil-mask{
  display:block; width:9.5rem; height:9.5rem; border-radius:12px; overflow:hidden;
  background:var(--surface-hover); border:1px solid var(--border);
}
.quiz-sil{
  display:block; width:100%; height:100%; object-fit:contain;
}
/* The progressive-reveal levels are kept as no-ops rather than deleted: cards
   rendered before this change still carry data-obscure in their markup, and a
   missing rule would leave them styled by whatever came last. They now all
   resolve to the same clear image. */
.quiz-sil-mask[data-obscure] .quiz-sil,
.quiz-sil-mask.revealed .quiz-sil,
.quiz-sil.revealed{ filter:none; transform:none }
/* A player with no photograph renders as initials rather than an <img>. Those
   must not be blurred: "MP" for "Mystery player" gives nothing away, and a
   blurred pair of letters just looks broken. */
.quiz-sil-mask .mt-ini{
  display:flex; align-items:center; justify-content:center;
  width:100%; height:100%; filter:none; transform:none;
}
.quiz-sil-hint{
  font-family:'JetBrains Mono',monospace; font-size:.62rem;
  color:var(--text-secondary); text-transform:uppercase; letter-spacing:.06em;
}

/* The archive excerpt a Frivolities question is asked about. Set apart from
   the question itself because it is evidence, not phrasing: the reader is
   meant to reason from it. */
.friv-body{
  margin:.15rem 0 .7rem; padding:.55rem .7rem;
  border-left:3px solid var(--t-rumor); background:var(--surface-hover);
  border-radius:0 8px 8px 0; font-size:.85rem; line-height:1.5;
}
.friv-source{ margin-top:.55rem; font-size:.72rem }
.friv-source a{ color:var(--accent) }
.friv-meta{ color:var(--text-secondary); font-family:'JetBrains Mono',monospace; font-size:.68rem }

/* Ranked salary cards: a payroll's top five, earnings by country. Numbers are
   tabular so the column of money lines up. */
.sr-list{ display:flex; flex-direction:column; gap:.4rem; margin:.5rem 0 .2rem }
.sr-row{ display:flex; align-items:center; gap:.55rem }
.sr-rank{ color:var(--text-secondary); font-size:.7rem; width:1rem; flex:0 0 auto }
.sr-main{ display:flex; flex-direction:column; min-width:0; flex:1 1 auto }
.sr-name{ font-weight:600; font-size:.85rem }
.sr-sub{ color:var(--text-secondary); font-size:.7rem }
.sr-val{ flex:0 0 auto; font-size:.82rem; font-variant-numeric:tabular-nums }
/* The denominator line. Deliberately quiet but always present: every rate on
   these cards is a division, and the reader should be able to see by what. */
.sal-denom{ margin-top:.45rem; font-size:.68rem; opacity:.85 }
/* A long call-to-action used to wrap and drag LIKE / SAVE / SHARE onto two
   lines with it. The action row keeps its buttons intact and the link
   truncates instead. */
.card-actions .act-label{ white-space:nowrap }
.card-actions a[href]{ min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap }
.sal-headline{ margin-top:.4rem }

.quiz-opts{ display:grid; grid-template-columns:1fr 1fr; gap:.5rem }
.quiz-opt{
  background:var(--surface); border:1px solid var(--border); border-radius:10px;
  padding:.55rem .5rem; cursor:pointer; transition:.15s;
  color:var(--text); font-family:inherit; font-size:.82rem; font-weight:600;
}
.quiz-opt:hover{ border-color:var(--accent); color:var(--accent) }
.quiz-opt.correct,.trivia-opt.correct{ border-color:var(--green); background:var(--green-dim); color:var(--green) }
.quiz-opt.wrong,.trivia-opt.wrong{ border-color:var(--red); background:var(--red-dim); color:var(--red) }

.quiz-result{ margin-top:.5rem; font-weight:700; font-size:.85rem }
.quiz-result.good{ color:var(--green) }
.quiz-result.bad{ color:var(--red) }
.quiz-detail{
  display:block; margin-top:.2rem; font-weight:400; font-size:.76rem;
  color:var(--text-secondary);
}

/* -------- vault cards -------- */
.sal-head{ display:flex; align-items:center; gap:.7rem; margin-bottom:.5rem }
.sal-name{ font-weight:650; font-size:1rem }
.sal-line{ font-size:.95rem; display:flex; flex-wrap:wrap; gap:.2rem .8rem; align-items:baseline }
.sal-line b{ color:var(--t-vault); font-size:1.15rem; font-family:'JetBrains Mono',monospace }
.sal-today{ color:var(--text-secondary); font-size:.8rem }
.sal-today b{ color:var(--text); font-size:.9rem }

.otd-label{
  font-family:'JetBrains Mono',monospace; color:var(--t-vault); font-size:.6rem;
  letter-spacing:.06em; text-transform:uppercase; font-weight:600; margin-bottom:.5rem;
}
.otd-score{ display:flex; align-items:center; justify-content:center; gap:.9rem }
.otd-team{ display:flex; flex-direction:column; align-items:center; gap:.15rem; font-size:.78rem; font-weight:600 }
.otd-team b{ font-size:1.35rem; color:var(--text-secondary); font-weight:500; font-family:'JetBrains Mono',monospace }
.otd-team b.win{ color:var(--text); font-weight:700 }
.otd-at{ color:var(--text-secondary); font-size:.7rem; font-family:'JetBrains Mono',monospace }

.sal-note{ margin-top:.5rem; font-size:.9rem; font-weight:500; line-height:1.45 }

/* The season's whole field on a cost-per-win card. Thirty rows is more than a
   card should be tall and it is also exactly what was asked for, so it scrolls
   inside the card rather than pushing the next one off the screen.

   ROWS ARE ONE LINE AND THE SCROLLBAR IS ALWAYS VISIBLE. The first version
   stacked the record under the team name and let the box end wherever 12rem
   fell, which sliced a row through the middle with no scrollbar in sight - it
   read as a broken card rather than as a list you can scroll. Single-line rows
   at a fixed height mean the cut always lands between rows, and a visible
   gutter says why there is a cut at all. */
.sal-field-label{ margin-top:.6rem; font-weight:600 }
.sal-field{ --field-row:1.55rem; max-height:calc(var(--field-row) * 7);
            overflow-y:auto; scrollbar-gutter:stable; margin:.35rem 0 .2rem;
            border-left:2px solid var(--border); padding-left:.6rem; gap:0 }
/* flex:0 0 and not height: these rows sit in a flex column with a capped
   height, so the default flex-shrink squeezed them and eight and a half rows
   turned up in a box sized for seven - which put the cut back through the
   middle of a row, the exact thing the fixed height was for. */
.sal-field .sr-row{ flex:0 0 var(--field-row); align-items:baseline }
.sal-field .sr-main{ flex-direction:row; gap:.4rem; align-items:baseline }
.sal-field .sr-sub{ flex:0 0 auto }
.sal-field .sr-row.me .sr-name{ color:var(--accent); font-weight:700 }
.sal-field .sr-row.me .sr-val{ color:var(--accent) }
.sal-field::-webkit-scrollbar{ width:6px }
.sal-field::-webkit-scrollbar-thumb{ background:var(--border); border-radius:3px }
.otd-story{ margin-top:.6rem; font-size:.95rem; font-weight:600; text-align:center }
.oddity-detail{ margin-top:.4rem; font-size:.85rem; color:var(--text-secondary); line-height:1.5 }
/* 11 relocated/defunct franchises in the game data have no current logo file;
   the slot is held so the two teams stay aligned. */
.logo-none{ display:inline-block; background:var(--surface-hover); border-radius:4px }

.race-placeholder{
  margin-top:.5rem; border:1px dashed var(--border); border-radius:10px;
  padding:1.5rem .8rem; text-align:center; color:var(--text-secondary); font-size:.8rem;
  background:var(--bg);
}
.race-placeholder .mono{ color:var(--t-vault); letter-spacing:.06em; font-weight:600 }
.race-title{ text-align:left }
.race-sub{ margin-top:.2rem; margin-bottom:.1rem }

/* The race is a canvas drawn by js/race-player.js. It sets its own height from
   the row count, so only the width is styled here. */
.race-wrap{ position:relative; margin-top:.55rem }
/* The chart itself is a dark broadcast panel — a port of the bar-chart-race
   "hoopshype-official" theme — sitting inside the light card. Background is set
   here too so there is no white flash before the first paint. */
.race-canvas{
  width:100%; display:block; border-radius:10px;
  background:#1a1a1a; border:1px solid #2a2a2a;
  cursor:pointer; opacity:0; transition:opacity .25s;
}
/* The line that says a trade was built by a member of the public. It sits
   above the trade rather than under it, and it is not the usual muted grey,
   because it is the single most important thing on the card: without it the
   card is two team logos and an arrow, which reads as a real transaction. */
.trade-origin{
  font-family:'JetBrains Mono',monospace; font-size:.62rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.05em;
  color:var(--t-trade); margin-bottom:.5rem;
}
/* A fallback card is not a user build, so it does not get the trade colour
   that signals one. */
.trade-origin.is-sample{ color:var(--text-secondary) }

.race-canvas.ready{ opacity:1 }

/* The Comparison card is the one canvas that is light: it ports
   nba-comparison-video-generator, which renders on the same --bg / --surface /
   --text tokens this stylesheet sets. The rule above paints a dark background
   before the first frame so a dark race does not flash white; this does the
   same job in the other direction, so the Comparison card does not flash dark.
   The :has() is only for the loading label - if a browser lacks it the label
   stays dark for the moment before the canvas paints, which is survivable. */
.race-canvas[data-player="compare"]{
  background:var(--bg); border:1px solid var(--border);
}
.race-wrap:has(canvas[data-player="compare"]) .race-status{
  background:var(--bg); color:var(--text-secondary);
}
.race-status{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:.65rem; letter-spacing:.06em; text-transform:uppercase;
  color:rgba(255,255,255,.5); background:#1a1a1a; border-radius:10px;
  pointer-events:none;
}
.race-bar{ display:flex; align-items:center; gap:.55rem; margin-top:.5rem }
.race-btn{
  flex:0 0 auto; font-family:'JetBrains Mono',monospace; font-size:.62rem; font-weight:600;
  padding:.28rem .7rem; border-radius:999px; min-width:4.4rem;
  border:1px solid var(--border); background:var(--surface); color:var(--text);
  cursor:pointer; text-transform:uppercase; letter-spacing:.05em; transition:.15s;
}
.race-btn:hover{ background:var(--surface-hover) }
.race-btn:disabled{ opacity:.55; cursor:default }
.race-scrub{ flex:1 1 auto; min-width:0; accent-color:var(--accent); height:1.2rem }
.race-group{ flex:0 0 auto; font-size:.6rem; color:var(--text-secondary) }
@media(max-width:420px){ .race-group{ display:none } }
.race-note{ margin-top:.45rem; font-size:.72rem; color:var(--text-secondary); line-height:1.45 }

/* Group filter on the Races tab: same chip as .tab-action but unselected by
   default, so the row reads as a filter rather than eight primary buttons. */
.race-group-btn{
  background:var(--surface); color:var(--text-secondary); border-color:var(--border);
  text-transform:none; letter-spacing:.02em;
}
.race-group-btn:hover{ background:var(--surface-hover); filter:none }
.race-group-btn.on{ background:var(--accent); border-color:var(--accent); color:#fff }

/* -------- card actions -------- */
.card-actions{
  display:flex; align-items:center; gap:.2rem;
  margin-top:.6rem; padding-top:.5rem; border-top:1px solid var(--border);
}
.act{
  display:inline-flex; align-items:center; gap:.35rem;
  background:none; border:none; cursor:pointer;
  color:var(--text-secondary); font-family:'JetBrains Mono',monospace;
  font-size:.66rem; font-weight:600; text-transform:uppercase; letter-spacing:.04em;
  padding:.3rem .5rem; border-radius:6px; transition:.15s; text-decoration:none;
}
.act:hover{ color:var(--accent); background:var(--surface-hover); text-decoration:none }
.act .ico{ font-size:.9rem; line-height:1 }
.act.like.on{ color:var(--red) }
.act.save.on{ color:var(--orange) }
.act.tap{ margin-left:auto; color:var(--accent) }
.act.tap:hover{ background:var(--accent-dim) }
.act.pulse .ico{ animation:pop .3s ease }
@keyframes pop{ 50%{ transform:scale(1.45) } }
@media(max-width:420px){ .act-label{ display:none } }

/* -------- modals -------- */
.modal{
  position:fixed; inset:0; z-index:200;
  background:rgba(29,29,31,.35); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  display:flex; align-items:flex-end; justify-content:center;
}
@media(min-width:640px){ .modal{ align-items:center } }
.modal[hidden]{ display:none }
.modal-card{
  background:var(--surface); border:1px solid var(--border);
  border-radius:14px 14px 0 0; width:100%; max-width:34rem;
  max-height:88vh; overflow-y:auto; padding:1.2rem 1.1rem 1.6rem;
  box-shadow:0 8px 30px rgba(0,0,0,.10);
}
@media(min-width:640px){ .modal-card{ border-radius:14px } }
.modal-card h2{ font-size:1.15rem; font-weight:700; letter-spacing:-.02em; margin-bottom:.2rem }
.modal-card .lede{ color:var(--text-secondary); font-size:.82rem; margin-bottom:.9rem; line-height:1.45 }
.modal-card h3{
  font-family:'JetBrains Mono',monospace; font-size:.62rem; font-weight:600;
  letter-spacing:.08em; text-transform:uppercase; color:var(--text-secondary);
  margin:1rem 0 .45rem;
}

.ob-teams{ display:grid; grid-template-columns:repeat(6,1fr); gap:.35rem }
@media(max-width:420px){ .ob-teams{ grid-template-columns:repeat(5,1fr) } }
.ob-eras{ display:grid; grid-template-columns:repeat(4,1fr); gap:.35rem }
.ob-pick{
  display:flex; flex-direction:column; align-items:center; gap:.15rem;
  background:var(--surface); border:1px solid var(--border); border-radius:8px;
  padding:.4rem .15rem; cursor:pointer; transition:.15s;
  color:var(--text-secondary); font-family:'JetBrains Mono',monospace; font-size:.58rem;
}
.ob-pick img{ width:1.5rem; height:1.5rem; object-fit:contain }
.ob-pick.era{ flex-direction:row; font-size:.7rem; padding:.5rem .3rem }
.ob-pick:hover{ border-color:var(--accent); color:var(--accent) }
.ob-pick.on{ background:var(--accent); border-color:var(--accent); color:#fff }

.modal-actions,.panel-danger{ display:flex; gap:.5rem; margin-top:1rem }
/* Five destinations, wrapping rather than scrolling, with the two that actually
   post to a network first and full width on a phone. The old sheet had two rows
   of two and no social at all. */
.share-actions{ display:flex; flex-wrap:wrap; gap:.5rem; margin-top:1rem }
.share-actions .btn{ flex:1 1 calc(50% - .25rem) }
@media(max-width:420px){ .share-actions .btn{ flex:1 1 100% } }
.btn{
  flex:1; text-align:center; cursor:pointer; font-family:'JetBrains Mono',monospace;
  font-size:.7rem; font-weight:600; text-transform:uppercase; letter-spacing:.05em;
  border-radius:8px; padding:.6rem .5rem;
  border:1px solid var(--border); background:var(--surface); color:var(--text-secondary);
  transition:.15s;
}
.btn:hover{ border-color:var(--accent); color:var(--accent) }
.btn.primary{ background:var(--accent); border-color:var(--accent); color:#fff }
.btn.primary:hover{ filter:brightness(1.08); color:#fff }
.btn.danger{ color:var(--red); border-color:var(--border) }
.btn.danger:hover{ border-color:var(--red); color:var(--red) }

/* profile panel */
.panel-head{ display:flex; align-items:center; justify-content:space-between; margin-bottom:.2rem }
.panel-head .icon-btn{ margin-left:0 }
.panel-body section{ margin-bottom:.3rem }
.panel-empty{ color:var(--text-secondary); font-size:.75rem; padding:.2rem 0 .4rem; font-family:'JetBrains Mono',monospace }

.w-row{ display:flex; align-items:center; gap:.6rem; padding:.15rem 0; font-size:.76rem }
.w-label{ flex:0 0 42%; color:var(--text-secondary); white-space:nowrap; overflow:hidden; text-overflow:ellipsis }
.w-bar{ flex:1; height:.4rem; background:var(--surface-hover); border-radius:3px; overflow:hidden }
.w-bar i{ display:block; height:100%; border-radius:3px }
.w-bar i.up{ background:var(--accent) }
.w-bar i.down{ background:var(--red) }
.w-num{ color:var(--text-secondary); font-size:.66rem; width:2.2rem; text-align:right; font-family:'JetBrains Mono',monospace }

.mini-card{
  display:flex; align-items:center; gap:.45rem; padding:.35rem .5rem;
  border:1px solid var(--border); border-radius:8px; margin-bottom:.3rem;
  color:var(--text-secondary); font-size:.76rem; text-decoration:none;
  white-space:nowrap; overflow:hidden; background:var(--surface);
}
.mini-card:hover{ border-color:var(--accent); color:var(--accent); text-decoration:none }
.mini-card span:last-child{ overflow:hidden; text-overflow:ellipsis }
.panel-meta{
  color:var(--text-secondary); font-size:.65rem; margin-top:.9rem;
  font-family:'JetBrains Mono',monospace; line-height:1.6;
}

.share-url{
  background:var(--bg); border:1px solid var(--border); border-radius:8px;
  padding:.55rem .7rem; font-size:.68rem; color:var(--text-secondary);
  word-break:break-all; line-height:1.5;
}

/* -------- toast + footer -------- */
.toast{
  position:fixed; left:50%; bottom:1.5rem; transform:translate(-50%,1rem);
  background:var(--text); color:#fff; border-radius:999px;
  padding:.5rem 1.1rem; font-size:.78rem; font-weight:600;
  opacity:0; pointer-events:none; transition:opacity .2s,transform .2s; z-index:300;
}
.toast.show{ opacity:1; transform:translate(-50%,0) }

.sentinel{ height:2px }
.foot{
  text-align:center; font-size:.7rem; color:var(--text-secondary);
  margin-top:1.6rem; font-family:'JetBrains Mono',monospace; line-height:1.7;
}

/* -------- progressive quiz hints -------- */
.quiz-hints{ margin:.5rem 0 .1rem }
.quiz-hint-btn{
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  padding:.25rem .6rem; border-radius:999px;
  border:1px dashed var(--border); background:none; color:var(--text-secondary);
  cursor:pointer; text-transform:uppercase; letter-spacing:.05em; transition:.15s;
}
.quiz-hint-btn:hover{ border-color:var(--t-quiz); color:var(--t-quiz) }
.quiz-hint-btn:disabled{ opacity:.5; cursor:default; border-style:solid }
.quiz-hint-list{ list-style:none; margin:.4rem 0 0; padding:0 }
.quiz-hint-list li{
  font-size:.8rem; line-height:1.45; color:var(--text-secondary);
  padding:.3rem 0 .3rem .7rem; border-left:2px solid var(--t-quiz);
  margin-bottom:.25rem;
}

/* -------- onboarding: content types -------- */
.ob-types{
  display:grid; grid-template-columns:repeat(auto-fill,minmax(8.5rem,1fr));
  gap:.35rem; margin-bottom:.9rem;
}
.ob-pick.type{
  display:flex; flex-direction:column; align-items:flex-start; gap:.1rem;
  padding:.45rem .55rem; text-align:left; width:auto; height:auto;
}
.ob-pick.type b{ font-size:.82rem; font-weight:600 }
.ob-pick.type span{
  font-family:'JetBrains Mono',monospace; font-size:.58rem;
  color:var(--text-secondary); line-height:1.3;
}
.ob-pick.type.on span{ color:inherit; opacity:.85 }

/* -------- clickable type chip -------- */
/* The chip names the section a card belongs to, so it navigates there. */
.chip-link{ border:0; cursor:pointer; font:inherit; font-size:.58rem; font-weight:600;
            letter-spacing:.04em; text-transform:uppercase; transition:.15s }
.chip-link:hover{ filter:brightness(.92); text-decoration:underline;
                  text-underline-offset:2px }

/* -------- race speed control -------- */
.race-speed{ min-width:3rem; flex:0 0 auto }

/* -------- onboarding: favourite players -------- */
.ob-optional{ font-family:'JetBrains Mono',monospace; font-size:.55rem; font-weight:500;
              color:var(--text-secondary); text-transform:uppercase; letter-spacing:.06em;
              margin-left:.4rem }
.ob-players{ position:relative; margin-bottom:.9rem }
.ob-search{
  width:100%; padding:.5rem .6rem; border-radius:8px;
  border:1px solid var(--border); background:var(--surface); color:var(--text);
  font-family:'DM Sans',sans-serif; font-size:.85rem;
}
.ob-search:focus{ outline:none; border-color:var(--accent) }
.ob-player-results{
  position:absolute; z-index:5; left:0; right:0; top:100%; margin-top:.25rem;
  background:var(--surface); border:1px solid var(--border); border-radius:8px;
  overflow:hidden; box-shadow:0 6px 20px rgba(0,0,0,.10);
}
.ob-result{
  display:block; width:100%; text-align:left; border:0; background:none;
  padding:.45rem .6rem; font-family:'DM Sans',sans-serif; font-size:.82rem;
  color:var(--text); cursor:pointer;
}
.ob-result:hover{ background:var(--surface-hover) }
.ob-player-picked{ display:flex; flex-wrap:wrap; gap:.3rem; margin-top:.45rem }
.ob-chip{
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  padding:.25rem .5rem; border-radius:999px; cursor:pointer;
  border:1px solid var(--accent); background:var(--accent); color:#fff;
}

/* -------- privacy note, now inside the algorithm panel -------- */
.panel-privacy{
  margin-top:.5rem; padding:.55rem .7rem;
  font-size:.78rem; color:var(--text-secondary); line-height:1.45;
  background:var(--bg); border:1px solid var(--border); border-radius:8px;
}
.panel-privacy b{ color:var(--green) }

/* -------- clickable entities -------- */
/* A player name or team on a card. Styled as text, not a button, so a card
   does not read as a row of controls — the underline is the only affordance
   until you hover. */
.ent{
  border:0; background:none; padding:0; margin:0; font:inherit; color:inherit;
  cursor:pointer; text-align:inherit;
  text-decoration:underline; text-decoration-style:dotted;
  text-decoration-color:var(--border); text-underline-offset:2px;
  transition:.15s;
}
.ent:hover{ color:var(--accent); text-decoration-style:solid;
            text-decoration-color:var(--accent) }
.ent:focus-visible{ outline:2px solid var(--accent); outline-offset:2px; border-radius:3px }
.trade-team .ent, .otd-team .ent{ color:inherit }

/* -------- the "filtered to X" strip -------- */
.ent-bar{ margin-top:.35rem }
.ent-bar-inner{
  display:flex; align-items:center; gap:.5rem; flex-wrap:wrap;
  padding:.4rem .6rem; border-radius:8px;
  background:var(--accent-dim); border:1px solid var(--accent);
}
.ent-bar-label{
  font-size:.55rem; font-weight:600; text-transform:uppercase; letter-spacing:.08em;
  color:var(--accent);
}
.ent-bar-name{ font-size:.9rem; font-weight:700; letter-spacing:-.01em }
.ent-bar-count{ font-size:.62rem; color:var(--text-secondary) }
.ent-bar-clear{
  margin-left:auto; cursor:pointer;
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.05em;
  padding:.22rem .55rem; border-radius:999px;
  border:1px solid var(--accent); background:var(--accent); color:#fff;
}
.ent-bar-clear:hover{ filter:brightness(1.08) }

/* The media lean card is a canvas now, a port of the HoopsHype media-vote
   video, so it borrows .race-wrap and .race-bar from the races rather than
   carrying a layout of its own. The one thing it needs: the video is square-ish
   and the panel is very dark, so the rounded corner has to clip the canvas. */
.ln-wrap canvas{ border-radius:10px }

/* -------- the Daily Five -------- */
/* The question inside is a real card element, so app.js's closest(".card")
   resolves to it and every existing answer handler works untouched. That means
   it arrives wearing the card frame, which has to come back off - a card inside
   a card reads as a mistake. */
.daily-inner{
  border:0; background:none; border-radius:0; padding:0; margin:0;
  transition:none;
}
.daily-inner:hover{ border-color:transparent }

.dq-head{ display:flex; align-items:center; justify-content:space-between;
          gap:.6rem; flex-wrap:wrap; margin-bottom:.55rem }
.dq-title{ font-size:.82rem; font-weight:600; color:var(--text-secondary) }
.dq-pips{ display:flex; gap:.22rem }
.dq-pip{
  width:.7rem; height:.7rem; border-radius:3px;
  background:var(--surface-hover); border:1px solid var(--border);
}
.dq-pip.hit{ background:var(--green); border-color:var(--green) }
.dq-pip.miss{ background:var(--red); border-color:var(--red) }
.dq-pip.now{ border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-dim) }
.dq-step{ font-size:.6rem; color:var(--text-secondary); text-transform:uppercase;
          letter-spacing:.06em; margin-bottom:.4rem }
.dq-next{ margin-top:.7rem }

/* The grid is the thing people copy, so it is the thing they can see. */
.dq-grid{ font-size:2rem; line-height:1.2; letter-spacing:.12em; margin:.5rem 0 .35rem }
.dq-score{ font-size:.95rem; color:var(--text-secondary) }
.dq-score strong{ color:var(--text); font-size:1.15rem; font-weight:700 }
.dq-actions{ display:flex; flex-wrap:wrap; gap:.5rem; margin-top:.8rem }
.dq-actions .btn{ flex:1 1 8rem }
.dq-note{ font-size:.75rem; color:var(--text-secondary); margin-top:.6rem }

/* -------- game cards -------- */
/* A challenge, not a nav link. The hook carries the card, so it gets the size
   a headline gets; the name above it is a label, not the point. */
.game-name{
  font-size:.6rem; color:var(--t-quiz); text-transform:uppercase;
  letter-spacing:.07em; font-weight:600; margin-bottom:.3rem;
}
.game-hook{ font-size:1.02rem; font-weight:600; line-height:1.4; letter-spacing:-.005em }
.game-note{ font-size:.8rem; color:var(--text-secondary); margin-top:.4rem; line-height:1.45 }
