/* fyzx — Fleet tier design language.
 *
 * Tokens, surfaces, and motion are carried over from dfu-cl's fleet tier:
 * near-black canvas lit by a radial source, layered panels, tight hardware
 * radii, Inter for text and Sora for display, blue accent triad.
 *
 * The house motion rule comes with them, and it is the one thing not to
 * "simplify": wires are always present and thin, traffic is a discrete pulse
 * that fires and stops. That rule was written for bots placing orders and it
 * happens to describe this app exactly — a frame either goes or it does not.
 * Nothing here loops on the traffic layer, so a still screen means a still
 * link rather than a broken animation.
 */

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('./fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('./fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('./fonts/sora-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 300 800;
  font-display: swap;
  src: url('./fonts/sora-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Surfaces — near-black canvas lit by a radial source, layered panels */
  --color-background: #04050a;
  --color-background-near: #12151c;
  --color-slip: #020308;
  --color-surface-0: #080b13;
  --color-surface-1: #0a0d15;
  --color-surface-2: #0c1019;
  --color-surface-3: #11151f;

  --color-foreground: #e7ebf2;
  --color-foreground-secondary: #828b9e;
  --color-foreground-tertiary: #4c5466;

  --color-border: #161a25;
  --color-border-strong: #20262f;

  /* Accent triad: solid / 10% wash / 35% line / 55% glow */
  --color-accent: #3b7bf0;
  --color-accent-bright: #5a93ff;
  --color-accent-muted: rgba(59, 123, 240, 0.1);
  --color-accent-line: rgba(59, 123, 240, 0.35);
  --color-accent-glow: rgba(59, 123, 240, 0.55);

  --color-yes: #29b07a;
  --color-yes-secondary: #3ecf94;
  --color-yes-line: rgba(41, 176, 122, 0.35);

  --color-no: #e2495c;
  --color-no-secondary: #f06a7b;
  --color-no-line: rgba(226, 73, 92, 0.35);

  --color-warning: #f0a742;
  --color-warning-line: rgba(240, 167, 66, 0.35);

  --font-sans: "Inter", system-ui, -apple-system, sans-serif;
  --font-display: "Sora", system-ui, sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;

  /* Tight, hardware-like radii */
  --radius-xs: 3px;
  --radius-sm: 4px;
  --radius-md: 5px;
  --radius-lg: 6px;
  --radius-xl: 10px;

  /* Motion.
   *
   * This is an instrument, so the timings are short and the curves decelerate
   * hard: things arrive and stop rather than drifting into place. Nothing
   * overshoots — a bounce reads as toy, and everything here is meant to read as
   * equipment. Only transform, opacity, filter and shadow are ever animated,
   * which keeps every one of these off the layout thread and means none of it
   * can move a box the fit test measured. */
  --t-tap: 90ms;      /* the finger is still down */
  --t-fast: 150ms;    /* hover, colour, borders */
  --t-base: 240ms;    /* the default for anything that moves */
  --t-slow: 380ms;    /* something entering the screen */
  --t-stage: 560ms;   /* the largest move there is */

  --ease-snappy: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-out: cubic-bezier(0.33, 1, 0.68, 1);
  --ease-glide: cubic-bezier(0.4, 0, 0.1, 1);
  --text-2xs: 0.6875rem;

  color-scheme: dark;
}

* { box-sizing: border-box; }
[hidden] { display: none !important; }
.view-off { display: none !important; }

/* A view arrives; it does not simply appear.
 *
 * The move is small and genuinely three-dimensional: fourteen pixels up, thirty
 * back, and two and a half degrees of tilt about the x-axis, under a 900px
 * perspective. That is enough for the eye to read a plane settling toward the
 * glass rather than a box fading in, and little enough that nobody will ever
 * consciously notice it happened.
 *
 * It replays on every route change with nothing having to trigger it: a hidden
 * section is display:none, and coming back from that restarts the animation. */
@keyframes section-in {
  from {
    opacity: 0;
    transform: perspective(900px) translate3d(0, 14px, -30px) rotateX(2.5deg);
  }
}
.stage > [data-views]:not(.view-off),
.work > [data-views]:not(.view-off) {
  animation: section-in var(--t-slow) var(--ease-snappy) both;
  animation-delay: calc(var(--i, 0) * 42ms);
  transform-origin: 50% 0;
}

/* The stage takes a step back while the drawer is over it. A layer transform,
   so the compositor scales what it already rasterised rather than repainting a
   live camera frame by frame. */
.stage {
  transition: transform var(--t-base) var(--ease-snappy);
  transform-origin: 50% 42%;
}
body.drawer-open .stage { transform: scale(0.985) translateX(8px); }

/* The menu unpacks itself, top to bottom. 26ms apart is below the point where
   it reads as a list being drawn and above the one where it reads as
   everything landing at once. */
@keyframes nav-in {
  from { opacity: 0; transform: translate3d(-12px, 0, 0); }
}
.drawer-panel > * {
  animation: nav-in var(--t-base) var(--ease-snappy) both;
  animation-delay: calc(60ms + var(--i, 0) * 26ms);
}

/* ── The layout rules ──────────────────────────────────────────────────────
 *
 * Four of them, and the rest follows:
 *
 *   1. The page never scrolls. The app is a fixed-height shell.
 *   2. Exactly one region per view scrolls, and only when its content
 *      genuinely cannot fit — a settings list, a frame lab. Never the page.
 *   3. The optical panes are the subject. They take whatever height is left
 *      after the chrome and stay square.
 *   4. One column on a phone; two panes from 900px, where two things of equal
 *      weight sit side by side instead of one pushing the other off screen.
 */

html {
  height: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  font-family: var(--font-sans);
  font-size: 13px;
  line-height: 1.5;
  letter-spacing: -0.012em;
  background-color: var(--color-background);
  background-image: radial-gradient(130% 130% at 32% 22%, var(--color-background-near) 0%, var(--color-background) 74%);
  background-attachment: fixed;
  color: var(--color-foreground);
  overscroll-behavior-y: none;
}

h1, h2 { margin: 0; font-family: var(--font-display); letter-spacing: -0.03em; font-weight: 600; }
h1 { font-size: 15px; }
h2 { font-size: 15px; }

.num { font-variant-numeric: tabular-nums; }

/* Uppercase wide-tracked micro-label */
.eyebrow {
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--color-foreground-tertiary);
}

:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--color-accent-line);
  outline-offset: 2px;
  animation: focus-close var(--t-base) var(--ease-snappy) both;
}
/* It closes in on what it is marking, which points the eye at the target
   instead of just decorating it. */
@keyframes focus-close {
  from { outline-color: transparent; outline-offset: 7px; }
}

* { scrollbar-width: thin; scrollbar-color: var(--color-surface-3) transparent; }
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #1d2433; border-radius: 2px; }
::-webkit-scrollbar-thumb:hover { background: #2a3346; }

/* ── The lit backdrop ───────────────────────────────────────────────────────
   A band of hairlines at the top of the page, faded out through a radial mask
   so it never reaches the content below. The halo sits under the lattice and
   gives the band a light source. Deliberately near-invisible: at 1.6% white it
   reads as surface texture rather than as a grid you are meant to look at. */

.bg {
  position: fixed;
  inset: 0 0 auto 0;
  height: 62vh;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  mask-image: radial-gradient(132% 100% at 62% -8%, #000 22%, transparent 94%);
  -webkit-mask-image: radial-gradient(132% 100% at 62% -8%, #000 22%, transparent 94%);
}
.bg-halo {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(46% 60% at 72% -10%, var(--color-accent-muted), transparent 70%);
}
.bg-rules {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(to right, rgba(255, 255, 255, 0.016) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(255, 255, 255, 0.016) 1px, transparent 1px);
  background-size: 48px 48px;
}

/* ── Elevation: recessed slip → well → raised card ─────────────────────── */

.card {
  position: relative;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.022) 0, rgba(255, 255, 255, 0) 88px),
    var(--color-surface-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.04),
    0 1px 2px rgba(0, 0, 0, 0.4),
    0 14px 30px -20px rgba(0, 0, 0, 0.7);
  transition: border-color 0.16s ease, box-shadow 0.2s ease;
}

.well {
  background: var(--color-surface-0);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.45);
}

.slip {
  background: var(--color-slip);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow:
    inset 0 4px 12px rgba(0, 0, 0, 0.85),
    inset 0 1px 2px rgba(0, 0, 0, 0.7),
    0 1px 0 rgba(255, 255, 255, 0.04);
}

/* A hairline that fades to nothing at both ends. It separates without drawing
   a box around anything, which is why almost nothing below is boxed. */
.rule {
  height: 1px;
  border: 0;
  margin: 0;
  background: linear-gradient(
    90deg,
    transparent 0%,
    var(--color-border-strong) 22%,
    var(--color-border-strong) 78%,
    transparent 100%
  );
}

/* ── App bar ───────────────────────────────────────────────────────────── */

.bar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  height: calc(52px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) clamp(16px, 4vw, 28px) 0;
  background: color-mix(in srgb, var(--color-background) 85%, transparent);
  backdrop-filter: blur(24px);
  border-bottom: 1px solid var(--color-border);
}

.brand { display: flex; align-items: baseline; gap: 9px; min-width: 0; }
.status { display: flex; align-items: center; gap: 6px; margin-left: auto; }
.view-title {
  font-size: 13px; color: var(--color-foreground-tertiary);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.view-title::before { content: "/ "; }
body[data-view="lander"] .view-title { display: none; }
@media (max-width: 520px) { .brand .name { display: none; } .view-title::before { content: none; } }
.brand .mark {
  width: 15px; height: 15px; border-radius: var(--radius-xs);
  background: var(--color-accent);
  box-shadow: inset 0 0 0 3px var(--color-background), inset 0 0 0 5px var(--color-accent), 0 0 12px -2px var(--color-accent-glow);
}
.brand .name { font-family: var(--font-display); font-weight: 600; font-size: 15px; letter-spacing: -0.03em; }
.brand .proto {
  font-family: var(--font-mono); font-size: var(--text-2xs); color: var(--color-foreground-tertiary);
  border: 1px solid var(--color-border-strong); border-radius: var(--radius-xs); padding: 1px 5px;
}

.ident { display: flex; align-items: center; gap: 6px; }
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono); font-size: var(--text-2xs); font-weight: 500;
  color: var(--color-foreground-secondary);
  background: var(--color-surface-3);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-xs); padding: 3px 7px;
}
.dot {
  width: 5px; height: 5px; border-radius: 50%; flex: none;
  background: var(--color-foreground-tertiary);
}
/* Only a genuinely live link gets the live dot. Blinking for a peer we have
   merely seen once would make the signal a lie. */
.badge.live { color: var(--color-yes-secondary); border-color: var(--color-yes-line); }
.badge.mono { font-family: var(--font-mono); }
.badge.live .dot { background: var(--color-yes); box-shadow: 0 0 6px var(--color-yes-line); animation: blink 1.6s ease-in-out infinite; }
.badge.stale { color: var(--color-warning); border-color: var(--color-warning-line); }
.badge.stale .dot { background: var(--color-warning); box-shadow: 0 0 6px var(--color-warning-line); }

@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }


/* ── Controls ──────────────────────────────────────────────────────────────
   Two sizes, three intents, nothing else. The header previously mixed a
   button with two badges, which is why it read as three unrelated things
   crammed together. */

.icon-btn {
  appearance: none; display: grid; place-items: center;
  width: 32px; height: 32px; flex: none;
  background: transparent; color: var(--color-foreground-secondary);
  border: 1px solid transparent; border-radius: var(--radius-sm);
  cursor: pointer; transition: color 0.14s ease, background-color 0.14s ease;
}
.icon-btn svg { width: 18px; height: 18px; }
@media (hover: hover) {
  .icon-btn:hover { color: var(--color-foreground); background: var(--color-surface-3); }
}
.icon-btn[aria-expanded="true"] { color: var(--color-foreground); background: var(--color-surface-3); }

/* ── Drawer ────────────────────────────────────────────────────────────── */

.drawer { position: fixed; inset: 0; z-index: 45; }
.drawer[hidden] { display: none; }
.drawer-scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--color-background) 70%, transparent);
  backdrop-filter: blur(3px);
  animation: fade-in 160ms ease both;
}
.drawer-panel {
  position: absolute; inset: 0 auto 0 0;
  width: min(280px, 82vw);
  display: flex; flex-direction: column; gap: 2px;
  padding: 12px;
  padding-top: calc(12px + env(safe-area-inset-top));
  background: var(--color-surface-1);
  border-right: 1px solid var(--color-border-strong);
  box-shadow: 24px 0 60px -30px rgba(0, 0, 0, 0.9);
  overflow-y: auto;
  animation: slide-in 200ms var(--ease-snappy) both;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes slide-in { from { transform: translateX(-14px); opacity: 0; } to { transform: none; opacity: 1; } }

.nav-item {
  appearance: none; display: flex; align-items: center; gap: 9px;
  padding: 9px 10px; border: 0; border-radius: var(--radius-sm);
  background: transparent; color: var(--color-foreground-secondary);
  font: inherit; font-size: 13px; text-align: left; text-decoration: none;
  cursor: pointer;
  transition:
    color var(--t-fast) var(--ease-out),
    background-color var(--t-fast) var(--ease-out),
    transform var(--t-fast) var(--ease-out);
}
@media (hover: hover) {
  .nav-item:hover {
    color: var(--color-foreground);
    background: var(--color-surface-3);
    transform: translateX(2px);
  }
}
.nav-item:active { transform: translateX(2px) scale(0.99); }
.nav-item[aria-current="page"] {
  color: var(--color-foreground);
  background: var(--color-accent-muted);
  box-shadow: inset 2px 0 0 var(--color-accent);
}
.nav-home {
  display: flex; align-items: center; gap: 9px;
  padding: 6px 10px 14px; text-decoration: none;
  font-family: var(--font-display); font-size: 15px; font-weight: 600;
  letter-spacing: -0.03em; color: var(--color-foreground);
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 6px;
}
.nav-home .mark {
  width: 14px; height: 14px; flex: none; border-radius: var(--radius-xs);
  background: var(--color-accent);
  box-shadow: inset 0 0 0 3px var(--color-surface-1), inset 0 0 0 5px var(--color-accent);
}
.nav-label:first-of-type { margin-top: 2px; }
.nav-label {
  margin: 14px 0 2px; padding: 0 10px;
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.18em; color: var(--color-foreground-tertiary);
}

/* ── Lander ────────────────────────────────────────────────────────────── */

.lander-block { display: flex; flex-direction: column; gap: 10px; margin-top: 34px; }
.lander-block > h2 { font-size: 15px; }
.lander-block .uses { margin-top: 4px; }
a.use { text-decoration: none; }

/* ── Segmented control ─────────────────────────────────────────────────── */

.segmented {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: 2px; padding: 2px;
  background: var(--color-surface-0);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
}
.mode {
  appearance: none; background: none; border: 0;
  font: inherit; font-size: 12px; font-weight: 500;
  color: var(--color-foreground-secondary);
  padding: 6px 8px; border-radius: var(--radius-sm);
  cursor: pointer; white-space: nowrap;
  transition: color 0.14s ease, background-color 0.14s ease;
}
@media (hover: hover) {
  .mode:hover { color: var(--color-foreground); }
}
.mode[aria-selected="true"] {
  color: var(--color-foreground);
  background: var(--color-surface-3);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
}


/* ── Paired banner ─────────────────────────────────────────────────────── */

.banner {
  display: flex; align-items: center; gap: 10px;
  margin: 0 0 20px;
  padding: 9px 12px;
  background: var(--color-accent-muted);
  border: 1px solid var(--color-accent-line);
  border-radius: var(--radius-md);
  color: var(--color-foreground);
  position: relative; z-index: 1;
}
.banner .dot { background: var(--color-accent); box-shadow: 0 0 6px var(--color-accent-glow); }
.banner span { flex: 1; min-width: 0; }
.banner b { font-variant-numeric: tabular-nums; }

/* ── Welcome ───────────────────────────────────────────────────────────────
   Shown once, on a first open. Four things the tool is actually for, and every
   one of them is a door rather than a picture: the tile starts the flow it
   describes. A device that arrived from a pairing link never sees this — it
   already has a job. */

.welcome {
  position: fixed; inset: 0; z-index: 60;
  display: grid; place-items: center;
  padding: clamp(12px, 4vw, 32px);
  background: color-mix(in srgb, var(--color-background) 88%, transparent);
  backdrop-filter: blur(24px);
  overflow-y: auto;
  animation: welcome-in 260ms var(--ease-snappy) both;
}
@keyframes welcome-in { from { opacity: 0; } to { opacity: 1; } }

.welcome-panel {
  width: min(680px, 100%);
  padding: clamp(20px, 4vw, 28px);
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.022) 0, rgba(255, 255, 255, 0) 130px),
    var(--color-surface-2);
  border: 1px solid var(--color-border-strong);
  border-radius: 14px;
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.08),
    0 0 0 1px rgba(0, 0, 0, 0.6),
    0 28px 70px -24px rgba(0, 0, 0, 0.9);
  animation: welcome-rise 320ms var(--ease-snappy) both;
}
@keyframes welcome-rise {
  from { transform: translateY(10px) scale(0.99); opacity: 0; }
  to { transform: none; opacity: 1; }
}
.welcome-panel:focus { outline: none; }
.welcome-panel h2 { font-size: clamp(17px, 3vw, 20px); }
.welcome-panel .lede { margin-top: 6px; font-size: 13px; }

.uses {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 20px;
}

.use {
  appearance: none; font: inherit; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 8px;
  padding: 15px;
  background: var(--color-surface-0);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  color: var(--color-foreground);
  transition: border-color 0.16s ease, background-color 0.16s ease, transform 0.16s var(--ease-snappy);
}
@media (hover: hover) {
  .use:hover {
    border-color: var(--color-accent-line);
    background: var(--color-surface-3);
    transform: translateY(-1px);
  }
}
.use:active { transform: none; }
.use svg { width: 44px; height: 30px; color: var(--color-accent-bright); }
.use h3 { margin: 0; font-family: var(--font-display); font-size: 14px; font-weight: 600; letter-spacing: -0.02em; }
.use p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--color-foreground-secondary); }

/* One quiet way out, centred under the whole grid rather than under a column. */
.dismiss {
  display: block; margin: 18px auto 0; padding: 4px 8px;
  appearance: none; background: none; border: 0;
  font: inherit; font-size: 11px; letter-spacing: 0.01em;
  color: var(--color-foreground-tertiary); cursor: pointer;
  transition: color 0.14s ease;
}
@media (hover: hover) {
  .dismiss:hover { color: var(--color-foreground-secondary); text-decoration: underline; text-underline-offset: 3px; }
}

/* ── Welcome tabs ──────────────────────────────────────────────────────── */

.wtabs {
  display: flex; gap: 2px; margin-top: 16px;
  border-bottom: 1px solid var(--color-border);
}
.wtab {
  position: relative; appearance: none; background: none; border: 0;
  font: inherit; font-size: 12px; font-weight: 500;
  color: var(--color-foreground-secondary);
  padding: 7px 12px; cursor: pointer;
  transition: color 0.15s ease;
}
@media (hover: hover) {
  .wtab:hover { color: var(--color-foreground); }
}
.wtab[aria-selected="true"] { color: var(--color-foreground); }
.wtab[aria-selected="true"]::after {
  content: ''; position: absolute; bottom: -1px; left: 6px; right: 6px;
  height: 2px; border-radius: 1px; background: var(--color-accent);
}
.uses[data-pane] { margin-top: 14px; }

/* ── Workspaces ────────────────────────────────────────────────────────── */


/* A workspace is a point of view on the same link, not a different app. Which
   sections belong to which view is declared on the markup itself. */
.room { display: flex; flex-direction: column; gap: 14px; }
.room-grid { display: grid; gap: 12px; min-width: 0; }
.room-grid > * { min-width: 0; }
@media (min-width: 640px) { .room-grid.two { grid-template-columns: 1fr 1fr; } }
.warn-box {
  padding: 10px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--color-warning-line);
  color: var(--color-warning); font-size: 12px; line-height: 1.55;
}
.mono-out {
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-secondary);
  word-break: break-all; overflow-wrap: anywhere; min-width: 0; margin: 0;
}
textarea {
  appearance: none; font: inherit; font-family: var(--font-mono); font-size: 12px;
  width: 100%; min-height: 90px; resize: vertical;
  background: rgba(0, 0, 0, 0.35); color: var(--color-foreground);
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  padding: 8px 9px;
}
textarea:focus { outline: none; border-color: var(--color-accent-line); }
input[type="password"], input[type="number"] {
  appearance: none; font: inherit; font-size: 13px; width: 100%;
  background: rgba(0, 0, 0, 0.35); color: var(--color-foreground);
  border: 1px solid var(--color-border); border-radius: var(--radius-sm);
  padding: 7px 9px;
}
input[type="password"]:focus, input[type="number"]:focus { outline: none; border-color: var(--color-accent-line); }

/* ── Pairing ───────────────────────────────────────────────────────────── */

.pairing {
  position: fixed; inset: 0; z-index: 40;
  display: grid; place-items: center;
  padding: clamp(16px, 4vw, 32px);
  background: color-mix(in srgb, var(--color-background) 86%, transparent);
  backdrop-filter: blur(24px);
  overflow-y: auto;
}
.pair-panel {
  width: min(420px, 100%);
  padding: 16px;
  background:
    linear-gradient(180deg, rgba(255, 255, 255, 0.022) 0, rgba(255, 255, 255, 0) 130px),
    var(--color-surface-2);
  border: 1px solid var(--color-border-strong);
  border-radius: 14px;
  box-shadow:
    inset 0 1px 0 0 rgba(255, 255, 255, 0.08),
    0 0 0 1px rgba(0, 0, 0, 0.6),
    0 28px 70px -24px rgba(0, 0, 0, 0.9);
}
/* The pairing code is read by a phone's own camera app from across a desk, so
   it is deliberately small and low-density — nothing like the data frames. */
/* The code scales with the height it has, so a short phone still gets a
   square big enough to scan rather than a cropped page. */
.plate.pair-plate { max-width: min(240px, 34vh); margin: 0 auto; padding: 10px; }
.pair-panel .plate.pair-plate { margin-top: 16px; }

/* Every code canvas needs a CSS box. Without one it lays out at its bitmap
   size and pushes the page sideways — the same bug twice now, so they are
   listed together rather than fixed one at a time. */
#qr-pair, #qr-pair-inline, #cx-qr, #cx-ur-qr {
  width: 100%; height: 100%; display: block; image-rendering: pixelated;
}

.steps {
  margin: 16px 0 0; padding: 0 0 0 18px;
  color: var(--color-foreground-secondary);
  display: flex; flex-direction: column; gap: 6px;
}
.steps li::marker { color: var(--color-foreground-tertiary); font-variant-numeric: tabular-nums; }
.steps b { color: var(--color-foreground); font-weight: 600; }

.pair-url {
  margin: 14px 0 0;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-tertiary);
  word-break: break-all;
}
.pair-warn {
  margin-top: 10px; padding: 8px 10px;
  color: var(--color-warning);
  border: 1px solid var(--color-warning-line);
  border-radius: var(--radius-sm);
}

/* ── Stage ─────────────────────────────────────────────────────────────── */

/* Rule 1: the shell fills the window and nothing below it grows the page. */
.stage {
  position: relative;
  z-index: 1;
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: 980px;
  margin: 0 auto;
  padding: 18px clamp(14px, 3vw, 24px) calc(16px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 14px;
  overflow: hidden;
}

/* Rule 2: a view that genuinely cannot fit scrolls inside itself. */
body[data-view="vault"] .stage,
body[data-view="settings"] .stage,
body[data-view="keys"] .stage,
body[data-view="deaddrop"] .stage { overflow-y: auto; }

/* Which sections belong to which destination is declared on the markup and
   applied by the router. Phase now only describes what the link is doing. */

/* Mid-transfer, the drop zone is an invitation to start a second one. */
body[data-phase="active"] #drop { display: none; }

.work { display: flex; flex-direction: column; gap: 14px; }
.work > .section { flex: none; }
body[data-view="send"] .work,
body[data-view="share"] .work,
body[data-view="messages"] .work { flex: 1; min-height: 0; }

/* .work > .section is flex: none, so a view that wants to fill the stage has to
   say so at the same specificity. The thread then takes the height nothing else
   claimed, which is what puts the composer on the bottom edge rather than
   halfway up a desktop window. */
body[data-view="messages"] .work > .msg-view { flex: 1; min-height: 0; }

/* Rule 3. The panes are the subject: they take the height nothing else
   claimed, and the code inside scales to fit rather than to a fixed size. */
body[data-view="send"] .instruments,
body[data-view="share"] .instruments { flex: 1; min-height: 0; }
.card.panel { min-height: 0; display: flex; flex-direction: column; gap: 12px; }
/* Fitting a square into a flexible box.
 *
 * aspect-ratio alone cannot do it: flex fixes the height, max-width clamps the
 * width, and the ratio has no way to push back — the box came out 434x557.
 * The container is sized by flex and the code inside is sized against it, so
 * min(100cqw, 100cqh) is exactly "as big as it can be while staying square". */
body[data-view="send"] .display .plate,
body[data-view="share"] .display .plate {
  flex: 1 1 auto; min-height: 0;
  container-type: size;
  display: grid; place-items: center;
  aspect-ratio: auto;
  width: 100%; max-width: none;
  background: transparent;
  padding: 0;
}
body[data-view="send"] .display .plate > canvas,
body[data-view="share"] .display .plate > canvas {
  width: min(100cqw, 100cqh);
  height: min(100cqw, 100cqh);
  max-width: none; max-height: none;
  background: #fff;
  border-radius: var(--radius-sm);
}

/* The camera pane had a fixed 4:3 box, so it could not give height back and
   the code pane took whatever was left — which on a phone was almost nothing.
   In the transmitting views it fills its pane the same way the code does. */
body[data-view="send"] .scan .viewport,
body[data-view="share"] .scan .viewport {
  aspect-ratio: auto;
  flex: 1 1 auto;
  min-height: 90px;
}
.display .readout, .scan .cam-row { flex: none; }
.section { display: flex; flex-direction: column; gap: 10px; min-height: 0; }
.panel { padding: 12px; }
/* A header holds a title and a button, and neither of them gets shorter when
   you squeeze them. Left shrinkable it was compressed to 14px around content
   that still needed 27, and the button drew straight over the pane below. The
   space has to come from the pane, which can genuinely give it. */
.head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; min-height: 26px; flex: none;
}
.head-actions { display: flex; gap: 6px; }

/* ── Connect hero ──────────────────────────────────────────────────────── */

/* The lander fits, always. The tiles lose their body copy — the title and the
   picture carry it, and two paragraphs per tile is what made this a page you
   had to scroll past the thing you came for. */
.hero {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column; gap: 8px;
}
/* The pairing card keeps its natural height — it is the point of the page and
   there is nothing in it that can usefully shrink. */
.hero .connect { flex: 0 0 auto; margin-top: 10px; }
.hero .use p { display: none; }
.hero .use { padding: 11px; gap: 6px; }
.hero .use h3 { font-size: 12px; }
.hero .use svg { width: 34px; height: 22px; }
.hero .uses { gap: 8px; }
.lander-block { margin-top: 12px; gap: 6px; }
.lander-block > h2 {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.18em; color: var(--color-foreground-tertiary);
  font-family: var(--font-sans);
}
.lander-block > .note { display: none; }

@media (max-width: 720px), (max-height: 620px) {
  /* A phone cannot hold a scannable code, a headline and eight tiles at once —
     in either orientation. The tiles are every one of them in the menu, one tap
     away, so the lander keeps the thing you came for and drops the index. */
  .hero > .lander-block { display: none; }
  .hero h1 { font-size: 20px; }
  .hero .lede { font-size: 12px; }
  .hero .pair-url, .hero #connect-mode { display: none; }
  .hero .steps { font-size: 12px; gap: 3px; }
  .hero .steps li:last-child { display: none; }
  .hero .pair-row { gap: 12px; padding: 14px; }
  .hero .use { padding: 9px; }
  .hero .use svg { width: 28px; height: 19px; }
  .lander-block { margin-top: 8px; }
}
.hero h1 {
  font-family: var(--font-display); font-size: clamp(21px, 4vw, 26px);
  font-weight: 600; letter-spacing: -0.03em; line-height: 1.15; margin: 0;
}
.lede {
  margin: 0; max-width: 52ch;
  color: var(--color-foreground-secondary); font-size: 14px; line-height: 1.55;
}

.connect { display: flex; flex-direction: column; gap: 18px; margin-top: 16px; }

/* One primary path, laid out as one object: the code and what to do with it. */
.pair-row { display: flex; flex-direction: column; gap: 16px; padding: 16px; }
.pair-body { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.pair-body .steps { margin: 0; }
.pair-body #btn-scanned { align-self: flex-start; }
.pair-body.showing-frames .plate { outline: 2px solid var(--color-accent); outline-offset: 3px; }

/* The secondary path is a line of text with a button on it, not a rival card. */
.connect-alt {
  display: flex; flex-direction: column; gap: 12px; align-items: flex-start;
  padding-top: 4px;
}
.alt-line {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 12px;
  color: var(--color-foreground-secondary);
}
.connect-alt { padding-top: 0; gap: 8px; }

/* Side by side once there is width for it — and always in landscape, where
   height is the scarce thing and stacking the code on top of the steps is
   what does not fit. 667x375 sat 13px under a threshold written for portrait. */
@media (min-width: 680px), (orientation: landscape) and (min-width: 560px) {
  .pair-row { flex-direction: row; align-items: flex-start; gap: 20px; padding: 20px; }
  .pair-row .plate.pair-plate { flex: none; margin: 0; }
  .pair-body { flex: 1; padding-top: 2px; }
}

@media (min-width: 980px) {
  .hero {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(300px, 0.72fr);
    grid-template-rows: auto auto auto 1fr;
    column-gap: 26px; row-gap: 6px;
    align-content: start;
  }
  .hero > h1, .hero > .lede { grid-column: 1 / -1; }
  .hero > .connect { grid-column: 1; grid-row: 3 / 5; margin-top: 12px; align-self: start; }
  .hero > .lander-block:first-of-type { grid-column: 2; grid-row: 3; }
  .hero > .lander-block:last-of-type { grid-column: 2; grid-row: 4; align-self: start; }
}

/* ── Details disclosure ────────────────────────────────────────────────── */

.details { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--color-border); }
.details-toggle {
  appearance: none; background: none; border: 0; padding: 0;
  display: flex; align-items: center; gap: 6px;
  font: inherit; font-size: 12px; font-weight: 500;
  color: var(--color-foreground-tertiary); cursor: pointer;
  transition: color 0.14s ease;
}
@media (hover: hover) {
  .details-toggle:hover { color: var(--color-foreground-secondary); }
}
.details-toggle .chev { transition: transform 0.18s var(--ease-snappy); }
.details-toggle[aria-expanded="true"] .chev { transform: rotate(180deg); }
.details-body { display: flex; flex-direction: column; gap: 20px; margin-top: 18px; }

/* Reading codes makes our own screen the back-channel: it still has to be on
   screen for ACKs, but as a strip rather than a half-empty panel. */
.mode-receive .display { display: flex; align-items: center; gap: 14px; }
.mode-receive .display .head { order: 2; flex: 1; min-height: 0; flex-wrap: wrap; }
.mode-receive .display .plate { order: 1; width: 76px; flex: none; margin: 0; }
.mode-receive .display .readout,
.mode-receive .display .tick { display: none; }
.mode-receive .display .head h2 { display: flex; flex-direction: column; white-space: nowrap; }
.mode-receive .display .head h2::after {
  content: 'back-channel';
  font-family: var(--font-sans); font-size: 11px; font-weight: 400;
  letter-spacing: 0; color: var(--color-foreground-tertiary);
}

/* On the device that is reading, the camera is the job. Put it first and let
   the outgoing side sit under it. */
.mode-receive .work > .instruments { order: 1; }
.mode-receive .work > #sec-payload { order: 2; }
.mode-receive .work > #sec-chat { order: 3; }
.mode-receive .instruments { flex-direction: column-reverse; }

/* ── The transmit plate ────────────────────────────────────────────────── */

.plate {
  width: 100%;
  max-width: 560px;
  margin-inline: auto;
  aspect-ratio: 1;
  min-height: 0;
  background: #fff;
  border-radius: var(--radius-sm);
  overflow: hidden;
  display: grid;
  place-items: center;
  transition: max-width 0.25s var(--ease-snappy);
}
#qr { display: block; max-width: 100%; max-height: 100%; width: auto; height: auto; image-rendering: pixelated; }

/* One frame leaving is one pulse. It fires and stops — nothing here loops. */
.tick {
  position: absolute; left: 12px; right: 12px; top: 38px;
  height: 1px; background: var(--color-accent);
  opacity: 0; pointer-events: none;
}
.tick.fire { animation: tick 340ms var(--ease-snappy) 1; }
@keyframes tick {
  0% { opacity: 0; transform: scaleX(0.2); }
  35% { opacity: 0.9; }
  100% { opacity: 0; transform: scaleX(1); }
}

/* ── Camera ────────────────────────────────────────────────────────────── */

.viewport {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: #000;
}
#video { width: 100%; height: 100%; object-fit: cover; display: block; }
.hud {
  position: absolute; left: 8px; bottom: 8px; right: 8px;
  display: flex; gap: 6px;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-secondary);
  pointer-events: none;
}
.hud span {
  background: color-mix(in srgb, var(--color-background) 78%, transparent);
  border: 1px solid var(--color-border);
  padding: 2px 6px; border-radius: var(--radius-xs);
}
.viewport-off {
  position: absolute; inset: 0; display: grid; place-items: center;
  color: var(--color-foreground-tertiary);
  font-family: var(--font-mono); font-size: var(--text-2xs);
}
.viewport.live .viewport-off { display: none; }

.cam-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }

/* ── Readouts ──────────────────────────────────────────────────────────── */

.readout {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  margin: 0;
  background: var(--color-border);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  overflow: hidden;
}
@media (min-width: 420px) {
  .readout { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}
.readout > div { background: var(--color-surface-0); padding: 7px 9px; min-width: 0; }
.readout dt {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.18em; color: var(--color-foreground-tertiary);
}
.readout dd {
  margin: 3px 0 0;
  font-family: var(--font-display); font-size: 14px; font-weight: 600;
  letter-spacing: -0.03em; font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* ── Controls ──────────────────────────────────────────────────────────── */

.btn {
  appearance: none; font: inherit; font-size: 12px; font-weight: 500;
  cursor: pointer; white-space: nowrap;
  background: var(--color-surface-3);
  color: var(--color-foreground-secondary);
  border: 1px solid var(--color-border-strong);
  border-radius: var(--radius-sm);
  padding: 5px 11px;
  position: relative;
  transition:
    color var(--t-fast) var(--ease-out),
    border-color var(--t-fast) var(--ease-out),
    background-color var(--t-fast) var(--ease-out),
    box-shadow var(--t-fast) var(--ease-out),
    transform var(--t-tap) var(--ease-out);
}
/* One pixel. It is enough to read as a raised surface and small enough that a
   row of buttons does not appear to wobble as the pointer crosses it. */
@media (hover: hover) {
  .btn:hover {
    color: var(--color-foreground);
    border-color: var(--color-foreground-tertiary);
    transform: translateY(-1px);
    box-shadow: 0 4px 14px -8px rgba(0, 0, 0, 0.95);
  }
}
a.btn { text-decoration: none; display: inline-block; }
/* Down, and very slightly smaller — the press goes into the screen rather than
   sliding down it. */
.btn:active { transform: translateY(0) scale(0.985); box-shadow: none; }
.btn:disabled { opacity: 0.4; cursor: not-allowed; transform: none; box-shadow: none; }

/* A slow specular pass across the primary action on hover. Low contrast and
   over half a second, so it reads as light moving over a surface rather than
   as an effect; anything faster or brighter is a casino. */
.btn-primary { overflow: hidden; }
.btn-primary::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(105deg,
    transparent 32%,
    rgba(255, 255, 255, 0.2) 48%,
    transparent 64%);
  transform: translateX(-130%);
  transition: transform var(--t-stage) var(--ease-glide);
  pointer-events: none;
}
@media (hover: hover) {
  .btn-primary:hover::after { transform: translateX(130%); }
}
.btn-primary:disabled::after { display: none; }

.btn-primary {
  background: var(--color-accent);
  color: #fff;
  font-weight: 600;
  border-color: transparent;
  box-shadow: 0 6px 18px -6px var(--color-accent-glow);
}
@media (hover: hover) {
  .btn-primary:hover { background: var(--color-accent-bright); border-color: transparent; color: #fff; }
}

.linkbtn {
  background: none; border: 0; padding: 0; font: inherit;
  color: var(--color-accent-bright); cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}

.field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.field > span {
  font-size: 10px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.18em; color: var(--color-foreground-tertiary);
}
.field > span b {
  color: var(--color-foreground); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: 0;
}
select, input[type="text"] {
  appearance: none; font: inherit; font-size: 13px; width: 100%;
  background: rgba(0, 0, 0, 0.35);
  color: var(--color-foreground);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: 7px 9px;
  transition: border-color 0.14s ease;
}
select:focus, input[type="text"]:focus { outline: none; border-color: var(--color-accent-line); }

/* Every field, one rule. A ring of the accent wash rather than a hard second
   border: it reads as the field being lit from behind. */
:where(select, textarea, input[type="text"], input[type="password"], input[type="number"]) {
  transition:
    border-color var(--t-fast) var(--ease-out),
    box-shadow var(--t-base) var(--ease-out),
    background-color var(--t-fast) var(--ease-out);
}
:where(select, textarea, input[type="text"], input[type="password"], input[type="number"]):focus {
  box-shadow: 0 0 0 3px var(--color-accent-muted);
  background: rgba(0, 0, 0, 0.5);
}
@media (hover: hover) {
  :where(select, textarea, input[type="text"], input[type="password"], input[type="number"]):hover:not(:focus) {
    border-color: var(--color-border-strong);
  }
}
input[type="range"] { width: 100%; accent-color: var(--color-accent); }

.settings { display: grid; grid-template-columns: 1fr; gap: 16px; }
@media (min-width: 560px) { .settings { grid-template-columns: 1fr 1fr; } }
.note {
  margin: 0; color: var(--color-foreground-tertiary); font-size: 12px; line-height: 1.55;
}
.settings .note { grid-column: 1 / -1; }

/* ── Payload ───────────────────────────────────────────────────────────── */

.drop {
  padding: 12px 14px; text-align: center;
  color: var(--color-foreground-secondary);
  transition: border-color 0.16s ease, background-color 0.16s ease;
}
.drop p { margin: 0; }
.drop p + p { margin-top: 4px; }
@media (max-height: 720px) { .drop-detail { display: none; } }
.drop.over { border-color: var(--color-accent-line); }

.transfers { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.transfers:empty { display: none; }
.tr { padding: 10px 11px; }
.tr-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.tr-name { font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tr-dir {
  font-family: var(--font-mono); font-size: 10px; font-weight: 600;
  padding: 1px 5px; border-radius: var(--radius-xs);
  border: 1px solid var(--color-border-strong); color: var(--color-foreground-tertiary);
}
.tr-dir.out { color: var(--color-accent-bright); border-color: var(--color-accent-line); background: var(--color-accent-muted); }
.tr-dir.in { color: var(--color-yes-secondary); border-color: var(--color-yes-line); }
.tr-meta {
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-tertiary); margin-top: 3px;
  font-variant-numeric: tabular-nums;
}
.bar-track {
  height: 3px; border-radius: 999px; margin: 9px 0 6px;
  background: var(--color-surface-3); overflow: hidden;
}
/* Full width, scaled down — not a narrow box being widened. A transfer moves
   this several times a second, and animating width relayouts and repaints the
   row on every frame of every step, on a phone, with a camera running. scaleX
   is the same picture on the compositor. */
.bar-fill {
  height: 100%; width: 100%;
  transform: scaleX(0); transform-origin: left;
  background: var(--color-accent);
  transition: transform var(--t-base) var(--ease-snappy);
}
.tr.done .bar-fill { background: var(--color-yes); }
.tr.bad .bar-fill { background: var(--color-no); }
/* The chunk map is reference, not progress — the bar above already carries
   that. Kept short and muted so the two do not compete. */
.grid {
  display: block; width: 100%; height: 8px;
  image-rendering: pixelated; border-radius: var(--radius-xs);
  opacity: 0.7;
}
.tr-actions { display: flex; gap: 6px; margin-top: 9px; }
.tr-actions:empty { display: none; }

/* ── Printable sheet ───────────────────────────────────────────────────────
   Paper is the one medium here with no battery, no format rot and no vendor.
   It is also unforgiving: ink black on white, generous quiet zones, and the
   restore instructions on the page itself, because whoever finds this in a
   drawer will not have the app open. */

.sheet { display: none; }

@media print {
  body > *:not(.sheet) { display: none !important; }
  .sheet { display: block; color: #000; background: #fff; font-family: var(--font-sans); }
  .sheet-head { margin-bottom: 10mm; page-break-after: avoid; }
  .sheet-head h1 { font-size: 16pt; margin: 0 0 2mm; }
  .sheet-head p { font-size: 9pt; margin: 0 0 1mm; color: #333; line-height: 1.5; }
  .sheet-head code { font-family: var(--font-mono); font-size: 8pt; }
  .sheet-grid {
    display: grid; grid-template-columns: repeat(3, 1fr); gap: 6mm;
  }
  .sheet-cell { page-break-inside: avoid; text-align: center; }
  .sheet-cell img { width: 100%; image-rendering: pixelated; border: 0.3mm solid #000; }
  .sheet-cell span { font-family: var(--font-mono); font-size: 7pt; color: #444; }
  @page { margin: 12mm; }
}

/* ── Received previews ─────────────────────────────────────────────────── */

.preview {
  margin-top: 10px; padding: 8px;
  background: var(--color-slip);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  display: grid; place-items: center;
}
.preview img, .preview video {
  max-width: 100%; max-height: 300px; display: block;
  border-radius: var(--radius-xs);
}
.preview pre {
  margin: 0; max-height: 220px; overflow: auto; width: 100%;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-secondary); white-space: pre-wrap; word-break: break-word;
}
.preview-note {
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-tertiary);
}

.restore { border: 1px solid var(--color-border); border-radius: var(--radius-md); padding: 10px 12px; }
.restore > summary {
  cursor: pointer; font-size: 12px; font-weight: 500;
  color: var(--color-foreground-secondary); list-style: none;
}
.restore > summary::-webkit-details-marker { display: none; }
.restore > summary::before { content: '▸ '; color: var(--color-foreground-tertiary); }
.restore[open] > summary::before { content: '▾ '; }
.restore > *:not(summary) { margin-top: 10px; }

/* ── Quick send ────────────────────────────────────────────────────────── */

.quick {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px;
  border: 1px solid var(--color-accent-line);
  background: var(--color-accent-muted);
  border-radius: var(--radius-md);
}
.quick-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.quick-body strong { font-weight: 600; font-variant-numeric: tabular-nums; }
.quick-actions { display: flex; gap: 8px; }

/* ── Archive ───────────────────────────────────────────────────────────── */

.arch-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.arch-actions .btn { display: inline-flex; align-items: center; gap: 7px; }

.arch-status { min-height: 1.5em; }
.arch-status.ok { color: var(--color-yes-secondary); }
.arch-status.warn { color: var(--color-warning); }
.arch-status.err { color: var(--color-no-secondary); }

.frame-lab {
  display: flex; flex-direction: column; gap: 12px;
  margin-top: 4px; padding: 14px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-surface-0);
}
.frame-stage { padding: 10px; display: grid; place-items: center; }
#frame-canvas {
  width: 100%; max-width: 520px; height: auto; display: block;
  image-rendering: pixelated; border-radius: var(--radius-xs);
}
.frame-meta {
  display: flex; justify-content: space-between;
  font-family: var(--font-mono); font-size: var(--text-2xs);
  color: var(--color-foreground-tertiary);
}
.lab-controls { display: grid; gap: 12px; }
@media (min-width: 560px) { .lab-controls { grid-template-columns: 1fr 1fr; } }

.check { display: flex; align-items: flex-start; gap: 8px; cursor: pointer; }
.check input { accent-color: var(--color-accent); margin-top: 2px; flex: none; }
.check > span { font-size: 12px; color: var(--color-foreground-secondary); }
.lab-controls .check { grid-column: 1 / -1; }

.tease {
  padding: 12px; border-radius: var(--radius-md);
  border: 1px solid var(--color-accent-line);
  background: var(--color-accent-muted);
  display: flex; flex-direction: column; gap: 8px;
}
.tease h3 {
  margin: 0; font-family: var(--font-display); font-size: 13px;
  font-weight: 600; letter-spacing: -0.02em;
}
.tease p { margin: 0; font-size: 12px; line-height: 1.55; color: var(--color-foreground-secondary); }
.tease b { color: var(--color-foreground); font-weight: 500; }
.tease em { color: var(--color-foreground); font-style: italic; }

.photo-note { margin-top: 10px; }

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

.chat {
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 6px;
  max-height: 200px; overflow-y: auto;
}
.chat:empty { display: none; }
.chat li {
  max-width: 82%; padding: 6px 10px; border-radius: var(--radius-md);
  word-break: break-word;
}
.chat li.out {
  align-self: flex-end;
  background: var(--color-accent-muted);
  border: 1px solid var(--color-accent-line);
}
.chat li.in {
  align-self: flex-start;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
}
.composer { display: flex; gap: 8px; }
.composer input { flex: 1; }

/* ── Log ───────────────────────────────────────────────────────────────── */

.log {
  list-style: none; margin: 0; padding: 10px 11px;
  font-family: var(--font-mono); font-size: var(--text-2xs); line-height: 1.7;
  max-height: 220px; overflow-y: auto;
}
.log li { display: flex; gap: 10px; color: var(--color-foreground-secondary); }
.log .t { color: var(--color-foreground-tertiary); flex: none; font-variant-numeric: tabular-nums; }
.log .lv { flex: none; width: 30px; font-size: 9px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; padding-top: 3px; }
.log .lv-tx { color: var(--color-accent-bright); }
.log .lv-rx { color: var(--color-warning); }
.log .lv-ok { color: var(--color-yes-secondary); }
.log .lv-err { color: var(--color-no-secondary); }
.log .lv-link { color: var(--color-foreground-tertiary); }

/* ── The messages page ─────────────────────────────────────────────────── */

/* The familiar shape, because there is no reason to make anyone learn a new
   one. The view fills the stage and the thread is the only thing that scrolls:
   a composer that walks off the bottom of a phone is the one thing a messenger
   is not allowed to do. */
.msg-view { min-height: 0; }

.msg-bar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px;
  background: var(--color-surface-0);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  transition: border-color 0.2s ease;
}
.msg-bar.live { border-color: var(--color-accent-line); }
.msg-bar.sending { border-color: var(--color-accent); animation: transmitting 1.3s ease-in-out infinite; }

/* The one animation in here that means something: it runs exactly while sound
   is leaving the speaker. */
@keyframes transmitting {
  50% { box-shadow: 0 0 26px -10px var(--color-accent-glow); }
}
.msg-bar .btn { flex: none; }

/* A strip of the band, so you can see the channel is alive before you trust
   it with anything. It is the same waterfall as the instrument view, small. */
.msg-scope-wrap {
  flex: 1; min-width: 40px;
  border-radius: var(--radius-sm);
  background: var(--color-slip);
  overflow: hidden;
}
.msg-scope-wrap canvas { display: block; width: 100%; height: 34px; }
/* Nothing is listening, so there is nothing to show — an empty black strip
   reads as a broken picture rather than as an idle one. */
.msg-bar:not(.live) .msg-scope-wrap { display: none; }

/* Shrinkable, and every line in it truncates. A peer name and a band label are
   both as long as they happen to be, and on a 390px bar that was 22px of the
   page hanging off the right edge. */
.msg-who { flex: 1 1 90px; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.msg-peer, .msg-carrier {
  position: relative; padding-right: 12px; text-align: right;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msg-peer {
  font-size: var(--text-2xs); font-weight: 600;
  color: var(--color-foreground-secondary);
}
/* A ring leaving the light, once every couple of seconds. Only while somebody
   is actually there — a beacon that pulses at an empty room is decoration. */
@keyframes ping {
  from { opacity: 0.5; transform: scale(1); }
  to { opacity: 0; transform: scale(2.8); }
}
.msg-peer[data-state="near"]::before {
  content: '';
  position: absolute; right: 0; top: 50%; margin-top: -3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-yes);
  animation: ping 2s var(--ease-out) infinite;
  pointer-events: none;
}

/* On the right, where a status light belongs. */
.msg-peer::after {
  content: ''; position: absolute; right: 0; top: 50%; margin-top: -3px;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--color-foreground-tertiary);
}
.msg-peer[data-state="near"] { color: var(--color-yes-secondary); }
.msg-peer[data-state="near"]::after { background: var(--color-yes); }
.msg-peer[data-state="far"]::after { background: var(--color-warning); }
.msg-carrier { font-size: 10px; color: var(--color-foreground-tertiary); }

.thread-wrap { flex: 1; min-height: 0; position: relative; display: flex; }
.thread {
  flex: 1; min-height: 0; overflow-y: auto;
  list-style: none; margin: 0; padding: 4px 2px 2px;
  display: flex; flex-direction: column; gap: 8px;
  scrollbar-width: thin;
}

/* A short conversation belongs at the bottom of the panel, against the
   composer, not stranded at the top of it.
   justify-content: flex-end would do it and would also make the earliest
   messages unreachable once the list overflows — the well-known way to break a
   scrolling flex column. An auto top margin on the first visible child does the
   same job and collapses to nothing the moment there is more content than room.
   The empty state is always the first child and is hidden once anything is
   said, so the rule hangs off that. */
.thread > .thread-empty[hidden] + * { margin-top: auto; }

.thread-day {
  align-self: center;
  font-size: 10px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--color-foreground-tertiary);
  padding: 6px 0 2px;
}

.thread-line {
  position: relative;
  max-width: min(78%, 520px);
  padding: 8px 11px 6px;
  border-radius: var(--radius-lg);
  display: flex; flex-direction: column; gap: 3px;
  overflow: hidden;
}
.thread-line.out {
  align-self: flex-end;
  background: var(--color-accent-muted);
  border: 1px solid var(--color-accent-line);
}
.thread-line.in {
  align-self: flex-start;
  background: var(--color-surface-2);
  border: 1px solid var(--color-border);
}
/* A line comes in from behind the glass, from its own side of the thread.
   Forty-six pixels of z under a 700px perspective is a small move that reads as
   depth rather than as a slide, and the origin makes it grow out of the corner
   the bubble is anchored to. */
@keyframes line-in {
  from { opacity: 0; transform: perspective(700px) translate3d(0, 10px, -46px); }
}
.thread-line.fresh { animation: line-in var(--t-base) var(--ease-snappy) both; }
.thread-line.out.fresh { transform-origin: 100% 100%; }
.thread-line.in.fresh { transform-origin: 0 100%; }

/* While the tones are still leaving the speaker the bubble breathes. It stops
   the moment the last sample is played, which is the only honest signal there
   is on this channel. */
@keyframes on-air {
  50% { box-shadow: 0 0 0 1px var(--color-accent-line), 0 0 22px -8px var(--color-accent-glow); }
}
.thread-line.settling { animation: on-air 1.5s ease-in-out infinite; }

.thread-text { margin: 0; word-break: break-word; white-space: pre-wrap; line-height: 1.45; }
.thread-meta {
  font-size: 10px; font-variant-numeric: tabular-nums;
  color: var(--color-foreground-tertiary);
  align-self: flex-end;
}
.thread-line.in .thread-meta { align-self: flex-start; }

/* Not a spinner standing in for a network round trip — the buffer is scheduled,
   so this is tracking sound that is genuinely still leaving the speaker. */
.thread-air {
  position: absolute; left: 0; bottom: 0; height: 2px;
  width: 100%;
  transform: scaleX(var(--air, 0)); transform-origin: left;
  background: var(--color-accent);
  box-shadow: 0 0 10px -1px var(--color-accent-glow);
  transition: transform 0.1s linear;
}

.thread-empty {
  /* Centred by auto margins in a column flex, so when there is room it sits in
     the middle of the panel and when there is not it simply scrolls. */
  margin: auto 0;
  display: flex; flex-direction: column; gap: 8px;
  padding: 8px clamp(12px, 8vw, 60px);
  text-align: center;
  color: var(--color-foreground-secondary);
  font-size: 13px; line-height: 1.5;
  pointer-events: none;
}
.thread-empty p { margin: 0; }
.thread-empty b { color: var(--color-foreground); }
.thread-empty-fine { font-size: var(--text-2xs); color: var(--color-foreground-tertiary); }

.msg-composer { display: flex; align-items: center; gap: 8px; flex: none; }
.msg-composer input { flex: 1; min-width: 0; }
.msg-cost {
  flex: none;
  font-size: 10px; font-variant-numeric: tabular-nums;
  color: var(--color-foreground-tertiary);
  min-width: 0; white-space: nowrap;
}

@media (max-width: 520px) {
  /* The band strip is the first thing to go: on a narrow bar it is a sliver
     that says nothing, and the instrument view exists for looking properly. */
  .msg-scope-wrap { display: none; }
  .thread-line { max-width: 86%; }
}

/* ── Splitting a picture ───────────────────────────────────────────────── */

/* The stage is black on purpose. `difference` blends against whatever is
   behind, and black is the identity for it — so where only one plane covers a
   pixel you see that plane unchanged, and where both do you see the xor. Any
   other ground would tint the half that is not overlapping. */
.split-stage {
  position: relative;
  display: grid; place-items: center;
  min-height: 220px;
  padding: 22px 12px;
  background: #000;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

/* Light passes over the picture at the instant it resolves — one pass, nine
   hundred milliseconds, and gone. It leaves nothing behind: the animation ends
   at zero opacity, so what remains on screen is the exclusive-or and nothing
   laid over it. */
@keyframes reveal-sweep {
  from { transform: translateX(-140%); opacity: 1; }
  to { transform: translateX(140%); opacity: 0; }
}
.split-planes::after {
  content: '';
  position: absolute; inset: 0;
  background: linear-gradient(100deg,
    transparent 40%, rgba(255, 255, 255, 0.15) 50%, transparent 60%);
  transform: translateX(-140%);
  opacity: 0;
  pointer-events: none;
  z-index: 2;
}
.split-planes.met::after { animation: reveal-sweep 900ms var(--ease-glide) both; }

.split-planes {
  position: relative;
  /* An ordinary isolated stacking context. Emphatically not preserve-3d: that
     would flatten the blend and the reveal would never happen. */
  isolation: isolate;
  display: grid;
  place-items: center;
}
.split-planes canvas {
  grid-area: 1 / 1;
  display: block;
  max-width: 100%;
  /* Nearest neighbour, so a fractional device pixel ratio cannot turn a hard
     0 or 255 into a grey. Both planes scale by the same rule, so the same
     source pixel is chosen for each and the xor comes through intact. */
  image-rendering: pixelated;
  transition: box-shadow 0.35s ease;
}
/* Apart, they are two objects with edges. Together, one surface. */
#split-key { box-shadow: 0 0 0 1px rgba(231, 235, 242, calc(0.22 * var(--edge, 1))); }
#split-cipher {
  mix-blend-mode: difference;
  box-shadow: 0 0 0 1px rgba(59, 123, 240, calc(0.4 * var(--edge, 1)));
}
.split-planes.met canvas { box-shadow: none; }

.split-empty {
  margin: 0; padding: 0 16px;
  text-align: center;
  font-size: var(--text-2xs);
  color: var(--color-foreground-tertiary);
}

#split-align { width: 100%; }

/* ── Sound link ────────────────────────────────────────────────────────── */

/* Frequency up the side, time across, brightness for level.

   This is not decoration. Every way this channel fails is invisible in a status
   line and obvious here in one second: a muted microphone is a flat black
   panel, a phone that quietly kept noise suppression on shows the band being
   scrubbed as we transmit, and a speaker that cannot reach 18 kHz shows the top
   lane arriving at a fraction of the bottom one. */
.scope {
  position: relative;
  border: 1px solid var(--color-border);
  border-radius: var(--radius-lg);
  background: var(--color-slip);
  overflow: hidden;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.scope canvas { display: block; width: 100%; height: clamp(150px, 30vh, 320px); }
.scope.live { border-color: var(--color-accent-line); }
.scope.sending {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 1px var(--color-accent-line), 0 0 26px -8px var(--color-accent-glow);
  animation: transmitting 1.3s ease-in-out infinite;
}

.scope-ticks {
  position: absolute; inset: 0 auto 0 0;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 6px 0 24px 8px;
  font: 500 var(--text-2xs)/1 var(--font-mono);
  color: var(--color-foreground-tertiary);
  pointer-events: none;
  /* The labels sit on top of live signal, so they carry their own ground. */
  text-shadow: 0 0 5px var(--color-slip), 0 0 3px var(--color-slip), 0 0 2px var(--color-slip);
}

.scope-status {
  position: absolute; left: 0; right: 0; bottom: 0;
  margin: 0; padding: 7px 10px;
  font-size: var(--text-2xs);
  color: var(--color-foreground-secondary);
  background: linear-gradient(to top, rgba(2, 3, 8, 0.94), rgba(2, 3, 8, 0));
  pointer-events: none;
}
.scope.sending .scope-status { color: var(--color-accent-bright); }

#room-sonic .readout { align-content: start; }

/* ── Fullscreen plate ──────────────────────────────────────────────────── */

/* Expanding the code should not blast a white screen at you. The code itself
   stays dark-on-light — that is what a scanner expects, and our own reader runs
   with inversion attempts off — but it keeps its quiet zone as a white plate on
   the app's own ground rather than turning the whole display white. */
.fullscreen {
  position: fixed; inset: 0; z-index: 50;
  background: var(--color-background);
  display: grid; place-items: center;
  padding: max(16px, env(safe-area-inset-top)) 16px 16px;
}
#qr-full {
  width: min(92vw, 86vh); height: min(92vw, 86vh);
  image-rendering: pixelated;
  background: #fff;
  border-radius: var(--radius-sm);
}
.fullscreen .btn { position: absolute; top: 14px; right: 14px; }

/* ── Desktop ───────────────────────────────────────────────────────────── */

.instruments { display: flex; flex-direction: column; gap: 12px; }
.instruments > * { min-height: 0; flex: 1; }

@media (min-width: 900px), (orientation: landscape) and (min-width: 560px) {
  /* Rule 4. Two things of equal weight sit beside each other rather than one
     pushing the other off the screen — decided by the shape of the space, not
     by width alone. A phone on its side is 844x390, and stacking there leaves
     each pane about 90px tall. */
  .instruments { flex-direction: row; }
  .instruments > * { flex: 1 1 0; }
  .mode-send .instruments > .display { max-width: 620px; margin-inline: auto; }
  .log, .chat { max-height: 280px; }
}


/* Short screens. The frame counters are diagnostics and are repeated under
   Link details; the reachability caveat is advisory. Both cost the panes more
   height than they are worth when there is none to spare. */
@media (max-height: 880px) {
  body[data-view="send"] #tx-readout,
  body[data-view="share"] #tx-readout { display: none; }
}

@media (max-height: 750px) {
  .panel { padding: 9px; }
  .card.panel { gap: 8px; }
  .stage { gap: 10px; padding-top: 12px; }
  .hero .pair-warn { display: none; }
  .hero .steps { display: none; }
  .chat { max-height: 84px; }
  body[data-view="send"] .scan .viewport,
  body[data-view="share"] .scan .viewport { min-height: 70px; }
}

/* A phone on its side has 390px of height and is 844px wide, so none of the
   narrow-screen reductions above reach it — width said "roomy" while height
   said the opposite. Everything that is explanation rather than function goes,
   in both orientations, and what is left is the code and the two ways out. */
@media (max-height: 620px) {
  .hero h1 { font-size: 19px; }
  .hero .lede { display: none; }
  .hero .pair-url, .hero #connect-mode { display: none; }
  .hero .connect { margin-top: 4px; }
  .hero .pair-row { padding: 12px; gap: 14px; }

  /* Share's panel is a heading, a button and a switch; its explanation and its
     counters are what a landscape phone cannot afford. */
  body[data-view="share"] #room-share > .note,
  body[data-view="share"] #share-stats { display: none; }
  #room-share { gap: 8px; }

  /* The waterfall is the one thing in the sound view that can give up height
     without losing its job — a shorter band still shows the lanes stepping. */
  .scope canvas { height: 104px; }
  #room-sonic > .note:last-of-type { display: none; }
}

/* Below this the headers cost more than they are worth at full size. Share
   keeps its header in a .section rather than a .card, so this is written for
   every head there is. */
@media (max-height: 430px) {
  .head { min-height: 0; }
  .head .btn { padding: 4px 9px; font-size: 11px; }
  .head h2 { font-size: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  /* ::before was missing here, so every pseudo-element built on one — the
     status light, the sheen — kept animating for exactly the people who asked
     it not to. */
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
  /* A sheen that cannot travel should not be parked mid-button. */
  .btn-primary::after { display: none !important; }
}

/* ── Touch ─────────────────────────────────────────────────────────────── */

/* A fingertip is about nine millimetres across and it cannot see what it is
   covering. Everything above is sized for a cursor, which is one pixel wide
   and lands exactly where it is pointed; at 12px type and 5px of padding a
   button comes out 30px tall, and three of those in a panel header is a coin
   toss on a phone. This section is the one place the design trades density
   for reach — every control a finger has to find is at least 44px in the
   direction it is hardest to hit.

   Keyed on the pointer rather than the width, so a touchscreen laptop gets it
   and a narrow desktop window does not. */

@media (pointer: coarse) {
  .btn { min-height: 44px; padding: 10px 15px; font-size: 13px; }
  .icon-btn { width: 44px; height: 44px; }
  .icon-btn svg { width: 20px; height: 20px; }
  .nav-item { min-height: 44px; display: flex; align-items: center; }

  /* iOS zooms the page in when a field smaller than 16px takes focus, and it
     does not zoom back out on blur. In a shell pinned to the viewport height
     that is not cosmetic: the layout is then wider than the screen, and the
     panes it was so carefully fitted into are off the side of it. Sixteen is
     the threshold, so sixteen it is. */
  select,
  textarea,
  input[type="text"],
  input[type="password"],
  input[type="number"] { font-size: 16px; min-height: 44px; }
  textarea { min-height: 90px; }
  input[type="checkbox"] { width: 20px; height: 20px; }
}

/* Two things a browser does on tap that are wrong here. The grey flash is
   drawn on the box rather than the shape, so on a 4px radius it paints a
   rectangle over the button for a fifth of a second — and the press
   already has feedback. And a control that might be double-tapped waits to
   find out whether you are going to, which reads as the button not firing. */
.btn, .icon-btn, .nav-item, .mode, .use, .wtab, .dismiss, .details-toggle,
select, input, textarea, label {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
}

/* Without a pointer there is no hover, so the press is the only feedback
   there is. Make it carry. */
@media (hover: none) {
  .btn:active { background: var(--color-surface-3); border-color: var(--color-foreground-tertiary); }
  .btn-primary:active { background: var(--color-accent-bright); }
  .icon-btn:active, .nav-item:active { background: var(--color-surface-3); color: var(--color-foreground); }
}

/* A header holds a title and its actions, and on a narrow screen neither of
   them gets shorter. Rather than let the actions run off the side of the
   panel — which is what put Start camera outside the clip on a phone — they
   go under the title. */
@media (max-width: 620px) {
  .head { flex-wrap: wrap; row-gap: 8px; }
  .head-actions { flex-wrap: wrap; flex: 1 1 100%; }
}

/* ── Warnings ──────────────────────────────────────────────────────────── */

/* Something the hardware refused to do. Said where the thing that failed is,
   because on a phone the protocol log is three taps away and nobody is going
   to go and look. */
.note.warn { color: var(--color-warning); }
.viewport-off.warn {
  color: var(--color-warning);
  padding: 0 18px; text-align: center; line-height: 1.45;
  font-family: var(--font-sans); font-size: 12px;
}

/* ── Copy that earns its place ─────────────────────────────────────────── */

/* The rule the layout already follows — what does not fit goes, rather than
   being crushed — applied to sentences. On a phone these paragraphs were the
   difference between Share showing its optical panes and painting the camera
   over the message thread. The heading and the control say enough; the
   paragraph explaining them comes back when there is room for it. */
@media (max-height: 750px), (max-width: 420px) {
  .prose-extra { display: none; }
}

@media (pointer: coarse) {
  .check { min-height: 44px; align-items: center; }
}

/* ── What Share gives up on a short phone ──────────────────────────────── */

/* Share asks one screen to hold its own controls, both optical panes and the
   message thread. On a 667px-tall phone the two panes are left about 130px
   between them, which is not enough for either: the plate collapsed to nothing
   and the camera painted itself over the thread. Both panes half-working is
   worse than one working, so in send mode the camera goes — it is the pane
   doing no work there, and it is one tap away on Send, which fits at every
   size. Receive mode keeps it, because there the camera is the job and the
   plate has already shrunk to its 76px back-channel strip. */
@media (max-height: 750px) {
  body[data-view="share"] .stage.mode-send .scan { display: none; }
}

/* ── The controls that are not buttons ─────────────────────────────────── */

@media (pointer: coarse) {
  /* The role switcher is a real control, not a label. */
  .mode { min-height: 44px; padding: 10px 12px; }

  /* A slider whose thumb is the height of the line it sits on cannot be
     grabbed; it can only be tapped at, which jumps the value somewhere you
     did not ask for. */
  input[type="range"] { height: 44px; }

  /* A link standing on its own line can simply be taller. One inside a
     sentence cannot — and the trick of reaching a transparent box out into
     the leading does not work here either, because that box counts towards
     the scrollable overflow of everything above it, and the first rule of
     this layout is that the page does not scroll. So the inline ones stay
     the size the sentence makes them, and each has a full-sized way to do
     the same job: Choose a file for "choose one", and the drawer for the
     Messages link. */
  #btn-skip-pair { min-height: 44px; padding: 10px 2px; }
  /* That padding is now the spacing around the skip link, so the column gap
     above it would be counted twice — which on a short screen is exactly the
     few pixels between the lander fitting and painting over itself. */
  .connect-alt { gap: 2px; }
}
