:root {
  color-scheme: dark;
  --ink: #f5f3ea;
  --muted: #a8adbb;
  --panel: #151922;
  --panel-strong: #1b202c;
  --line: #313847;
  --accent: #d5ff5f;
  --accent-ink: #172000;
  --danger: #ff9b8f;
  --background: #0c0f15;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 20rem;
  min-height: 100vh;
  margin: 0;
  color: var(--ink);
  background:
    radial-gradient(circle at 82% 12%, rgba(213, 255, 95, 0.08), transparent 28rem),
    var(--background);
}

a {
  color: inherit;
}

button,
input {
  font: inherit;
}

button {
  min-height: 2.75rem;
  border: 1px solid var(--accent);
  border-radius: 0.55rem;
  padding: 0.65rem 1rem;
  color: var(--accent-ink);
  background: var(--accent);
  font-weight: 750;
  cursor: pointer;
}

button:hover:not(:disabled) {
  filter: brightness(1.08);
}

button:disabled {
  cursor: not-allowed;
  opacity: 0.48;
}

button:focus-visible,
input:focus-visible,
a:focus-visible,
pre:focus-visible,
#room-title:focus-visible {
  outline: 3px solid #ffffff;
  outline-offset: 3px;
}

input {
  min-height: 2.75rem;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 0.55rem;
  padding: 0.7rem 0.8rem;
  color: var(--ink);
  background: #0b0e13;
}

label {
  display: block;
  margin-bottom: 0.45rem;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 700;
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  min-height: 4.5rem;
  border-bottom: 1px solid var(--line);
  padding: 0.8rem clamp(1rem, 4vw, 3rem);
  background: rgba(12, 15, 21, 0.92);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
  text-decoration: none;
  font-size: 1.1rem;
  font-weight: 800;
  letter-spacing: -0.02em;
}

.brand-mark {
  display: grid;
  width: 2rem;
  height: 2rem;
  place-items: center;
  border-radius: 0.45rem;
  color: var(--accent-ink);
  background: var(--accent);
}

.mode,
.event-count {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.35rem 0.65rem;
  color: var(--muted);
  font-size: 0.76rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

main {
  min-height: calc(100vh - 8.5rem);
}

.login-card {
  width: min(44rem, calc(100% - 2rem));
  margin: clamp(3rem, 10vh, 8rem) auto;
  border: 1px solid var(--line);
  border-radius: 1rem;
  padding: clamp(1.4rem, 5vw, 3rem);
  background: rgba(21, 25, 34, 0.92);
  box-shadow: 0 2rem 5rem rgba(0, 0, 0, 0.3);
}

.eyebrow {
  margin: 0 0 0.5rem;
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.11em;
  text-transform: uppercase;
}

h1,
h2,
p {
  overflow-wrap: anywhere;
}

h1 {
  margin: 0;
  font-size: clamp(1.8rem, 5vw, 3.4rem);
  line-height: 1.02;
  letter-spacing: -0.045em;
}

h2 {
  margin: 0;
  font-size: 1.2rem;
}

.lede {
  max-width: 38rem;
  margin: 1.25rem 0 2rem;
  color: var(--muted);
  font-size: 1.03rem;
  line-height: 1.65;
}

.credential-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.65rem;
}

.status {
  min-height: 1.5rem;
  margin: 0.8rem 0;
  color: var(--muted);
}

.status.error {
  color: var(--danger);
}

.boundary {
  margin-top: 1.8rem;
  border-left: 3px solid var(--accent);
  padding: 0.15rem 0 0.15rem 1rem;
}

.boundary p {
  margin: 0.4rem 0 0;
  color: var(--muted);
  line-height: 1.55;
}

.workspace {
  display: grid;
  grid-template-columns: minmax(14rem, 18rem) 1fr;
  min-height: calc(100vh - 4.5rem);
}

.room-sidebar {
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  border-right: 1px solid var(--line);
  padding: 1.5rem;
  background: var(--panel);
}

.operator-id {
  margin: 0;
  font-weight: 750;
}

.sidebar-heading,
.panel-heading,
.room-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.quiet-button,
.logout-button,
.load-more {
  border-color: var(--line);
  color: var(--ink);
  background: transparent;
}

.quiet-button {
  min-height: 2rem;
  padding: 0.3rem 0.55rem;
  font-size: 0.78rem;
}

.logout-button {
  width: 100%;
  margin-top: auto;
}

.room-list {
  display: grid;
  gap: 0.45rem;
}

.room-button {
  width: 100%;
  border-color: transparent;
  padding: 0.7rem 0.8rem;
  color: var(--muted);
  background: transparent;
  text-align: left;
  overflow-wrap: anywhere;
}

.room-name {
  display: block;
  color: var(--ink);
  font-weight: 650;
  overflow-wrap: anywhere;
}

/* The ID stays visible and legible rather than being replaced by the name: it
   is what every event and export references, and two rooms may share a label. */
.room-id {
  display: block;
  margin-top: 0.15rem;
  color: var(--muted);
  font: 0.72rem/1.35 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  overflow-wrap: anywhere;
}

.room-button:hover,
.room-button[aria-current="page"] {
  border-color: var(--line);
  color: var(--ink);
  background: var(--panel-strong);
}

.room-button[aria-current="page"] {
  box-shadow: inset 3px 0 var(--accent);
}

.room-content {
  width: min(100%, 75rem);
  padding: clamp(1rem, 4vw, 3rem);
}

.room-header h1 {
  font-size: clamp(1.7rem, 4vw, 2.8rem);
}

.panel {
  margin-top: 1.5rem;
  border: 1px solid var(--line);
  border-radius: 0.85rem;
  padding: clamp(1rem, 3vw, 1.5rem);
  background: var(--panel);
}

.thread-form {
  width: min(23rem, 100%);
}

.thread-form label span {
  font-weight: 500;
}

.thread-form > div {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.5rem;
}

pre {
  max-width: 100%;
  margin: 1.2rem 0 0;
  border: 1px solid var(--line);
  border-radius: 0.55rem;
  padding: 1rem;
  color: #dce2ee;
  background: #090c11;
  font: 0.82rem/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  overflow-x: auto;
}

.member-list {
  display: grid;
  gap: 0.9rem;
  margin: 1.2rem 0;
  padding: 0;
  list-style: none;
}

/* The record is append-only and unbounded, so the panel cannot be. Height is
   capped in vh so it fits the viewport it is actually being read on rather than
   a pixel count guessed for one monitor, with a rem floor so a short window
   still shows more than a single card. The scroll lives here rather than on the
   page, so the heading and the older-events control stay put while the record
   moves under them. */
.event-list {
  display: grid;
  gap: 0.9rem;
  margin: 0.6rem 0 0;
  padding: 0.2rem;
  list-style: none;
  max-height: min(60vh, 640px);
  min-height: 12rem;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 1px solid var(--line);
  border-radius: 0.4rem;
  background: color-mix(in srgb, var(--line) 12%, transparent);
}

/* Anchors the view to the newest event while older pages are prepended above,
   so loading history does not drag the reader away from where they were. */
@supports (overflow-anchor: auto) {
  .event-list {
    overflow-anchor: auto;
  }
}

.load-older {
  width: 100%;
}

/* A control rather than a label. It only appears once the whole record is
   loaded, so the reader has somewhere to go and no more to fetch -- clicking
   it is the cheapest way back to the first event, which is otherwise a long
   scroll through everything that has happened since. */
.event-list-oldest {
  width: 100%;
  margin: 0.4rem 0 0;
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--line);
  border-radius: 0.3rem;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: 0.78rem;
  text-align: center;
  cursor: pointer;
}

.event-list-oldest:hover {
  color: inherit;
}

.member-card {
  border: 1px solid var(--line);
  border-radius: 0.65rem;
  padding: 1rem;
  background: #10141c;
}

.member-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem 0.8rem;
  margin: 0;
}

.member-role {
  border: 1px solid var(--line);
  border-radius: 0.3rem;
  padding: 0.1rem 0.4rem;
  color: var(--muted);
  font: 0.72rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.member-keys-count {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.78rem;
}

.member-key-list {
  display: grid;
  gap: 0.5rem;
  margin: 0.8rem 0 0;
  padding: 0;
  list-style: none;
}

.member-key {
  border-left: 2px solid var(--line);
  padding-left: 0.7rem;
}

.member-room-role,
.member-granted,
.member-removed {
  border: 1px solid var(--line);
  border-radius: 0.3rem;
  padding: 0.1rem 0.4rem;
  font: 0.72rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.member-room-role {
  color: var(--muted);
}

.member-granted {
  color: var(--muted);
}

/* A removed member stays listed and is marked, never dimmed into the
   background: a revocation an operator can miss is one this panel failed to
   report. */
.member-removed {
  border-color: var(--danger);
  color: var(--danger);
  font-weight: 700;
}

.member-card-removed {
  border-style: dashed;
}

/* An inactive key is dimmed but never hidden: a revocation that disappeared
   from the roster would be indistinguishable from a key that never existed. */
.member-key-inactive {
  opacity: 0.62;
  border-left-style: dashed;
}

.event-card {
  border: 1px solid var(--line);
  border-radius: 0.65rem;
  padding: 1rem;
  background: #10141c;
}

/* The committed record read as a conversation.
   Deliberately quieter than a card: consecutive messages should scan as one
   thread, so the separators are hairlines rather than boxes and the eye lands
   on who spoke and what they said. */
.chat-message {
  padding: 0.7rem 0.2rem 0.85rem;
  border-bottom: 1px solid var(--line);
}

.chat-message:last-child {
  border-bottom: none;
}

.chat-header {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0 0 0.35rem;
}

.chat-author {
  color: var(--ink);
  font-weight: 600;
}

/* The author name is a button that opens a profile, and it must not look like
   one -- the message list is prose, and a row of buttons reads as a toolbar.
   Underlined on hover and focus so it is discoverable and reachable by
   keyboard, which a bare span was not. */
button.chat-author {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: 600;
  color: var(--ink);
  cursor: pointer;
}

button.chat-author:hover,
button.chat-author:focus-visible {
  text-decoration: underline;
}

.member-key-inactive {
  opacity: 0.62;
}

/* An operator is a person, and the record cannot say so in a field of its own,
   so the badge is how a reader tells a human from an agent at a glance. */
.chat-badge {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.02rem 0.45rem;
  color: var(--ink);
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* Quieter than the kind label beside it: the sequence is for citing an event to
   somebody else, not for reading every line. */
.chat-seq {
  color: var(--muted);
  font: 0.7rem ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  opacity: 0.75;
}

/* The link between a question and its answer.
   Styled like .chat-seq because it is the same thing being cited -- a ledger
   sequence -- and a reader who has learned to read the number should not have to
   learn a second appearance for it. A button, not a link: there is no URL. */
.chat-reply-link {
  border: 0;
  background: none;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  font: 0.7rem ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  opacity: 0.75;
  text-decoration: underline dotted;
}

.chat-reply-link:hover,
.chat-reply-link:focus-visible {
  color: var(--ink);
  opacity: 1;
}

/* Where a reader has just been sent. Without this, scrollIntoView on a dense
   page moves the view and leaves the reader guessing which message it meant. */
.chat-message-targeted {
  outline: 2px solid var(--accent, currentColor);
  outline-offset: 2px;
}

/* What this message answers. Quiet, like the sequence it names. */
.compose-reply {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  margin: 0 0 0.5rem;
  color: var(--muted);
  font-size: 0.78rem;
}

.compose-reply button {
  border: 1px solid var(--line);
  background: none;
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  color: var(--muted);
  font-size: 0.7rem;
  cursor: pointer;
}

.compose-reply button:hover,
.compose-reply button:focus-visible {
  color: var(--ink);
}

.chat-kind {
  color: var(--muted);
  font: 0.72rem ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.chat-when {
  margin-left: auto;
  color: var(--muted);
  font-size: 0.74rem;
  white-space: nowrap;
}

.chat-body {
  margin: 0;
  color: var(--ink);
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.chat-body p {
  margin: 0 0 0.6rem;
}

.chat-body p:last-child {
  margin-bottom: 0;
}

.chat-empty {
  color: var(--muted);
  font-style: italic;
}

.chat-addressees {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin: 0.5rem 0 0;
}

.chat-chip {
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  color: var(--muted);
  font: 0.72rem ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

/* The tamper-evident record, one disclosure away. It is why this product
   exists, so it stays reachable on every message -- it is simply no longer the
   first thing a person has to read past. */
.chat-record {
  margin-top: 0.55rem;
}

.chat-record summary {
  color: var(--muted);
  cursor: pointer;
  font-size: 0.75rem;
}

.chat-record pre {
  margin-top: 0.5rem;
}

.event-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem 1rem;
  margin: 0;
  color: var(--muted);
  font: 0.78rem/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.event-summary strong {
  color: var(--ink);
}

.event-card pre {
  margin-top: 0.8rem;
}

.load-more {
  width: 100%;
}

footer {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  border-top: 1px solid var(--line);
  padding: 1.25rem clamp(1rem, 4vw, 3rem);
  color: var(--muted);
  font-size: 0.82rem;
}

[hidden] {
  display: none !important;
}

@media (max-width: 48rem) {
  .mode {
    display: none;
  }

  .workspace {
    display: block;
  }

  .room-sidebar {
    border-right: 0;
    border-bottom: 1px solid var(--line);
  }

  .room-list {
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  }

  .logout-button {
    margin-top: 0;
  }

  .panel-heading,
  .room-header,
  footer {
    align-items: stretch;
    flex-direction: column;
  }
}

@media (max-width: 34rem) {
  .credential-row,
  .thread-form > div {
    grid-template-columns: 1fr;
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition: none !important;
  }
}

/* Shown only when events arrive while the operator is reading history. The
   alternative -- scrolling them down to the newest event -- takes the page away
   from what they deliberately scrolled to, which is why a live log that always
   follows is unusable for reading. */
.jump-newest {
  width: 100%;
  margin-top: 0.4rem;
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--accent, var(--line));
  border-radius: 0.3rem;
  background: color-mix(in srgb, var(--line) 30%, transparent);
  color: inherit;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
}

.event-live {
  font-size: 0.72rem;
  color: var(--muted);
}

/* Who a message is for. Laid out as a wrapping row so a room with many agents
   does not push the compose box off the screen, and each choice keeps its label
   and box together so the whole thing is a click target. */
.compose-addressees {
  border: 1px solid var(--line);
  border-radius: 6px;
  margin: 0 0 0.75rem;
  padding: 0.5rem 0.75rem 0.75rem;
}

.compose-addressees legend {
  color: var(--muted);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  padding: 0 0.35rem;
  text-transform: uppercase;
}

#compose-addressee-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem 1rem;
}

.compose-addressee {
  align-items: center;
  cursor: pointer;
  display: inline-flex;
  gap: 0.4rem;
}

/* The authority's signature, checked here rather than asserted. Three states,
   visually distinct on purpose: an operator must be able to tell "I checked and
   it is wrong" from "I could not check", because only the first is alarming. */
.chat-verification {
  border-radius: 3px;
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  padding: 0.05rem 0.35rem;
  text-transform: uppercase;
}

.chat-verification-checking,
.chat-verification-unverifiable {
  color: var(--muted);
}

.chat-verification-verified {
  color: var(--accent);
}

.chat-verification-failed {
  background: var(--danger);
  color: var(--background);
  font-weight: 700;
}

/* The answer box on a gate this operator can close.
   It appears only on gates they may answer and only when this browser holds a
   key, so its presence is itself the affordance: a card with a box is a
   question you can settle, and a card without one is not. */
.gate-answer {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.6rem;
}

.gate-answer input {
  flex: 1 1 14rem;
  min-width: 0;
}

/* The status line takes the full width under the control, so a refusal does not
   squeeze the box it is about. */
.gate-answer .status {
  flex-basis: 100%;
  margin: 0;
}

/* WHICH ROOM A CREDENTIAL IS FOR, stated beside the token.

   Not styled as an error, because it is shown on every issuance including the
   correct ones. A line that only appears when something is wrong teaches the
   reader to treat its absence as approval, and absence is also what a failed
   roster read looks like. */
.invite-provenance {
  margin: 0 0 0.4rem;
  padding: 0.5rem 0.6rem;
  border-left: 3px solid var(--accent);
  background: var(--panel-strong);
  color: var(--ink);
  font-size: 0.88rem;
  line-height: 1.45;
}

/* OFFERED ANSWERS. A gate that carries options renders them as choices rather
   than as a paragraph the operator has to read an exact string out of.

   Full width and stacked: an option carries a label, what it will cause, and the
   reasons either way, so laying them side by side would make the longest one set
   the height of every other and push the reasons out of view. */
.gate-options {
  flex-basis: 100%;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0 0 0.2rem;
  padding: 0;
  border: 0;
}

.gate-options legend {
  padding: 0;
  color: var(--muted);
  font-size: 0.85rem;
}

/* The whole card is the label, so the click target is the option rather than a
   radio a pointer has to find. */
.gate-option {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.2rem 0.5rem;
  align-items: start;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--line);
  border-radius: 0.4rem;
  background: var(--panel);
  cursor: pointer;
}

.gate-option:hover {
  border-color: var(--muted);
}

/* Selection is shown by the border and the accent bar, not by colour alone:
   an operator who cannot distinguish the accent still sees which card is
   chosen, and the radio itself remains visible and checked. */
.gate-option:has(input:checked) {
  border-color: var(--accent);
  box-shadow: inset 0.2rem 0 0 var(--accent);
  background: var(--panel-strong);
}

.gate-option:focus-within {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.gate-option input {
  margin: 0.2rem 0 0;
}

.gate-option-label {
  font-weight: 600;
  color: var(--ink);
}

/* What the agent will do if this is chosen. Indented under the label rather than
   beside it, so it reads as consequence rather than as a second option. */
.gate-option-effect,
.gate-option-pros,
.gate-option-cons {
  grid-column: 2;
  margin: 0;
  color: var(--muted);
  font-size: 0.88rem;
}

.gate-option-pros,
.gate-option-cons {
  list-style: none;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.gate-option-pros li,
.gate-option-cons li {
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
}

/* A word, not a glyph or a colour. It is read aloud correctly, it survives a
   monochrome display, and it needs no legend. */
.gate-option-marker {
  flex: 0 0 auto;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--line);
  border-radius: 0.25rem;
  padding: 0 0.3rem;
}

.gate-option-cons .gate-option-marker {
  color: var(--danger);
  border-color: var(--danger);
}

/* Free text stays reachable when options are offered, and stays out of the way
   until it is wanted. */
.gate-answer-other {
  flex-basis: 100%;
}

.gate-answer-other summary {
  color: var(--muted);
  font-size: 0.85rem;
  cursor: pointer;
}

.gate-answer-other input {
  width: 100%;
  margin-top: 0.4rem;
}

/* Admit and remove controls on the roster. Laid out like the gate answer box
   because they do the same thing: one action, its status underneath. */
.member-action {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.5rem;
}

.member-action .status,
.member-action .panel-note {
  flex-basis: 100%;
  margin: 0.3rem 0 0;
}

/* A gate somebody else must answer. Shown rather than hidden -- an operator
   needs to see why a room is blocked even when the answer is not theirs -- and
   dimmed, so the panel headed "what is waiting on you" does not read as though
   it all is. */
.event-card-not-yours {
  opacity: 0.62;
}

/* The compose box sits inside the record panel, below the event list, at the
   list's own width. Requested by the operator: reading and writing in one
   column, the way a chat client does it, rather than a separate panel beside
   the conversation. */
/* COLLAPSED SECTIONS, requested by the operator so the page reaches the messages
   sooner: members and invitations, this browser's key, and the room-state
   detail all close by default and carry their state in the summary.

   <details> rather than a scripted toggle. It collapses with no JavaScript, it
   is keyboard-operable and announced by a screen reader without help, and the
   console's asset guard forbids the inline styles a hand-rolled one would want.
   The one behaviour worth scripting is the browser-key section OPENING when
   there is no key, and app.js does that. */
.collapsible > summary {
  align-items: center;
  cursor: pointer;
  display: flex;
  gap: 0.75rem;
  justify-content: space-between;
  list-style: none;
}

/* Safari draws its own triangle through a pseudo-element the standard property
   does not reach. */
.collapsible > summary::-webkit-details-marker {
  display: none;
}

.collapsible > summary::after {
  color: var(--muted);
  content: "expand";
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.collapsible[open] > summary::after {
  content: "collapse";
}

.collapsible-title {
  font-size: 1.05rem;
  font-weight: 600;
}

/* A visible focus ring on the summary. Without it the one keyboard-reachable
   control in each collapsed section is invisible to a keyboard user, which is
   the population most affected by collapsing things in the first place. */
.collapsible > summary:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}

/* Plain-language notes under the room-state JSON. The operator asked what those
   fields meant, which is the answer to whether a panel explains itself. */
.field-notes {
  border-top: 1px solid var(--line);
  margin: 0.9rem 0 0;
  padding-top: 0.9rem;
}

.field-notes dt {
  color: var(--accent);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  margin-top: 0.7rem;
}

.field-notes dd {
  color: var(--muted);
  font-size: 0.88rem;
  margin: 0.2rem 0 0;
}

.compose-inline {
  border-top: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
}

.compose-title {
  color: var(--muted);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  margin: 0;
  text-transform: uppercase;
}

.compose-label {
  color: var(--muted);
  font-size: 0.8rem;
}

/* Full width, so the input matches the messages above it rather than sitting in
   a narrower column of its own. */
.compose-form {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.compose-form textarea {
  box-sizing: border-box;
  resize: vertical;
  width: 100%;
}

.compose-form button {
  align-self: flex-end;
}

.compose-note {
  color: var(--muted);
  font-size: 0.75rem;
  margin: 0;
}
