/* Message Hub — simple, clean, dark-friendly. No information by color alone. */
:root {
  --bg: #141a22; --panel: #1b2430; --text: #e8edf3; --muted: #9aa7b8;
  --accent: #7fb3ff; --border: #2c3a4d; --sys: #3a2f16;
}
* { box-sizing: border-box; }
/* `hidden` must ALWAYS hide. The UA stylesheet's [hidden] rule is a
   normal declaration, so any author display rule on the same element
   (label { display: block }, .badge { display: inline-block }, …)
   silently defeats it in Blink browsers — the element stays rendered
   and reachable by screen readers even with the hidden attribute set.
   That is exactly how the conversation notification-preference select
   survived the v14 "hide it for character DMs" fix in real browsers
   while jsdom (whose UA sheet gives [hidden] priority) showed it
   hidden: Nasko kept meeting the stray edit control in Emily Howard's
   DM (2026-10-13). This one guard makes every hidden toggle in the
   app mean what the code already assumes it means. */
[hidden] { display: none !important; }
/* Platform + device conformance (2026-10-21): app.js stamps the
   device truth on <body> (data-platform, data-device-form,
   data-os, data-constrained). The stylesheet carries no
   transitions or animations today, and this guard keeps it
   that way on a device that reports constrained resources:
   under data-constrained, ALL motion is stilled. Hover is
   decoration only in this stylesheet — every :hover rule
   restyles a border or a color and stands paired with its
   :focus-visible / :focus equivalent, so nothing is reachable
   or visible by hover alone on any device (audited 2026-10-21:
   six :hover rules, no title tooltips, no mouse-only handlers
   in app.js). Touch targets stand as sized — the tab bar and
   buttons keep their padding on every form; no rule shrinks a
   target for a desktop form or the reverse. */
body[data-constrained="true"] *,
body[data-constrained="true"] *::before,
body[data-constrained="true"] *::after {
  animation: none !important; transition: none !important;
}
/* The sign-in gate (v47, Nasko's rule 2026-10-14): the sign-in screen
   and the signed-in app are mutually exclusive — signed out, you see
   the sign-in screen and NOTHING else; signed in, the sign-in block
   does not exist on the page. app.js stamps the current view on
   <body data-view="..."> at every view change, and these two rules
   make exclusivity a property of the stylesheet itself: the auth
   section is paintable ONLY while it is the current view, and every
   other section is unpaintable while it is. No hidden-flag mistake,
   stale script, or half-run view switch can paint both at once —
   the failure that put a "Sign in to Discord" block inside a
   signed-in app on 2026-10-14. */
body:not([data-view="auth"]) #view-auth { display: none !important; }
body[data-view="auth"] #main > section:not(#view-auth) { display: none !important; }
/* The loading gate (2026-10-23, Nasko's ruling): a cold start
   paints the LOADING screen first — the document itself opens on
   it (<body data-view="boot">) — and the same exclusivity holds
   for it as for the sign-in screen: the boot section is paintable
   ONLY while it is the current view, and every other section is
   unpaintable while it is. The sign-in screen therefore can never
   flash during a boot: it paints only when the boot hands over
   to it because a session is truly over. */
body:not([data-view="boot"]) #view-boot { display: none !important; }
body[data-view="boot"] #main > section:not(#view-boot) { display: none !important; }
body {
  margin: 0; background: var(--bg); color: var(--text);
  font: 1.05rem/1.5 system-ui, sans-serif;
}
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
.skip {
  position: absolute; left: -9999px; top: 0; background: var(--accent);
  color: #000; padding: .5rem 1rem; z-index: 99;
}
.skip:focus { left: 0; }
header {
  display: flex; justify-content: space-between; align-items: center;
  padding: .6rem 1rem; background: var(--panel); border-bottom: 2px solid var(--border);
}
header h1 { font-size: 1.3rem; margin: 0; }
.header-actions { display: flex; gap: .6rem; align-items: center; }
.layout { display: flex; min-height: calc(100vh - 3.5rem); }
nav {
  width: 16rem; flex-shrink: 0; background: var(--panel);
  border-right: 2px solid var(--border); padding: .8rem;
}
nav ul { list-style: none; margin: 0; padding: 0; }
nav button {
  display: block; width: 100%; text-align: left; margin: .25rem 0;
  background: none; border: 2px solid transparent; color: var(--text);
  font-size: 1.05rem; padding: .6rem .7rem; border-radius: .4rem; cursor: pointer;
}
nav button:hover, nav button:focus-visible { border-color: var(--accent); outline: none; }
nav button[aria-current="page"] { border-color: var(--accent); background: #223046; }
.key {
  display: inline-block; min-width: 1.4em; text-align: center; font-weight: bold;
  border: 1px solid var(--border); border-radius: .25em; margin-right: .4em;
  background: #0f141b;
}
main { flex: 1; padding: 1rem 1.4rem; max-width: 60rem; }
h2 { font-size: 1.35rem; }
h3 { font-size: 1.1rem; margin-top: 1.4rem; }
.hint { color: var(--muted); font-size: .95rem; }
/* The city heading over the street list (2026-10-19): a plain
   note line, not a button — it says which city you are in. */
.city-heading { color: var(--muted); font-size: .95rem; padding: .4rem 0; list-style: none; }
.note { color: var(--muted); font-style: italic; }
label { display: block; margin-top: .8rem; font-weight: bold; }
input, textarea {
  display: block; width: 100%; max-width: 34rem; margin-top: .25rem;
  background: #0f141b; color: var(--text); border: 2px solid var(--border);
  border-radius: .4rem; padding: .55rem .7rem; font-size: 1.05rem;
}
input:focus-visible, textarea:focus-visible, button:focus-visible {
  outline: 3px solid var(--accent); outline-offset: 2px;
}
button {
  background: #223046; color: var(--text); border: 2px solid var(--border);
  border-radius: .4rem; padding: .55rem 1rem; font-size: 1.02rem; cursor: pointer;
  margin-top: .7rem;
}
button:hover { border-color: var(--accent); }
.item-list { list-style: none; margin: 0; padding: 0; }
/* Room-list floor sections (2026-10-19, the floors round): a
   section heading over its floor's rooms — the directory a
   screen reader navigates by headings. */
.room-section > h4 { margin: 1rem 0 .2rem; font-size: 1.05rem; }
.item-list > li {
  border: 2px solid var(--border); border-radius: .5rem; padding: .7rem .9rem;
  margin: .5rem 0; background: var(--panel);
}
.item-list button { margin: 0; background: none; border: none; padding: 0;
  font-size: 1.05rem; text-align: left; width: 100%; }
.item-list .meta { color: var(--muted); font-size: .92rem; display: block; }
.profile-list { margin: 0 0 1rem; }
.profile-list dt { font-weight: bold; margin-top: .8rem; }
.profile-list dd { margin: .2rem 0 0; }
.msg { margin: .5rem 0; }
.msg .who { font-weight: bold; }
.msg .receipt { display: block; color: var(--muted); font-size: .85rem; }
.msg .time { color: var(--muted); font-size: .85rem; }
.msg.system-msg {
  border-left: 4px solid #d8a93c; padding-left: .7rem;
  background: var(--sys); border-radius: .25rem;
}
/* Thought bubble (2026-10-27, Nasko): a non-speaking
   character's thought — dashed and rounded like a thought
   bubble in a story, never dressed as speech. */
.msg.thought-msg {
  border: 2px dashed var(--border); border-radius: 1rem;
  padding: .4rem .7rem; background: var(--panel);
}
.msg .sys-tag {
  font-size: .8rem; font-weight: bold; text-transform: uppercase;
  letter-spacing: .06em; color: #d8a93c; margin-right: .5em;
}
#messages {
  border: 2px solid var(--border); border-radius: .5rem; padding: .8rem;
  max-height: 50vh; overflow-y: auto; background: #10151c; margin: .8rem 0;
}
#compose-form { display: flex; gap: .6rem; align-items: flex-end; }
#compose-form textarea { flex: 1; }
kbd {
  border: 1px solid var(--border); border-radius: .25em; padding: 0 .4em;
  background: #0f141b; font-family: inherit;
}
dialog {
  background: var(--panel); color: var(--text); border: 2px solid var(--border);
  border-radius: .6rem; padding: 1.2rem 1.6rem; max-width: 32rem;
}
dialog::backdrop { background: rgba(0,0,0,.6); }
dialog dt { font-weight: bold; margin-top: .5rem; }
dialog dd { margin: .1rem 0 .4rem 0; color: var(--muted); }
@media (max-width: 48rem) {
  .layout { flex-direction: column; }
  nav { width: auto; border-right: none; border-bottom: 2px solid var(--border); }
}
.linklike {
  background: none; border: none; padding: 0; margin: 0;
  color: var(--accent, #7cc4ff); text-decoration: underline; cursor: pointer;
  font: inherit;
}
.linklike:hover, .linklike:focus { color: #a8d8ff; }

/* ---- v2.0 top-level tabs (Chats / Transit / Events / Radio) ---- */
#tabs {
  display: flex; gap: .4rem; padding: .5rem 1rem;
  background: var(--panel); border-bottom: 2px solid var(--border);
}
#tabs button {
  border: 2px solid transparent; background: none; color: var(--text);
  font-size: 1.1rem; padding: .55rem 1.2rem; border-radius: .4rem .4rem 0 0;
  cursor: pointer; margin: 0;
}
#tabs button:hover, #tabs button:focus-visible { border-color: var(--accent); outline: none; }
#tabs button[aria-selected="true"] {
  border-color: var(--accent); border-bottom-color: var(--panel);
  background: var(--bg); font-weight: bold;
}

/* ---- transit sub-tabs (one view per service) ---- */
#transit-tabs {
  display: flex; flex-wrap: wrap; gap: .4rem; padding: .5rem 0;
  border-bottom: 2px solid var(--border); margin-bottom: .5rem;
}
#transit-tabs button {
  border: 2px solid transparent; background: none; color: var(--text);
  font-size: 1rem; padding: .45rem 1rem; border-radius: .4rem .4rem 0 0;
  cursor: pointer; margin: 0;
}
#transit-tabs button:hover, #transit-tabs button:focus-visible { border-color: var(--accent); outline: none; }
#transit-tabs button[aria-selected="true"] {
  border-color: var(--accent); border-bottom-color: var(--panel);
  background: var(--bg); font-weight: bold;
}
#panel-transit [role="tabpanel"] { padding: 0 .2rem; }
#panel-chats { display: flex; width: 100%; }
#panel-chats[hidden] { display: none; }
button.primary {
  background: var(--accent); color: #000; font-weight: bold;
  border-color: var(--accent); font-size: 1.15rem; padding: .7rem 1.6rem;
}
.badge {
  display: inline-block; min-width: 1.5em; text-align: center;
  background: var(--accent); color: #000; font-weight: bold;
  border-radius: 1em; padding: 0 .35em; margin-left: .3em; font-size: .85em;
}
.meta { color: var(--muted); font-size: .9rem; }
.event-card { margin-top: .5rem; padding: .6rem .7rem; border: 1px solid var(--border); border-radius: .5rem; }
.event-card .meta { display: block; }
.event-card .actions { margin-top: .45rem; }
.event-card .actions button { margin-right: .4rem; }
.event-move-form { margin-top: .5rem; }
.typing { color: var(--muted); font-style: italic; margin: .25rem 0; }
.presence-line { color: var(--muted); margin: .25rem 0; }
.status { min-height: 1.2em; }

/* Action buttons inside Transit/Events list items (Board, RSVP, …): small
   and inline, unlike the full-row buttons of navigable lists. */
.item-list .actions { margin-top: .45rem; }
.item-list .actions button {
  width: auto; background: #223046; border: 2px solid var(--border);
  border-radius: .4rem; padding: .4rem .85rem; margin: 0 .4rem .2rem 0;
  font-size: .98rem; text-align: center;
}
.item-list .actions button:hover, .item-list .actions button:focus-visible {
  border-color: var(--accent); outline: none;
}

/* Shared media (2026-10-17): the in-conversation media card, its
   player, the room's now-playing line, and the share block under
   the composer. Plain and roomy — the player is the browser's own
   native control, sized to be an easy target. */
.media-card {
  margin: .35rem 0 .15rem; padding: .6rem .7rem;
  border: 2px solid var(--border); border-radius: .5rem;
  background: #16202f; max-width: 34rem;
}
.media-title { font-weight: bold; margin: 0 0 .45rem; overflow-wrap: anywhere; }
.media-card audio { width: 100%; }
.media-card .media-video { width: 100%; max-height: 60vh; background: #000; }
.media-np { margin: .4rem 0 0; }
.media-np:empty { display: none; }
/* A voice message's transcript (2026-10-26, the truth round):
   the words stand with the message, in its own reading order —
   plain text in the message's flow, never a card of their own. */
.voice-transcript { margin-top: .3rem; overflow-wrap: anywhere; }
.voice-transcript-note { margin: .3rem 0 0; }
.media-delete { margin-top: .5rem; }
.media-bring { margin-top: .5rem; margin-left: .5rem; }
.media-bring-box { margin-top: .5rem; }
.media-bring-box select { margin-left: .5rem; }
.now-playing { font-weight: bold; margin: .25rem 0; }
/* Now sounding (2026-10-25, the sounds playback round): the
   strip that names a sounding sound and carries its stop —
   it reads like the call ring's bar: plain, reachable, gone
   when nothing sounds. */
.sound-strip {
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: .6rem 1rem; background: var(--sys);
  border-bottom: 3px solid var(--accent);
}
.sound-strip[hidden] { display: none; }
#media-upload-wrap { margin-top: 1rem; }
#media-upload-wrap input[type="file"] { display: block; margin: .4rem 0; }

/* Update notices + the update offer (2026-10-19): a notice reads
   as a notice — the system's own gold (--sys), set apart from the
   announcement rows; the banner sits under the header and waits
   for a tap; the follow-through panel appears once, then never. */
.update-notice-card {
  border: 2px solid var(--accent); border-radius: .5rem;
  padding: .7rem .9rem; margin: .5rem 0; background: var(--sys);
}
.update-notice-card .meta { color: var(--muted); font-size: .92rem; }
.item-list > li.update-notice-row { background: var(--sys); }
.update-banner {
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: .6rem 1rem; background: var(--sys);
  border-bottom: 2px solid var(--accent);
}
.update-banner[hidden] { display: none; }
/* An incoming group call (2026-10-20): the ring stands above
   whatever you are doing until it is answered, declined, or
   lapses — like a phone ringing on the desk. */
.call-ring {
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: .6rem 1rem; background: var(--sys);
  border-bottom: 3px solid var(--accent);
}
.call-ring[hidden] { display: none; }
.call-ring h2 { margin: 0; font-size: 1.05rem; }
.call-ring p { margin: 0; }
.place-prompt {
  padding: .6rem 1rem; background: var(--sys);
  border-bottom: 3px solid var(--accent);
}
.place-prompt[hidden] { display: none; }
.place-prompt h2 { margin: 0 0 .3rem; font-size: 1.05rem; }
.place-prompt p { margin: .2rem 0; }
.place-prompt-item { margin: .3rem 0; }
.whats-new-once {
  border: 2px solid var(--accent); border-radius: .5rem;
  padding: .7rem .9rem; margin: .8rem 1rem; background: var(--panel);
}

/* Aboard a vehicle (2026-10-19): the vehicle is where you are —
   the other places' doors are not offered until you step off.
   (The server enforces the same law; this is its face.) */
body.aboard #view-groups,
body.aboard #view-places { display: none; }

/* The update window bar (2026-10-23): the update banner's
   sibling, in the same voice — the word of a coming close,
   the close itself, and "We're back." */
.update-window-bar {
  display: flex; align-items: center; gap: .8rem; flex-wrap: wrap;
  padding: .6rem 1rem; background: var(--sys);
  border-bottom: 2px solid var(--accent);
}
.update-window-bar[hidden] { display: none; }

/* The picker Done button (2026-10-24): stands right after its
   control, close enough to find, never crowding it. */
.picker-done { margin-left: 0.5em; }

/* Typing inside the picker (2026-10-29): the picker's own
   segments stand inside its area, between the control and
   its Done — small labelled fields in the form's own flow. */
.picker-type { display: inline-block; margin-left: 0.5em; }
.picker-seg-wrap { white-space: nowrap; }
.picker-seg { margin: 0 0.1em; }
.picker-type-error { display: block; margin-top: 0.25em; }

/* Companion picks sections (2026-10-28): the People /
   Characters division headings inside a picks container —
   quiet headings a screen reader lands on as it reads the
   list, in the page's own heading voice. */
.companion-picks-heading { margin: 0.6em 0 0.25em; font-size: 1em; }

/* Living in the Network (2026-10-29): the building record
   stands UNSEEN in the living surface — the frame holds it
   out of the place's everyday face while a living standing
   stands, and its door (Ctrl+Shift+F on Windows, the
   Building record button on the phone, leadership only)
   lets it stand again. A class on <body> carries the
   state so no render order can undo it. */
body.living-feed-unseen #building-record-wrap { display: none !important; }

/* Booking pick lists (2026-10-30): the button lists that stand
   where the booking place comboboxes stood — layout only; the
   pick itself is carried by aria-pressed, as on the mic lists. */
.pick-list { margin: 0.25em 0 0.75em; }
.pick-list button { margin: 0 0.35em 0.35em 0; }
.pick-list-state { margin: 0.25em 0; }
.pick-retry { margin-left: 0.5em; }

/* Turn-by-turn narration pick (2026-10-30, the narration
   witness): the request stands in the ride form where its
   label stood — a block of its own in the form's flow, its
   name in the label weight, two real buttons whose pressed
   state is the request, and the standing words after them. */
.narration-pick { display: block; margin-top: .8rem; }
.narration-pick-name { font-weight: bold; }
