/* Dropbox look, using real DIG tokens.
 *
 * Values copied from the monorepo's design system at
 * server/design-systems/dig-components/tokens.css, specifically the current
 * `.dig-Theme-vis2023--bright` block. Token names are kept so they can be
 * matched back to source. Fonts are Atlas Grotesk (text) and Sharp Grotesk
 * (titles and actions), the same pairing DIG uses.
 */

/* @font-face lives in fonts.css, which is only served when SERVE_FONTS is on. */

:root {
  /* colour — dig-Theme-vis2023--bright */
  --dig-color__primary__base: #0061fe;
  --dig-color__primary__base--state-1: #004dc7;
  --dig-color__primary__base--state-2: #0044af;
  --dig-color__primary__surface: #deebff;
  --dig-color__primary-cta__base: #1a1918;
  --dig-color__primary-cta__on-base: #f7f5f2;
  --dig-color__text__base: #1a1918;
  --dig-color__text__subtle: #736c64;
  --dig-color__background__base: #fff;
  --dig-color__background__subtle: #f6f6f7;
  --dig-color__background__sunken: #f1f2f4;
  --dig-color__border__base: #bbb5ae;
  --dig-color__border__subtle: #00000024;
  --dig-color__border__bold: #8c8279;
  --dig-color__success__base: #0f503c;
  --dig-color__success__surface: #e2ece8;
  --dig-color__warning__surface: #fbe8ae;
  --dig-color__warning__on-surface: #755f1a;
  --dig-color__alert__base: #9b0032;
  --dig-color__alert__surface: #f5e5eb;
  --dig-color__utility__focus-ring: #3080ff;

  /* type */
  --dig-type__family__text: "Atlas Grotesk Web", "Atlas Grotesk", AtlasGrotesk,
    -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Helvetica,
    Arial, sans-serif;
  --dig-type__family__title: "Sharp Grotesk", SharpGrotesk,
    var(--dig-type__family__text);
  --dig-type__weight__base: 400;
  --dig-type__weight__strong: 500;
  --dig-type__letterspacing__action: 0.02em;

  /* radius — vis2023 */
  --dig-radius__xsmall: 4px;
  --dig-radius__small: 6px;
  --dig-radius__medium: 8px;
  --dig-radius__large: 12px;
  --dig-radius__xlarge: 16px;
  --dig-radius__xxlarge: 20px;
  --dig-radius__circular: 999px;

  /* spacing — 8px base */
  --dig-spacing__micro__small: 8px;
  --dig-spacing__micro__medium: 12px;
  --dig-spacing__micro__large: 16px;
  --dig-spacing__macro__small: 24px;
  --dig-spacing__macro__medium: 32px;

  --dig-utilities__focus-ring__thickness: 3px;

  --shadow: 0 1px 2px rgba(26, 25, 24, 0.06), 0 4px 12px rgba(26, 25, 24, 0.05);
  --shadow-hover: 0 2px 4px rgba(26, 25, 24, 0.08), 0 12px 28px rgba(26, 25, 24, 0.1);
  --narrow: 34rem;
  --wide: 76rem;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--dig-color__background__subtle);
  color: var(--dig-color__text__base);
  font-family: var(--dig-type__family__text);
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a { color: var(--dig-color__primary__base); }

h1, h2, h3 {
  font-family: var(--dig-type__family__title);
  font-weight: 500;
  letter-spacing: -0.01em;
}

.skip { position: absolute; left: -999px; top: 0; }
.skip:focus {
  left: 8px;
  top: 8px;
  z-index: 20;
  background: var(--dig-color__background__base);
  padding: 10px 14px;
  border-radius: var(--dig-radius__medium);
}

:focus-visible {
  outline: var(--dig-utilities__focus-ring__thickness) solid var(--dig-color__utility__focus-ring);
  outline-offset: 1px;
}

/* ---------- header ---------- */

.top {
  background: var(--dig-color__background__base);
  border-bottom: 1px solid var(--dig-color__border__subtle);
  position: sticky;
  top: 0;
  z-index: 10;
}

.top-inner {
  max-width: var(--wide);
  margin: 0 auto;
  padding: 14px var(--dig-spacing__macro__small);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--dig-spacing__micro__large);
}

.logo {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--dig-type__family__title);
  font-weight: 600;
  font-size: 1.0625rem;
  color: var(--dig-color__text__base);
  text-decoration: none;
}

.logo-mark {
  width: 26px;
  height: 24px;
  fill: var(--dig-color__primary__base);
  flex: none;
}

/* The wordmark and its tagline, stacked. Centred as a block against the mark
 * rather than aligned on either baseline: the two lines differ enough in size
 * and weight that matching a baseline leaves the mark visibly high. */
.logo-text {
  display: inline-flex;
  flex-direction: column;
  justify-content: center;
  line-height: 1.15;
  min-width: 0;
}

.logo-tag {
  font-family: var(--dig-type__family__base);
  font-weight: var(--dig-type__weight__base);
  font-size: 0.75rem;
  color: var(--dig-color__text__subtle);
  /* The header is sticky, so this sits above the wall the whole way down the
   * page. Quiet enough to read once and then ignore. */
  white-space: nowrap;
}

nav {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

nav a {
  color: var(--dig-color__text__base);
  text-decoration: none;
  padding: 8px 12px;
  border-radius: var(--dig-radius__medium);
  font-weight: var(--dig-type__weight__strong);
  font-size: 0.9375rem;
}

nav a:hover { background: var(--dig-color__background__subtle); }

.who {
  color: var(--dig-color__text__subtle);
  font-size: 0.875rem;
  padding-left: 10px;
  max-width: 12rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Add a photo. The same floating button at every width: on a laptop the header
 * link sat in the corner competing with Feed and Switch, and posting is the one
 * thing this page is for, so it gets a target that does not move or shrink. */
.add-fab {
  display: inline-flex;
  position: fixed;
  right: 14px;
  bottom: calc(14px + env(safe-area-inset-bottom, 0px));
  z-index: 9;
  align-items: center;
  gap: 8px;
  padding: 13px 20px 13px 16px;
  border-radius: var(--dig-radius__circular);
  background: var(--dig-color__primary__base);
  color: var(--dig-color__background__base);
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
  font-size: 0.9375rem;
  text-decoration: none;
  box-shadow: 0 8px 24px rgba(26, 25, 24, 0.32);
}

.add-fab:hover { background: var(--dig-color__primary__base--state-1); }
.add-fab:active { background: var(--dig-color__primary__base--state-2); }
.add-fab-plus { font-size: 1.25rem; line-height: 1; font-weight: 400; }

/* An open comment popover owns the bottom of the screen while it is up. */
body.panel-open .add-fab { display: none; }

form.inline { display: inline-flex; }

/* ---------- buttons ---------- */

button {
  font-family: var(--dig-type__family__title);
  font-size: 0.9375rem;
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
  background: var(--dig-color__background__base);
  color: var(--dig-color__text__base);
  border: 1px solid var(--dig-color__border__base);
  padding: 9px 18px;
  border-radius: var(--dig-radius__circular);
  cursor: pointer;
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}

button:hover { background: var(--dig-color__background__subtle); }
button:active { transform: translateY(1px); }

/* vis2023 primary call to action is near-black, not blue. */
button.primary, .btn-link.primary {
  background: var(--dig-color__primary-cta__base);
  border-color: var(--dig-color__primary-cta__base);
  color: var(--dig-color__primary-cta__on-base);
}

button.primary:hover, .btn-link.primary:hover { background: #302c29; border-color: #302c29; }

.btn-link.primary {
  display: inline-block;
  text-decoration: none;
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
  font-size: 0.9375rem;
  padding: 9px 18px;
  border-radius: var(--dig-radius__circular);
}

button.big { padding: 12px 24px; font-size: 1rem; }

button.ghost {
  background: transparent;
  border-color: transparent;
  color: var(--dig-color__text__subtle);
  padding: 8px 12px;
}
button.ghost:hover {
  background: var(--dig-color__background__subtle);
  color: var(--dig-color__text__base);
}

button.danger {
  color: var(--dig-color__alert__base);
  border-color: var(--dig-color__border__base);
  background: transparent;
}
button.danger:hover {
  background: var(--dig-color__alert__surface);
  border-color: var(--dig-color__alert__base);
}

.chip { padding: 5px 13px; font-size: 0.8125rem; }

/* ---------- banners ---------- */

.banner {
  background: var(--dig-color__primary__surface);
  padding: 9px var(--dig-spacing__macro__small);
  font-size: 0.875rem;
  text-align: center;
}

.banner.warn { background: var(--dig-color__warning__surface); color: var(--dig-color__warning__on-surface); }

.flash {
  max-width: var(--wide);
  width: 100%;
  margin: var(--dig-spacing__micro__large) auto 0;
  padding: 12px var(--dig-spacing__micro__large);
  background: var(--dig-color__success__surface);
  color: var(--dig-color__success__base);
  border-radius: var(--dig-radius__medium);
  font-size: 0.9375rem;
}

.flash.error { background: var(--dig-color__alert__surface); color: var(--dig-color__alert__base); }

/* ---------- layout ---------- */

.wrap {
  width: 100%;
  max-width: var(--narrow);
  margin: 0 auto;
  /* The tall bottom is room for the floating Add button, so it never parks on top
   * of the last row of the wall. */
  padding: var(--dig-spacing__macro__medium) var(--dig-spacing__macro__small) 96px;
  flex: 1;
}

.wrap.wide { max-width: var(--wide); }

h1 { font-size: 1.75rem; line-height: 1.2; margin: 0 0 6px; }
h2 { font-size: 1.125rem; margin: 0 0 12px; }

.lede { color: var(--dig-color__text__subtle); margin: 0; }
.empty { color: var(--dig-color__text__subtle); }
.hint { color: var(--dig-color__text__subtle); font-size: 0.8125rem; margin: 0; }

/* Available to assistive technology, absent from the layout. Used for headings that
 * a sighted reader gets from context and would only read as repetition. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.page-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
}

/* With the repeated title gone this is the first thing read on the page, so it
 * carries a little more weight than a subtitle would. */
.page-head .lede {
  font-size: 0.9375rem;
  color: var(--dig-color__text__base);
  max-width: 44ch;
}

/* Which vibe the room rewards, echoing the "vibe:" label on the tiles so the two
 * read as the same idea. Quiet by design: it is a standing, slow-moving fact
 * about the wall, not a call to action. */
.best-vibe {
  margin: 6px 0 0;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 7px;
  font-size: 0.8125rem;
}

.best-vibe-k {
  color: var(--dig-color__text__subtle);
  text-transform: lowercase;
}

.best-vibe-n { color: var(--dig-color__text__subtle); }

.panel {
  background: var(--dig-color__background__base);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__xlarge);
  padding: 28px;
  box-shadow: var(--shadow);
}

.panel + .panel { margin-top: 18px; }
.panel > h2:first-child { margin-top: 0; }
.panel h3 { margin: 22px 0 8px; font-size: 0.9375rem; }

/* Numbered build steps, and the key/value list for the request fields. */
.steps { margin: 10px 0 0; padding-left: 22px; }
.steps li { margin-bottom: 8px; }

.kv {
  display: grid;
  grid-template-columns: minmax(8rem, auto) 1fr;
  gap: 6px 16px;
  margin: 0;
  font-size: 0.875rem;
}

.kv dt { color: var(--dig-color__text__subtle); }
.kv dd { margin: 0; }

/* A secret meant to be copied by hand onto a phone, so it gets room to breathe
 * and never wraps mid-token. */
.keyline { margin: 0 0 10px; }

.key {
  display: inline-block;
  padding: 8px 12px;
  border: 1px solid var(--dig-color__border__base);
  border-radius: var(--dig-radius__medium);
  background: var(--dig-color__background__sunken);
  font-size: 1rem;
  letter-spacing: 0.04em;
  word-break: break-all;
  user-select: all;
}

/* ---------- tabs + live badge ---------- */

.tabbar {
  display: flex;
  align-items: center;
  gap: var(--dig-spacing__micro__large);
  margin: var(--dig-spacing__macro__small) 0;
  flex-wrap: wrap;
}

/* Two words, and a mark under the one you are on. No track, no pill, no box: with
 * only two tabs, anything drawn around them is more furniture than the choice
 * warrants, and the wall is what the page is for. */
.tabs {
  display: inline-flex;
  align-items: center;
  gap: var(--dig-spacing__macro__small);
}

.tabs a {
  padding: 4px 1px;
  color: var(--dig-color__text__subtle);
  text-decoration: none;
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
  font-size: 0.9375rem;
  border-bottom: 2px solid transparent;
  transition: color 120ms ease;
}

.tabs a:hover { color: var(--dig-color__text__base); }

.tabs a.active {
  color: var(--dig-color__text__base);
  border-bottom-color: var(--dig-color__primary__base);
}

.live {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 0.75rem;
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dig-color__text__subtle);
}

.live .dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--dig-color__success__base);
  animation: pulse 2s ease-in-out infinite;
}

.live.stale .dot { background: var(--dig-color__text__subtle); animation: none; }

.live-toggle {
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 4px 10px;
  color: var(--dig-color__text__subtle);
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}

/* ---------- the grid ---------- */

/* auto-fill reflows 1 -> 4+ columns on its own; tiles stay square.
 * Gap and tile radius match Dropbox's own file-browser grid; the 15rem track
 * sits inside the 156-256px tile width range that grid clamps to. */
.feed {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 15rem), 1fr));
  gap: var(--dig-spacing__micro__large);
  align-items: start;
}

/* Like Dropbox's own grid, the name and controls sit on the thumbnail under a
 * gradient rather than in a strip below it, so the photo gets the whole tile. */
.tile {
  position: relative;
  background: var(--dig-color__primary-cta__base);
  border-radius: var(--dig-radius__xxlarge);
  overflow: hidden;
  box-shadow: var(--shadow);
  transition: opacity 300ms ease, transform 300ms ease, box-shadow 200ms ease;
}

.tile:hover { box-shadow: var(--shadow-hover); transform: translateY(-2px); }

.tile-shot {
  display: block;
  aspect-ratio: 1 / 1;
  background: var(--dig-color__background__sunken);
  overflow: hidden;
}

.tile-shot img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 400ms ease;
}

.tile:hover .tile-shot img { transform: scale(1.03); }
.tile.open:hover .tile-shot img { transform: none; }

/* A clip fills its tile exactly like a photo does. No hover scale: the frame is
 * already moving, and zooming it as well was too much at once. */
.tile-clip {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  background: var(--dig-color__background__sunken);
}

/* Marks a tile as a clip before it starts playing, and doubles as the length. Top
 * right, which is the one corner the overlay gradient never reaches. */
.tile-clip-tag {
  position: absolute;
  top: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 7px;
  border-radius: var(--dig-radius__circular);
  background: rgba(26, 25, 24, 0.72);
  color: #fff;
  font-size: 0.6875rem;
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
  font-variant-numeric: tabular-nums;
  pointer-events: none;
  transition: opacity 160ms ease;
}

.tile-clip-play { font-size: 0.5625rem; line-height: 1; }

/* Out of the way once it is actually playing: the badge has said what it needed
 * to, and by then the moving frame is the thing worth looking at. */
.tile:hover .tile-clip-tag,
.tile:focus-within .tile-clip-tag { opacity: 0; }

.tile-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 36px 14px 12px;
  color: #fff;
  background: linear-gradient(
    to top,
    rgba(26, 25, 24, 0.92) 0%,
    rgba(26, 25, 24, 0.66) 45%,
    rgba(26, 25, 24, 0) 100%
  );
  /* The gradient is decoration: clicks fall through to the photo link, and only
   * the real controls inside it take pointer events. */
  pointer-events: none;
}

.tile-overlay a,
.tile-overlay button,
.tile-overlay form { pointer-events: auto; }

.tile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

/* The text block flows first, then the controls; spacing lives on the rows so
 * the vibe and caption can each be absent without leaving a gap behind. */
.tile-overlay .tile-row { margin-top: 9px; }

/* One line of identity, ordered by weight rather than by rules or boxes: the
 * name reads first, the handle and age recede. Nothing here has a background. */
.tile-meta {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 5px;
  font-size: 0.8125rem;
  min-width: 0;
}

.tile-who {
  font-weight: var(--dig-type__weight__strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tile-at,
.tile-sep,
.tile-time {
  color: rgba(255, 255, 255, 0.62);
  font-size: 0.75rem;
  font-weight: var(--dig-type__weight__base);
  flex: none;
}

/* The handle is the first thing to go when the tile runs out of room; the name
 * and the age are the parts that carry information. */
.tile-at {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  flex: 0 1 auto;
}

.tile-vibe {
  margin: 5px 0 0;
  font-size: 0.8125rem;
  color: #fff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tile-vibe-k { color: rgba(255, 255, 255, 0.55); }

.tile-caption {
  margin: 3px 0 0;
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.82);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.tile-stats {
  font-size: 0.8125rem;
  color: rgba(255, 255, 255, 0.88);
  text-decoration: none;
  cursor: pointer;
}

.tile-stats:hover { color: #fff; text-decoration: underline; }
.tile-stats .sep { opacity: 0.5; padding: 0 2px; }

.tile-actions { display: flex; gap: 6px; flex: none; }

.tile-actions .chip,
.chip-link {
  background: rgba(255, 255, 255, 0.18);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
  backdrop-filter: blur(8px);
}

.tile-actions .chip:hover, .chip-link:hover { background: rgba(255, 255, 255, 0.32); }
.tile-actions .chip.danger { color: #ffcedb; }
.tile-actions .chip.danger:hover { background: rgba(155, 0, 50, 0.6); border-color: rgba(255, 206, 219, 0.6); }

.chip-link {
  display: inline-block;
  text-decoration: none;
  border-style: solid;
  border-width: 1px;
  border-radius: var(--dig-radius__circular);
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
}

/* ---------- in-grid comment popover ---------- */

/* Anchored just above the Like row so the thread opens where you clicked
 * instead of sending you to another page. */
/* Takes nearly the whole tile: stacked comments cost a line each, and a thread
 * you have to scroll two at a time reads as cut off rather than as a list. */
.tile-panel {
  position: absolute;
  left: 8px;
  right: 8px;
  top: 8px;
  bottom: 44px;
  z-index: 3;
  display: flex;
}

.tile-panel[hidden] { display: none; }

.cbox {
  /* Fully opaque. A tint let the photo through behind the text, which on a busy
   * photo is the difference between reading a comment and squinting at it. */
  background: var(--dig-color__background__base);
  color: var(--dig-color__text__base);
  border-radius: var(--dig-radius__large);
  box-shadow: 0 12px 32px rgba(26, 25, 24, 0.32);
  /* Smaller than anywhere else in the app, deliberately: the point of this panel
   * is how much of the thread you can see at once. */
  font-size: 0.75rem;
  display: flex;
  flex-direction: column;
  width: 100%;
  min-height: 0;
  overflow: hidden;
}

.cbox-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 6px 5px 11px;
  font-weight: var(--dig-type__weight__strong);
  border-bottom: 1px solid var(--dig-color__border__subtle);
  flex: none;
}

.cbox-x {
  background: none;
  border: 0;
  padding: 0 6px;
  font-size: 1.125rem;
  line-height: 1;
  color: var(--dig-color__text__subtle);
}

.cbox-x:hover { background: none; color: var(--dig-color__text__base); }

.cbox-list {
  list-style: none;
  margin: 0;
  padding: 4px 11px;
  overflow-y: auto;
  overscroll-behavior: contain;
  min-height: 0;
  flex: 1;
}

/* Threads are separated by a hairline rather than a gap, so the space goes to
 * text instead of padding. */
.cbox-list > li { padding: 6px 0; }
.cbox-list > li + li { border-top: 1px solid var(--dig-color__border__subtle); }

/* Stacked: who said it on one line, what they said underneath. The byline costs
 * a line per comment, but the body then gets the full width of the tile instead
 * of starting halfway across it behind a name. */
.cbox-said { min-width: 0; }

.cbox-line {
  display: flex;
  align-items: baseline;
  gap: 6px;
  margin: 0;
}

.cbox-who {
  font-weight: var(--dig-type__weight__strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cbox-body {
  margin: 1px 0 0;
  line-height: 1.45;
  /* Long words and pasted URLs break rather than forcing the panel wider than
   * the tile, which is what was clipping them. */
  overflow-wrap: anywhere;
}

.cbox-empty { color: var(--dig-color__text__subtle); padding: 3px 0; }

/* The byline is identity and the age. Both icons live down on the comment row
 * instead, so nothing lands in the middle of the sentence. */
.cbox-when {
  color: var(--dig-color__text__subtle);
  font-size: 0.6875rem;
  white-space: nowrap;
  flex: none;
}

/* Reply is the only thing left up there, at the far end. */
.cbox-when + .cbox-reply { margin-left: auto; }

/* The comment and its icons, side by side. Top-aligned rather than centred: on a
 * comment that runs to three lines, vertically centred icons float away from the
 * row they belong to. */
.cbox-text {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.cbox-text .cbox-body { flex: 1; min-width: 0; }

/* Heart then bin, together at the end of the comment. The destructive one is
 * last, furthest from the words and from where the pointer arrives. */
.cbox-acts {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex: none;
}

.cbox-text .cbox-acts { padding-top: 2px; }

/* A heart and a number. Outline until you like it, filled after, and the number
 * is left off entirely at zero — a row of lone zeroes reads as scoring. */
.clike-wrap { display: inline-flex; flex: none; }

.clike {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 0;
  background: none;
  padding: 0;
  color: var(--dig-color__text__subtle);
  font-family: var(--dig-type__family__text);
  font-size: 0.6875rem;
  letter-spacing: normal;
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}

.clike-i {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  display: block;
}

.clike:hover { background: none; color: var(--dig-color__alert__base); }

.clike.on { color: var(--dig-color__alert__base); }
.clike.on .clike-i { fill: currentColor; stroke: currentColor; }

.clike-n:empty { display: none; }

.cbox-reply {
  border: 0;
  background: none;
  padding: 0;
  color: var(--dig-color__text__subtle);
  font-family: var(--dig-type__family__text);
  font-size: 0.6875rem;
  letter-spacing: normal;
  cursor: pointer;
  flex: none;
}

.cbox-reply::before {
  content: "·";
  margin-right: 6px;
}

.cbox-reply:hover { background: none; color: var(--dig-color__primary__base); }

/* Destructive, so it stays quiet until you approach it: same muted grey as the
 * age, red only on hover or focus. */
.cdel-form { display: inline-flex; flex: none; }

.cdel {
  border: 0;
  background: none;
  padding: 0;
  color: var(--dig-color__text__subtle);
  line-height: 1;
  cursor: pointer;
}

.cdel svg { display: block; fill: currentColor; }

.cdel:hover,
.cdel:focus-visible {
  background: none;
  color: var(--dig-color__alert__base);
}

.cbox-gone,
.said-gone {
  margin: 0;
  color: var(--dig-color__text__subtle);
  font-style: italic;
}

/* The byline on the roomier photo page: same order, more air. */
.said-line {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
}

.said-line time { flex: none; }

.said-text {
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

.said-text p { flex: 1; min-width: 0; }
.said-text .cbox-acts { padding-top: 8px; gap: 12px; }

/* The one nested level. A hairline and an indent, no avatars or connectors: at
 * this size anything heavier reads as clutter, and there is no third level to
 * disambiguate from. */
.cbox-replies {
  list-style: none;
  margin: 3px 0 0;
  padding: 0 0 0 9px;
  border-left: 2px solid var(--dig-color__border__subtle);
}

.cbox-replies > li { padding: 2px 0; }

/* Which comment the box at the bottom is currently aimed at. */
.cbox-replying {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin: 0;
  padding: 6px 12px;
  border-top: 1px solid var(--dig-color__border__subtle);
  color: var(--dig-color__text__subtle);
  font-size: 0.75rem;
  flex: none;
}

.cbox-replying[hidden] { display: none; }

.cbox-cancel {
  border: 0;
  background: none;
  padding: 0;
  color: var(--dig-color__primary__base);
  font-family: var(--dig-type__family__text);
  font-size: 0.75rem;
  letter-spacing: normal;
  cursor: pointer;
}

.cbox-form {
  display: flex;
  gap: 6px;
  padding: 8px;
  border-top: 1px solid var(--dig-color__border__subtle);
  flex: none;
}

.cbox-form input[type="text"] {
  font-size: 0.8125rem;
  padding: 6px 12px;
  border-radius: var(--dig-radius__circular);
}

.cbox-form .chip { flex: none; }

.cbox-note {
  margin: 0;
  padding: 8px 12px;
  border-top: 1px solid var(--dig-color__border__subtle);
  color: var(--dig-color__text__subtle);
  flex: none;
}

/* New tiles start collapsed, then settle in once the browser has painted. */
.tile.arriving { opacity: 0; transform: translateY(-10px) scale(0.98); }

.tile.bumped { animation: bump 500ms ease; }

@keyframes bump {
  0% { box-shadow: 0 0 0 0 rgba(0, 97, 254, 0.4); }
  40% { box-shadow: 0 0 0 6px rgba(0, 97, 254, 0.16); }
  100% { box-shadow: 0 0 0 0 rgba(0, 97, 254, 0); }
}

/* ---------- photo detail ---------- */

.back { margin: 0 0 16px; }
.back a { text-decoration: none; font-weight: var(--dig-type__weight__strong); }

.card {
  background: var(--dig-color__background__base);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__xlarge);
  overflow: hidden;
  box-shadow: var(--shadow);
}

.card-head {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 18px;
  align-items: center;
  border-bottom: 1px solid var(--dig-color__border__subtle);
}

.card-head strong { font-size: 0.9375rem; }

/* Same one-line identity as a tile, in dark-on-light. */
.who-line {
  margin: 0;
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.who-at,
.who-sep,
.who-line time {
  color: var(--dig-color__text__subtle);
  font-size: 0.8125rem;
  font-weight: var(--dig-type__weight__base);
}

.meta {
  color: var(--dig-color__text__subtle);
  font-size: 0.75rem;
  display: flex;
  gap: 10px;
  align-items: center;
}

.pill {
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: var(--dig-type__weight__strong);
  font-size: 0.625rem;
  border: 1px solid var(--dig-color__border__base);
  padding: 2px 9px;
  border-radius: var(--dig-radius__circular);
}

.shot { display: block; background: var(--dig-color__background__sunken); }

.shot img,
.shot-clip {
  display: block;
  width: 100%;
  height: auto;
  max-height: 70vh;
  object-fit: contain;
}

.card-body { padding: 18px; }
.vibe { margin: 0 0 6px; }
.vibe-k { color: var(--dig-color__text__subtle); }

.caption { margin: 0 0 10px; }
.counts { margin: 0 0 14px; color: var(--dig-color__text__subtle); font-size: 0.875rem; }

/* ---------- forms ---------- */

.stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 18px;
}

.stack button { align-self: flex-start; }

label { font-weight: var(--dig-type__weight__strong); font-size: 0.875rem; }

input[type="text"],
input[type="password"],
input[type="file"],
input:not([type]),
textarea {
  font-family: var(--dig-type__family__text);
  font-size: 1rem;
  padding: 11px 14px;
  border: 1px solid var(--dig-color__border__base);
  border-radius: var(--dig-radius__medium);
  width: 100%;
  background: var(--dig-color__background__base);
  color: var(--dig-color__text__base);
}

input:focus, textarea:focus {
  outline: none;
  border-color: var(--dig-color__primary__base);
  box-shadow: 0 0 0 var(--dig-utilities__focus-ring__thickness) var(--dig-color__primary__surface);
}

textarea { resize: vertical; }

/* ---------- comments ---------- */

.comments { margin-top: var(--dig-spacing__macro__medium); }

.comment-list { list-style: none; padding: 0; margin: 0 0 20px; }

.comment-list li { border-top: 1px solid var(--dig-color__border__subtle); padding: 14px 0; }
.comment-list strong { font-size: 0.9375rem; }
.comment-list time { color: var(--dig-color__text__subtle); font-size: 0.75rem; margin-left: 8px; }
.comment-list p { margin: 6px 0 0; }

/* Same single nested level as the popover, with room to breathe. */
.reply-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0 0 0 16px;
  border-left: 2px solid var(--dig-color__border__subtle);
}

.reply-list li { border: 0; padding: 8px 0; }
.reply-list li:first-child { padding-top: 0; }

.reply-form {
  display: flex;
  gap: 8px;
  margin-top: 10px;
  max-width: 32rem;
}

.reply-form input[type="text"] {
  flex: 1;
  min-width: 0;
  font-size: 0.875rem;
  padding: 7px 13px;
  border-radius: var(--dig-radius__circular);
}

.reply-form .ghost { flex: none; }

/* ---------- join / QR ---------- */

/* Small code parked next to the heading: on a projector the room can scan the
 * wall itself, so nobody has to be told a URL. */
.join-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--dig-spacing__micro__medium);
  padding: 8px 14px 8px 8px;
  background: var(--dig-color__background__base);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__large);
  box-shadow: var(--shadow);
  color: var(--dig-color__text__base);
  text-decoration: none;
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
  font-size: 0.875rem;
  line-height: 1.3;
  flex: none;
}

.join-chip:hover { box-shadow: var(--shadow-hover); }

.join-chip img {
  width: 72px;
  height: 72px;
  display: block;
  border-radius: var(--dig-radius__small);
  image-rendering: pixelated;
}

.join-chip small {
  font-family: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, monospace;
  font-weight: var(--dig-type__weight__base);
  font-size: 0.6875rem;
  color: var(--dig-color__text__subtle);
}

.join { text-align: center; }
.join h1 { font-size: 2rem; }
.join .lede { margin-bottom: var(--dig-spacing__macro__small); }

.join-qr {
  display: block;
  width: min(360px, 80vw);
  height: auto;
  margin: 0 auto;
  padding: var(--dig-spacing__micro__large);
  background: #fff;
  border-radius: var(--dig-radius__xlarge);
  box-shadow: var(--shadow);
  image-rendering: pixelated;
}

.join-url {
  font-family: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, monospace;
  font-size: 1.25rem;
  margin: var(--dig-spacing__micro__large) 0 4px;
  word-break: break-all;
}

/* Wraps rather than scrolls: on a phone these stack, and a row of three that
 * runs off the side of the screen would hide the one that matters most there. */
.join-acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--dig-spacing__micro__medium);
  margin-top: var(--dig-spacing__macro__small);
}

/* Reserves its line whether or not there is anything to say, so confirming a
 * copy does not shove the buttons upward under the cursor. */
.join-note {
  min-height: 1.25rem;
  margin-top: var(--dig-spacing__micro__medium);
}

/* ---------- admin ---------- */

.admin-h2 { margin-top: var(--dig-spacing__macro__medium); }

.admin-state { margin: 0 0 12px; font-size: 0.9375rem; }
.admin-state.ok { color: var(--dig-color__success__base); }
.admin-state.off { color: var(--dig-color__warning__on-surface); }

.admin-step { margin-top: var(--dig-spacing__micro__large); }
.admin-step h2 { margin-bottom: 8px; }

.row-form {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: var(--dig-spacing__micro__medium);
  flex-wrap: wrap;
}

.row-form input[type="text"] { flex: 1 1 14rem; }
.row-form button { flex: none; }

.crumbs { margin: 0 0 12px; font-size: 0.875rem; }
.crumbs .sep { color: var(--dig-color__text__subtle); padding: 0 6px; }

/* ---------- walls (admin) ---------- */

/* The create form is the point of the page, so it sits above the list rather
 * than behind a disclosure: publishing a wall is the thing you came to do. */
.wall-new {
  padding: var(--dig-spacing__macro__small);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__medium);
  margin-bottom: var(--dig-spacing__macro__medium);
}

.wall-new .opt {
  color: var(--dig-color__text__subtle);
  font-weight: var(--dig-type__weight__base);
  font-size: 0.8125rem;
}

.check {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.9375rem;
}

.check input { width: auto; margin: 0; }

.wall-list { list-style: none; margin: 0; padding: 0; }

.wall-item {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--dig-spacing__macro__small);
  flex-wrap: wrap;
  padding: var(--dig-spacing__micro__large) 0;
  border-top: 1px solid var(--dig-color__border__subtle);
}

/* Closed walls stay legible rather than greyed to the point of being unreadable:
 * a moderator still has to be able to read one to decide to reopen it. */
.wall-item.is-archived .wall-name a { color: var(--dig-color__text__subtle); }

.wall-meta { min-width: 0; flex: 1 1 18rem; }

.wall-name {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 0 0 2px;
  font-family: var(--dig-type__family__title);
  font-weight: var(--dig-type__weight__strong);
}

.wall-name a { text-decoration: none; }

.wall-facts,
.wall-tag {
  margin: 0;
  font-size: 0.8125rem;
  color: var(--dig-color__text__subtle);
}

.wall-facts code { font-size: 0.8125rem; }

/* The Dropbox path can be long and is the least urgent thing on the row, so it
 * is the part allowed to run out of space. */
.wall-folder {
  display: inline-block;
  max-width: 22rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  vertical-align: bottom;
}

.wall-acts {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.chip.on {
  background: var(--dig-color__primary__surface);
  border-color: var(--dig-color__primary__base);
  color: var(--dig-color__primary__base);
}

.review-wall { color: var(--dig-color__text__subtle); }

/* Filter the review list down to one wall. Chips rather than a <select> so the
 * photo counts are visible without opening anything — which wall has the backlog
 * is the thing a moderator wants to know before choosing. */
.wall-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 var(--dig-spacing__macro__small);
}

.wall-filter .chip { text-decoration: none; }

.wall-filter-n {
  color: var(--dig-color__text__subtle);
  font-variant-numeric: tabular-nums;
  padding-left: 2px;
}

.wall-filter .chip.on .wall-filter-n { color: inherit; opacity: 0.7; }

/* States the scope in words. Quiet when showing everything, tinted when narrowed:
 * the mistake to prevent is acting on a filtered list while believing it is the
 * whole deployment, and that only needs emphasis in the filtered case. */
.scope {
  margin: 0 0 var(--dig-spacing__macro__small);
  font-size: 0.875rem;
  color: var(--dig-color__text__subtle);
}

.scope.is-filtered {
  padding: 8px 12px;
  border-radius: var(--dig-radius__small);
  background: var(--dig-color__primary__surface);
  color: var(--dig-color__text__base);
}

.scope.is-filtered a { padding-left: 6px; }

/* The Dropbox root currently in effect, called out because the picker below it
 * used to give no indication which of the folders it listed was the live one. */
.root-now {
  margin: 0 0 var(--dig-spacing__micro__large);
  font-size: 0.9375rem;
}

.root-now code { font-size: 0.9375rem; }

.folder-list .is-current > a { font-weight: var(--dig-type__weight__strong); }

/* A wall whose media sits outside the current default. Not an error, so it reads
 * as a note rather than a failure. */
.wall-warn {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--dig-radius__small);
  border: 1px solid var(--dig-color__border__subtle);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dig-color__text__subtle);
  white-space: nowrap;
}

.wall-folder.is-stray { text-decoration: underline dotted; }

/* Rename, behind a disclosure so the list of walls stays scannable. */
.wall-edit { margin-top: 8px; }

.wall-edit > summary {
  cursor: pointer;
  display: inline-block;
  list-style: none;
  font-size: 0.8125rem;
  color: var(--dig-color__text__subtle);
}

.wall-edit > summary::-webkit-details-marker { display: none; }
.wall-edit > summary::after { content: " ▾"; font-size: 0.75em; }
.wall-edit > summary:hover { color: var(--dig-color__text__base); }

.wall-edit > summary:focus-visible {
  outline: 2px solid var(--dig-color__primary__base);
  outline-offset: 2px;
  border-radius: var(--dig-radius__small);
}

.wall-edit form {
  margin-top: 10px;
  padding: var(--dig-spacing__micro__large);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__medium);
  max-width: 30rem;
}

/* ---------- wall switcher (moderators only) ---------- */

.wallsw { position: relative; }

.wallsw > summary {
  cursor: pointer;
  list-style: none;
  font-size: 0.875rem;
  padding: 6px 10px;
  border-radius: var(--dig-radius__small);
  color: var(--dig-color__text__subtle);
}

.wallsw > summary::-webkit-details-marker { display: none; }
.wallsw > summary::after { content: " ▾"; font-size: 0.75em; }
.wallsw > summary:hover { color: var(--dig-color__text__base); }

.wallsw > summary:focus-visible {
  outline: 2px solid var(--dig-color__primary__base);
  outline-offset: 2px;
}

.wallsw-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  /* Above the header's own stacking context, and wide enough for a room name
   * without being wide enough to reach the edge of a phone. */
  z-index: 11;
  min-width: 13rem;
  max-width: min(20rem, calc(100vw - 32px));
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
  background: var(--dig-color__background__base);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__medium);
  box-shadow: var(--shadow-hover);
}

.wallsw-menu a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 10px;
  border-radius: var(--dig-radius__small);
  font-size: 0.9375rem;
  text-decoration: none;
  color: var(--dig-color__text__base);
}

.wallsw-menu a:hover { background: var(--dig-color__background__subtle); }
.wallsw-menu a.on { font-weight: var(--dig-type__weight__strong); }

.wallsw-flag {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--dig-color__text__subtle);
  flex: none;
}

/* Separated from the rooms: it goes somewhere else rather than switching. */
.wallsw-manage {
  margin-top: 4px;
  border-top: 1px solid var(--dig-color__border__subtle);
  border-radius: 0;
  color: var(--dig-color__text__subtle) !important;
  font-size: 0.875rem !important;
}

.folder-list { list-style: none; margin: 0 0 16px; padding: 0; }

.folder-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 10px 0;
  border-top: 1px solid var(--dig-color__border__subtle);
}

.folder-list li.empty { color: var(--dig-color__text__subtle); }

.folder-list a {
  text-decoration: none;
  font-weight: var(--dig-type__weight__strong);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.danger-panel { margin-top: var(--dig-spacing__macro__small); }
.danger-panel h2 { margin-bottom: 6px; }

/* ---------- emoji reactions ---------- */

/* The three sit slightly stacked, like overlapping chips, so the row reads as one
 * object. The overlap is a fixed margin and the pop is a transform: animating the
 * margin instead would reflow the row out from under the cursor. */
/* Anchor for the reactor tooltips. They hang off the row rather than off each
 * chip: centred on a chip, the bubble ran past the edge of the tile and the
 * tile clips its overflow, so the names came out sliced. */
.rx { display: flex; align-items: center; flex: none; position: relative; }

.rx-item { display: inline-flex; margin: 0; }

.rx-item + .rx-item { margin-left: -7px; }

/* Who tapped this emoji. Above the row, because the row sits at the bottom of a
 * tile and a bubble below it would be cut off. Left-aligned to the row rather
 * than centred on the chip, so it grows into the tile instead of out of it. */
.rx-tip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  transform: translateY(3px);
  z-index: 5;
  /* max-content keeps a short list on one line; the cap stops a long one from
   * running past the edge of a tile, where it would be clipped. */
  width: max-content;
  max-width: 200px;
  /* A well-liked photo names everybody, so the bubble scrolls rather than growing
   * taller than the tile and getting cut off at the top. */
  max-height: 108px;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px 9px;
  border-radius: var(--dig-radius__medium);
  background: rgba(26, 25, 24, 0.94);
  color: #fff;
  font-family: var(--dig-type__family__text);
  font-size: 0.6875rem;
  font-weight: var(--dig-type__weight__base);
  line-height: 1.35;
  letter-spacing: normal;
  /* Left edge of the text, not the middle: a list of names centred on itself
   * looks ragged once it wraps to a second line. */
  text-align: left;
  box-shadow: 0 6px 18px rgba(26, 25, 24, 0.35);
  /* Invisible but laid out, so appearing is a fade rather than a reflow. */
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 120ms ease, transform 120ms ease, visibility 120ms;
}

.rx-tip[hidden] { display: none; }

/* Keyboard focus gets it too, not just the mouse. Pointer events come back once
 * it is up, so a long list can be scrolled — the bubble is inside the chip's own
 * element, so moving onto it does not count as leaving the chip. */
.rx-item:hover .rx-tip,
.rx-item:focus-within .rx-tip {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  pointer-events: auto;
}

/* Lift the whole chip above its neighbours while its tooltip is up, or the
 * overlapping chip next door draws over the bubble. */
.rx-item:hover,
.rx-item:focus-within { z-index: 6; }

/* No hover on a touch screen, so the bubble would only ever flash on tap. */
@media (hover: none) {
  .rx-tip { display: none; }

  /* A tap can leave :hover stuck on a tile, and a transformed ancestor becomes
   * the containing block for position: fixed — which would pull the comment sheet
   * back inside the tile and clip it. No hover states here to lose anyway. */
  .tile:hover { transform: none; }
  .tile:hover .tile-shot img { transform: none; }
}

.rx-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  /* Extra padding on the right is the room the next chip overlaps into, so the
   * count stays legible under the stack. */
  padding: 4px 11px 4px 9px;
  border: 1px solid var(--dig-color__border__base);
  border-radius: var(--dig-radius__circular);
  /* Opaque on purpose: stacked chips would show each other through a tint. */
  background: var(--dig-color__background__base);
  color: var(--dig-color__text__base);
  font-family: var(--dig-type__family__text);
  font-size: 0.8125rem;
  font-weight: var(--dig-type__weight__base);
  letter-spacing: normal;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: transform 150ms ease, box-shadow 150ms ease, background-color 150ms ease;
}

.rx-btn:hover {
  background: var(--dig-color__background__subtle);
  border-color: var(--dig-color__border__bold);
}

/* The pop. Raised above its neighbours so the lift is not clipped by the stack. */
.rx-btn:hover,
.rx-btn:focus-visible {
  transform: translateY(-3px) scale(1.09);
  box-shadow: 0 6px 16px rgba(26, 25, 24, 0.26);
  z-index: 3;
}

.rx-btn:active { transform: translateY(-1px) scale(1.03); }

.rx-btn.on {
  background: var(--dig-color__primary__surface);
  border-color: var(--dig-color__primary__base);
  color: var(--dig-color__primary__base--state-2);
  font-weight: var(--dig-type__weight__strong);
}

.rx-emoji { font-size: 0.9375rem; line-height: 1; }
.rx-n { font-variant-numeric: tabular-nums; }

/* The emoji is decoration; the reaction still needs a name for screen readers. */
.rx-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* On a tile the row sits on top of the photo, so it flips to light-on-dark. The
 * scrim is dark rather than a white tint: stacked chips need to hide the one
 * underneath, and a light tint just piles up into a bright smear. */
.tile-overlay .rx-btn {
  /* No backdrop blur here: blurring while the chip is being transformed is what
   * makes the pop stutter, and the scrim is dark enough not to need it. */
  background: rgba(26, 25, 24, 0.82);
  border-color: rgba(255, 255, 255, 0.3);
  color: #fff;
}

.tile-overlay .rx-btn:hover { background: rgba(26, 25, 24, 0.9); }

.tile-overlay .rx-btn.on {
  background: rgba(255, 255, 255, 0.94);
  border-color: #fff;
  color: var(--dig-color__text__base);
}

/* Reactions and the comment count share a row, and three chips plus a label can
 * outgrow a narrow tile, so this row is the one allowed to wrap. */
.tile-row:has(.rx) { flex-wrap: wrap; row-gap: 6px; }

.tile-mod { justify-content: flex-end; }

/* ---------- upload pickers ---------- */

/* Shortcuts for the vibe field. Outline only, so they read as suggestions
 * rather than as the buttons that actually post anything. */
.picks {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: -4px 0 4px;
}

.ghost-chip {
  padding: 4px 11px;
  border: 1px solid var(--dig-color__border__base);
  border-radius: var(--dig-radius__circular);
  background: transparent;
  color: var(--dig-color__text__subtle);
  font-family: var(--dig-type__family__text);
  font-size: 0.8125rem;
  letter-spacing: normal;
  cursor: pointer;
}

.ghost-chip:hover { border-color: var(--dig-color__border__bold); color: var(--dig-color__text__base); }

.ghost-chip.on {
  border-color: var(--dig-color__text__base);
  color: var(--dig-color__text__base);
}

.pick {
  display: flex;
  gap: var(--dig-spacing__micro__medium);
  flex-wrap: wrap;
}

/* What you picked, before it goes anywhere. Capped by viewport height so a tall
 * photo cannot push the Post button off the bottom of a phone screen. */
.shot-preview {
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.shot-preview img,
.shot-preview video {
  display: block;
  width: 100%;
  max-height: 46vh;
  object-fit: contain;
  background: var(--dig-color__background__sunken);
  border: 1px solid var(--dig-color__border__subtle);
  border-radius: var(--dig-radius__large);
}

.shot-preview img:not([src]),
.shot-preview video:not([src]) { display: none; }

.pick-btn {
  position: relative;
  flex: 1 1 11rem;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 56px;
  padding: var(--dig-spacing__micro__large);
  border: 1px solid var(--dig-color__border__base);
  border-radius: var(--dig-radius__medium);
  background: var(--dig-color__background__base);
  font-weight: var(--dig-type__weight__strong);
  letter-spacing: var(--dig-type__letterspacing__action);
  text-align: center;
  cursor: pointer;
}

.pick-btn:hover {
  border-color: var(--dig-color__border__bold);
  background: var(--dig-color__background__subtle);
}

.pick-btn:focus-within {
  outline: 2px solid var(--dig-color__utility__focus-ring);
  outline-offset: 2px;
}

/* Hidden but still focusable, so the label keeps working as the visible control. */
.pick-btn input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---------- review queue ---------- */

.review { list-style: none; margin: var(--dig-spacing__micro__large) 0 0; padding: 0; }

.review-item {
  display: flex;
  align-items: center;
  gap: var(--dig-spacing__micro__medium);
  padding: 12px 0;
  border-top: 1px solid var(--dig-color__border__subtle);
}

.review-item.is-hidden { opacity: 0.55; }

.review-thumb {
  position: relative;
  flex: none;
  display: block;
  width: 72px;
  height: 72px;
  border-radius: var(--dig-radius__small);
  overflow: hidden;
  background: var(--dig-color__background__subtle);
}

.review-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Stands in for a thumbnail when a clip has no poster frame. */
.review-noshot {
  display: flex;
  width: 100%;
  height: 100%;
  align-items: center;
  justify-content: center;
  color: var(--dig-color__text__subtle);
  font-size: 0.75rem;
}

/* Top corner, so it does not collide with the Hidden strip along the bottom. */
.review-kind {
  position: absolute;
  top: 2px;
  right: 2px;
  padding: 1px 5px;
  border-radius: var(--dig-radius__circular);
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 0.625rem;
}

.review-flag {
  position: absolute;
  inset: auto 0 0 0;
  background: rgba(0, 0, 0, 0.72);
  color: #fff;
  font-size: 0.6875rem;
  text-align: center;
  padding: 2px 0;
}

.review-meta { flex: 1 1 auto; min-width: 0; }
.review-meta p { margin: 0; }
.review-who { font-size: 0.9375rem; }
.review-dot { color: var(--dig-color__text__subtle); padding: 0 5px; }
.review-when, .review-src, .review-counts {
  color: var(--dig-color__text__subtle);
  font-size: 0.8125rem;
}

.review-caption {
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.review-action { flex: none; }

.pager {
  display: flex;
  align-items: center;
  gap: var(--dig-spacing__micro__medium);
  margin-top: var(--dig-spacing__micro__large);
}

.pager-at { color: var(--dig-color__text__subtle); font-size: 0.875rem; }

@media (max-width: 30rem) {
  .review-thumb { width: 56px; height: 56px; }
  .review-caption, .review-src, .review-dot:last-of-type { display: none; }
}

/* ---------- footer ---------- */

.foot {
  max-width: var(--wide);
  width: 100%;
  margin: 0 auto;
  padding: 20px var(--dig-spacing__macro__small) 32px;
  display: flex;
  justify-content: space-between;
  color: var(--dig-color__text__subtle);
  font-size: 0.8125rem;
  border-top: 1px solid var(--dig-color__border__subtle);
}

.foot a { color: var(--dig-color__text__subtle); text-decoration: none; }
.foot a:hover { color: var(--dig-color__primary__base); }

code {
  font-family: ui-monospace, "SFMono-Regular", "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.875em;
  background: var(--dig-color__background__sunken);
  padding: 1px 5px;
  border-radius: var(--dig-radius__xsmall);
}

@media (max-width: 640px) {
  .top-inner, .banner, .wrap, .foot { padding-left: 12px; padding-right: 12px; }
  h1 { font-size: 1.5rem; }
  .panel { padding: 20px; }

  /* The header was carrying a wordmark, two links and an identity chip, which on
   * a phone wrapped into a squashed two-line bar. Strip it back to the logo mark
   * and who you are; Feed lives on the logo and Add a photo becomes the button
   * fixed above the thumb, which is a far bigger target than the link was. */
  .logo-text, .nav-wide { display: none; }
  .top-inner { padding-top: 10px; padding-bottom: 10px; }
  /* The switcher stays, unlike the Feed link: an event gets moderated from a
   * phone while walking around, so this is exactly where it is needed. Just
   * tightened, since the header has little width to spare. */
  .wallsw > summary { padding: 6px 4px; font-size: 0.8125rem; }
  .logo-mark { width: 30px; height: 28px; }
  /* Shorter when the switcher is also competing for the bar. */
  .who { max-width: 9rem; }
  .wallsw ~ .who { max-width: 6rem; }

  /* The 15rem floor above is wider than half a phone screen, so it collapses to
   * a single column. Drop the floor and tighten the gap to fit two across. */
  .feed {
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 9.5rem), 1fr));
    gap: 10px;
  }

  /* Less furniture on a small tile, so the photo stays the point. */
  .tile { border-radius: var(--dig-radius__xlarge); }
  .tile-overlay { padding: 26px 10px 9px; }
  .tile-meta { font-size: 0.75rem; gap: 4px; }
  /* The handle is the one part of the line worth losing at this width: the age
   * is now short enough to keep, which it was not as a full timestamp. */
  .tile-at { display: none; }
  .tile-time { font-size: 0.6875rem; }
  .tile-vibe { font-size: 0.75rem; margin-top: 3px; }
  .tile-caption {
    font-size: 0.75rem;
    -webkit-line-clamp: 1;
    line-clamp: 1;
  }
  .tile-stats { font-size: 0.75rem; }
  .tile-actions .chip, .chip-link { padding: 4px 10px; font-size: 0.75rem; }
  .tile-overlay .rx-btn { padding: 3px 8px 3px 6px; font-size: 0.6875rem; gap: 2px; }
  .tile-overlay .rx-emoji { font-size: 0.8125rem; }
  /* Less overlap on a phone: the chips are smaller, so the tap targets need it. */
  .tile-overlay .rx-item + .rx-item { margin-left: -5px; }

  /* On a phone the wall is two across, so a tile is about 170px square and the
   * popover inside it came out ~130px tall: two comments and a sliced third. It
   * becomes a bottom sheet instead, fixed to the viewport rather than trapped in
   * the tile, which is the same tap-the-count gesture with room to actually read
   * the thread. */
  .tile-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    z-index: 40;
    max-height: 76vh;
    /* Clear of the iOS home indicator, so the compose box is not under it. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  .tile-panel .cbox {
    border-radius: var(--dig-radius__large) var(--dig-radius__large) 0 0;
    /* Back up to the body size: the tiny type existed to fit a tile, and the
     * sheet is not short of space. */
    font-size: 0.8125rem;
  }

  /* Dims the wall behind the sheet, so it reads as one thread rather than a box
   * floating over a grid. A tap out here already closes it. */
  body.panel-open::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 39;
    background: rgba(26, 25, 24, 0.45);
  }

  .join-chip { padding: 6px 10px 6px 6px; font-size: 0.8125rem; }
  .join-chip img { width: 56px; height: 56px; }
  .page-head { gap: 12px; }
}

/* Very narrow phones: keep two across, and let the vibe stand in for the
 * caption rather than stacking both. */
@media (max-width: 380px) {
  .feed { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .tile-caption { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .tile, .tile.arriving, .tile-shot img { transition: none; transform: none; opacity: 1; }
  .tile:hover { transform: none; }
  /* clips.js does not play anything at this setting, so the badge stays put. */
  .tile-clip-tag { transition: none; }
  .tile:hover .tile-clip-tag, .tile:focus-within .tile-clip-tag { opacity: 1; }
  .tile.bumped, .live .dot { animation: none; }
  .tabs a { transition: none; }
  .rx-btn { transition: none; }
  .rx-btn:hover, .rx-btn:focus-visible, .rx-btn:active { transform: none; }
}

/* ---------- print ----------
 *
 * Only /qr is worth printing, and what it is worth printing for is a sheet taped
 * up next to the actual wall. So everything that is navigation, chrome or screen
 * affordance goes, and what is left is the code and the address it encodes.
 *
 * Backgrounds are dropped rather than kept because browsers do not print them by
 * default anyway, and a QR code needs black on white to scan reliably. */
@media print {
  .top, .banner, .foot, .flash, .skip, .add-fab, .join-acts, .join-note {
    display: none !important;
  }

  body {
    background: #fff;
    color: #000;
  }

  .wrap {
    padding: 0;
    max-width: none;
  }

  .join h1 { font-size: 28pt; margin: 0 0 8pt; }
  .join .lede { font-size: 13pt; margin: 0 0 18pt; }

  /* Big enough to scan from across a room, and sized in physical units because
   * that is what actually governs whether a phone can read it off paper. */
  .join-qr {
    width: 4.5in;
    height: 4.5in;
    box-shadow: none;
    border: 1pt solid #000;
    padding: 0.15in;
    /* Keep the modules hard-edged; a smoothed QR code scans worse. */
    image-rendering: pixelated;
  }

  .join-url {
    font-size: 14pt;
    margin-top: 18pt;
  }

  /* The instruction to drop the scheme is screen advice about typing it into a
   * phone. On paper the full address is the useful thing. */
  .join .hint { display: none; }
}
