/* Card internals, panel internals, composer, empty states. */

/* ── Shared bits ────────────────────────────────────────────────────────── */

.meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 400;
  color: var(--ink-dim);
}

.heading {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: var(--leading-tight);
  margin: 0;
}

.button {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--paper);
  background: var(--sage);
  border: 0;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-4);
  cursor: pointer;
}

.button:hover { filter: brightness(1.08); }

.button--quiet {
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--edge);
}

.board__title {
  font-size: var(--text-xl);
}

/* ── User panel ─────────────────────────────────────────────────────────── */

.panel__wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-xl);
  line-height: var(--leading-tight);
  margin: 0;
}

.panel__identity {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.panel__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-dim);
}

.panel__username {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: var(--text-lg);
}

/* The name is a button, but it should read as the name — no chrome until you hover it. */
.panel__username--button {
  appearance: none;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  padding: var(--space-1) var(--space-2);
  margin-left: calc(var(--space-2) * -1);
  cursor: pointer;
}

.panel__username--button:hover,
.panel__username--button:focus-visible {
  border-color: var(--edge);
  background: var(--board);
}

.panel__username--button[hidden] { display: none; }

.panel__claim {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.panel__claim[hidden] { display: none; }

.panel__input {
  font-family: var(--font-display);
  font-size: var(--text-lg);
  background: var(--paper);
}

.panel__claim-actions {
  display: flex;
  gap: var(--space-2);
}

.panel__info {
  font-size: var(--text-sm);
  color: var(--ink-dim);
  margin: 0;
}

/* Revealed only while the name box reads the sentinel. Smaller than the name itself: it is a
   field you type into once, not the thing the panel is about. */
.panel__password {
  font-family: var(--font-body);
  font-size: var(--text-sm);
}

.panel__password[hidden] { display: none; }

/* A disabled Settings button is the resting state for everyone who is not the operator, so it
   has to read as inert rather than as broken. */
.button:disabled {
  opacity: 0.45;
  cursor: default;
}

.button:disabled:hover { filter: none; }

/* ── Settings sheet ─────────────────────────────────────────────────────────
   Rises from the bottom of the panel, under the button that opens it — a control should
   open where it lives rather than throwing the eye to the middle of the screen.

   .panel__foot is the positioning context, so the sheet is anchored to the button and
   needs no knowledge of the rail's height. */

.panel__foot { position: relative; }

.sheet {
  position: absolute;
  inset: auto 0 100% 0;
  z-index: var(--z-sheet);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-bottom: var(--space-2);
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-3);
  /* Starts down and transparent; the --open class does the rising. Driving this off a class
     rather than the hidden attribute is the whole trick — an element coming out of
     display:none cannot transition. */
  opacity: 0;
  transform: translateY(var(--space-3));
  transition: opacity 140ms ease, transform 140ms ease;
}

.sheet[hidden] { display: none; }

.sheet--open {
  opacity: 1;
  transform: translateY(0);
}

.sheet__item { width: 100%; }

/* Clear all is the destructive one and should not look like the other two. */
.sheet__item:not(.button--quiet) { background: var(--danger); }

@media (prefers-reduced-motion: reduce) {
  .sheet { transition: none; }
}

/* ── Blabthread card ────────────────────────────────────────────────────── */

.card {
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
}

/* Folded, the head is the whole card: a live peach spine carrying the
   chevron, the title, the No. and room for the count badge. */
.card[data-folded="true"] .card__head {
  background: var(--board);
}

/* Operator only. The node is always in the markup — one data-admin flip on <body> reveals
   every card's at once, which is what lets a card that predates the login sprout one. */
.card__delete {
  display: none;
  flex: none;
  place-items: center;
  width: var(--space-5);
  height: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-md);
  line-height: 1;
  color: var(--paper);
  background: var(--danger);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
}

body[data-admin="true"] .card__delete { display: grid; }

.card__delete:hover { filter: brightness(1.12); }

.card__fold {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--space-6);
  height: var(--space-6);
  color: var(--ink);
  background: transparent;
  border: 1px solid var(--edge);
  border-radius: var(--radius-pill);
  cursor: pointer;
}

.card__chevron {
  display: block;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  transform: rotate(90deg);
  transition: transform 120ms ease;
}

.card[data-folded="true"] .card__chevron { transform: rotate(0deg); }

.card__titles {
  min-width: 0;
  display: flex;
  align-items: baseline;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.card__title {
  font-size: var(--text-lg);
  overflow-wrap: anywhere;
}

.card__badge {
  flex: none;
  margin-left: auto;
  min-width: var(--space-5);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-weight: 500;
  text-align: center;
  color: var(--paper);
  background: var(--pop);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-2);
}

.card__badge[hidden] { display: none; }

.card__badge.is-pulsing { animation: badge-pulse 1.6s ease-in-out infinite; }

@keyframes badge-pulse {
  0%, 100% { transform: scale(1);    background: var(--pop); }
  50%      { transform: scale(1.14); background: var(--sage); }
}

@media (prefers-reduced-motion: reduce) {
  .card__badge.is-pulsing { animation: none; }
  .card__chevron { transition: none; }
}

/* ── Panes ──────────────────────────────────────────────────────────────── */

.pane {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--board);
  border: 1px solid var(--edge);
  border-radius: var(--radius-pane);
  padding: var(--space-3);
  min-height: calc(var(--space-6) * 4);
}

.pane__label {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-dim);
  text-transform: lowercase;
}

/* ── Empty and error states ─────────────────────────────────────────────── */

.empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-5);
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-card);
}

/* display:flex above beats the hidden attribute's UA rule, so it has to be said again. */
.empty[hidden] { display: none; }

.empty__title {
  font-size: var(--text-lg);
}

.empty__body {
  margin: 0;
  color: var(--ink-dim);
}

/* Inside a pane the empty state is just quiet text, not another card. */
.pane .empty {
  padding: var(--space-2) 0;
  background: transparent;
  border: 0;
  border-radius: 0;
}

/* ── Composer ───────────────────────────────────────────────────────────── */

.composer {
  display: flex;
  align-items: flex-end;
  gap: var(--space-3);
  flex-wrap: wrap;
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-card);
  padding: var(--space-4);
}

.composer[hidden] { display: none; }

.composer__field {
  flex: 1 1 240px;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* The image half of the board composer. Does not stretch like the title field — it is a
   button and a filename, both of which want their own width.
   Positioned so .media-upload__input, which is absolute, anchors against THIS box rather than
   the page, where a stray 1px would sit over the layout. */
.composer__field--image {
  position: relative;
  flex: 0 1 auto;
  gap: var(--space-3);
  /* A row, unlike the base field: the preview goes beside the picker, not above it. */
  flex-direction: row;
  align-items: flex-end;
}

.composer__picker {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* Fixed height with contain, so a tall image is bounded by the box rather than the box by the
   image — otherwise one portrait photo shoves Send it off the screen. */
.composer__preview {
  display: block;
  flex: none;
  height: calc(var(--space-6) * 2);
  max-width: calc(var(--space-6) * 3);
  object-fit: contain;
  border: 1px solid var(--edge);
  border-radius: var(--radius-pane);
  background: var(--board);
}

.composer__preview[hidden] { display: none; }

/* Desktop shows the label and the input outright, so the disclosure has no job here. It is
   revealed at the breakpoint, where the label and input hide behind it instead. */
.composer__disclose { display: none; }

.composer__filename {
  overflow-wrap: anywhere;
  color: var(--ink-dim);
}

.composer__label {
  font-size: var(--text-sm);
  color: var(--ink-dim);
}

.composer__input {
  font-family: var(--font-body);
  font-size: var(--text-md);
  color: var(--ink);
  background: var(--board);
  border: 1px solid var(--edge);
  border-radius: var(--radius-pane);
  padding: var(--space-2) var(--space-3);
  width: 100%;
}

.composer__error {
  flex: 1 1 100%;
  font-size: var(--text-sm);
  color: var(--pop);
}

.composer__error[hidden] { display: none; }

/* In a pane the composer is the pane's own footer, not a card floating inside one — so it
   drops its box entirely and keeps a hairline as the only division from the log above. */
.composer--blab {
  flex: none;
  align-items: center;
  gap: var(--space-2);
  background: transparent;
  border: 0;
  border-top: 1px solid var(--edge);
  border-radius: 0;
  padding: var(--space-2) 0 0;
}

.composer__input--blab {
  /* The base rule's width:100% claims the whole flex line and pushes Send onto the next
     row. The bar sizes from flex instead, so the width has to be given back. */
  width: auto;
  flex: 1 1 auto;
  min-width: 0;
  min-height: calc(var(--space-5) + var(--space-1));
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  resize: none;   /* it is a one-line send bar; a drag handle only breaks the alignment */
}

.composer__send {
  flex: 0 0 auto;
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-4);
}

/* ── Messages ───────────────────────────────────────────────────────────── */

/* The list carries the pane's scroll, so the composer below it stays put. Chaining is
   contained deliberately: several cards stack on the board and reaching the end of one
   pane must not start scrolling the page. */
.messages {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Bounded in lines, not in spacing units — see --blab-lines. An unbounded log grows its
     card until the one thread you are reading has pushed the whole board off screen. */
  max-height: var(--blab-max);
}

/* One line per blab: dot, name, body, all in the same flow. No surface and no border —
   the pane is the surface, and per-message chrome is what made the log read as a stack of
   receipts rather than a conversation. */
.message {
  margin: 0;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-pane);
  font-size: var(--text-sm);
  line-height: var(--leading-body);
  overflow-wrap: anywhere;
}

.message__dot {
  display: inline-block;
  width: var(--space-2);
  height: var(--space-2);
  margin-right: var(--space-1);
  border-radius: var(--radius-pill);
  background: var(--dot-1);
}

.message__dot--1 { background: var(--dot-1); }
.message__dot--2 { background: var(--dot-2); }
.message__dot--3 { background: var(--dot-3); }
.message__dot--4 { background: var(--dot-4); }
.message__dot--5 { background: var(--dot-5); }
.message__dot--6 { background: var(--dot-6); }

.message__who {
  font-family: var(--font-display);
  font-weight: 600;
  color: var(--ink);
}

.message__who::after {
  content: ':';
  color: var(--ink-dim);
}

.message__body {
  margin-left: var(--space-1);
  white-space: pre-wrap;
}

/* ── Media pane ─────────────────────────────────────────────────────────── */

/* One image, centred and as large as the pane allows. Positioned, because the arrows overlay
   its edges rather than sitting in the flow — in a pane this narrow, a row of controls beneath
   the image would cost more height than the image itself. */
.media-view {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 0;
  min-width: 0;
}

/* The button is only a hit target; the image is the thing you see. Bounded by
   --media-single-max rather than by the image's own pixel size, so a 2048px upload still fits
   the pane. */
.media-view__frame {
  display: block;
  max-width: 100%;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  line-height: 0;
}

.media-view__frame[hidden] { display: none; }

/* contain, not cover: there is one image and the whole point is to see all of it. */
.media-view__img {
  display: block;
  max-width: 100%;
  max-height: var(--media-single-max);
  object-fit: contain;
  border: 1px solid var(--edge);
  border-radius: var(--radius-pane);
  background: var(--board);
}

.media-view__frame:hover .media-view__img { border-color: var(--sage); }

/* Overlaid on the pane's edges, vertically centred. Only rendered when there is more than one
   image, and disabled at the ends — position stays legible without reading the counter. */
.media-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: var(--space-5);
  height: var(--space-6);
  display: grid;
  place-items: center;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  line-height: 1;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-pill);
  cursor: pointer;
}

.media-nav[hidden] { display: none; }

.media-nav--prev { left: 0; }
.media-nav--next { right: 0; }

.media-nav:disabled {
  opacity: 0.35;
  cursor: default;
}

.media-view__counter {
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  background: var(--paper);
  border-radius: var(--radius-pill);
  padding: 0 var(--space-2);
}

.media-view__counter[hidden] { display: none; }

/* ── Media upload ───────────────────────────────────────────────────────── */

/* The pane's footer, mirroring .composer--blab's role in the blab pane. */
.media-upload {
  /* Positioned so the hidden input below is absolute against THIS box and not against the
     viewport, where a stray 1px would sit over the page. */
  position: relative;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-pane);
  padding: var(--space-2);
}

/* Hidden but still in the DOM and still focusable-by-proxy: the visible button forwards its
   click here. display:none would make .click() a no-op in some browsers. */
.media-upload__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.media-upload__button { width: 100%; }

/* ── Lightbox ───────────────────────────────────────────────────────────── */

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-lightbox);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: var(--scrim);
}

.lightbox[hidden] { display: none; }

/* Locking the body is what stops the board scrolling behind the overlay under a trackpad. */
body.is-locked { overflow: hidden; }

.lightbox__figure {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  /* The image is bounded by the viewport, not by its own pixel size, so a 2048px upload
     still fits on a phone. */
  max-width: 100%;
  max-height: 100%;
}

.lightbox__img {
  display: block;
  max-width: 100%;
  /* Leaves room for the caption beneath without either one overflowing. */
  max-height: calc(100vh - var(--space-6) * 4);
  object-fit: contain;
  border-radius: var(--radius-pane);
  background: var(--paper);
}

.lightbox__caption {
  margin: 0;
  color: var(--paper);
}

.lightbox__close {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  width: var(--space-6);
  height: var(--space-6);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--text-xl);
  line-height: 1;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-pill);
  cursor: pointer;
}

/* ── Dialog ─────────────────────────────────────────────────────────────────
   The scrim and the body lock are shared with the lightbox above; the box is its own
   thing, because a lightbox is an image on a dark field and this is a card with text on
   one. See dialog.js for why they are not the same object. */

.dialog {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-5);
  background: var(--scrim);
}

.dialog__box {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  /* Wide enough for a sentence, narrow enough not to become a page. */
  width: min(420px, 100%);
  max-height: 100%;
  overflow-y: auto;
  background: var(--paper);
  border: 1px solid var(--edge);
  border-radius: var(--radius-card);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}

.dialog__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--text-lg);
  line-height: var(--leading-tight);
  margin: 0;
}

.dialog__body {
  margin: 0;
  font-size: var(--text-md);
}

.dialog__actions {
  display: flex;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* ── Users list ─────────────────────────────────────────────────────────────
   The live sessions, editable in place. */

.users {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.users__note { margin: 0; }

.users__row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.users__input {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--text-sm);
}

.users__seen {
  flex: none;
  white-space: nowrap;
}

/* Only ever holds a failure, so it can carry the failure colour unconditionally. */
.users__status {
  flex: none;
  color: var(--pop);
  white-space: nowrap;
}
