/* ══ feed: three voices, three shapes ════════════════════════════════════
 *
 * Every post is a card. This is the one place the redesign overturned an older
 * decision on purpose: the feed used to be hairline-parted rows, on the
 * argument that a feed is read in a run rather than acted on one at a time.
 * That is true of a timeline you only read. Orbis's feed is a timeline you
 * *press* - vote, reply, quote, tip, open the author - and once every row
 * carries five targets, the row needs an edge saying where it ends.
 *
 * What the shapes say, before a word of any of it is read:
 *
 *   a citizen  - a slab, a byline, reaction capsules, and the thread under it
 *   the city   - an inset amber panel, signed with the city's own name
 *   Orbis      - the same panel in blue, signed with the world's
 *
 * That amber/blue split is `FeedScope` made visible. It already decided who
 * gets told; now it decides how the row looks, so "my street" and "the world"
 * stop arriving in the same envelope.
 */

/* ── the layout ─────────────────────────────────────────────────────── */
/* The feed and its rail. One column of reading, one of context.
 *
 * The only thing in Orbis with a width limit of its own. Everywhere else is
 * cards in grids, which are happy to fill a wide window by fitting more of
 * themselves across it; a paragraph is not - a post set 1500px wide is a post
 * nobody's eye can get back to the start of. So the reading column stops at a
 * sane measure and the pair sits at the left, where the eye already is. */
.feed-layout { display: grid; grid-template-columns: minmax(0, 880px) 300px;
  gap: 40px; align-items: start; justify-content: center; }
.feed-main { min-width: 0; display: grid; align-content: start; gap: 14px; }

/* Which voices you want to hear at all - the coarsest cut, so it stands alone
   above the box you write in. */
.feed-kinds { display: flex; justify-content: flex-end; }
/* Where it was said, and what comes first: two different questions, so a switch
   and a set of capsules rather than two rows of the same control. */
.feed-controls { display: flex; align-items: center; justify-content: space-between;
  gap: 10px; flex-wrap: wrap; }
.feed-controls .subtabs { margin-left: auto; }

/* The box you say something in. A card, like the posts under it, so writing one
   and reading one are visibly the same kind of act. */
.compose { display: flex; gap: 11px; padding: 13px 14px; align-items: center;
  background: var(--panel-2); border: 2px solid var(--border); border-radius: var(--r-lg); }
.compose input { flex: 1; min-width: 0; }
/* The composer, when it is answering or quoting something: the thing being
   answered sits above the box, so you can see what you're talking about. */
.compose-context { padding: 10px 14px; margin: 10px 0 -6px; }
.compose-context .row { justify-content: space-between; }
.topic-banner { justify-content: space-between; font-size: 12.5px;
  color: var(--gold); padding: 10px 0 0; }

/* ── a row ──────────────────────────────────────────────────────────── */
.feed { display: grid; gap: 10px; }
.feed-row { border-bottom: 0; }
/* One page of the feed at a time renders; this is how the rest are reached
   without the browser paying for a DOM row it hasn't been asked for yet. */
.feed-more { width: 100%; margin-top: 4px; }

/* A citizen: standing in the gutter, words in the column. */
.voice { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 11px;
  padding: 14px 16px; background: var(--panel-2);
  border: 2px solid var(--border); border-radius: var(--r-lg);
  transition: border-color .15s; }
@media (hover: hover) { .voice:hover { border-color: var(--border-hi); } }
.voice-body { min-width: 0; display: grid; gap: 5px; }
.voice-head { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.voice-head .dot { white-space: nowrap; }
.post-text { overflow-wrap: break-word; line-height: 1.55; font-size: 15px;
  color: #dfe3f2; text-wrap: pretty; }
.post-actions { margin-top: 9px; gap: 7px; }
.voice-actions { gap: 7px; margin-top: 2px; }
/* A proclamation is a citizen's post wearing the office. The badge carries it
   and the card barely warms - it is still a post you can answer. */
.voice.official { background: #2a2620; border-color: var(--tint-gold-line); }
/* Somebody said your name: the one card on the page that is *for* you. */
.voice.to-me { background: rgba(75, 123, 236, .09); border-color: rgba(75, 123, 236, .45); }
/* A reply inside an open thread is not a card of its own - it is part of the
   conversation the card above it started, so it gives the border back. */
.voice.reply { padding: 11px 0 11px 2px; background: none; border: 0; border-radius: 0; }
@media (hover: hover) { .voice.reply:hover { border-color: transparent; } }

/* The world narrating itself. Inset to the same gutter a citizen's words start
   at, so an announcement reads as something that happened *in* the conversation
   rather than as a banner over it. */
.event-row { padding: 0; }
/* Flush with the posts around it. The inset this used to carry lined the world's
   voice up with the *text* column of a citizen's post, back when a post was a
   row with a gutter. Now that a post is a card, that same inset just made every
   announcement look nudged out of place. */
.event { display: flex; align-items: flex-start; gap: 11px;
  padding: 12px 14px; border-radius: var(--r-lg);
  background: var(--tint-cool);
  border: 2px solid var(--tint-cool-line);
  border-left: 4px solid var(--cool); }
.event-mark { width: 28px; height: 28px; border-radius: var(--r-sm); flex-shrink: 0;
  display: grid; place-items: center; font-size: 13px;
  background: rgba(124, 199, 238, .14); color: var(--cool); }
.event-body { min-width: 0; display: grid; gap: 4px; }
.event-meta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.event-label { font-size: 10px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--cool); }
.event-text { font-size: 14px; font-weight: 700; line-height: 1.45;
  overflow-wrap: break-word; text-wrap: pretty; }
/* …and the city narrating itself: the same panel, in the city's own amber. */
.event.local { background: var(--tint-gold);
  border-color: var(--tint-gold-line); border-left-color: var(--warn); }
.event.local .event-mark { background: rgba(255, 159, 67, .14); color: var(--warn); }
.event.local .event-label { color: var(--warn); }

.quote { border-left: 2px solid var(--border-hi); padding: 4px 10px; margin: 6px 0;
  font-size: 12.5px; color: var(--muted); background: rgba(0, 0, 0, .22); border-radius: 0 8px 8px 0; }

/* ── threads ────────────────────────────────────────────────────────── */

/* Closed: one line per reply, truncated. Enough to tell whether the conversation
   went anywhere; cheap enough to sit under every post on the page. The whole
   strip is one press, which is why the lines are plain text - a mention in here
   would be a button inside a button. */
.peek { display: grid; gap: 4px; width: 100%; margin-top: 3px;
  padding: 3px 0 3px 12px; text-align: left;
  border: 0; border-left: 1px solid var(--border-hi); border-radius: 0;
  background: none; font: inherit; font-weight: 400; }
.peek-line { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.peek-line b { font-size: 12px; color: var(--muted); flex-shrink: 0; }
.peek-said { font-size: 12.5px; color: var(--muted-2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.peek-more { font-size: 11.5px; color: var(--muted-2); }
@media (hover: hover) {
  .peek:hover { border-color: transparent; border-left-color: var(--accent); }
  .peek:hover .peek-said { color: var(--muted); }
}
/* Open: the conversation, with somewhere to answer it at the foot - so replying
   to a thread you are reading never means scrolling back to the top of the page. */
.thread { display: grid; margin-top: 6px; padding-left: 14px;
  border-left: 1px solid var(--border); }
.thread > .feed-row:last-of-type { border-bottom: 0; }
/* The box at the foot of a thread is the composer at the top of the page,
   smaller: a face, a field and a press, inside an edge that says where writing
   happens. Without the edge it was a naked input lying under the last reply,
   which read as part of the conversation rather than as the place you join it. */
.thread-reply { display: flex; align-items: center; gap: 8px; margin: 9px 0 2px;
  padding: 8px 9px; background: var(--panel-2);
  border: 1.5px solid var(--border); border-radius: var(--r-md); }
.thread-reply input { flex: 1; min-width: 0; font-size: 12.5px; padding: 7px 11px; }
.thread-reply .avatar.sm { width: 28px; height: 28px; font-size: 13px; }
/* A thread is already indented, so the deepest ones have the least room - the
   field keeps it and the button gives its padding up. */
@media (max-width: 560px) {
  .thread-reply { gap: 6px; padding: 7px; }
  .thread-reply .avatar.sm { display: none; }
}
/* Past a couple of levels, stop indenting: a column three characters wide is
   not a conversation. */
.thread.flat { margin-left: 0; padding-left: 0; border-left: 0; }

/* ── rich content: what a post and a message are both made of ──────────── */

/* A handle and a topic are the two things in somebody's words you can press.
   Both read as text until then - inline, no button chrome - because a post that
   looks like a toolbar is not a post. */
.rich { overflow-wrap: break-word; }
.mention, .topic { display: inline; padding: 0; border: 0; background: none;
  font: inherit; font-weight: 600; cursor: pointer; }
.mention { color: var(--link); }
.topic { color: var(--gold); }
@media (hover: hover) {
  .mention:hover, .topic:hover { text-decoration: underline; }
}

/* A byline is a doorway to a person: avatar, name, handle, and whatever badges
   say what kind of voice this is.

   Two names of unknown length on one line, in a card of fixed width: the layout
   rule is that it *wraps* rather than squeezing. Ellipsis alone let the name
   collapse to nothing while the handle kept its full width, which is the wrong
   thing to lose - a citizen is a person before they are a handle. */
.byline { display: inline-flex; align-items: center; gap: 7px;
  min-width: 0; max-width: 100%; flex-wrap: wrap; }
.byline-avatar { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  background: #7c8db5; color: var(--on-accent);
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 600; font-size: 15px; }
.byline-avatar.sm { width: 26px; height: 26px; font-size: 12px; }
.byline-who { display: inline-flex; align-items: baseline; gap: 6px;
  min-width: 0; flex: 1 1 auto; flex-wrap: wrap;
  padding: 0; border: 0; background: none; color: inherit; font: inherit;
  cursor: pointer; text-align: left; }
.byline-who:disabled { cursor: default; }
/* The name may shrink and ellipsise, but never below something readable - past
   that the handle wraps to its own line instead. */
.byline-who b { min-width: 4ch; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
@media (hover: hover) {
  .byline-who:not(:disabled):hover b { color: var(--accent-2); }
}
/* The unique name, quieter than the display name it follows - it is what you
   type, not what you read. */
.handle { font-size: 12px; color: var(--muted-2); font-weight: 500;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.handle.big { font-size: 13.5px; color: var(--accent-2); font-weight: 600; }
.badge.mention-badge { background: rgba(75, 123, 236, .22); color: var(--accent-2); }
/* When something was said, on the world's own calendar. Quiet, and the exact
   date is in the `title` - the badge answers "how long ago", which is the
   question actually being asked. */
.when { color: var(--muted-2); white-space: nowrap; cursor: help;
  font-variant-numeric: tabular-nums; }

/* ── embeds: the thing a post or message is pointing at ────────────────── */

.embed { display: flex; gap: 11px; align-items: flex-start; width: 100%;
  margin-top: 9px; padding: 11px 12px; text-align: left;
  background: var(--panel); border: 1.5px solid var(--border);
  border-radius: var(--r-md); box-shadow: none; color: inherit; font: inherit; }
button.embed:active:not(:disabled) { transform: none; }
.embed-body { flex: 1; min-width: 0; display: grid; gap: 5px; }
.embed-text { font-size: 13.5px; color: var(--muted); }
.embed-meta { margin-top: 2px; }
.embed-post { flex-direction: column; gap: 5px; cursor: pointer; }
@media (hover: hover) { .embed-post:hover { border-color: var(--border-hi); } }
.embed-citizen { flex-direction: column; gap: 7px; }
.embed-cover { font-size: 26px; line-height: 1; }
.embed-excerpt { font-size: 13px; color: var(--muted); font-style: italic;
  overflow-wrap: break-word; }
/* Shelves empty and buildings come down. Saying so is the honest card. */
.embed-gone { color: var(--muted-2); font-size: 13px; font-style: italic;
  border-style: dashed; }
.chip.price { background: rgba(255, 196, 66, .1); border-color: rgba(255, 196, 66, .32);
  color: var(--gold); font-family: var(--font-num); }
.chip.price.good { background: rgba(66, 201, 124, .14);
  border-color: rgba(66, 201, 124, .35); color: var(--ok); }

/* ── votes: a stance, not a counter ────────────────────────────────────── */

.votes { display: inline-flex; align-items: center; gap: 2px; }
.vote { padding: 3px 7px; border: 0; background: none; box-shadow: none;
  color: var(--muted-2); font-size: 12px; line-height: 1; cursor: pointer;
  border-radius: var(--r-sm); transition: transform .12s, color .15s, background .15s; }
@media (hover: hover) {
  .vote.up:hover { color: var(--ok); background: rgba(66, 201, 124, .12); }
  .vote.down:hover { color: var(--err); background: rgba(255, 93, 115, .12); }
}
/* Pressing a stance pops rather than sinks: it is a reaction, not a machine
   you operate, and the whole point is that it feels good to give. */
button.vote:active:not(:disabled) { transform: scale(1.18); }
/* The side you already pressed stays lit, so your own stance survives a reload. */
.vote.up.on { color: var(--ok); }
.vote.down.on { color: var(--err); }
.score { min-width: 1.6em; text-align: center; font-size: 12.5px; font-weight: 700;
  color: var(--muted); }
.score.positive { color: var(--ok); }
.score.negative { color: var(--err); }
/* On its side, as a gutter: a post's standing sits *beside* the post instead of
   below it, which is what lets a row be scanned without being read. */
.votes.stack { flex-direction: column; gap: 0; align-self: start;
  width: 28px; padding-top: 2px; }
.votes.stack .vote { padding: 2px 0; width: 100%; }
.votes.stack .score { font-size: 12px; }

/* ── the rail: talked about, your city, who is standing next to you ─────
   All three are read off state the client already holds, so the column costs a
   pass over a vector rather than a request - and it moves the instant a post or
   a city update lands. */

.feed-rail { display: grid; gap: 14px; align-content: start;
  position: sticky; top: 0; }
.rail-card { padding: 15px; display: grid; gap: 11px; }
/* Not a filing label but a small heading: an icon and a name, the way every
   other panel in Orbis introduces itself. */
.rail-title { display: flex; align-items: center; gap: 8px;
  font-size: 13px; font-weight: 800; letter-spacing: 0;
  text-transform: none; color: var(--text-hi); }
.rail-title .rail-count { margin-left: auto; font-size: 11px; font-weight: 800;
  color: var(--ok); }
.rail-list { display: grid; gap: 8px; }
/* One row, whether it is a fact, a topic or a person: label left, value right. */
.rail-row { display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px; min-width: 0; padding: 0; border: 0; border-radius: var(--r-sm);
  background: none; box-shadow: none; font: inherit; font-weight: 600; text-align: left; }
button.rail-row:active:not(:disabled) { transform: none; }
.rail-label { font-size: 12.5px; font-weight: 600; color: var(--muted); }
.rail-value { font-size: 12.5px; color: var(--text); white-space: nowrap; }
.rail-num { font-size: 12px; color: var(--muted-2); }
/* The line that is the reason to look at the panel at all - a ballot open right
   now is not another statistic. */
.rail-row.loud .rail-label, .rail-row.loud .rail-value { color: var(--warn); font-weight: 600; }
button.rail-row .topic { font-size: 13px; }
button.rail-row.active .topic { color: var(--accent-2); }
/* A neighbour is a doorway rather than a reading: it gets padding of its own
   and lights up under the pointer, which is the difference between a row you
   press and a row you read. The `.person-id` inside it is `people.css`. */
.rail-row.person { justify-content: flex-start; align-items: center; gap: 10px;
  padding: 6px 7px; margin: 0 -7px; border-radius: var(--r-md); }
@media (hover: hover) {
  button.rail-row:hover:not(:disabled) { border-color: transparent; opacity: .75; }
  button.rail-row.person:hover:not(:disabled) { opacity: 1; background: var(--panel-3); }
}
/* Below this the reading column and the rail no longer both fit, so the rail
   stops being a column and becomes a strip of panels under the feed - still
   there, still live, no longer competing for the width. */
@media (max-width: 1100px) {
  .feed-layout { grid-template-columns: minmax(0, 1fr); gap: 18px; }
  .feed-rail { position: static;
    grid-template-columns: repeat(auto-fit, minmax(228px, 1fr)); }
}

/* ── the phone ──────────────────────────────────────────────────────── */
@media (max-width: 920px) {
  /* The feed gives its padding back: on a phone the card *is* the page, so the
     gutter, the byline and the words get the width instead. */
  .voice { padding-left: 12px; padding-right: 12px; }
  .voice.reply { padding-left: 0; }
  .event { margin-left: 0; }
  .feed-controls { align-items: stretch; flex-direction: column; }
  .feed-controls .subtabs { margin-left: 0; }
  /* Room to type: the send button may not squeeze the field into nothing. */
  .compose { flex-wrap: wrap; }
  .compose input { flex: 1 1 150px; min-width: 0; }
  /* Mobile is a big part of the audience, and a post's actions are the
     part a thumb has to hit. Let them wrap rather than shrink, and give the vote
     arrows a real target. */
  .post-actions { flex-wrap: wrap; row-gap: 6px; }
  .vote { padding: 6px 9px; font-size: 13px; }
  /* Nesting eats the width a phone hasn't got: past the first level, replies
     line up with their parent instead of stepping further right. */
  .reply-thread:not(.flat) { margin-left: 0; }
  .post.reply { padding: 10px 11px; }
  .embed { flex-wrap: wrap; }
}
