/* ==========================================================================
   Bereka
   Direction: buna. The Ethiopian coffee ceremony is the ritual of sitting
   down with people, strangers included, and talking. Palette is roasted
   coffee, unbleached cotton, and the flag colours at their real muted
   values rather than neon. Signature element is the tibeb band, the woven
   border on Ethiopian cotton, which carries matchmaking state.
   ========================================================================== */

:root {
  /* Roast — warm darks, never neutral black */
  --ink-900: #14100E;
  --ink-800: #1C1613;
  --ink-700: #251D18;
  --ink-600: #322620;
  --ink-500: #45352C;

  /* Cotton */
  --cotton: #F4EBE0;
  --cotton-dim: #CDBDAD;
  --taupe: #93816F;
  --taupe-deep: #6B5B4D;

  /* Flag, as woven not as neon */
  --green: #2E7D53;
  --gold: #E0A82E;
  --red: #C0392B;

  --font-display: 'Fraunces', 'Noto Sans Ethiopic', Georgia, serif;
  --font-ui: 'Archivo', 'Noto Sans Ethiopic', -apple-system, BlinkMacSystemFont, sans-serif;

  --r-sm: 6px;
  --r-md: 10px;
  --r-lg: 16px;
  --r-xl: 22px;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  /* Tibeb geometry — one place to retune the weave */
  --weave: 9px;
  --band-h: 7px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

html { -webkit-text-size-adjust: 100%; }

body {
  font-family: var(--font-ui);
  background: var(--ink-900);
  color: var(--cotton);
  height: 100dvh;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

button { font-family: inherit; cursor: pointer; border: none; background: none; color: inherit; }
input { font-family: inherit; }


/* ==========================================================================
   Tibeb band — the signature
   Two opposed diagonal repeats over gold read as a woven lattice. Because
   the whole thing is background-position driven, it can travel.
   ========================================================================== */

.tibeb-band {
  height: var(--band-h);
  flex-shrink: 0;
  background-color: var(--gold);
  background-image:
    repeating-linear-gradient(45deg,
      var(--green) 0 calc(var(--weave) / 2),
      transparent calc(var(--weave) / 2) var(--weave)),
    repeating-linear-gradient(-45deg,
      var(--red) 0 calc(var(--weave) / 2),
      transparent calc(var(--weave) / 2) var(--weave));
  background-size: var(--weave) var(--weave);
  background-position: 0 0;
}

.band-header { opacity: 0.5; }

.band-stage {
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  opacity: 0.65;
  transition: opacity 0.4s var(--ease);
}

.band-top { border-radius: var(--r-lg) var(--r-lg) 0 0; }

/* The band travels while the app is looking for a partner. State lives in
   the status capsule, so this is driven off it via :has(). */
@keyframes weaveTravel {
  to { background-position: var(--weave) 0, calc(var(--weave) * -1) 0; }
}

body:has(.status-capsule.searching) .band-stage {
  opacity: 1;
  animation: weaveTravel 0.9s linear infinite;
}


/* ==========================================================================
   Shell
   ========================================================================== */

.app-shell {
  display: flex;
  flex-direction: column;
  height: 100dvh;
}

.app-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
  padding: 14px 22px;
  background: var(--ink-800);
}

/* Let either side shrink past its min-content width instead of pushing the
   header — and the page — wider than the viewport. */
.app-header > * { min-width: 0; }

.wordmark {
  display: flex;
  align-items: baseline;
  gap: 10px;
}

/* Shared three-cups favicon, legible on both ink and cotton surfaces. */
.wordmark-mark {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  align-self: center;
  color: var(--cotton);
}

.wordmark-text {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.32rem;
  font-style: italic;
  font-weight: 600;
  letter-spacing: -0.055em;
  color: var(--cotton);
}

/* Brand lettering stays Latin in either UI language; Ethiopic sits beside it. */
.brand-wordmark {
  font: italic 600 30px/1.1 'Fraunces', Georgia, serif;
  letter-spacing: -0.055em;
  text-transform: none;
  color: var(--ink-900);
}
.brand-am {
  font: 500 13px/1.5 'Noto Sans Ethiopic', sans-serif;
  letter-spacing: 0;
  color: var(--taupe);
}
.brand-logo { display: block; width: 40px; height: 40px; flex: none; }
.doc-brand { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 12px; }

.header-dock {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

.status-capsule {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 13px;
  border-radius: var(--r-sm);
  background: var(--ink-700);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--taupe);
  white-space: nowrap;
}

.status-pulse-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--taupe-deep);
  flex-shrink: 0;
  transition: background 0.3s ease;
}

.status-capsule.active { color: var(--cotton); }
.status-capsule.active .status-pulse-dot { background: var(--green); }

.status-capsule.searching { color: var(--gold); }
.status-capsule.searching .status-pulse-dot {
  background: var(--gold);
  animation: emberPulse 1.4s ease-in-out infinite;
}

.status-capsule.quiet { color: var(--taupe); }
.status-capsule.quiet .status-pulse-dot { background: var(--gold); animation: none; }

.status-capsule.stopped { color: var(--red); }
.status-capsule.stopped .status-pulse-dot { background: var(--red); }

.status-capsule.blocked { color: var(--red); }
.status-capsule.blocked .status-pulse-dot { background: var(--red); animation: none; }

@keyframes emberPulse {
  0%, 100% { opacity: 0.35; }
  50% { opacity: 1; }
}

.btn-lang {
  padding: 6px 13px;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid var(--ink-600);
  color: var(--cotton-dim);
  font-size: 0.78rem;
  font-weight: 600;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}

.btn-lang:hover {
  border-color: var(--gold);
  color: var(--gold);
}

/* Connection quality — same pill shape as .status-capsule */
.conn-quality {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border-radius: var(--r-sm);
  background: var(--ink-700);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--taupe);
  white-space: nowrap;
}

.conn-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--taupe-deep);
  flex-shrink: 0;
  transition: background 0.3s ease;
}

.conn-quality.good { color: var(--cotton); }
.conn-quality.good .conn-dot { background: var(--green); }
.conn-quality.fair { color: var(--gold); }
.conn-quality.fair .conn-dot { background: var(--gold); }
.conn-quality.poor { color: var(--red); }
.conn-quality.poor .conn-dot { background: var(--red); }

/* Per-call data total */
.data-counter {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  color: var(--taupe);
  white-space: nowrap;
}

/* Data Saver reuses .btn-lang; pressed = gold fill */
.data-saver[aria-pressed="true"] {
  border-color: var(--gold);
  background: var(--gold);
  color: var(--ink-900);
}

/* Layout-mode segmented control */
.layout-switch {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: var(--r-sm);
  background: var(--ink-700);
}

.layout-opt {
  padding: 5px 10px;
  border-radius: calc(var(--r-sm) - 1px);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--taupe);
  white-space: nowrap;
  transition: background 0.18s ease, color 0.18s ease;
}

.layout-opt:hover { color: var(--cotton-dim); }

.layout-opt[aria-pressed="true"] {
  background: var(--gold);
  color: var(--ink-900);
}


/* ==========================================================================
   Main
   ========================================================================== */

.app-main {
  flex: 1;
  display: flex;
  gap: 16px;
  padding: 16px 22px 22px;
  overflow: hidden;
}

.stage {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.stage-frame {
  flex: 1;
  position: relative;
  background: #000;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  overflow: hidden;
  min-height: 0;

  /* PiP geometry — one source of truth, read by whichever feed is the small
     one. JS overrides --pip-w / --pip-h inline when the user resizes. */
  --pip-w: 176px;
  --pip-h: 118px;
  --pip-gap: 16px;
}

.stage-frame > video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Idle state */
.stage-idle {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 26px;
  padding: 0 28px;
  text-align: center;
  background: var(--ink-800);
  z-index: 2;
}

.stage-idle.is-hidden { display: none; }

.stage-state-text,
.stage-idle-note { display: none; }

.stage-retry {
  display: none;
  min-height: 44px;
  padding: 10px 20px;
  border: 1px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--gold);
  color: var(--ink-900);
  font: 700 0.84rem var(--font-ui);
  cursor: pointer;
}
.stage-retry:hover { background: var(--cotton); border-color: var(--cotton); }
.stage-idle[data-match-state="no-match"] .stage-retry {
  display: inline-flex;
  align-items: center;
}

.sini-mark {
  position: relative;
  width: 64px;
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.sini { width: 52px; height: 52px; display: block; }

/* Steam rising from the cup rim */
.steam {
  position: absolute;
  bottom: 34px;
  width: 2px;
  height: 14px;
  border-radius: 2px;
  background: linear-gradient(to top, rgba(224, 168, 46, 0.5), transparent);
  animation: steamRise 3.2s ease-in-out infinite;
}

.steam-1 { left: 24px; animation-delay: 0s; }
.steam-2 { left: 31px; height: 20px; animation-delay: 0.5s; }
.steam-3 { left: 38px; animation-delay: 1s; }

@keyframes steamRise {
  0%   { opacity: 0; transform: translateY(6px) scaleY(0.6); }
  35%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-14px) scaleY(1.1); }
}

.stage-idle-text {
  font-size: 0.92rem;
  font-weight: 500;
  color: var(--taupe);
  max-width: 30ch;
  line-height: 1.5;
}

/* Labels */
.feed-tag {
  position: absolute;
  top: 12px;
  left: 12px;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  z-index: 4;
}

.tag-stranger {
  background: rgba(20, 16, 14, 0.72);
  color: var(--cotton-dim);
  backdrop-filter: blur(6px);
}

.tag-self {
  background: rgba(20, 16, 14, 0.72);
  color: var(--gold);
  backdrop-filter: blur(6px);
  font-size: 0.56rem;
  padding: 3px 7px;
  top: 8px;
  left: 8px;
}

/* Self preview */
.self-pip {
  position: absolute;
  right: var(--pip-gap);
  bottom: var(--pip-gap);
  width: var(--pip-w);
  height: var(--pip-h);
  z-index: 6;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--ink-700);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
  transition: transform 0.3s var(--ease), width 0.22s var(--ease), height 0.22s var(--ease);
}

.self-pip:hover { transform: translateY(-3px); }

.self-pip video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scaleX(-1); /* mirror, so it reads as a mirror not a stranger */
  display: block;
}

.pip-dock {
  position: absolute;
  right: 7px;
  bottom: 7px;
  display: flex;
  gap: 5px;
  z-index: 3;
}

.pip-btn {
  width: 29px;
  height: 29px;
  border-radius: 50%;
  background: rgba(20, 16, 14, 0.82);
  backdrop-filter: blur(6px);
  font-size: 0.72rem;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.18s var(--ease), background 0.2s ease;
}

.pip-btn:hover:not(:disabled) { transform: scale(1.12); }
.pip-btn:disabled { opacity: 0.3; cursor: not-allowed; }
.pip-btn.off { background: var(--red); }
.pip-btn.active { background: var(--gold); color: var(--ink-900); }

/* Resize grip — top-left corner of the self-preview, surfaced on hover. Two
   short gold ticks over an ink square; no colours beyond the existing tokens. */
.pip-resize {
  position: absolute;
  top: 0;
  left: 0;
  width: 20px;
  height: 20px;
  z-index: 5;
  cursor: nwse-resize;
  touch-action: none;
  opacity: 0;
  border-radius: var(--r-md) 0 var(--r-md) 0;
  background-color: var(--ink-700);
  background-image: linear-gradient(135deg,
    transparent 0 4px,
    var(--gold) 4px 5.5px,
    transparent 5.5px 8px,
    var(--gold) 8px 9.5px,
    transparent 9.5px 20px);
  transition: opacity 0.18s var(--ease);
}

.self-pip:hover .pip-resize,
.self-pip.resizing .pip-resize { opacity: 1; }

/* No width/height tween while dragging — it should track the pointer exactly. */
.self-pip.resizing { transition: none; }

/* ── Desktop only, from here to the end of "Layout modes" ───────────────────
   The PiP swap and the three layout modes are a pointer / wide-screen
   concern. They are gated to >=901px so the phone layout further down can own
   everything at <=900px without these higher-specificity [data-layout] and
   .is-swapped rules fighting it. Behaviour at desktop widths is unchanged. */
@media (min-width: 901px) {

/* Swapped: the self-preview takes the stage, the remote feed drops into the
   PiP slot. Only classes move — the media streams are never reassigned. */
.stage-frame.is-swapped > .self-pip {
  inset: 0;
  right: auto;
  bottom: auto;
  width: 100%;
  height: 100%;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: none;
  transform: none;
  z-index: 1;
}

.stage-frame.is-swapped > .self-pip:hover { transform: none; }

.stage-frame.is-swapped .pip-resize { display: none; }

/* The dock rides in the swapped self-preview's stacking context, which sits
   below the remote PiP. Move it to the clear top-right corner so the mic, cam
   and swap-back controls stay reachable. */
.stage-frame.is-swapped > .self-pip .pip-dock {
  top: 12px;
  right: 12px;
  bottom: auto;
}

.stage-frame.is-swapped > #remoteVideo {
  position: absolute;
  top: auto;
  left: auto;
  right: var(--pip-gap);
  bottom: var(--pip-gap);
  width: var(--pip-w);
  height: var(--pip-h);
  border-radius: var(--r-md);
  box-shadow: 0 12px 30px rgba(0, 0, 0, 0.55);
  z-index: 6;
  transition: width 0.22s var(--ease), height 0.22s var(--ease);
}

.stage-frame.is-swapped > #remoteLabel {
  top: auto;
  right: auto;
  left: calc(100% - var(--pip-gap) - var(--pip-w) + 8px);
  bottom: calc(var(--pip-gap) + var(--pip-h) - 24px);
  z-index: 7;
}

/* The idle art belongs to the remote feed, including when that feed is the
   small card. A retained swap also applies in Theater; Split resets below. */
.app-shell:not([data-layout="split"]) .stage-frame.is-swapped > .stage-idle {
  inset: auto var(--pip-gap) var(--pip-gap) auto;
  width: var(--pip-w);
  height: var(--pip-h);
  padding: 6px;
  gap: 4px;
  border-radius: var(--r-md);
  z-index: 6;
}

.app-shell:not([data-layout="split"]) .stage-frame.is-swapped .stage-idle .sini-mark,
.app-shell:not([data-layout="split"]) .stage-frame.is-swapped .stage-idle .sini {
  width: clamp(20px, calc(var(--pip-h) * 0.22), 40px);
  height: clamp(20px, calc(var(--pip-h) * 0.22), 40px);
}

.app-shell:not([data-layout="split"]) .stage-frame.is-swapped .stage-idle .steam {
  display: none;
}

.app-shell:not([data-layout="split"]) .stage-frame.is-swapped .stage-idle-text {
  max-width: 100%;
  font-size: 0.65rem;
  line-height: 1.2;
}


/* ==========================================================================
   Layout modes
   Driven entirely by [data-layout] on .app-shell. client.js only writes the
   attribute — no <video> is re-mounted or re-sourced — so playback carries
   straight through a mode change. "pip" is the base (rules above); "split"
   and "theater" override from here. Selectors lead with
   .app-shell[data-layout=...] so they outrank the .is-swapped rules above.
   ========================================================================== */

/* ---- Split: two equal feeds, no PiP ---- */
.app-shell[data-layout="split"] .stage-frame {
  --split-gap: 3px;
  --split-panel-w: calc((100% - var(--split-gap)) / 2);
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-template-rows: minmax(0, 1fr);
  gap: var(--split-gap);
  background: var(--ink-900);
}

.app-shell[data-layout="split"] .stage-frame > .stage-idle {
  inset: 0 auto 0 0;
  width: var(--split-panel-w);
}

.app-shell[data-layout="split"] .stage-frame > #remoteVideo,
.app-shell[data-layout="split"] .stage-frame > #localPipCard {
  position: relative;
  inset: auto;
  grid-row: 1;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
  border-radius: 0;
  box-shadow: none;
  z-index: auto;
  transition: none;
}

.app-shell[data-layout="split"] .stage-frame > #remoteVideo { grid-column: 1; }

.app-shell[data-layout="split"] .stage-frame > #localPipCard {
  grid-column: 2;
  background: var(--ink-700);
  transform: none;
  /* sit above the idle placeholder (z-index 2) so the self-view stays
     visible while waiting for a partner */
  z-index: 3;
}

.app-shell[data-layout="split"] .stage-frame > #remoteVideo,
.app-shell[data-layout="split"] .self-pip video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* mirror stays on the local feed in every mode */
.app-shell[data-layout="split"] .self-pip video { transform: scaleX(-1); }
.app-shell[data-layout="split"] .self-pip:hover { transform: none; }

/* Reset the tag + dock in case a swap was left active in pip before switching */
.app-shell[data-layout="split"] .stage-frame > #remoteLabel {
  top: 12px;
  left: 12px;
  right: auto;
  bottom: auto;
}

.app-shell[data-layout="split"] #localPipCard .pip-dock {
  /* Local-panel toolbar: same corner as the expanded self-view's dock in
     swapped PiP/Theater, aligned with the feed labels rather than adrift. */
  top: 12px;
  right: 12px;
  bottom: auto;
  padding: 5px;
  border: 1px solid var(--ink-600);
  border-radius: var(--r-md);
  background: var(--ink-800);
}

/* ---- Theater: expand the stage into the space not occupied by chat ---- */
.app-shell[data-layout="theater"] .app-main {
  --theater-panel-w: min(360px, 84vw);
  --theater-chat-space: 0px;
  position: relative;
  /* The 60px lane holds a compact 44px toggle plus 8px either side. Reserving
     chat space here keeps the stage, both feeds and their placeholders aligned,
     including a retained swap, instead of moving individual video elements. */
  padding: 16px calc(60px + var(--theater-chat-space)) 22px 22px;
  gap: 0;
  min-height: 0;
}

.app-shell[data-layout="theater"][data-chat="open"] .app-main {
  --theater-chat-space: var(--theater-panel-w);
}

.app-shell[data-layout="theater"] .stage { flex: 1; min-height: 0; }

.app-shell[data-layout="theater"] .panel {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: var(--theater-panel-w);
  border-radius: var(--r-lg) 0 0 var(--r-lg);
  transform: translateX(100%);
  /* Reflow and visibility change together; a closing panel must not briefly
     cover the PiP as the stage expands back underneath it. */
  transition: none;
  z-index: 20;
  box-shadow: -16px 0 44px rgba(0, 0, 0, 0.5);
}

.app-shell[data-layout="theater"][data-chat="open"] .panel {
  transform: translateX(0);
}

/* Desktop Theater only: an explicit chat/close button, not a full-height bar. */
.chat-edge-toggle { display: none; }

.app-shell[data-layout="theater"] .chat-edge-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  position: absolute;
  top: 50%;
  right: calc(var(--theater-chat-space) + 8px);
  bottom: auto;
  width: 44px;
  height: 44px;
  transform: translateY(-50%);
  border-radius: var(--r-md);
  background: var(--ink-800);
  color: var(--gold);
  font-size: 1.1rem;
  z-index: 25;
  border: 1px solid var(--ink-600);
  transition: background 0.2s ease, color 0.2s ease;
}

.app-shell[data-layout="theater"] .chat-edge-toggle:hover { background: var(--ink-700); }

.app-shell[data-layout="theater"] .chat-edge-toggle[aria-expanded="true"] {
  background: var(--ink-700);
  color: var(--gold);
}

.app-shell[data-layout="theater"] .chat-edge-toggle[aria-expanded="true"] .chat-edge-toggle-icon {
  font-size: 0;
}

.app-shell[data-layout="theater"] .chat-edge-toggle[aria-expanded="true"] .chat-edge-toggle-icon::before {
  content: '\00d7';
  font-size: 1.3rem;
}

/* Swap + PiP-resize chrome only exist in "pip" mode */
.app-shell:not([data-layout="pip"]) .pip-resize,
.app-shell:not([data-layout="pip"]) #swapVideosBtn {
  display: none;
}

} /* end @media (min-width: 901px) — desktop layout modes */


/* ==========================================================================
   Chat panel
   ========================================================================== */

/* Transient message bubbles are a phone-only affordance; the media query
   below turns them on. Never shown on desktop. */
.bubble-layer { display: none; }

.panel {
  width: 372px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  background: var(--ink-800);
  border-radius: var(--r-lg);
  overflow: hidden;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 16px 18px 13px;
  border-bottom: 1px solid var(--ink-600);
}

.panel-title {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--taupe);
}

.panel-title-am {
  font-family: var(--font-display);
  font-size: 0.92rem;
  color: var(--taupe-deep);
}

.panel-feed {
  flex: 1;
  padding: 16px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 9px;
  scrollbar-width: thin;
  scrollbar-color: var(--ink-500) transparent;
}

.panel-feed::-webkit-scrollbar { width: 5px; }
.panel-feed::-webkit-scrollbar-thumb {
  background: var(--ink-500);
  border-radius: 3px;
}

/* System notices */
.alert-bubble {
  padding: 9px 12px;
  border-left: 2px solid var(--ink-500);
  font-size: 0.79rem;
  line-height: 1.5;
  color: var(--taupe);
  animation: settleIn 0.28s var(--ease);
}

/* Messages */
.feed-bubble {
  max-width: 84%;
  padding: 9px 13px;
  font-size: 0.87rem;
  line-height: 1.5;
  overflow-wrap: anywhere;
  animation: settleIn 0.24s var(--ease);
}

@keyframes settleIn {
  from { opacity: 0; transform: translateY(5px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Phone message column (see the overlay-model block near the end). The pill
   grows in from zero height at the bottom, which is what shifts the rows above
   it up; the reverse collapse eases the oldest row out. The row fades and
   slides a touch as it does. prefers-reduced-motion collapses the duration
   globally (see the reduced-motion block). */
@keyframes rowIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes rowOut { to { opacity: 0; transform: translateY(-6px); } }
@keyframes pillGrow { from { max-height: 0; } to { max-height: 8rem; } }
@keyframes pillCollapse { to { max-height: 0; padding-top: 0; padding-bottom: 0; } }

.feed-bubble.user {
  align-self: flex-end;
  background: var(--gold);
  color: var(--ink-900);
  font-weight: 500;
  border-radius: var(--r-md) var(--r-md) 2px var(--r-md);
}

.feed-bubble.stranger {
  align-self: flex-start;
  background: var(--ink-700);
  color: var(--cotton);
  border-radius: var(--r-md) var(--r-md) var(--r-md) 2px;
}

/* Composer */
.panel-compose {
  padding: 13px;
  border-top: 1px solid var(--ink-600);
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.compose-actions { display: flex; gap: 8px; }

.btn-stop {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  min-width: 44px;
  min-height: 44px;
  padding: 8px 10px;
  border: 1px solid var(--taupe);
  border-radius: var(--r-md);
  background: var(--ink-900);
  color: var(--cotton);
  font-size: 0.82rem;
}
.btn-stop:hover { border-color: var(--cotton); }
.btn-stop:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
#stopBtn[hidden] { display: none; }

.btn-start {
  flex: 1;
  padding: 13px;
  border-radius: var(--r-md);
  background: var(--gold);
  color: var(--ink-900);
  font-size: 0.88rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  transition: background 0.2s ease, transform 0.15s var(--ease);
}

.btn-start:hover { background: #EDB53C; }
.btn-start:active { transform: scale(0.985); }

.btn-report {
  padding: 13px 16px;
  border-radius: var(--r-md);
  background: transparent;
  border: 1px solid var(--ink-500);
  color: var(--taupe);
  font-size: 0.82rem;
  font-weight: 600;
  white-space: nowrap;
  transition: all 0.2s var(--ease);
}

.btn-report:hover:not(:disabled) {
  border-color: var(--red);
  color: var(--red);
}

.btn-report:disabled { opacity: 0.32; cursor: not-allowed; }

.compose-input { display: flex; gap: 8px; }

.compose-input input {
  flex: 1;
  min-width: 0;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--ink-700);
  border: 1px solid transparent;
  color: var(--cotton);
  font-size: 0.88rem;
  outline: none;
  transition: border-color 0.2s ease;
}

.compose-input input::placeholder { color: var(--taupe-deep); }
.compose-input input:focus { border-color: var(--gold); }
.compose-input input:disabled { opacity: 0.45; }

.btn-send {
  width: 44px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background: var(--ink-700);
  color: var(--cotton-dim);
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease, color 0.2s ease;
}

.btn-send:hover:not(:disabled) {
  background: var(--gold);
  color: var(--ink-900);
}

.btn-send:disabled { opacity: 0.3; cursor: not-allowed; }


/* ==========================================================================
   Entry card — flips to cotton against the dark app, like a netela laid out
   ========================================================================== */

.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: rgba(12, 9, 8, 0.9);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  overflow-y: auto;
}

.modal-card {
  width: 100%;
  max-width: 468px;
  background: var(--cotton);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6);
  animation: cardRise 0.45s var(--ease);
}

@keyframes cardRise {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: translateY(0); }
}

.modal-body {
  position: relative;
  padding: 30px 32px 32px;
  color: var(--ink-900);
}

.modal-mark {
  display: block;
  width: 34px;
  height: 34px;
  color: var(--ink-900);
  margin-bottom: 14px;
}

.modal-eyebrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--taupe);
  margin-bottom: 16px;
}

/* Quiet language toggle on the light card — .btn-lang's proportions, inverted */
.modal-lang {
  position: absolute;
  top: 24px;
  right: 28px;
  padding: 5px 11px;
  border-radius: var(--r-sm);
  background: transparent;
  border: 1px solid rgba(20, 16, 14, 0.18);
  color: var(--ink-900);
  font-size: 0.76rem;
  font-weight: 600;
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}

.modal-lang:hover {
  border-color: var(--green);
  color: var(--green);
}

.modal-card h2 {
  font-family: var(--font-display);
  font-size: 2.1rem;
  font-weight: 500;
  line-height: 1.12;
  letter-spacing: -0.025em;
  color: var(--ink-900);
  margin-bottom: 12px;
}

.modal-card h2 em {
  font-style: italic;
  color: var(--green);
}

.modal-lede {
  font-size: 0.92rem;
  line-height: 1.6;
  color: var(--taupe-deep);
  margin-bottom: 24px;
}

.rules-list {
  list-style: none;
  border-top: 1px solid rgba(20, 16, 14, 0.1);
  margin-bottom: 22px;
}

.rules-list li {
  display: flex;
  gap: 13px;
  padding: 13px 0;
  border-bottom: 1px solid rgba(20, 16, 14, 0.1);
}

.rule-marker {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex-shrink: 0;
  margin-top: 5px;
}

.marker-red { background: var(--red); }
.marker-gold { background: var(--gold); }
.marker-green { background: var(--green); }

.rules-list strong {
  display: block;
  font-size: 0.87rem;
  font-weight: 600;
  color: var(--ink-900);
  margin-bottom: 2px;
}

.rules-list span {
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--taupe-deep);
}

.age-gate {
  display: flex;
  align-items: flex-start;
  gap: 11px;
  padding: 13px 14px;
  border-radius: var(--r-md);
  background: rgba(224, 168, 46, 0.13);
  cursor: pointer;
  font-size: 0.85rem;
  line-height: 1.45;
  color: var(--ink-900);
  margin-bottom: 16px;
  transition: background 0.2s ease;
}

.age-gate:hover { background: rgba(224, 168, 46, 0.22); }

.age-gate a {
  color: var(--ink-900);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.age-gate a:hover { color: var(--green); }

.age-gate input[type="checkbox"] {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  margin-top: 1px;
  accent-color: var(--green);
  cursor: pointer;
}

.btn-enter {
  width: 100%;
  padding: 15px;
  border-radius: var(--r-md);
  background: var(--ink-900);
  color: var(--cotton);
  font-size: 0.92rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  transition: background 0.2s ease, opacity 0.2s ease, transform 0.15s var(--ease);
}

.btn-enter:hover:not(:disabled) { background: var(--ink-700); }
.btn-enter:active:not(:disabled) { transform: scale(0.99); }

.btn-enter:disabled {
  background: transparent;
  border: 1px solid rgba(20, 16, 14, 0.18);
  color: var(--taupe);
  cursor: not-allowed;
}

/* Hint under the entry button while the age box is unchecked */
.btn-hint {
  margin-top: 8px;
  text-align: center;
  font-size: 0.78rem;
  color: var(--taupe);
}

/* Terms · Privacy, on their own line below the entry button */
.modal-legal {
  margin-top: 14px;
  text-align: center;
  font-size: 0.78rem;
  color: var(--taupe);
}

.modal-legal a {
  color: var(--taupe);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.modal-legal a:hover { color: var(--green); }


/* ==========================================================================
   Report-reason chooser — same cotton card as the entry modal, one tap per
   reason. The "under 18" option is set apart in red because it triggers the
   child-safety path server-side and in the mod queue.
   ========================================================================== */

.report-dialog .modal-card { max-width: 420px; }

.report-dialog .modal-card h2 {
  font-size: 1.5rem;
  margin-bottom: 8px;
}

.report-dialog .modal-lede { margin-bottom: 18px; }

.report-reasons {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-bottom: 16px;
}

.report-reason {
  width: 100%;
  text-align: left;
  padding: 13px 15px;
  border-radius: var(--r-md);
  background: transparent;
  border: 1px solid rgba(20, 16, 14, 0.18);
  color: var(--ink-900);
  font-size: 0.9rem;
  font-weight: 500;
  line-height: 1.35;
  transition: border-color 0.15s var(--ease), background 0.15s var(--ease);
}

.report-reason:hover {
  border-color: var(--gold);
  background: rgba(224, 168, 46, 0.1);
}

.report-reason-minor {
  border-color: var(--red);
  color: var(--red);
  font-weight: 600;
}

.report-reason-minor:hover {
  border-color: var(--red);
  background: rgba(192, 57, 43, 0.1);
}

.btn-report-cancel {
  display: block;
  width: 100%;
  padding: 11px;
  border-radius: var(--r-md);
  background: transparent;
  border: 1px solid rgba(20, 16, 14, 0.14);
  color: var(--taupe);
  font-size: 0.84rem;
  font-weight: 600;
  transition: color 0.15s var(--ease), border-color 0.15s var(--ease);
}

.btn-report-cancel:hover {
  color: var(--ink-900);
  border-color: rgba(20, 16, 14, 0.32);
}


/* ==========================================================================
   Policy pages — terms.html, privacy.html, safety.html
   Cotton card on the dark ground, same treatment as the entry modal. The
   page body scrolls (the app shell does not), so .doc-body opts back out of
   the global height/overflow lock.
   ========================================================================== */

.doc-body {
  height: auto;
  min-height: 100dvh;
  overflow-y: auto;
  padding: 44px 20px 64px;
}

.doc-page {
  max-width: 720px;
  margin: 0 auto;
  background: var(--cotton);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: 0 30px 70px rgba(0, 0, 0, 0.6);
}

.doc-inner {
  padding: 34px 38px 42px;
  color: var(--ink-900);
}

.doc-back {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  min-width: 0;
  gap: 7px;
  margin-bottom: 22px;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--taupe);
  text-decoration: none;
  transition: color 0.2s var(--ease);
}

.doc-back:hover { color: var(--green); }

.doc-toolbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  margin-bottom: 22px;
}

.doc-toolbar .doc-back { margin-bottom: 0; }

.doc-lang {
  flex: 0 0 auto;
  padding: 6px 10px;
  border: 1px solid rgba(20, 16, 14, 0.2);
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--taupe-deep);
  font: 600 0.72rem var(--font-ui);
  cursor: pointer;
}

.doc-lang:hover { border-color: var(--green); color: var(--green); }

.doc-eyebrow {
  display: block;
  margin-bottom: 12px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--taupe);
}

.doc-page h1 {
  font-family: var(--font-display);
  font-size: 2rem;
  font-weight: 500;
  line-height: 1.14;
  letter-spacing: -0.025em;
  color: var(--ink-900);
  margin-bottom: 4px;
}

.doc-sub {
  font-family: var(--font-display);
  font-size: 1rem;
  color: var(--taupe-deep);
  margin-bottom: 16px;
}

.doc-meta {
  padding-bottom: 20px;
  margin-bottom: 24px;
  border-bottom: 1px solid rgba(20, 16, 14, 0.1);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--taupe);
}

.doc-page h2 {
  font-family: var(--font-display);
  font-size: 1.16rem;
  font-weight: 600;
  color: var(--ink-900);
  margin: 26px 0 8px;
}

.doc-page p,
.doc-page li {
  font-size: 0.9rem;
  line-height: 1.65;
  color: var(--taupe-deep);
  /* Long unbreakable tokens (the contact emails, the version string) must not
     be able to force the card wider than a narrow screen. */
  overflow-wrap: break-word;
}

.doc-page p { margin-bottom: 12px; }
.doc-page ul { margin: 0 0 12px 20px; }
.doc-page li { margin-bottom: 6px; }
.doc-page strong { color: var(--ink-900); }

.doc-page a {
  color: var(--ink-900);
  text-decoration: underline;
  text-underline-offset: 2px;
  overflow-wrap: break-word;
}

.doc-page a:hover { color: var(--green); }

.doc-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  padding-top: 20px;
  margin-top: 26px;
  border-top: 1px solid rgba(20, 16, 14, 0.1);
  font-size: 0.82rem;
  font-weight: 600;
}

.doc-links a[aria-current="page"] { color: var(--taupe); }

.system-entry a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

@media (max-width: 620px) {
  .doc-body { padding: 24px 12px 44px; }
  .doc-inner { padding: 26px 22px 30px; }
  .doc-page h1 { font-size: 1.62rem; }
}


/* ==========================================================================
   Narrow screens
   ========================================================================== */

/* ==========================================================================
   Phone layout  (<=900px)
   A full-bleed two-feed split with every control floating over the video.
   Independent of [data-layout] — PiP / Split / Theater are gated to >=901px,
   so nothing here is fighting them. Every rule below is boxes, positions and
   classes only: no <video>, no srcObject, no peer connection is ever touched,
   so a stream plays straight through open/close, rotate and every state.
   ========================================================================== */

@media (max-width: 900px) {
  .app-shell {
    overflow: hidden;
    /* Tall enough to read a real conversation while leaving the live call
       visibly present above it. The cap protects the top actions on short
       phones and browser-chrome-heavy viewports. */
    --sheet: min(68dvh, calc(100dvh - 112px));
  }

  /* --- No header, no band above the video --------------------------------- */
  /* The header box is gone; three of its dock items live on as floating
     controls (status pill, Data Saver, data counter). The rest — wordmark,
     layout switch, connection-quality chip, language toggle — are dropped. */
  .app-header,
  .header-dock { display: contents; }
  .wordmark,
  .band-header,
  .header-dock > *:not(#status):not(#dataSaverToggle):not(#dataCounter) { display: none; }

  /* --- Two feeds: edge to edge, remote on top, local below, cover --------- */
  .app-main {
    position: relative;
    display: block;
    flex: 1;
    padding: 0;
    gap: 0;
    overflow: hidden;
  }
  .stage,
  .stage-frame {
    position: absolute;
    inset: 0;
    flex: none;
    height: auto;
    border-radius: 0;
  }
  .stage-frame { overflow: hidden; }

  #remoteVideo {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 50%;
    border-radius: 0;
    object-fit: cover;
  }
  .self-pip {
    position: absolute;
    top: 50%;
    left: 0;
    right: auto;
    bottom: auto;
    width: 100%;
    height: 50%;
    z-index: 1;
    margin: 0;
    border-radius: 0;
    background: #000;
    box-shadow: none;
    transform: none;
    transition: none;
  }
  .self-pip:hover { transform: none; }
  .self-pip > video { width: 100%; height: 100%; object-fit: cover; } /* mirror kept from the base rule */

  /* The woven band survives only as a hairline seam between the two feeds. */
  .band-stage {
    position: absolute;
    left: 0;
    right: 0;
    top: calc(50% - var(--band-h) / 2);
    height: var(--band-h);
    border-radius: 0;
    opacity: 0.8;
    z-index: 5;
  }

  /* No feed badges; no desktop-only PiP chrome. */
  .feed-tag,
  .pip-resize,
  #swapVideosBtn { display: none; }

  /* Idle / connecting art fills the remote feed only, so the self-view
     stays live while matching. */
  .stage-idle { inset: 0 0 50% 0; padding: 0 24px; gap: 20px; }

  /* Searching and connecting are deliberately louder than the resting state.
     The same sini and steam motif stays centred over the remote half; only
     state attributes select the copy and animation. */
  .stage-idle[data-match-state] {
    gap: 24px;
    background: var(--ink-800);
  }
  .stage-idle[data-match-state] .stage-idle-default { display: none; }
  .stage-idle[data-match-state="searching"] .stage-state-searching,
  .stage-idle[data-match-state="connecting"] .stage-state-connecting,
  .stage-idle[data-match-state="no-match"] .stage-state-no-match,
  .stage-idle[data-match-state="no-match"] .stage-state-no-match-note,
  .stage-idle[data-match-state="searching"][data-quiet="true"] .stage-state-quiet {
    display: block;
  }
  .stage-idle[data-match-state] .sini-mark {
    width: 96px;
    height: 96px;
  }
  .stage-idle[data-match-state] .sini {
    width: 84px;
    height: 84px;
  }
  .stage-idle[data-match-state] .steam {
    bottom: 51px;
    width: 3px;
    height: 24px;
  }
  .stage-idle[data-match-state] .steam-1 { left: 37px; }
  .stage-idle[data-match-state] .steam-2 { left: 47px; height: 31px; }
  .stage-idle[data-match-state] .steam-3 { left: 58px; }
  .stage-idle[data-match-state] .stage-state-text {
    max-width: min(82vw, 340px);
    color: var(--cotton);
    font-size: clamp(1.18rem, 5.4vw, 1.48rem);
    font-weight: 700;
    line-height: 1.25;
    letter-spacing: -0.015em;
  }
  .stage-idle[data-match-state] .stage-idle-note {
    max-width: min(82vw, 340px);
    color: var(--taupe);
    font-size: 0.9rem;
    line-height: 1.45;
  }
  .stage-idle[data-match-state="no-match"] .sini-mark,
  .stage-idle[data-match-state="no-match"] .steam {
    animation: none;
  }
  .stage-idle[data-match-state="no-match"] .steam { opacity: 0.45; }
  .stage-idle[data-match-state="searching"] .sini-mark {
    animation: siniSearching 1.7s ease-in-out infinite;
  }
  .stage-idle[data-match-state="searching"] .steam { animation-duration: 1.8s; }
  .stage-idle[data-match-state="connecting"] .sini-mark {
    animation: siniConnecting 0.9s ease-in-out infinite alternate;
  }
  .stage-idle[data-match-state="connecting"] .steam { animation-duration: 1.15s; }

  @keyframes siniSearching {
    0%, 100% { transform: translateY(3px); }
    50% { transform: translateY(-5px); }
  }

  @keyframes siniConnecting {
    from { transform: scale(0.96); }
    to { transform: scale(1.04); }
  }

  /* --- Floating controls: scrim + shadow + >=44px target ----------------- */
  .chat-edge-toggle,
  .pip-dock,
  #nextBtn,
  #reportBtn,
  #dataSaverToggle,
  #dataCounter,
  #status {
    position: fixed;
    z-index: 40;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5);
  }

  /* Bottom edge, left to right: chat toggle, mic, camera, Data Saver — one
     horizontal row so the left side stays free for the message column. Report
     stays bottom-right, well clear of the row's right end. */
  .chat-edge-toggle {
    display: flex;
    align-items: center;
    justify-content: center;
    left: 12px;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    width: 44px;
    height: 44px;
    border: none;
    border-radius: 50%;
    background: rgba(20, 16, 14, 0.82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--gold);
    font-size: 1.05rem;
    transition: bottom 0.3s var(--ease);
  }
  .pip-dock {
    left: 64px;                   /* after the 44px chat toggle + 8px */
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    right: auto;
    top: auto;
    flex-direction: row;          /* mic, camera — left to right */
    gap: 8px;
    transition: bottom 0.3s var(--ease);
  }
  .pip-btn { width: 44px; height: 44px; font-size: 0.95rem; }

  /* Data Saver: compact icon toggle, last in the row. The "Data Saver" text
     label is collapsed to nothing (kept for the a11y name); the glyph and the
     gold aria-pressed fill carry the state. */
  #dataSaverToggle {
    left: 168px;                  /* after chat (44) + gap + pip-dock (96) + gap */
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    width: 44px;
    height: 44px;
    padding: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: 50%;
    border: 1px solid var(--ink-600);
    background: rgba(20, 16, 14, 0.82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    font-size: 0;
    transition: bottom 0.3s var(--ease), background 0.2s ease, border-color 0.2s ease;
  }
  #dataSaverToggle::before { content: "📉"; font-size: 1.05rem; }
  #dataSaverToggle[aria-pressed="true"] {
    background: var(--gold);
    border-color: var(--gold);
  }

  /* Bottom-right: Report. Never hidden, never in an overflow, always during a call. */
  #reportBtn {
    right: 12px;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    height: 44px;
    padding: 0 15px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    max-width: 42vw;
    background: rgba(20, 16, 14, 0.82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    border: 1px solid var(--red);
    color: var(--red);
    white-space: nowrap;
    transition: bottom 0.3s var(--ease);
  }
  #reportBtn:disabled { opacity: 0.4; }

  /* Top-right: Start / Next. */
  #nextBtn {
    top: calc(12px + env(safe-area-inset-top, 0px));
    right: 12px;
    flex: none;
    height: 44px;
    padding: 0 18px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 0.85rem;
  }

  /* Data counter: a quiet top-left read-out. client.js toggles its [hidden]
     attribute so it is present only during a connected call and gone on an
     idle or searching screen. Non-interactive — never eats a tap. */
  #dataCounter {
    top: calc(12px + env(safe-area-inset-top, 0px));
    left: 12px;
    padding: 4px 9px;
    border-radius: var(--r-sm);
    background: rgba(20, 16, 14, 0.82);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    color: var(--cotton-dim);
    font-size: 0.62rem;
    pointer-events: none;
  }

  /* .compose-actions was only a desktop row wrapper; its two buttons float now. */
  .compose-actions { display: contents; }

  /* --- Status: a small centred pill, shown only when there's something to say - */
  #status {
    top: calc(14px + env(safe-area-inset-top, 0px));
    left: 50%;
    transform: translateX(-50%);
    display: none;
    max-width: calc(100vw - 24px);
    padding: 6px 14px;
    font-size: 0.66rem;
    white-space: normal;
    text-align: center;
    line-height: 1.3;
    pointer-events: none;
    z-index: 45;
  }
  #status.searching,
  #status.quiet,
  #status.stopped,
  #status.blocked { display: flex; }

  /* --- Chat: hidden by default; slides up over the local feed only -------- */
  .panel {
    position: fixed;
    left: 0;
    right: 0;
    top: 100dvh;
    height: var(--sheet);
    width: auto;
    flex: none;
    min-height: 0;
    border: 1px solid rgba(244, 235, 224, 0.1);
    border-bottom: 0;
    border-radius: var(--r-xl) var(--r-xl) 0 0;
    background: rgba(28, 22, 19, 0.98);
    z-index: 30;
    transition: top 0.3s var(--ease), left 0.3s var(--ease);
  }
  .app-shell[data-chat="open"] .panel { top: calc(100dvh - var(--sheet)); }

  /* The history sheet has its own compact action header. Report remains
     exposed there, while the chat toggle becomes an unambiguous close button.
     Start / Next stays in the live-video strip above the sheet. */
  .app-shell[data-chat="open"] #reportBtn {
    top: calc(100dvh - var(--sheet) + 16px);
    right: 64px;
    bottom: auto;
  }
  .app-shell[data-chat="open"] .chat-edge-toggle {
    top: calc(100dvh - var(--sheet) + 16px);
    right: 12px;
    bottom: auto;
    left: auto;
    background: transparent;
    box-shadow: none;
    color: var(--cotton-dim);
  }
  .app-shell[data-chat="open"] .chat-edge-toggle-icon { font-size: 0; }
  .app-shell[data-chat="open"] .chat-edge-toggle-icon::before {
    content: "\00d7";
    font-size: 1.6rem;
    font-weight: 400;
    line-height: 1;
  }

  /* The history view is intentionally quiet: Start / Next and Report cover
     the two consequential actions, while capture utilities return on close. */
  .app-shell[data-chat="open"] .pip-dock,
  .app-shell[data-chat="open"] #dataSaverToggle { display: none; }

  .panel-head {
    position: relative;
    min-height: 76px;
    align-items: center;
    padding: 24px 148px 12px 16px;
  }
  .panel-head::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 50%;
    width: 38px;
    height: 4px;
    border-radius: 999px;
    background: var(--ink-500);
    transform: translateX(-50%);
  }
  .panel-title {
    color: var(--cotton);
    font-size: 0.78rem;
    letter-spacing: 0.08em;
  }
  .panel-title-am { display: none; }
  .panel-feed {
    padding: 14px 16px;
    gap: 8px;
    overscroll-behavior: contain;
  }
  .feed-bubble { max-width: 80%; }
  .panel-compose { gap: 0; padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px)); }
  .compose-input input {
    min-height: 44px;
    font-size: 16px; /* >=16px keeps iOS from zooming and fighting the keyboard rise */
  }
  #sendBtn { min-height: 44px; }

  /* Entry + report modals keep their existing narrow-screen tuning. */
  .modal-body { padding: 24px 22px 26px; }
  .modal-card h2 { font-size: 1.72rem; }
}

/* --- Landscape phone: feeds side by side, same floating controls ---------
   Gated with min-height: 320px so a keyboard-collapsed portrait viewport
   (~283px tall, which reports orientation:landscape because it is now wider
   than it is tall) cannot trip this block. A genuine landscape phone always
   clears 320px. The typing-state rules further below also override every
   feed rule when data-keyboard="open", so the two guards cannot disagree. */
@media (max-width: 900px) and (orientation: landscape) and (min-height: 320px) {
  #remoteVideo { top: 0; left: 0; width: 50%; height: 100%; }
  .self-pip { top: 0; left: 50%; width: 50%; height: 100%; }
  .stage-idle { inset: 0 50% 0 0; }
  .band-stage {
    top: 0;
    bottom: 0;
    left: calc(50% - var(--band-h) / 2);
    right: auto;
    width: var(--band-h);
    height: auto;
  }

  /* Chat slides in from the right over the local (right) feed; the remote
     feed and the bottom-left cluster stay clear. */
  .panel {
    top: 0;
    height: 100dvh;
    left: 100%;
    transition: left 0.3s var(--ease);
  }
  .app-shell[data-chat="open"] .panel { left: 50%; }

  /* Panel comes in from the right here, so the bottom-left row stays put. */
  .app-shell[data-chat="open"] #nextBtn { right: calc(50% + 12px); }
  .app-shell[data-chat="open"] #reportBtn { top: 16px; right: 64px; bottom: auto; }
  .app-shell[data-chat="open"] .chat-edge-toggle { top: 16px; right: 12px; bottom: auto; left: auto; }
  .app-shell[data-chat="open"] #status { left: 25%; }
}


/* ==========================================================================
   Phone chat — overlay model  (<=900px)

   Resting:   two-feed split (above) + the message column over the lower video.
   Typing:    .app-shell[data-keyboard="open"] — set by client.js from a
              visualViewport height collapse, NOT a media query. The shell is
              pinned to the visible region (position: fixed; top: var(--vvtop);
              height: var(--vvh); + a transform so its position:fixed children
              anchor to it, not the layout viewport). The remote feed fills
              that region; the local feed is hidden; the message input sits
              flush at its bottom edge = flush above the keyboard; Start/Next
              and the mic/camera/Data-Saver cluster are hidden; Report moves
              to the top-right and stays visible. --vvh, --vvtop
              (and --kb, diagnostic only) are written by client.js.
   History:   .app-shell[data-chat="open"] — the scrollable log as the sliding
              sheet defined above; now the secondary view.
   Text-only: .app-shell[data-text-only] — set when video never connects
              (denied camera / no camera / no remote video). Falls back to a
              persistent chat panel; the overlay + bubble model is off.
   ========================================================================== */

@media (max-width: 900px) {
  /* ---- Message column (live-stream style) ----
     A single left-hugging column: every row is marker + pill, left-aligned,
     newest at the bottom, all at full opacity until evicted. Boxed on every
     edge so it can never reach a control:
       bottom  - sits just above the bottom control row (top edge ~56px);
       top     - clears the top row (Start/Next, data counter, status ~56px);
       width   - 70vw, so pills wrap rather than spanning the screen and never
                 reach the right edge;
       overflow:hidden - a row rising past the top edge is clipped, not spilled.
     Same box in the typing state, re-anchored just above the composer. */
  .bubble-layer {
    position: fixed;
    left: 12px;
    top: 64px;
    right: auto;
    width: 70vw;
    max-width: 320px;
    bottom: calc(68px + env(safe-area-inset-bottom, 0px));
    overflow: hidden;
    z-index: 20;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    gap: 6px;
    pointer-events: none;
  }
  .app-shell[data-chat="open"] .bubble-layer,
  .app-shell[data-text-only] .bubble-layer { display: none; }

  /* Typing state: same column, sitting just above the composer (which clears
     the input row plus the safe area); Report is top-right, so top:64px keeps
     the column clear of it. The remote feed shows through above. */
  .app-shell[data-keyboard="open"]:not([data-text-only]) .bubble-layer {
    display: flex;
    bottom: calc(84px + env(safe-area-inset-bottom, 0px));
    z-index: 42;
  }

  /* One pill per row. Bereka has no accounts, so there are no avatars, names,
     initials, presence dots, or neutral markers. Color alone distinguishes the
     user's gold messages from the partner's dark messages. */
  .bubble {
    position: relative;
    max-width: 100%;
    min-width: 0;
    font-size: 0.84rem;
    line-height: 1.4;
    animation: rowIn 0.22s var(--ease);
  }
  /* The pill hugs its text up to the column width; short messages stay short.
     The pill grows in from zero height, which is what shifts older rows up. */
  .bubble-text {
    display: block;
    max-width: 100%;
    max-height: 8rem;                     /* a very long message clips here; full text stays in the log */
    padding: 6px 10px;
    border-radius: var(--r-md);
    border-bottom-left-radius: 2px;
    overflow: hidden;
    overflow-wrap: anywhere;
    animation: pillGrow 0.22s var(--ease);
  }
  /* Received: near-opaque ink scrim, heavy blur, hairline cotton edge and a
     text shadow so it stays legible over arbitrary bright video. */
  .bubble-stranger .bubble-text {
    color: var(--cotton);
    background: rgba(20, 16, 14, 0.92);
    border: 1px solid rgba(244, 235, 224, 0.16);
    -webkit-backdrop-filter: blur(14px);
    backdrop-filter: blur(14px);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.55);
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.55);
  }
  /* Sent: gold fill, dark text. */
  .bubble-user .bubble-text {
    color: var(--ink-900);
    background: rgba(224, 168, 46, 0.92);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
  }
  .bubble-out { animation: rowOut 0.2s var(--ease) forwards; }
  .bubble-out .bubble-text { animation: pillCollapse 0.2s var(--ease) forwards; }

  /* ---- Unread dot on the chat icon ---- */
  .chat-edge-toggle.has-unread::after {
    content: "";
    position: absolute;
    top: 5px;
    right: 5px;
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: var(--gold);
    border: 2px solid var(--ink-900);
  }

  /* ---- Typing state ----
     The layout viewport does not shrink for the keyboard, so pin the whole
     shell to the VISIBLE region: offset it by --vvtop (which tracks any
     browser scroll-into-view) and size it to --vvh. transform makes it the
     containing block for the position:fixed children below, so they anchor to
     this box — the visible region — not the layout viewport; overflow:hidden
     leaves nothing to scroll. */
  .app-shell[data-keyboard="open"]:not([data-text-only]) {
    position: fixed;
    top: var(--vvtop, 0px);
    left: 0;
    right: 0;
    height: var(--vvh, 100dvh);
    overflow: hidden;
    transform: translateZ(0);
  }

  .app-shell[data-keyboard="open"]:not([data-text-only]) .self-pip,
  .app-shell[data-keyboard="open"]:not([data-text-only]) .band-stage,
  .app-shell[data-keyboard="open"]:not([data-text-only]) #nextBtn,
  .app-shell[data-keyboard="open"]:not([data-text-only]) #dataCounter,
  .app-shell[data-keyboard="open"]:not([data-text-only]) .pip-dock,
  .app-shell[data-keyboard="open"]:not([data-text-only]) #dataSaverToggle,
  .app-shell[data-keyboard="open"]:not([data-text-only]) .chat-edge-toggle {
    display: none;
  }

  /* All fixed to the shell (= the visible region) now, not the layout
     viewport. The remote feed fills it; the composer sits flush at its
     bottom edge = flush above the keyboard. <video> is replaced, so give it
     explicit 100%/100% rather than inset + auto. */
  .app-shell[data-keyboard="open"]:not([data-text-only]) #remoteVideo {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .app-shell[data-keyboard="open"]:not([data-text-only]) .stage-idle {
    position: fixed;
    inset: 0;
  }

  .app-shell[data-keyboard="open"]:not([data-text-only]) .panel {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    height: auto;
    max-height: none;
    border: 0;
    border-radius: 0;
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    transform: none;
    transition: none;
    z-index: 41;
  }
  .app-shell[data-keyboard="open"]:not([data-text-only]) .panel-head,
  .app-shell[data-keyboard="open"]:not([data-text-only]) .panel-feed {
    display: none;
  }
  .app-shell[data-keyboard="open"]:not([data-text-only]) .panel-compose {
    background: var(--ink-800);
    border-top: 1px solid var(--ink-600);
  }
  .app-shell[data-keyboard="open"]:not([data-text-only]) #reportBtn {
    position: fixed;
    top: calc(12px + env(safe-area-inset-top, 0px));
    right: 12px;
    bottom: auto;
    transition: none;
  }

  /* ---- Text-only fallback: persistent chat panel ----
     Video never connected, so there is no feed to overlay. The log is the
     product: dock it full-screen with Start/Next + Report as an in-flow
     composer row (as on desktop). Mic/camera move top-left (audio-only calls
     still happen; the camera toggle can still acquire video on demand);
     the seam, chat toggle, Data Saver, data counter and the bubble layer
     are all irrelevant here. */
  .app-shell[data-text-only] .stage,
  .app-shell[data-text-only] .band-stage,
  .app-shell[data-text-only] .chat-edge-toggle,
  .app-shell[data-text-only] #dataSaverToggle,
  .app-shell[data-text-only] #dataCounter {
    display: none;
  }
  .app-shell[data-text-only] .panel {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    height: auto;
    max-height: none;
    transform: none;
    transition: none;
    border-radius: 0;
    z-index: 20;
  }
  /* Keyboard up in text-only: pin the shell to the visible region too, so the
     persistent panel's composer clears the keyboard (the feed just scrolls in
     the smaller space). */
  .app-shell[data-text-only][data-keyboard="open"] {
    position: fixed;
    top: var(--vvtop, 0px);
    left: 0;
    right: 0;
    height: var(--vvh, 100dvh);
    overflow: hidden;
    transform: translateZ(0);
  }
  .app-shell[data-text-only] .pip-dock {
    top: 12px;
    left: 12px;
    bottom: auto;
    flex-direction: row;
  }
  .app-shell[data-text-only] .compose-actions {
    display: flex;
    gap: 8px;
    padding: 0 12px 8px;
  }
  .app-shell[data-text-only] #nextBtn,
  .app-shell[data-text-only] #reportBtn {
    position: static;
    inset: auto;
    top: auto;
    bottom: auto;
    left: auto;
    right: auto;
    flex: 1 1 0;
    max-width: none;
  }
}


/* Stop uses the sixth slot only while there is something to leave. Keep all
   six targets >=44px, with a full 64px Report slot even at 320px. */
@media (max-width: 900px) {
  #stopBtn {
    position: fixed;
    z-index: 40;
    left: 200px;
    bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    width: 44px;
    height: 44px;
    padding: 3px;
    flex-direction: column;
    gap: 2px;
    background: var(--ink-900);
    font-size: 0.62rem;
    line-height: 1.2;
  }
  .app-shell[data-can-stop]:not([data-chat="open"]):not([data-text-only]) .chat-edge-toggle { left: 8px; }
  .app-shell[data-can-stop]:not([data-chat="open"]):not([data-text-only]) .pip-dock { left: 56px; gap: 4px; }
  .app-shell[data-can-stop]:not([data-chat="open"]):not([data-text-only]) #dataSaverToggle { left: 152px; }
  .app-shell[data-can-stop]:not([data-chat="open"]):not([data-text-only]) #reportBtn {
    right: 8px;
    width: 64px;
    padding: 0 4px;
  }
  /* The utility row is absent in the log/typing states. Keep Stop in the
     exposed video corner, clear of the sheet, Report and its close button. */
  .app-shell[data-chat="open"]:not([data-text-only]) #stopBtn,
  .app-shell[data-keyboard="open"]:not([data-text-only]) #stopBtn {
    top: calc(12px + env(safe-area-inset-top, 0px));
    left: 12px;
    bottom: auto;
  }
  .app-shell[data-can-stop][data-chat="open"] #dataCounter {
    top: calc(64px + env(safe-area-inset-top, 0px));
  }
  .app-shell[data-text-only] #stopBtn {
    position: static;
    flex: 0 0 44px;
  }
  .app-shell[data-text-only] #nextBtn,
  .app-shell[data-text-only] #reportBtn { min-width: 0; padding-inline: 6px; }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }

  .stage-idle[data-match-state] .sini-mark,
  .stage-idle[data-match-state] .steam,
  .status-capsule.searching .status-pulse-dot {
    animation: none !important;
  }

  .stage-idle[data-match-state] .sini-mark {
    width: 96px;
    height: 96px;
  }
}
