/* news.css — Portal News, the feed tab.

   Part of the Teleport stylesheet, split by concern. index.html links these in
   source order; keep that order, the cascade depends on it.

   **A reading column, not a dashboard.** Every other screen here is a grid —
   columns of cards, a depth chart, a table of evaluations — because every other
   screen is something to scan. This one is read, top to bottom, so it takes the
   one shape the rest of the app never does: a single narrow column, centred,
   with nothing in the margins competing for the eye. That is also why the cards
   carry no colour of their own. A grade pill is red because the grade is the
   point; here the *text* is the point, and a feed where every card is tinted is
   a feed where nothing stands out. */
.shell--news { max-width: 720px; }

/* ── Header ─────────────────────────────────────────────────────────────
   The live dot is the only thing on this screen that animates, and it earns it:
   "nothing has happened" and "nothing has loaded" look identical on a news feed
   and mean opposite things, so something has to say which one this is. It stops
   pulsing — and the word changes — when the newest post is more than a few
   hours old, because a dot that always says "live" says nothing. */
.news-hero {
  display: flex; align-items: flex-start; gap: 16px;
  padding-bottom: 18px; margin-bottom: 18px;
  border-bottom: 1px solid var(--border-2);
}
.news-hero-main { flex: 1; min-width: 0; }
.news-hero-eyebrow {
  display: flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--label-3); margin-bottom: 7px;
}
.news-hero h1 { font-size: 27px; font-weight: 790; letter-spacing: -0.03em; line-height: 1.05; }
.news-hero-sub { margin-top: 6px; font-size: 13px; color: var(--label-2); }
.news-hero .btn svg { width: 14px; height: 14px; }

.news-dot {
  width: 7px; height: 7px; border-radius: var(--r-full);
  background: var(--label-3); flex-shrink: 0;
}
.news-dot.is-live { background: var(--green); animation: news-pulse 2.4s var(--ease-out) infinite; }
@keyframes news-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--green-light); }
  50%      { box-shadow: 0 0 0 5px transparent; }
}

/* ── Notices ────────────────────────────────────────────────────────────
   Above the feed, never instead of it. The feed underneath is real — the
   server serves its own last good answer when the upstream refuses — so this
   says the posts weren't re-checked rather than replacing them with an error.
   Same rule, and the same shape, as the board's own load-failure banner. */
.news-notice {
  display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 13px; margin-bottom: 16px;
  border-radius: var(--r-md); border: 1px solid var(--border-2);
  background: var(--surface-2); color: var(--label-2);
  font-size: 12.5px; line-height: 1.45;
}
.news-notice svg { width: 15px; height: 15px; flex-shrink: 0; margin-top: 1px; color: var(--label-3); }
.news-notice strong { color: var(--label); font-weight: 700; }
.news-notice--warn { border-color: var(--gold); background: var(--gold-light); }
.news-notice--warn svg { color: var(--gold); }

/* ── Following ──────────────────────────────────────────────────────────
   One button for the whole list, and one to add to it.

   This was a chip per account in a sideways-scrolling row, and it stopped
   scaling the moment following was possible: that row is as wide as the feed,
   so each new account pushed it further toward a strip of faces to be scrolled
   through, with the feed itself squeezed underneath. A count is the same size
   at six accounts or thirty. The facepile is what keeps it from reading as an
   abstract number — three faces say *whose* list this is at a glance. */
.news-toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin-bottom: 20px;
}
.news-following {
  display: inline-flex; align-items: center; gap: 9px;
  padding: 5px 11px 5px 6px; border-radius: var(--r-full);
  background: var(--surface); border: 1px solid var(--border);
  font-family: var(--font); font-size: 13px; font-weight: 650; color: var(--label);
  cursor: pointer; white-space: nowrap;
  transition: border-color 0.15s, box-shadow 0.15s, background 0.15s;
}
.news-following:hover { border-color: var(--label-3); box-shadow: var(--shadow-sm); }
.news-following[aria-expanded="true"] { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-light); }

/* Overlapped, newest-posting first, each ringed in the panel colour so the
   stack reads as depth rather than as three faces stuck together. */
.news-facepile { display: inline-flex; align-items: center; }
.news-pin {
  width: 22px; height: 22px; border-radius: var(--r-full);
  object-fit: cover; background: var(--surface-2);
  box-shadow: 0 0 0 2px var(--surface);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 750; color: var(--label-2);
}
.news-pin + .news-pin { margin-left: -8px; }
.news-following-label { color: var(--label-2); font-weight: 600; }
.news-following-n { font-weight: 780; font-variant-numeric: tabular-nums; }
.news-following-chev { display: inline-flex; color: var(--label-3); margin-left: -2px; }
.news-following-chev svg { width: 13px; height: 13px; display: block; }

.news-follow-btn svg { width: 13px; height: 13px; }

/* The one thing a count cannot say. A filtered feed looks exactly like an
   unfiltered one, and that is how a coach concludes the wire has gone quiet —
   so the narrowing is stated, with its own way out. */
.news-filtered {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 4px 4px 10px; border-radius: var(--r-full);
  background: var(--accent-light); color: var(--accent-deep);
  font-size: 12px; font-weight: 650;
}
:root[data-theme="dark"] .news-filtered { color: var(--accent); }
.news-filtered-label { opacity: 0.75; font-weight: 600; }
.news-filtered-who { font-weight: 750; }
.news-filtered-n { font-variant-numeric: tabular-nums; opacity: 0.75; }
.news-filtered-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: var(--r-full);
  background: none; border: none; color: inherit; cursor: pointer; padding: 0;
}
.news-filtered-x:hover { background: rgba(255, 87, 87, 0.18); }
.news-filtered-x svg { width: 10px; height: 10px; }

/* ── The Following popover ──────────────────────────────────────────────
   ui/following-popover.js. Anchored under its button rather than centred like
   the critical-factors and date-range dialogues — those are tasks that deserve
   the screen, this is a list you glance at and dismiss.

   A refusal from the server — a handle X does not have, a malformed one — is
   stated in the follow dialogue's own hint rather than as an alert, so it
   appears where the mistake was made with the text still typed. */
.field-hint.is-error { color: var(--accent-deep); font-weight: 600; }
:root[data-theme="dark"] .field-hint.is-error { color: var(--accent); }

.fpop {
  position: fixed; z-index: 90; width: 292px; max-width: calc(100vw - 24px);
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-lg); box-shadow: var(--shadow-lg);
  opacity: 0; transform: translateY(-4px);
  transition: opacity 0.14s var(--ease-out), transform 0.14s var(--ease-out);
  overflow: hidden;
}
.fpop.is-above { transform: translateY(4px); }
.fpop.is-in { opacity: 1; transform: none; }

.fpop-head {
  display: flex; align-items: center; gap: 7px;
  padding: 11px 14px 9px; border-bottom: 1px solid var(--border-2);
}
.fpop-title {
  font-size: 11px; font-weight: 750; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--label-3);
}
.fpop-count {
  font-size: 11px; font-weight: 750; color: var(--label-2);
  background: var(--surface-2); border-radius: var(--r-full); padding: 1px 7px;
  font-variant-numeric: tabular-nums;
}

/* Capped and scrolled: the list is meant to grow, and a panel that grew with it
   would eventually be taller than the window. */
.fpop-list { max-height: min(46vh, 330px); overflow-y: auto; padding: 5px; }
.fpop-item { position: relative; display: flex; align-items: center; }
.fpop-row {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 9px;
  padding: 7px 8px; border-radius: var(--r-md);
  background: none; border: none; cursor: pointer; text-align: left;
  font-family: var(--font); color: var(--label);
}
.fpop-row:hover { background: var(--surface-2); }
.fpop-row.is-on { background: var(--accent-light); }
.fpop-row.is-quiet { opacity: 0.6; }
.fpop-row.is-quiet:hover, .fpop-row.is-on { opacity: 1; }

.fpop-face, .fpop-all {
  width: 28px; height: 28px; border-radius: var(--r-full); flex-shrink: 0;
  object-fit: cover; background: var(--surface-2);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 780; color: var(--label-2);
}
.fpop-all svg { width: 13px; height: 13px; color: var(--label-3); }
.fpop-who { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.fpop-name {
  display: flex; align-items: center; gap: 4px;
  font-size: 13px; font-weight: 680; letter-spacing: -0.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fpop-tick-v { display: inline-flex; color: var(--blue); flex-shrink: 0; }
.fpop-tick-v svg { width: 12px; height: 12px; display: block; }
.fpop-handle {
  font-size: 11.5px; color: var(--label-3);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.fpop-n {
  font-size: 11.5px; font-weight: 700; color: var(--label-3);
  font-variant-numeric: tabular-nums; flex-shrink: 0;
}
/* The tick holds its slot whether or not it shows, so rows do not shift
   sideways as the selection moves down the list. */
.fpop-tick { display: inline-flex; color: var(--accent); flex-shrink: 0; visibility: hidden; }
.fpop-tick svg { width: 15px; height: 15px; display: block; }
.fpop-row.is-on .fpop-tick { visibility: visible; }

/* Unfollow sits outside the row button — a button inside a button is invalid,
   and browsers disagree about which one owns the click. Revealed on hover or
   focus so the list reads as a list rather than a row of delete controls, and
   it overlays the count rather than reserving a column that is empty on every
   seed account. */
.fpop-drop {
  position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
  width: 26px; height: 26px; border-radius: var(--r-sm);
  display: none; align-items: center; justify-content: center;
  background: var(--surface); border: 1px solid var(--border);
  color: var(--label-3); cursor: pointer; padding: 0;
}
.fpop-item.is-custom:hover .fpop-drop,
.fpop-item.is-custom:focus-within .fpop-drop { display: flex; }
.fpop-drop:hover { color: var(--accent); border-color: var(--accent); }
.fpop-drop svg { width: 13px; height: 13px; }

.fpop-foot { padding: 7px; border-top: 1px solid var(--border-2); }
/* Dashed, like the scenario page's own "add into this group" row — the same
   gesture in the same clothes, so the two read as one idea across the app. */
.fpop-add {
  width: 100%; display: flex; align-items: center; justify-content: center; gap: 7px;
  padding: 9px; border-radius: var(--r-md);
  background: none; border: 1px dashed var(--border);
  font-family: var(--font); font-size: 12.5px; font-weight: 680; color: var(--label-2);
  cursor: pointer; transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.fpop-add:hover { border-style: solid; border-color: var(--accent); color: var(--accent); background: var(--accent-light); }
.fpop-add svg { width: 14px; height: 14px; }

/* ── Day rail ───────────────────────────────────────────────────────────
   Sticky, because the question this feed is read with is "what came in today",
   and the answer has to stay visible while you scroll through the answer. The
   rule runs out from the label rather than behind it so the date reads as a
   marker on a timeline instead of a heading on a box. */
.news-day {
  position: sticky; top: 0; z-index: 2;
  display: flex; align-items: center; gap: 12px;
  padding: 14px 0 12px;
  background: linear-gradient(var(--bg) 62%, transparent);
  font-size: 11px; font-weight: 750; letter-spacing: 0.07em; text-transform: uppercase;
  color: var(--label-3);
}
.news-day::after { content: ''; flex: 1; height: 1px; background: var(--border-2); }

/* ── A post ─────────────────────────────────────────────────────────────
   The whole card is one anchor. A coach scanning a wire is either passing an
   item or opening it, so the entire surface is the target and the "Open on X"
   label only surfaces on hover — present when you reach for it, silent while
   you read. */
.news-card { margin-bottom: 10px; }
.news-card-link {
  display: block; padding: 15px 17px;
  background: var(--surface); border: 1px solid var(--border-2); border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm); color: inherit; text-decoration: none;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.12s var(--ease-out);
}
.news-card-link:hover {
  border-color: var(--border); box-shadow: var(--shadow-md);
  transform: translateY(-1px);
}
.news-card-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.news-card-head { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.news-face {
  width: 40px; height: 40px; border-radius: var(--r-full); flex-shrink: 0;
  object-fit: cover; background: var(--surface-2);
  display: inline-flex; align-items: center; justify-content: center;
}
.news-face--none { font-size: 16px; font-weight: 780; color: var(--label-2); }
.news-who { flex: 1; min-width: 0; }
.news-name {
  display: flex; align-items: center; gap: 5px;
  font-size: 14px; font-weight: 720; letter-spacing: -0.01em; color: var(--label);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.news-tick { display: inline-flex; color: var(--blue); flex-shrink: 0; }
.news-tick svg { width: 14px; height: 14px; display: block; }
.news-handle { font-size: 12px; color: var(--label-3); margin-top: 1px; }
.news-time {
  font-size: 12px; font-weight: 650; color: var(--label-3);
  font-variant-numeric: tabular-nums; flex-shrink: 0; align-self: flex-start;
}

/* A repost says whose post it actually is, above the text — without it the
   card attributes somebody else's reporting to the account that shared it. */
.news-badge {
  display: inline-flex; align-items: center; gap: 5px; margin-bottom: 7px;
  font-size: 11.5px; font-weight: 650; color: var(--label-3);
}
.news-badge svg { width: 12px; height: 12px; }

/* 15px, 1.55, and the full post — never truncated. A portal report is two
   sentences and the second one is usually the school he picked. */
.news-text {
  font-size: 14.5px; line-height: 1.55; color: var(--label);
  white-space: pre-wrap; word-break: break-word;
}
/* Mentions, hashtags and links are coloured but are *not* anchors — the card
   is already one, and a link inside a link is invalid HTML that browsers
   unnest in their own ways. See linkify() in views/news.js. */
.news-mention, .news-tag { color: var(--blue); font-weight: 600; }
.news-link { color: var(--blue); }

.news-quote {
  margin-top: 10px; padding: 9px 12px;
  border: 1px solid var(--border-2); border-radius: var(--r-md);
  background: var(--surface-2);
}
.news-quote-who { font-size: 11.5px; font-weight: 700; color: var(--label-2); margin-bottom: 3px; }
.news-quote-text { font-size: 13px; line-height: 1.45; color: var(--label-2); }

/* ── Media ──────────────────────────────────────────────────────────────
   The four layouts X itself settles on, because anything else makes a
   two-image post read as a broken three-image one. A video shows its still
   with a play badge — tapping through to X is where it plays, which is one tap
   away from anywhere on this card anyway. */
.news-media {
  display: grid; gap: 3px; margin-top: 11px;
  border-radius: var(--r-md); overflow: hidden;
  max-width: 100%;
}
/* Two, three and four photos are tiled into one fixed frame — the mosaic X
   itself renders — so each tile shares a uniform box and `cover` is the
   right call for it, the same trade X's own timeline makes. */
.news-media--2, .news-media--3, .news-media--4 { aspect-ratio: 16 / 9; }
.news-media--1 { grid-template-columns: 1fr; }
.news-media--2 { grid-template-columns: 1fr 1fr; }
.news-media--3 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.news-media--3 .news-shot:first-child { grid-row: span 2; }
.news-media--4 { grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; }
.news-shot { position: relative; background: var(--surface-2); overflow: hidden; }
.news-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* A single photo has no mosaic to stay uniform with, so it is shown whole
   rather than cropped to fit a fixed 16:9 frame — a portrait shot loses its
   top and bottom to `cover`, and a wide screenshot loses its sides. It scales
   to the card's width at its own natural height instead, capped so one very
   tall image can't dominate the feed; letterboxing (if any) blends into the
   same neutral fill every unloaded shot already uses. */
.news-media--1 .news-shot { aspect-ratio: auto; }
.news-media--1 .news-shot img {
  height: auto; max-height: 520px; object-fit: contain;
}
.news-play {
  position: absolute; inset: 0; margin: auto;
  width: 42px; height: 42px; border-radius: var(--r-full);
  background: rgba(0, 0, 0, 0.6); color: #fff;
  display: flex; align-items: center; justify-content: center;
}
.news-play svg { width: 16px; height: 16px; margin-left: 2px; }

/* ── Engagement ─────────────────────────────────────────────────────────
   Quiet on purpose. These are X's numbers, not ours, and they are context for
   how widely a report has travelled — not a metric this app is asking anybody
   to act on. */
.news-stats {
  display: flex; align-items: center; gap: 16px; margin-top: 12px;
  font-size: 11.5px; font-weight: 620; color: var(--label-3);
  font-variant-numeric: tabular-nums;
}
.news-stat { display: inline-flex; align-items: center; gap: 5px; }
.news-stat svg { width: 13px; height: 13px; }
.news-stat--views { margin-left: -6px; }
.news-open {
  margin-left: auto; display: inline-flex; align-items: center; gap: 5px;
  color: var(--label-3); opacity: 0; transition: opacity 0.15s;
}
.news-open svg { width: 12px; height: 12px; }
.news-card-link:hover .news-open { opacity: 1; }

/* ── Blank and loading ──────────────────────────────────────────────────
   Three states with three messages — loading, filtered to nothing, and
   genuinely empty — because on a feed they look the same and mean entirely
   different things. */
.news-blank {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 8px; padding: 64px 24px; text-align: center;
  border: 1px dashed var(--border); border-radius: var(--r-lg);
  color: var(--label-2);
}
.news-blank-title { font-size: 15px; font-weight: 700; color: var(--label); }
.news-blank-sub { font-size: 13px; color: var(--label-3); max-width: 42ch; }
.news-blank--loading { gap: 16px; }

/* ── Load more ───────────────────────────────────────────────────────────
   Reveals the next page of what's already in S.news — no request, so it
   sits as a plain footer control rather than anything that needs a loading
   state of its own. */
.news-load-more { display: flex; justify-content: center; padding: 20px 0 8px; }
