/* NBA Content Stream — styles
 *
 * Inherits the HoopsMatic / Media Vote Tracker visual system: light
 * surface, DM Sans body + JetBrains Mono accents, blue accent #3b82f6,
 * small rounded cards on a soft gray background. Same tokens, same
 * spacing rhythm, same nav-tab look. Read media-vote-tracker/docs/
 * index.html for the source of truth on tokens.
 */

: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-source brand colors, kept understated to not fight the accent */
  --src-bluesky:#0085ff;
  --src-google-news:#1a73e8;
  --src-reddit:#ff4500;
  --src-substack:#ff6719;
  --src-youtube:#cc0000;
}

*{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;
}
.container{ max-width:1100px; margin:0 auto; padding:1.1rem 1.5rem 4rem }
a{ color:var(--accent); text-decoration:none }
a:hover{ text-decoration:underline }

/* -------- nav tabs (matches Media Vote Tracker) -------- */
.tabs{display:flex; gap:.3rem; margin-bottom:.7rem; flex-wrap:wrap}
.tabs a{
  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); text-decoration:none;
}
.tabs a:hover{ border-color:var(--accent); color:var(--accent) }
.tabs a.active{ background:var(--accent); border-color:var(--accent); color:#fff }

/* -------- 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 }

/* -------- search + filter bar -------- */
/* Polish-7 (network-burst PR follow-up): compact the main-feed header
   so the first card sits above the fold on standard laptops.
   Controls is now a two-column grid on desktop — search input on the
   left, source-filter pills inline on the right. Stacks back to a
   single column on mobile via the @media block. */
.controls{
  position:sticky; top:0; z-index:100; background:var(--bg);
  padding:.5rem 0 .65rem; margin-bottom:.4rem;
  border-bottom:1px solid var(--border);
  display:grid; grid-template-columns:1fr auto; gap:.7rem; align-items:end;
}
.search-row{display:grid; grid-template-columns:1fr; gap:.6rem; align-items:end}
.search-group{display:flex; flex-direction:column; gap:.2rem; min-width:0; position:relative}
.search-group label{
  font-size:.62rem; color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; font-weight:600;
}
input[type=text],select{
  width:100%; padding:.5rem .8rem; font-size:.85rem; font-family:inherit;
  border:1px solid var(--border); border-radius:8px;
  background:var(--surface); color:var(--text); transition:.15s;
}
input[type=text]:hover,select:hover{ border-color:var(--accent) }
input[type=text]:focus,select:focus{
  outline:none; border-color:var(--accent); box-shadow:0 0 0 3px var(--accent-dim);
}

/* search autocomplete dropdown */
.suggest{
  position:absolute; top:100%; left:0; right:0;
  background:var(--surface); border:1px solid var(--border); border-radius:8px;
  margin-top:.3rem; max-height:18rem; overflow-y:auto; z-index:200;
  box-shadow:0 4px 12px rgba(0,0,0,.06); display:none;
}
.suggest.open{ display:block }
.suggest a{
  display:flex; align-items:center; justify-content:space-between;
  padding:.45rem .7rem; font-size:.85rem; color:var(--text); text-decoration:none;
  border-bottom:1px solid var(--border);
}
.suggest a:last-child{ border-bottom:none }
.suggest a:hover, .suggest a.kbd{ background:var(--surface-hover); color:var(--accent) }
.suggest .kind{
  font-family:'JetBrains Mono',monospace; font-size:.6rem;
  color:var(--text-secondary); text-transform:uppercase; letter-spacing:.06em;
}
.suggest .count{
  font-family:'JetBrains Mono',monospace; font-size:.7rem;
  color:var(--text-secondary); margin-left:.5rem;
}

/* source pills — single horizontal row on desktop (inline with the
   search input), single horizontal scroll row on mobile. nowrap is
   what keeps the .controls grid alignment from breaking when pills
   would otherwise wrap to two lines on narrower desktops. */
.pills{
  display:flex; gap:.3rem; flex-wrap:nowrap; margin-top:0;
  overflow-x:auto; padding-bottom:.15rem; max-width:100%;
  -webkit-overflow-scrolling:touch;
}
.pills::-webkit-scrollbar{ height:4px }
.pills::-webkit-scrollbar-thumb{ background:var(--border); border-radius:2px }
.pill{
  flex:0 0 auto;
  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; user-select:none;
  transition:.15s;
}
.pill:hover{ border-color:var(--accent); color:var(--accent) }
.pill.on{ background:var(--accent); border-color:var(--accent); color:#fff }
/* Polish-8 (Fix 2): when exactly one source is on, mark it "solo"
   for an even louder active state. Makes the single-click filter
   read at a glance against the other muted-but-still-clickable
   pills. */
.pill.solo{
  background:var(--accent); border-color:var(--accent); color:#fff;
  box-shadow:0 0 0 2px var(--accent-dim);
}
/* Polish-9 (Fix 2): inline live-fetch status badge attached to the
   .pills container. Sits to the right of the source pills. Three
   visual states reflect the async live-merge lifecycle:
     .loading — small spinner + "fetching live…"
     .done    — "+N live" confirmation
     .error   — "live unavailable"
   Auto-hides 4s after the terminal state; user perception of "fresh
   content is on the way" is the goal, not a permanent fixture. */
.live-status{
  flex:0 0 auto;
  display:inline-flex; align-items:center; gap:.35rem;
  padding:.2rem .55rem; border-radius:999px;
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.05em;
  border:1px solid var(--border); background:var(--surface);
  color:var(--text-secondary);
  transition:opacity .25s ease;
}
.live-status.loading{
  color:var(--accent); border-color:var(--accent-dim);
  background:var(--accent-dim);
}
.live-status.done{
  color:var(--accent); border-color:var(--accent); background:var(--surface);
}
.live-status.error{
  color:#b91c1c; border-color:#fecaca; background:#fef2f2;
}
.live-status-spinner{
  display:inline-block; width:.55rem; height:.55rem; border-radius:50%;
  border:2px solid var(--accent-dim); border-top-color:var(--accent);
  animation:ncs-live-spin .8s linear infinite;
}
@keyframes ncs-live-spin{
  to{ transform:rotate(360deg) }
}
.pill .dot{
  display:inline-block; width:.45rem; height:.45rem; border-radius:50%;
  background:currentColor; margin-right:.4rem; vertical-align:middle;
}

.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 }
.summary .live-dot{
  display:inline-block; width:.5rem; height:.5rem; border-radius:50%;
  background:var(--green); margin-right:.35rem; vertical-align:middle;
  animation:pulse 2s infinite;
}
@keyframes pulse{ 0%,100%{opacity:1} 50%{opacity:.4} }

/* -------- trending strip --------
   Polish-7: tightened so the strip lands at ~80px tall instead of
   ~120px. Smaller card padding, smaller title font, 2-line clamp
   (down from 3), tighter wrap margins. */
.trending-wrap{ margin:0 0 .55rem; }
.trending-label{
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.08em; color:var(--text-secondary);
  margin-bottom:.25rem;
}
.trending{
  display:flex; gap:.5rem; overflow-x:auto; padding-bottom:.35rem;
  scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
}
.trending::-webkit-scrollbar{ height:5px }
.trending::-webkit-scrollbar-thumb{ background:var(--border); border-radius:3px }
.trend-card{
  flex:0 0 min(17rem, 78vw); scroll-snap-align:start;
  background:var(--surface); border:1px solid var(--border); border-radius:10px;
  padding:.5rem .7rem; transition:.15s; cursor:pointer;
}
.trend-card:hover{ border-color:var(--accent) }
.trend-card .meta{
  display:flex; align-items:center; gap:.35rem; font-size:.58rem;
  color:var(--text-secondary); margin-bottom:.22rem;
  font-family:'JetBrains Mono',monospace; text-transform:uppercase;
}
.trend-card .title{
  font-size:.8rem; font-weight:600; color:var(--text); line-height:1.32;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.trend-card a{ color:inherit; text-decoration:none }

/* -------- feed cards -------- */
.feed{display:grid; gap:.6rem; grid-template-columns:minmax(0,1fr)}
.card{
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  padding:.8rem .9rem; transition:.15s; position:relative;
  /* min-width:0 lets the card actually shrink inside the grid track
     (grid children default to min-width:auto, which on mobile lets a
     long URL or unbreakable token in a Bluesky post push the whole
     card wider than the viewport — that's the horizontal-clip bug
     reported on the polish-7 screenshot). overflow-wrap:anywhere on
     this and the inner text containers gives the browser permission
     to break inside long tokens when needed. */
  min-width:0;
  overflow-wrap:anywhere;
}
.card:hover{ border-color:var(--accent) }
/* Fix A: content hierarchy. Top row is meta — quiet, monospace, low
   contrast. The headline/post body below it is what the eye lands on. */
.card .top{
  display:flex; align-items:center; gap:.45rem; flex-wrap:wrap;
  font-size:.6rem; color:var(--text-secondary); margin-bottom:.45rem;
  font-family:'JetBrains Mono',monospace; text-transform:uppercase;
  opacity:.85;
}
.card .src-badge{
  display:inline-flex; align-items:center; gap:.25rem;
  padding:.08rem .35rem; border-radius:3px; font-weight:600;
  letter-spacing:.04em; font-size:.58rem;
}
.card .src-badge .dot{
  display:inline-block; width:.45rem; height:.45rem; border-radius:50%;
  background:currentColor;
}
.card .src-bluesky    { color:var(--src-bluesky);     background:rgba(0,133,255,.10) }
.card .src-google-news{ color:var(--src-google-news); background:rgba(26,115,232,.10) }
.card .src-reddit     { color:var(--src-reddit);      background:rgba(255,69,0,.10) }
.card .src-substack   { color:var(--src-substack);    background:rgba(255,103,25,.10) }
.card .src-youtube    { color:var(--src-youtube);     background:rgba(204,0,0,.10) }
.card .live-flag{
  background:var(--green-dim); color:var(--green);
  padding:.1rem .35rem; border-radius:3px; font-weight:600;
}
/* Bluesky byline avatar: small circular author photo from AppView. */
.card .byline-avatar{
  width:1.25rem; height:1.25rem; border-radius:50%; object-fit:cover;
  display:inline-block; vertical-align:middle;
  background:var(--surface-hover); /* placeholder while loading */
}
/* Outlet favicon for Reddit / Google News / Substack cards. Small
   square, lighter weight than the avatar; falls back to nothing on 404
   (the onerror handler in renderCard hides the img). */
.card .byline-favicon{
  width:1rem; height:1rem; border-radius:3px; object-fit:contain;
  display:inline-block; vertical-align:middle;
}
/* Fix A1: source badge is clickable. Pointer cursor + subtle hover so
   discoverability is obvious without screaming. */
.card .src-badge[data-clickable]{ cursor:pointer; transition:opacity .15s }
.card .src-badge[data-clickable]:hover{ opacity:.7 }
.card .src-badge[data-clickable]:focus-visible{
  outline:2px solid var(--accent); outline-offset:2px;
}
/* Fix A3: outlet/channel name is now an <a>; keep the muted look. */
.card .author{
  color:var(--text-secondary); font-weight:400; font-size:.62rem;
  text-decoration:none;
}
a.author:hover{ color:var(--text); text-decoration:underline }
/* Fix A4: timestamp links to the original post; keep the muted look
   and put a subtle underline on hover so the affordance is visible.
   margin-left:auto moved to .meta-right (Fix 1 P-3-polish-5) so the
   timestamp + View-on-Bluesky link travel as one right-anchored
   group instead of orphan-wrapping on narrow viewports. */
.card .when{
  color:var(--text-secondary);
  font-weight:400; font-size:.6rem; text-decoration:none;
}
a.when:hover{ color:var(--text); text-decoration:underline }
/* Right-anchored meta group: timestamp + View on Bluesky stay
   together. Use margin-left:auto on the wrapper so flex layout pushes
   the whole group to the right edge in one move; gap keeps the items
   visually separated. */
.card .top .meta-right{
  display:inline-flex; align-items:center; gap:.45rem;
  margin-left:auto;
}
/* Fix A: headline is now the visual anchor — bigger, heavier, fully
   inked, with comfortable line-height. */
.card .title{
  font-size:1.05rem; font-weight:650; line-height:1.38;
  margin-bottom:.35rem; letter-spacing:-.005em;
}
.card .title a{ color:var(--text); text-decoration:none }
.card .title a:hover{ color:var(--accent) }
.card .excerpt{
  font-size:.82rem; color:var(--text-secondary); line-height:1.45;
  margin-bottom:.4rem;
}
.card .tags{ display:flex; flex-wrap:wrap; gap:.3rem; margin-top:.3rem }
.card .tag{
  font-family:'JetBrains Mono',monospace; font-size:.63rem; font-weight:600;
  padding:.13rem .4rem; border:1px solid var(--border); border-radius:4px;
  background:var(--bg); color:var(--text-secondary); text-decoration:none;
}
.card .tag:hover{ border-color:var(--accent); color:var(--accent); text-decoration:none }
.card .tag.team{ background:var(--accent-dim); color:var(--accent); border-color:transparent }
.card .thumb{ float:right; width:6rem; height:4rem; object-fit:cover;
              border-radius:6px; margin:0 0 .4rem .8rem }

/* --- per-source media variants --- */

/* Fix B: Bluesky body reads like a tweet — avatar on the left,
   "Author Name: post text..." flowing inline next to it. The meta row
   above has only source + LIVE + timestamp; the author lives down here
   as a bold inline byline. */
.bsky-body{
  display:flex; align-items:flex-start; gap:.6rem;
  margin-bottom:.3rem;
}
.bsky-avatar{
  width:2.4rem; height:2.4rem; border-radius:50%; object-fit:cover;
  flex:0 0 auto; background:var(--surface-hover);
  display:block;
}
.bsky-avatar-placeholder{ display:inline-block }
.bsky-body-text{
  flex:1 1 auto; min-width:0;
  font-size:1rem; line-height:1.45; color:var(--text);
}
.bsky-body-text .bsky-author{
  font-weight:650; color:var(--text); text-decoration:none;
}
.bsky-body-text .bsky-author:hover{ color:var(--accent) }
.bsky-body-text .bsky-author-sep{ color:var(--text); margin-right:.25rem }
.bsky-body-text .bsky-text{
  color:var(--text); word-wrap:break-word; overflow-wrap:anywhere;
  word-break:break-word; white-space:pre-wrap;
}
/* Fix E: inline URLs are linkified — render as accent-color underlines
   so they're discoverable without yelling. */
.bsky-body-text .inline-url,
.card .inline-url{
  color:var(--accent); text-decoration:none;
  word-break:break-all; /* don't overflow on huge URLs */
}
.bsky-body-text .inline-url:hover,
.card .inline-url:hover{ text-decoration:underline }

/* Polish-8 (Fix 1): unified media-embed width. Every media container
   rendered inside a card body claims the FULL card-body width so the
   feed reads as a single uniform column instead of a ragged mix of
   widths per source. The unified rule lives below as a single
   shared selector; individual containers still set their own layout
   (grid columns, flex direction, aspect-ratio) but the width
   baseline is shared. */
.bsky-images,
.bsky-video,
.bsky-video-link,
.bsky-linkcard,
.bsky-extcard,
.yt-embed,
.rss-thumb{
  width:100%; max-width:100%; box-sizing:border-box;
}

/* Fix A5: single image renders at natural aspect ratio (capped tall);
   grid uses aspect-ratio:1 per cell so the bottom row isn't clipped by
   the previous overflow:hidden + fixed max-height combination.
   Polish-8: single-image case now also fills the full card width
   (img width:100% + object-fit:contain) so portrait images sit
   centered with letterbox padding instead of rendering narrower
   than landscape neighbors. Twitter / Bluesky-native look. */
.bsky-images{ display:block; margin:.4rem 0; }
.bsky-images a{ display:block; width:100% }
.bsky-images img{
  display:block; width:100%; max-width:100%; max-height:28rem;
  border-radius:8px; object-fit:contain;
  background:var(--surface-hover);
}
.bsky-images.grid{
  display:grid; grid-template-columns:1fr 1fr; gap:.35rem;
}
.bsky-images.grid img{
  width:100%; height:auto; aspect-ratio:1/1; object-fit:cover;
  max-height:none;
}

/* Fix 1: Bluesky native video. Thumbnail-only (no inline HLS player),
   click opens the post on bsky.app where their native player handles
   playback. aspect-ratio comes from Bluesky's metadata; play overlay
   + caption hint sit on top of the poster. */
.bsky-video-link{
  position:relative; display:block; margin:.5rem 0;
  border-radius:10px; overflow:hidden; background:#000;
  width:100%; max-width:100%;
}
.bsky-video-poster{
  display:block; width:100%; height:100%; object-fit:cover;
  background:#000;
}
.bsky-video-play{
  position:absolute; inset:0; display:flex;
  align-items:center; justify-content:center;
  pointer-events:none;
  transition:transform .15s ease;
}
.bsky-video-link:hover .bsky-video-play{ transform:scale(1.08) }
.bsky-video-hint{
  position:absolute; left:.5rem; bottom:.5rem;
  padding:.2rem .5rem; border-radius:4px;
  background:rgba(0,0,0,.72); color:#fff;
  font-family:'JetBrains Mono',monospace; font-size:.66rem; font-weight:600;
  letter-spacing:.04em;
}

.bsky-video{
  position:relative; display:block; margin:.4rem 0;
  border-radius:8px; overflow:hidden; max-width:100%;
}
.bsky-video img{ display:block; width:100%; max-height:22rem; object-fit:cover }
.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:.4rem 0;
  border:1px solid var(--border); border-radius:8px; overflow:hidden;
  text-decoration:none; color:var(--text); background:var(--bg);
  max-width:100%;
}
.bsky-extcard:hover{ border-color:var(--accent); text-decoration:none }
.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 }
/* Polish-7: stack on narrow viewports for parity with .bsky-linkcard.
   The horizontal layout pushed text past the right edge on phone
   widths — the 6.5rem thumb left no room for legible meta. */
@media(max-width:600px){
  .bsky-extcard{ flex-direction:column }
  /* Polish-1: full-width thumb scales at the Open Graph 1.91:1 ratio
     instead of a fixed 9rem height, which cropped heads/feet once the
     card spanned the phone width. object-fit:cover inherited from base. */
  .bsky-extcard img{ width:100%; height:auto; aspect-ratio:1.91/1 }
}
.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-family:'JetBrains Mono',monospace; font-size:.62rem;
  color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; margin-top:.3rem;
}

/* Fix C: Bluesky live external link card. The poster attached a link
   preview in their post (article URL with metadata); render it as a
   bordered, rounded preview box matching the media-vote-tracker card
   style. Click anywhere on the box opens the external URL.
   Same visual shape as .bsky-extcard above (which is the archive
   equivalent rendered from item.media.type=link) but built from
   the live item.linkCard captured in bskyLiveItems(). */
.bsky-linkcard{
  display:flex; gap:.7rem; align-items:stretch; margin:.5rem 0;
  border:1px solid var(--border); border-radius:10px; overflow:hidden;
  text-decoration:none; color:var(--text); background:var(--bg);
  transition:.15s;
}
.bsky-linkcard:hover{ border-color:var(--accent); text-decoration:none }
.bsky-linkcard .lc-thumb{
  flex:0 0 auto; width:7.5rem; height:6rem; object-fit:cover;
  background:var(--surface-hover);
}
.bsky-linkcard .lc-meta{ padding:.55rem .7rem; min-width:0; display:flex;
  flex-direction:column; gap:.2rem; }
.bsky-linkcard .lc-title{
  font-size:.92rem; font-weight:600; line-height:1.35; color:var(--text);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.bsky-linkcard .lc-desc{
  font-size:.78rem; color:var(--text-secondary); line-height:1.4;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.bsky-linkcard .lc-host{
  font-family:'JetBrains Mono',monospace; font-size:.62rem;
  color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; margin-top:auto;
}
@media(max-width:600px){
  .bsky-linkcard{ flex-direction:column }
  /* Polish-1: full-width thumb scales at the Open Graph 1.91:1 ratio
     instead of a fixed 9rem height (same crop fix as .bsky-extcard). */
  .bsky-linkcard .lc-thumb{ width:100%; height:auto; aspect-ratio:1.91/1; }
}

/* YouTube: full-width 16:9 lazy embed. Click the poster to swap in the iframe. */
.yt-embed{
  position:relative; margin:.5rem 0; width:100%; aspect-ratio:16/9;
  background:#000; border-radius:8px; overflow:hidden;
}
.yt-embed .yt-poster{
  display:block; width:100%; height:100%; object-fit:cover; cursor:pointer;
}
.yt-embed .yt-play{
  position:absolute; inset:0; margin:auto; display:flex;
  align-items:center; justify-content:center; width:68px; height:48px;
  background:transparent; border:none; cursor:pointer; padding:0;
  transition:transform .15s ease;
}
.yt-embed .yt-play:hover{ transform:scale(1.08) }
.yt-embed iframe{
  position:absolute; inset:0; width:100%; height:100%; border:0;
}

/* Reddit / Google News / Substack media preview. Polish-5: bumped
   from a 6.5×4.5rem right-floated thumb to a full-width preview
   below the headline, similar shape to YouTube embeds. Natural
   aspect with a cap so portrait images don't dominate. */
.rss-thumb{ display:block; margin:.5rem 0 .3rem }
.rss-thumb img{
  display:block; width:100%; max-width:100%;
  max-height:24rem; object-fit:contain;
  border-radius:8px; background:var(--surface-hover);
}
@media (max-width:520px){
  .rss-thumb img{ max-height:18rem }
}

/* Fix A2: avatar link wrapper on Bluesky cards keeps the image inline
   without underline artifacts. The hover lightens the avatar slightly. */
.bsky-avatar-link{
  display:flex; flex:0 0 auto; text-decoration:none;
}
.bsky-avatar-link:hover .bsky-avatar{ opacity:.85 }
/* Fix B2: linked @-mentions in Bluesky body + quoted post text. Same
   accent color as inline URLs but bolder weight to mark the identity. */
.inline-mention{
  color:var(--accent); text-decoration:none; font-weight:500;
}
.inline-mention:hover{ text-decoration:underline }

/* Fix A6: initials-avatar fallback for non-Bluesky cards with no
   thumbnail. Renders as a small circular block to the left of the
   headline; source-tinted background, source-tinted ink. The
   card-body-with-avatar wrapper keeps the headline + excerpt aligned
   to the right of the avatar. */
.card-body-with-avatar{
  display:flex; align-items:flex-start; gap:.7rem;
}
.card-body-main{ flex:1 1 auto; min-width:0 }
.initials-avatar{
  flex:0 0 auto; width:2.4rem; height:2.4rem; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  font-family:'DM Sans',sans-serif; font-weight:700; font-size:.85rem;
  letter-spacing:.02em; user-select:none;
}
.initials-avatar.src-tint-reddit     { background:rgba(255,69,0,.14);    color:var(--src-reddit) }
.initials-avatar.src-tint-google-news{ background:rgba(26,115,232,.14);  color:var(--src-google-news) }
.initials-avatar.src-tint-substack   { background:rgba(255,103,25,.14);  color:var(--src-substack) }

/* Cluster C: player headshot or team logo in the visual-avatar slot
   (same dimensions and position as the initials fallback). The
   img-fallback-wrap layers the img over an initially-hidden initials
   block; if the image 404s the inline onerror swaps display. */
.img-fallback-wrap{
  flex:0 0 auto; width:2.4rem; height:2.4rem; display:inline-block;
  position:relative;
}
.visual-avatar{
  width:2.4rem; height:2.4rem; border-radius:50%;
  object-fit:cover; display:block;
  background:var(--surface-hover);
}
.visual-team-logo{ object-fit:contain; padding:.15rem; background:var(--surface) }
.visual-league-logo{ object-fit:contain; padding:.2rem; background:var(--surface) }
.img-fallback-content{
  position:absolute; inset:0; width:100%; height:100%;
}
.img-fallback-content .initials-avatar{ width:100%; height:100%; }

/* Fix B1: quoted-post inline box. Bordered, slightly inset, lighter
   surface; readable but visually subordinate to the parent post. The
   whole box is a clickable <a> that opens the quoted post on bsky.app. */
.bsky-quoted{
  display:block; margin:.5rem 0;
  border:1px solid var(--border); border-radius:10px;
  padding:.6rem .7rem;
  background:var(--bg);
  text-decoration:none; color:var(--text);
  transition:.15s;
}
.bsky-quoted:hover{ border-color:var(--accent); text-decoration:none }
.bsky-quoted-missing{
  font-family:'JetBrains Mono',monospace; font-size:.7rem;
  color:var(--text-secondary); padding:.5rem .7rem; 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-avatar-placeholder{ display:inline-block }
.bsky-quoted-author{
  font-weight:650; font-size:.8rem; color:var(--text);
}
.bsky-quoted-handle{
  font-size:.7rem; color:var(--text-secondary);
  font-family:'JetBrains Mono',monospace;
}
/* Polish-9 (Fix 1): quoted-post timestamp, mirrors the outer-post
   meta row. Pushed to the right of the head row via margin-left:auto. */
.bsky-quoted-time{
  font-size:.65rem; color:var(--text-secondary);
  font-family:'JetBrains Mono',monospace;
  margin-left:auto;
}
/* Polish-9 (Fix 1): quoted-post video. Same thumbnail-only treatment
   as the outer .bsky-video-link; click-through goes through the
   parent .bsky-quoted anchor to the quoted post on bsky.app. */
.bsky-quoted-video{
  position:relative; display:block; margin:.4rem 0 0;
  border-radius:8px; overflow:hidden; background:#000;
  width:100%; max-width:100%;
}
.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;
  letter-spacing:.04em;
}
.bsky-quoted-text{
  font-size:.85rem; line-height:1.4; color:var(--text);
  white-space:pre-wrap; word-wrap:break-word; overflow-wrap:anywhere;
  word-break:break-word;
}
.bsky-quoted-images{
  display:block; width:100%; max-width:100%; margin:.4rem 0 0;
}
.bsky-quoted-images img{
  display:block; width:100%; max-width:100%; max-height:14rem;
  border-radius:6px; object-fit:contain; background:var(--surface-hover);
}
.bsky-quoted-images.grid{
  display:grid; grid-template-columns:1fr 1fr; gap:.25rem;
}
.bsky-quoted-images.grid img{
  width:100%; height:auto; aspect-ratio:1/1; object-fit:cover;
  max-height:none;
}
.bsky-quoted-linkcard{
  margin-top:.35rem; padding:.35rem .5rem;
  border:1px solid var(--border); border-radius:6px;
  background:var(--surface);
  display:flex; align-items:center; gap:.5rem;
  font-size:.72rem;
}
.bsky-quoted-linkcard .qlc-title{
  font-weight:600; color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
  flex:1 1 auto; min-width:0;
}
.bsky-quoted-linkcard .qlc-host{
  font-family:'JetBrains Mono',monospace; font-size:.62rem;
  color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.05em;
}

.empty{
  text-align:center; padding:3rem 1rem; color:var(--text-secondary);
  font-family:'JetBrains Mono',monospace; font-size:.85rem;
}
.foot{
  text-align:center; font-size:.7rem; color:var(--text-secondary);
  margin-top:1.6rem; font-family:'JetBrains Mono',monospace; line-height:1.7;
}

/* -------- per-entity page header -------- */
.entity-hero{
  display:flex; align-items:center; gap:1rem;
  background:var(--surface); border:1px solid var(--border); border-radius:12px;
  padding:1rem 1.2rem; margin-bottom:1rem;
}
.avatar{
  width:4rem; height:4rem; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  font-family:'DM Sans',sans-serif; font-weight:700; font-size:1.3rem;
  color:#fff; flex-shrink:0; letter-spacing:.02em;
}
/* Polish-9 (Fix 3): real player headshot / team logo in the entity
   hero. Layered structure: the real image sits on top; the initials
   block underneath stays hidden until the image's onerror flips the
   image to display:none and the initials to display:flex. */
.entity-portrait{
  width:7rem; height:7rem; flex-shrink:0; position:relative;
  border-radius:50%; overflow:hidden;
  background:var(--surface-hover);
}
.entity-portrait-img{
  display:block; width:100%; height:100%;
  object-fit:cover; object-position:center top;
}
/* Team logos are usually transparent PNGs with strong brand colors;
   `contain` + a small padding so they don't crop into a tight circle
   like player headshots, plus a neutral background to read on dark
   logos. */
.entity-portrait-img--team{
  object-fit:contain; padding:.5rem; background:var(--surface);
}
.entity-portrait-initials{
  position:absolute; inset:0; width:100%; height:100%;
  display:none;  /* revealed by inline onerror when the image 404s */
  align-items:center; justify-content:center;
  font-family:'DM Sans',sans-serif; font-weight:700; font-size:2rem;
  color:#fff; letter-spacing:.02em;
}
.entity-hero .name{ font-size:1.6rem; font-weight:700; letter-spacing:-.02em }
.entity-hero .sub{
  font-size:.72rem; color:var(--text-secondary);
  font-family:'JetBrains Mono',monospace; text-transform:uppercase;
  letter-spacing:.06em; margin-top:.15rem;
}

/* Bonus A (pathfix PR): chart + trending live side-by-side instead
   of stacked. Chart is a fixed-width column on the left (260px),
   trending rail takes the remaining width and scrolls horizontally.
   Whole header (hero + this row + summary) lands under ~130px on
   standard laptops so the first card is above the fold. Mobile
   falls back to stacked via the @media block below. */
.header-row{
  display:flex; align-items:flex-start; gap:.6rem;
  margin:0 0 .6rem;
}
.header-row .chart-wrap{
  flex:0 0 260px; margin:0;
}
.header-row .nav-rail{
  flex:1 1 auto; min-width:0; margin:0;
}

/* mini-chart — compacted in Cluster C: the entity-page header was
   eating the whole above-the-fold area (chart + 2 rail rows + summary).
   Smaller chart + tighter rail rows below land the first card well
   within the viewport on standard laptops. */
.chart-wrap{ margin:0 0 .6rem }
.chart-label{
  font-family:'JetBrains Mono',monospace; font-size:.56rem;
  color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; margin-bottom:.2rem; font-weight:600;
}
.chart-wrap svg{
  display:block; width:100%; height:50px;
  background:var(--surface); border:1px solid var(--border); border-radius:6px;
  padding:.2rem .35rem; overflow:visible;
}
.chart-wrap svg rect.bar{ fill:var(--accent); opacity:.85 }
.chart-wrap svg rect.bar:hover{ opacity:1 }
.chart-wrap svg text{
  font-family:'JetBrains Mono',monospace; font-size:8px;
  fill:var(--text-secondary);
}

/* Fix 3: explicit attribution link in the Bluesky meta row. Same muted
   look as .when, with a left margin to separate it from the timestamp. */
.card .view-on-source{
  color:var(--text-secondary); font-size:.6rem; font-weight:400;
  font-family:'JetBrains Mono',monospace; text-transform:uppercase;
  letter-spacing:.04em; text-decoration:none;
  margin-left:.5rem;
}
.card .view-on-source:hover{ color:var(--accent); text-decoration:underline }

/* Fix 5: trending nav rail on per-entity pages. Two horizontal-scroll
   rows of pills (top players + top teams). Mobile: scrolls horizontally
   with momentum. Desktop: same — fits within the existing single-column
   layout without forcing a sidebar, which would complicate the chart
   + feed positioning. */
/* Fix 3: nav rail compacted — smaller pills + tighter vertical
   spacing so the entire entity header (avatar + chart + rail + summary)
   fits in ~250-280px on a standard laptop, leaving the first card
   above the fold. */
.nav-rail{ margin:0 0 .6rem }
.rail-section{ margin-bottom:.3rem }
.rail-section:last-child{ margin-bottom:0 }
.rail-label{
  font-family:'JetBrains Mono',monospace; font-size:.55rem; font-weight:600;
  color:var(--text-secondary); text-transform:uppercase;
  letter-spacing:.06em; margin-bottom:.15rem;
}
.rail-pills{
  display:flex; gap:.3rem; overflow-x:auto; padding-bottom:.25rem;
  scroll-snap-type:x mandatory; -webkit-overflow-scrolling:touch;
}
.rail-pills::-webkit-scrollbar{ height:4px }
.rail-pills::-webkit-scrollbar-thumb{ background:var(--border); border-radius:2px }
.rail-pill{
  flex:0 0 auto; scroll-snap-align:start;
  display:inline-flex; align-items:center; gap:.3rem;
  padding:.2rem .5rem; border-radius:999px;
  background:var(--surface); border:1px solid var(--border);
  color:var(--text); text-decoration:none;
  font-size:.7rem; font-weight:600; transition:.15s;
  white-space:nowrap;
}
.rail-pill:hover{
  border-color:var(--accent); color:var(--accent); text-decoration:none;
}
.rail-pill-team{
  background:var(--accent-dim); border-color:transparent; color:var(--accent);
}
.rail-pill-team:hover{ background:var(--accent); color:#fff }
.rail-count{
  font-family:'JetBrains Mono',monospace; font-size:.58rem; font-weight:500;
  color:var(--text-secondary);
}
.rail-pill-team .rail-count{ color:inherit; opacity:.75 }
.summary{ margin:.3rem 0 .6rem }

/* -------- directory grid (players.html, teams.html) -------- */
.dir-grid{
  display:grid; grid-template-columns:repeat(auto-fill, minmax(15rem, 1fr));
  gap:.5rem;
}
.dir-card{
  background:var(--surface); border:1px solid var(--border); border-radius:10px;
  padding:.7rem .9rem; transition:.15s; cursor:pointer; display:flex;
  align-items:center; justify-content:space-between; gap:.5rem;
}
.dir-card:hover{ border-color:var(--accent) }
.dir-card a{ color:var(--text); text-decoration:none; font-weight:600;
             font-size:.92rem; flex:1; min-width:0; overflow:hidden;
             text-overflow:ellipsis; white-space:nowrap }
.dir-card a:hover{ color:var(--accent); text-decoration:none }
.dir-card .count{
  font-family:'JetBrains Mono',monospace; font-size:.72rem;
  color:var(--text-secondary); font-weight:500;
}

/* Polish-10 (Fix 3): ranked leaderboards on players.html / teams.html.
   Each row is a clickable anchor that navigates to the entity page,
   laid out as a 5-column CSS grid:
     [rank] [portrait] [name] [secondary] [count]
   Mobile collapses secondary under the name (the rank/portrait/count
   columns stay fixed so the volume read stays consistent). */
.leaderboard{
  display:flex; flex-direction:column;
  border:1px solid var(--border); border-radius:10px;
  background:var(--surface); overflow:hidden;
}
.lb-row{
  display:grid;
  grid-template-columns:2.2rem 2.6rem 1fr 9rem 4rem;
  gap:.8rem; align-items:center;
  padding:.5rem .9rem;
  border-bottom:1px solid var(--border);
  text-decoration:none; color:var(--text);
  transition:background .12s ease;
}
.lb-row:last-child{ border-bottom:none }
.lb-row:hover{ background:var(--surface-hover); text-decoration:none }
.lb-rank{
  font-family:'JetBrains Mono',monospace; font-size:.78rem;
  color:var(--text-secondary); text-align:right; font-weight:600;
}
.lb-portrait-wrap{
  width:2.4rem; height:2.4rem; position:relative;
  border-radius:50%; overflow:hidden;
  background:var(--surface-hover);
  flex-shrink:0;
}
.lb-portrait{
  display:block; width:100%; height:100%;
  object-fit:cover; object-position:center top;
}
.lb-portrait--team{
  object-fit:contain; padding:.2rem; background:var(--surface);
}
.lb-portrait-fallback{
  position:absolute; inset:0; width:100%; height:100%;
  display:none; align-items:center; justify-content:center;
  background:var(--accent-dim); color:var(--accent);
  font-family:'DM Sans',sans-serif; font-weight:700; font-size:.75rem;
}
.lb-name{
  font-family:'DM Sans',sans-serif; font-weight:600; font-size:.92rem;
  color:var(--text);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
.lb-row:hover .lb-name{ color:var(--accent) }
.lb-secondary{
  font-size:.74rem; color:var(--text-secondary);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0;
}
.lb-team-text{ color:var(--text-secondary) }
.lb-team-empty{ color:var(--border) }
.lb-conf{
  display:inline-block; padding:.1rem .45rem; border-radius:999px;
  font-family:'JetBrains Mono',monospace; font-size:.6rem; font-weight:600;
  text-transform:uppercase; letter-spacing:.05em;
  background:var(--surface-hover); color:var(--text-secondary);
  border:1px solid var(--border);
}
.lb-conf--east{ color:#1d4ed8; background:#dbeafe; border-color:#bfdbfe }
.lb-conf--west{ color:#b91c1c; background:#fee2e2; border-color:#fecaca }
.lb-count{
  font-family:'JetBrains Mono',monospace; font-size:.86rem; font-weight:600;
  color:var(--text); text-align:right;
}
.lb-row:hover .lb-count{ color:var(--accent) }

/* -------- mobile -------- */
@media(max-width:760px){
  .container{ padding:.8rem 1rem 3rem }
  .subtitle{ display:none }
  .tabs{ margin-bottom:.5rem }
  .tabs a{ padding:.3rem .65rem; font-size:.68rem }
  .hdr h1{ font-size:1.1rem }
  .hdr .brand{ display:none }
  /* Mobile: collapse the two-column grid so search input stretches
     full width and pills wrap below in their own scroll row. */
  .controls{ padding:.4rem 0 .55rem; grid-template-columns:1fr; gap:.4rem }
  input[type=text],select{ padding:.4rem .6rem; font-size:.85rem }
  .pill{ flex-shrink:0 }
  .card{ padding:.7rem .8rem }
  .card .thumb{ width:4.5rem; height:3rem }
  .entity-hero{ padding:.8rem .9rem }
  .avatar{ width:3rem; height:3rem; font-size:1rem }
  /* Polish-9 (Fix 3): portrait shrinks on mobile so the hero stays
     under ~110px tall and the chart/rail/first-card layout from
     PR #20 still fits above the fold. */
  .entity-portrait{ width:5rem; height:5rem }
  .entity-portrait-initials{ font-size:1.4rem }
  .entity-portrait-img--team{ padding:.3rem }
  .entity-hero .name{ font-size:1.25rem }
  /* Polish-10 (Fix 3): leaderboard collapses the secondary column
     under the name on narrow viewports — rank / portrait / count
     stay locked to their columns so the volume read stays
     consistent. Two-row layout per entry; tap target stays full
     width. */
  .lb-row{
    grid-template-columns:2rem 2.2rem 1fr 3.5rem;
    grid-template-areas:
      "rank portrait name count"
      "rank portrait secondary count";
    gap:.5rem .6rem; padding:.45rem .65rem;
  }
  .lb-rank{ grid-area:rank; font-size:.7rem }
  .lb-portrait-wrap{ grid-area:portrait; width:2.2rem; height:2.2rem }
  .lb-name{ grid-area:name; font-size:.86rem }
  .lb-secondary{ grid-area:secondary; font-size:.68rem }
  .lb-count{ grid-area:count; font-size:.78rem }
  /* On narrow viewports the chart + rail stack vertically so the
     chart isn't squeezed to ~120px and the rail has room to breathe. */
  .header-row{ flex-direction:column; gap:.4rem }
  .header-row .chart-wrap{ flex:1 1 auto; width:100% }
}
