/* Nixdorf Sekretär — a handheld, not a dialog box.
   The chassis is a photograph; the UI lives inside its measured screen rect. */

:root {
  --screen: #0a0d0c;
  --screen-lit: #0f1513;
  --amber: #d99548;
  --amber-hi: #ffb75c;
  --phos: #7fd6a8;
  --red: #e2564a;
  --ink: #ddd8cc;
  --ink-soft: #8f8a7f;
  --ink-faint: #5a564f;
  --rule: #222a27;
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", "Cascadia Mono", Menlo, Consolas, monospace;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Source Serif 4", Georgia, serif;

  /* Built-in fallbacks. The page overrides these from the measured JSON —
     art/measure.py reads the chroma keys off each render, so swapping a
     chassis is a rerun of the script and nothing else. */
  --link-aspect: .4615;
  --link-fill: 1.07;
  --link-scr-left: 24.414%; --link-scr-top: 18.75%;
  --link-scr-width: 54.883%; --link-scr-height: 38.151%;
  --link-plate-left: 24.414%; --link-plate-top: 60.807%;
  --link-plate-width: 54.883%; --link-plate-height: 9.505%;

  --deck-aspect: 1.7787;
  --deck-fill: 1;
  --deck-scr-left: 21.053%; --deck-scr-top: 23.167%;
  --deck-scr-width: 57.835%; --deck-scr-height: 42.933%;
  --deck-plate-left: 21.053%; --deck-plate-top: 70.882%;
  --deck-plate-width: 57.895%; --deck-plate-height: 14.134%;

  /* The active rig. Portrait gets the handheld; the landscape query below
     swaps the whole set for the deck. */
  --rig-img: url("/static/img/link.jpg");
  --rig-aspect: var(--link-aspect);
  --rig-fill: var(--link-fill);
  --scr-left: var(--link-scr-left);
  --scr-top: var(--link-scr-top);
  --scr-width: var(--link-scr-width);
  --scr-height: var(--link-scr-height);
  --plate-left: var(--link-plate-left);
  --plate-top: var(--link-plate-top);
  --plate-width: var(--link-plate-width);
  --plate-height: var(--link-plate-height);
}

/* A landscape viewport is a desk, not a hand: give it the field terminal.
   Below this it stays the handheld, which is the shape a phone already is. */
@media (min-width: 900px) and (min-aspect-ratio: 13/10) {
  :root {
    --rig-img: url("/static/img/deck.jpg");
    --rig-aspect: var(--deck-aspect);
    --rig-fill: var(--deck-fill);
    --scr-left: var(--deck-scr-left);
    --scr-top: var(--deck-scr-top);
    --scr-width: var(--deck-scr-width);
    --scr-height: var(--deck-scr-height);
    --plate-left: var(--deck-plate-left);
    --plate-top: var(--deck-plate-top);
    --plate-width: var(--deck-plate-width);
    --plate-height: var(--deck-plate-height);
  }
}

/* What the phone's own hardware covers: a camera cut-out at the top, a home
   indicator at the bottom. Read from the platform, but named — so a test can
   set them and check the layout reacts. */
:root {
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  /* A deliberate sliver of chassis above the glass and below the keys. It used
     to be whatever height happened to be left over — 45px in full screen, zero
     the moment the browser's bars took the room — so the device lost its top
     and bottom edge exactly when it could least afford to look unfinished. */
  --shell: 7px;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  background: #050606;
  color: var(--ink);
  font-family: var(--mono);
  /* The frame scales with the viewport; so must the type inside it, or a 2560
     monitor renders a big device with tiny writing on it. Everything below is
     in rem, so moving the root moves the whole readout together.
     ~15px to 1400px wide, 17px at 1920, 19px at 2560. */
  font-size: clamp(15px, 0.3125vw + 11px, 19px);
  -webkit-text-size-adjust: 100%;
}

body {
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 14px;
  background-image: radial-gradient(ellipse at 50% 0%, #121514 0%, #050606 68%);
}

/* The device page is the device: no margin, no furniture around it. */
body.rigged {
  padding: 0;
  overflow: hidden;
  background: #000;
  background-image: none;
}

/* ----------------------------------------------------------------- the gate */

.gate {
  width: 100%;
  max-width: 26rem;
  background: #16181a;
  border: 1px solid #2a2f31;
  border-radius: 14px;
  padding: 0 24px 24px;
  overflow: hidden;
  box-shadow: 0 20px 60px rgba(0,0,0,.6), inset 0 1px 0 rgba(255,255,255,.04);
}
.gate .hero {
  display: block;
  width: calc(100% + 48px);
  margin: -1px -24px 0;
  height: auto;
  border-radius: 13px 13px 0 0;
  filter: saturate(.92) contrast(1.04);
}
.gate h1 { font-family: var(--serif); font-size: 1.4rem; margin: 16px 0 4px; }
.gate .sub { color: var(--ink-soft); font-size: .8rem; margin-bottom: 22px; line-height: 1.5; }
.gate label { display: block; font-size: .7rem; letter-spacing: .12em; color: var(--ink-faint); margin-bottom: 8px; }
.gate input {
  width: 100%; padding: 11px 12px;
  background: var(--screen); border: 1px solid #2a2f31; border-radius: 7px;
  color: var(--amber-hi); font-family: var(--mono); font-size: 1rem; letter-spacing: .25em;
}
.gate input:focus { outline: none; border-color: var(--amber); box-shadow: 0 0 0 3px rgba(217,149,72,.12); }
.gate button {
  margin-top: 16px; width: 100%; padding: 11px;
  background: linear-gradient(#2e2318, #241b12); color: var(--amber-hi);
  border: 1px solid #4a3a26; border-radius: 7px;
  font-family: var(--mono); font-size: .85rem; letter-spacing: .14em; cursor: pointer;
}
.gate button:hover { border-color: var(--amber); color: #fff; }
.gate .denied { margin-top: 14px; color: var(--red); font-size: .75rem; }

.gate.notice { padding: 30px 24px 26px; }
.gate.notice .code { font-size: .68rem; letter-spacing: .22em; color: var(--red); margin-bottom: 14px; }
.gate.notice h1 { margin-top: 0; }
.gate.notice .back {
  display: inline-block; margin-top: 4px; color: var(--amber);
  text-decoration: none; font-size: .68rem; letter-spacing: .14em;
}
.gate.notice .back:hover { color: var(--amber-hi); }

/* ------------------------------------------------------------------ the rig */

/* The frame takes the whole viewport in whichever dimension binds first, and
   the other one letterboxes against black. The box and the render share one
   aspect number, so `100% 100%` is exact rather than a stretch — and the
   measured screen and plate percentages land on real pixels of the art. */
.rig {
  position: relative;
  width: min(100dvw, calc(100dvh * var(--rig-aspect)));
  height: min(100dvh, calc(100dvw / var(--rig-aspect)));
  background: var(--rig-img) center / 100% 100% no-repeat;
}

.screen {
  container-type: inline-size;
  container-name: glass;
  position: absolute;
  left: var(--scr-left);
  top: var(--scr-top);
  width: var(--scr-width);
  height: var(--scr-height);
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: linear-gradient(170deg, var(--screen-lit), var(--screen) 55%, #080b0a);
  box-shadow: inset 0 0 26px rgba(0,0,0,.85), inset 0 0 3px rgba(255,183,92,.12);
}

/* glass: scanlines and a corner sheen, never intercepting a tap */
.screen::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* light falls off towards the corners the way it does behind glass */
    radial-gradient(120% 130% at 50% 45%, rgba(0,0,0,0) 42%, rgba(0,0,0,.5) 100%),
    linear-gradient(115deg, rgba(255,255,255,.045) 0%, rgba(255,255,255,0) 38%),
    repeating-linear-gradient(0deg, rgba(0,0,0,.20) 0 1px, rgba(0,0,0,0) 1px 3px);
  mix-blend-mode: normal;
}

/* ------------------------------------------------------------------ top bar */

.topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 9px 6px;
  border-bottom: 1px solid var(--rule);
  background: rgba(255,183,92,.025);
}

.pet {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border: 1px solid #2c3531;
  border-radius: 5px;
  background: #060908;
  display: grid;
  place-items: center;
  overflow: hidden;
}
.face {
  background-image: url("/static/img/faces.png");
  background-size: 900% 100%;
  background-position: calc(var(--cell) * 12.5%) 0;
  background-repeat: no-repeat;
  display: block;
}
.pet .face {
  width: 90%; height: 90%;
  background-image: url("/static/img/faces.png");
  background-size: 900% 100%;
  background-position: calc(var(--cell) * 12.5%) 0;
  background-repeat: no-repeat;
  filter: drop-shadow(0 0 4px rgba(255,160,60,.55));
  transition: background-position .001s;
}
.pet.changed { animation: petpop .45s ease-out; }
@keyframes petpop {
  0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(255,183,92,.55); }
  35%  { transform: scale(1.14); box-shadow: 0 0 0 5px rgba(255,183,92,0); }
  100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(255,183,92,0); }
}

.ident { flex: 1; min-width: 0; line-height: 1.25; }
.ident .brand {
  display: block;
  font-size: .56rem; letter-spacing: .17em;
  color: var(--amber);
  text-shadow: 0 0 8px rgba(217,149,72,.4);
}
.ident .sub {
  display: block;
  font-size: .56rem; color: var(--ink-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sig { flex: 0 0 auto; text-align: right; font-size: .52rem; letter-spacing: .1em; color: var(--ink-faint); line-height: 1.45; }
.sig > span { display: block; }
.dot { width: 6px; height: 6px; border-radius: 50%; background: var(--phos); display: inline-block; box-shadow: 0 0 5px currentColor; }
.dot.off { background: var(--red); }

.appbar {
  flex: 0 0 auto;
  min-width: 0;
  display: flex; align-items: center; gap: 8px;
  padding: 5px 10px;
  border-bottom: 1px solid var(--rule);
  font-size: .56rem; letter-spacing: .18em;
  color: var(--amber-hi);
  background: rgba(0,0,0,.25);
}

.appbar .grow { flex: 1; }
/* The title never wraps the bar onto a second line and never pushes a control
   off the end: it truncates instead, and the gauge stands down first. */
.appbar .t { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }



/* GARBAGE: how full the Agent's head is, 0-100% of the housekeeping ceiling. */
.garbage { display: flex; align-items: center; gap: 5px; flex: 0 0 auto; }
.garbage .lbl { font-size: .5rem; letter-spacing: .14em; color: var(--ink-faint); }
.garbage .bar {
  width: 40px; height: 5px; border-radius: 1px;
  background: #1a211f; box-shadow: inset 0 0 0 1px #242d2a;
  overflow: hidden; position: relative;
}
.garbage .bar::before {
  content: ""; position: absolute; inset: 0; width: var(--pct);
  background: repeating-linear-gradient(90deg, var(--amber) 0 3px, #0a0d0c 3px 4px);
  transition: width .8s ease;
}
.garbage .pct { font-size: .5rem; color: var(--ink-faint); min-width: 2.1em; text-align: right; }
.garbage.hot .bar::before { background: repeating-linear-gradient(90deg, var(--red) 0 3px, #0a0d0c 3px 4px); }
.garbage.hot .pct, .garbage.hot .lbl { color: var(--red); }

.iconbtn {
  background: none; border: 0; cursor: pointer;
  padding: 0; margin-left: 2px;
  color: var(--ink-faint);
  display: grid; place-items: center;
  flex: 0 0 auto; width: 30px; height: 30px;
}
.iconbtn svg { width: 17px; height: 17px; }
.iconbtn:hover, .iconbtn.on { color: var(--amber-hi); }
.iconbtn[hidden] { display: none; }

/* the icon shows the way out once you are in */
#fullbtn .fs-out { display: none; }
body.fs #fullbtn .fs-in { display: none; }
body.fs #fullbtn .fs-out { display: inline; }

.searchbar {
  flex: 0 0 auto; display: flex; gap: 6px; padding: 6px 8px;
  border-bottom: 1px solid var(--rule); background: rgba(0,0,0,.35);
}
.searchbar[hidden] { display: none; }
.searchbar input {
  flex: 1; min-width: 0; padding: 6px 8px;
  background: #0b100e; border: 1px solid var(--rule); border-radius: 5px;
  color: var(--amber-hi); font-family: var(--mono); font-size: .72rem;
}
.searchbar input:focus { outline: none; border-color: #4a3a26; }
.searchbar button {
  background: none; border: 0; color: var(--ink-faint); cursor: pointer; font-size: .8rem;
}

.hit {
  padding: 7px 9px; margin-bottom: 6px;
  border: 1px solid var(--rule); border-radius: 6px;
  background: #0d1211; cursor: pointer;
}
.hit:hover { border-color: #4a3a26; }
.hit .meta { font-size: .48rem; letter-spacing: .14em; color: var(--ink-faint); margin-bottom: 3px; }
.hit .snip { font-size: .68rem; line-height: 1.45; color: var(--ink-soft); }
.hit mark { background: none; color: var(--amber-hi); }

.msg.flash .bubble { animation: flash 1.6s ease-out; }
@keyframes flash {
  0% { background: rgba(255,183,92,.16); box-shadow: 0 0 0 6px rgba(255,183,92,.1); }
  100% { background: transparent; box-shadow: none; }
}

.older { text-align: center; padding: 6px 0; }
.older button {
  background: none; border: 1px solid var(--rule); border-radius: 5px;
  color: var(--ink-faint); font-family: var(--mono); font-size: .55rem;
  letter-spacing: .1em; padding: 4px 10px; cursor: pointer;
}
.older button:hover { color: var(--amber-hi); border-color: #4a3a26; }

/* HOUSEKEEPING: the device admits, out loud, that it is cleaning itself. */
.hk {
  flex: 0 0 auto;
  position: relative;
  padding: 6px 10px;
  border-bottom: 1px solid #4a3a26;
  background: rgba(255,183,92,.06);
  overflow: hidden;
}
.hk[hidden] { display: none; }
.hk .hklabel {
  position: relative; z-index: 1;
  font-size: .56rem; letter-spacing: .3em; color: var(--amber-hi);
}
.hk::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,183,92,.22), transparent);
  animation: sweep 1.7s linear infinite;
}
@keyframes sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }

.sysline {
  margin: 10px 0;
  font-size: .56rem;
  letter-spacing: .05em;
  color: var(--ink-faint);
  text-align: center;
  font-style: italic;
}

/* A conversation grows from the bottom. Top-aligned chat leaves a void with
   six lines of boot text stranded at the top of it. */
.scroll.stream { display: flex; flex-direction: column; position: relative; }

/* An empty conversation is a lot of black. Put the Agent in it, watching. */
.standby {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  pointer-events: none;
}
.standby .face { width: 46%; aspect-ratio: 1; opacity: .085; }
.standby[hidden] { display: none; }
#streamInner { margin-top: auto; }

/* -------------------------------------------------------------------- panes */

.pane { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.pane[hidden] { display: none; }

.scroll {
  flex: 1; min-height: 0; min-width: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 11px 12px;
  scrollbar-width: thin;
  overscroll-behavior: contain;
}
.scroll::-webkit-scrollbar { width: 5px; }
.scroll::-webkit-scrollbar-thumb { background: #29312e; border-radius: 3px; }

.msg { margin-bottom: 15px; }
.msg .who { font-size: .5rem; letter-spacing: .2em; color: var(--ink-faint); margin-bottom: 4px; }
.msg.me { text-align: right; }
.msg.me .bubble {
  display: inline-block; text-align: left;
  background: #17201d; border: 1px solid #27322e;
  border-radius: 9px 9px 2px 9px; padding: 7px 10px;
  color: #cbe3d7; font-size: .8rem; font-family: var(--mono);
}
.msg.agent .bubble { font-family: var(--serif); font-size: .92rem; line-height: 1.55; color: var(--ink); }
/* a URL or a long identifier is not a reason to widen the device */
.msg .bubble { overflow-wrap: anywhere; }
.msg.agent .bubble > *:first-child { margin-top: 0; }
.msg.agent .bubble > *:last-child { margin-bottom: 0; }

/* cursor rides the end of the last revealed block, not below it */
.bubble.typing > *:last-child::after,
.bubble.typing:not(:has(*))::after {
  content: "";
  display: inline-block;
  width: .48em; height: .95em;
  background: var(--amber-hi);
  margin-left: 2px;
  vertical-align: -.12em;
  box-shadow: 0 0 6px rgba(255,183,92,.6);
  animation: blink .85s steps(1) infinite;
}
@keyframes blink { 50% { opacity: 0; } }

.telemetry {
  font-size: .58rem; color: var(--amber); letter-spacing: .02em;
  margin: 2px 0; opacity: .9;
}
.telemetry::before { content: "◆ "; color: var(--ink-faint); }
.telemetry.done { color: var(--ink-faint); opacity: .55; }

/* ------------------------------------------------- it is doing something */

/* a live row while the Agent works: pulse, what it is doing, how long it has
   been doing it. Silence after SEND is the thing this exists to kill. */
.activity {
  display: flex; align-items: center; gap: 7px;
  margin: 6px 0 10px;
  font-size: .6rem; letter-spacing: .04em;
  color: var(--amber);
}
.activity .pulse {
  width: 7px; height: 7px; flex: 0 0 auto;
  background: var(--amber);
  transform: rotate(45deg);
  animation: beat 1.1s ease-in-out infinite;
}
@keyframes beat {
  0%, 100% { opacity: .25; transform: rotate(45deg) scale(.8); }
  50%      { opacity: 1;   transform: rotate(45deg) scale(1.25); }
}
.activity .verb { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.activity .verb::after {
  content: "";
  animation: dots 1.4s steps(4, end) infinite;
}
@keyframes dots { 0% { content: ""; } 25% { content: "·"; } 50% { content: "··"; } 75% { content: "···"; } }
.activity .elapsed { color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.activity.slow .elapsed { color: var(--red); }

/* indeterminate carrier above the composer */
.wire {
  display: block; height: 2px; flex: 0 0 auto;
  background: #161d1b; overflow: hidden; position: relative;
}
.wire[hidden] { display: none; }
.wire::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 40%;
  background: linear-gradient(90deg, transparent, var(--amber), transparent);
  animation: carrier 1.3s linear infinite;
}
@keyframes carrier { from { left: -40%; } to { left: 100%; } }

/* the pet gets a ring while working — the ring means busy, the face still
   means whatever it actually feels */
.pet.busy { box-shadow: 0 0 0 1px var(--amber), 0 0 9px rgba(255,183,92,.45); }

.sendbusy #send { position: relative; color: transparent; }
.sendbusy #send::after {
  content: ""; position: absolute; inset: 0; margin: auto;
  width: 9px; height: 9px; border-radius: 50%;
  border: 1.5px solid #4a3a26; border-top-color: var(--amber-hi);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* a fault is a fault, not a whisper */
.fault {
  margin: 8px 0 14px; padding: 9px 11px;
  border: 1px solid #5c2a26; border-left-width: 3px;
  border-radius: 6px; background: rgba(226,86,74,.07);
}
.fault .label {
  font-size: .5rem; letter-spacing: .2em; color: var(--red); margin-bottom: 4px;
}
.fault .what { font-size: .72rem; line-height: 1.5; color: var(--ink); }
.fault .hint { margin-top: 5px; font-size: .6rem; color: var(--ink-faint); }

.composer {
  flex: 0 0 auto;
  border-top: 1px solid var(--rule);
  padding: 7px;
  display: flex; gap: 6px;
  background: rgba(0,0,0,.3);
}
.composer textarea {
  flex: 1; resize: none; height: 34px; max-height: 84px;
  padding: 8px 9px;
  background: #0b100e; border: 1px solid var(--rule); border-radius: 6px;
  color: var(--ink); font-family: var(--mono); font-size: .78rem; line-height: 1.35;
}
.composer textarea:focus { outline: none; border-color: #3a4642; }
.composer button {
  flex: 0 0 auto; width: 38px;
  background: linear-gradient(#2e2318, #221a11);
  border: 1px solid #4a3a26; border-radius: 6px;
  color: var(--amber-hi); font-size: .95rem; cursor: pointer;
}
.composer button:disabled { opacity: .35; cursor: wait; }

/* --------------------------------------------------------------------- dock */

/* The keys are not on the screen. They sit in the recessed plate the chassis
   was rendered with, which is why the device reads as a device and not as a
   panel mounted on one. Container units size the caps off the plate itself, so
   they scale with the frame instead of drifting out of it. */
.dock {
  position: absolute;
  left: var(--plate-left);
  top: var(--plate-top);
  width: var(--plate-width);
  height: var(--plate-height);
  container-type: size;
  display: flex;
  gap: min(0.9cqw, 7px);
  padding: min(4cqh, 7px) min(0.8cqw, 7px);
}

/* The keycap is a photograph of a key, in two states, not a CSS gradient
   pretending to be one. border-image is the tool: the slice keeps the machined
   rim and the chipped corners at a fixed size while only the flat centre face
   stretches, so one render serves a wide deck key and a small handheld one
   without smearing the bevel. */
.dock button {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: min(4cqh, 4px);
  background: none;
  border-style: solid;
  border-color: transparent;
  border-width: clamp(7px, 19cqh, 32px);
  border-image: url("/static/img/key-off.png") 25% fill stretch;
  color: #7b838a;
  font-family: var(--mono);
  font-size: clamp(6.5px, 11cqh, 14px);
  letter-spacing: .07em;
  cursor: pointer;
  padding: 0;
  transition: color .12s;
}
.dock button .ico {
  width: clamp(12px, 24cqh, 34px);
  height: clamp(12px, 24cqh, 34px);
  opacity: .82;
}

/* it is a key: it goes down when you push it */
.dock button:active { transform: translateY(1.5px); filter: brightness(.82); }

/* lit from behind, and the legend reads as backlit rather than recoloured */
.dock button.on {
  border-image-source: url("/static/img/key-on.png");
  color: #3a2408;
  text-shadow: 0 0 6px rgba(255,214,150,.5);
}
.dock button.on .ico { opacity: 1; filter: drop-shadow(0 0 4px rgba(255,220,160,.7)); }

/* ------------------------------------------------------- markdown on glass */

.md h1, .md h2, .md h3 {
  font-family: var(--mono); letter-spacing: .05em;
  color: var(--amber); margin: 1.2em 0 .45em; font-size: .72rem;
}
.md h3 { color: var(--amber-hi); font-size: .66rem; }
.md p { margin: .6em 0; }
.md strong { color: var(--amber-hi); font-weight: 600; }
.md em { color: var(--ink-soft); }
.md ul, .md ol { margin: .5em 0; padding-left: 1.1em; }
.md li { margin: .25em 0; }
.md hr { border: 0; border-top: 1px solid var(--rule); margin: 1em 0; }
.md blockquote {
  margin: .75em 0; padding: .15em 0 .15em .8em;
  border-left: 2px solid var(--amber); color: var(--ink-soft);
}
.md code {
  font-family: var(--mono); font-size: .82em;
  background: #16201d; padding: .08em .3em; border-radius: 3px; color: var(--phos);
}
/* A wide table must scroll inside itself. Left as a normal table its
   min-content width is a floor on the whole message flow, so a five-column
   weapons table widened every paragraph past the glass and the text was cut off
   mid-word at the bezel — visible on a short viewport, where the glass is
   narrower than the table. */
.md table {
  /* A scroll container contributes nothing to min-content width, which is the
     point: the table scrolls inside itself instead of setting a floor for the
     device. `width: max-content` here would put the floor straight back. */
  display: block;
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  border-collapse: collapse;
  margin: .7em 0;
  font-family: var(--mono);
  font-size: .6rem;
}
.md th, .md td { border: 1px solid var(--rule); padding: 4px 6px; text-align: left; vertical-align: top; }
.md th { color: var(--ink-faint); letter-spacing: .06em; font-weight: 400; background: #0e1412; }

/* ------------------------------------------------------------------ storage */

.filelist { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 12px; }
.filelist button {
  padding: 4px 7px;
  background: #0d1211; border: 1px solid var(--rule); border-radius: 5px;
  color: var(--ink-soft); font-family: var(--mono); font-size: .56rem; cursor: pointer;
}
.filelist button:hover { color: var(--amber-hi); border-color: #4a3a26; }
.filelist button.on { background: #1a1510; border-color: #5a4529; color: var(--amber-hi); }
.filelist button.canon { border-style: dashed; }
.filelist .tag { width: 100%; font-size: .5rem; letter-spacing: .14em; color: var(--ink-faint); margin-top: 5px; }

.empty { color: var(--ink-faint); font-size: .7rem; font-style: italic; }

.portrait { margin: 0 0 12px; }
.portrait img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--rule); border-radius: 5px;
  filter: saturate(.88) contrast(1.03);
}
.portrait figcaption { margin-top: 5px; font-size: .5rem; letter-spacing: .12em; color: var(--ink-faint); }

.boot { font-size: .6rem; color: var(--phos); line-height: 1.75; white-space: pre-wrap; }
.boot.slow { color: var(--ink-faint); }

/* --------------------------------------------------------- the status rail */

/* Only the deck has a column to spare. On the handheld this is dead weight. */
.railstat { display: none; }

/* ------------------------------------------------ landscape: the deck layout */

/* The deck's glass is 2.4:1. A single top-to-bottom column in it strands the
   title on one side and wastes the other, which is the shape that read as a
   dialog box. So: conversation takes the width, and the identity, the carrier
   and the gauge move into a rail down the right edge — a readout, not a bar. */
@media (min-width: 900px) and (min-aspect-ratio: 13/10) {
  .screen {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(140px, 19%);
    grid-template-rows: auto auto auto 1fr;
    grid-template-areas:
      "appbar rail"
      "search rail"
      "hk     rail"
      "main   rail";
  }

  .appbar    { grid-area: appbar; }
  .searchbar { grid-area: search; }
  .hk        { grid-area: hk; }
  #comms, #panel { grid-area: main; min-width: 0; }

  .topbar {
    grid-area: rail;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 11px;
    padding: 13px 11px;
    border-bottom: 0;
    border-left: 1px solid var(--rule);
    background: rgba(255,183,92,.02);
    overflow: hidden;
  }

  .topbar .pet {
    width: clamp(44px, 64%, 92px);
    height: auto; aspect-ratio: 1;
    margin: 2px auto 0;
    border-radius: 6px;
  }
  .topbar .ident { flex: 0 0 auto; text-align: center; }
  .topbar .ident .brand { font-size: .6rem; }
  .topbar .ident .sub { white-space: normal; font-size: .54rem; line-height: 1.4; }
  .topbar .sig {
    text-align: center; font-size: .54rem; line-height: 1.7;
    padding-top: 9px; border-top: 1px solid var(--rule);
  }

  /* the gauge belongs on the rail here; the appbar keeps the title and search */
  .appbar .garbage { display: none; }

  .railstat {
    display: block;
    margin: 0; padding-top: 10px;
    border-top: 1px solid var(--rule);
    font-size: .52rem; letter-spacing: .1em;
  }
  .railstat > div {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: 6px; padding: 3px 0;
  }
  .railstat dt { color: var(--ink-faint); }
  .railstat dd {
    margin: 0; color: var(--amber);
    font-variant-numeric: tabular-nums;
    text-shadow: 0 0 7px rgba(217,149,72,.35);
  }
  .railstat dd.bad { color: var(--red); text-shadow: 0 0 7px rgba(226,86,74,.4); }

  /* wide glass, so let the prose take a comfortable measure rather than the
     full 1100px — a line you have to track back across is not readable */
  .scroll { padding: 14px 18px; }
  #streamInner { max-width: 74ch; margin-left: auto; margin-right: auto; }
  .msg.agent .bubble { font-size: .95rem; }
  /* documents get a measure too — a 1300px line is not read, it is scanned */
  #panel { padding-inline: max(18px, calc((100% - 96ch) / 2)); }

  /* a 34px composer under a 146px key looks like an afterthought */
  .composer { padding: 9px; gap: 9px; }
  .composer textarea { height: 44px; font-size: .84rem; }
  .composer button { width: 52px; font-size: 1.1rem; }
  .iconbtn { width: 36px; height: 36px; }
  .iconbtn svg { width: 19px; height: 19px; }
}

/* ----------------------------------------------------- phone: the handheld */

/* The phone keeps the chassis — it is a commlink, and the frame housing its own
   keys is the whole point. That costs glass, so the chrome inside pays it back:
   the top bar and the app bar used to be two rows and ~90px of a 933px viewport
   and now share one, and the brand block — decoration, already named in the
   title — stands down. */
@media (max-width: 760px), (max-height: 620px) {
  /* The deck letterboxes, which is right on a desk. A phone has no room to
     spend on black: the handheld is scaled to COVER the viewport and the shell
     runs off the edges. Aspect is preserved — this crops, it does not stretch —
     and --phone-zoom crops a little further still, trading outer shell for
     glass. Everything is measured off the same render, so the screen and the
     plate follow the crop automatically. */
  body.rigged { position: relative; }
  .rig {
    position: absolute;
    left: 50%; top: 0;
    /* Tall enough that the glass starts at the top edge and the key plate ends
       at the bottom one — --rig-fill is that ratio, derived from the measured
       rects rather than guessed, so it cannot clip the bottom row of keys on a
       phone shaped differently from this one. max() keeps it covering
       horizontally too, and width follows height so nothing stretches. */
    /* Height alone decides the scale. Covering the width as well cannot be
       satisfied at the same time on a squatter phone, and there it wins and
       clips the bottom row of keys — measured, on a 420x840 viewport. Losing a
       few pixels to letterbox at the sides is the cheaper failure. */
    /* Scaled against the SAFE height, not the whole viewport: the glass then
       starts below the camera cut-out and the key plate ends above the home
       indicator, and the shell — which is what the chassis has up there anyway
       — is what sits under the hardware. A punch-hole over the GARBAGE gauge is
       the phone putting a camera through the screen; the fix is to give it
       bezel to sit on. */
    --h: calc((100dvh - var(--safe-top) - var(--safe-bottom)
              - 2 * var(--shell)) * var(--rig-fill));
    height: var(--h);
    width: calc(var(--h) * var(--rig-aspect));
    /* a percentage in translate is of the element, so this lifts the shell
       above the glass off the top of the screen exactly; the inset pushes it
       back down by however much the hardware is covering */
    transform: translate(-50%, calc(var(--safe-top) + var(--shell) - var(--scr-top)));
  }

  .screen {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    grid-template-rows: auto auto auto 1fr;
    grid-template-areas:
      "top    appbar"
      "search search"
      "hk     hk"
      "main   main";
  }
  .screen::after { background: repeating-linear-gradient(0deg, rgba(0,0,0,.16) 0 1px, rgba(0,0,0,0) 1px 3px); }

  .topbar {
    grid-area: top;
    gap: 7px;
    padding: 0 2px 0 8px;
    border-bottom: 1px solid var(--rule);
  }
  .topbar .pet { width: 30px; height: 30px; }
  .topbar .ident { display: none; }
  .topbar .sig { font-size: 0; }          /* keep the carrier lamp, drop its prose */
  .topbar .sig #date { display: none; }
  .topbar .sig .dot { width: 7px; height: 7px; }

  .appbar { grid-area: appbar; min-height: 44px; }
  .searchbar { grid-area: search; }
  .hk { grid-area: hk; }
  #comms, #panel { grid-area: main; min-width: 0; }

  .scroll { padding: 10px 11px; }
  .msg { margin-bottom: 13px; }
  .msg.agent .bubble { font-size: 1rem; }

  /* ---- touch targets. Below ~44px a finger misses, and every control on this
     device was under 25px. Sizes here are floors, not decoration. ---- */
  .iconbtn { width: 44px; height: 44px; margin-left: 0; }
  .iconbtn svg { width: 20px; height: 20px; }

  .composer { padding: 6px 7px 7px; gap: 7px; }
  .composer textarea { font-size: 16px; height: 44px; } /* 16px stops iOS zooming on focus */
  .composer button { width: 52px; min-height: 44px; font-size: 1.1rem; }

  .searchbar { padding: 7px 8px; }
  .searchbar input { min-height: 44px; font-size: 16px; }
  .searchbar button { min-width: 44px; min-height: 44px; font-size: 1rem; }

  .older button { min-height: 44px; padding: 4px 16px; font-size: .62rem; }
  .filelist button { min-height: 44px; padding: 4px 12px; font-size: .64rem; }
  .hit { padding: 10px 11px; }

  /* Eight keys across a phone-width plate would be ~43px each — under the
     floor. Two rows of four are ~85px wide instead, and the plate is deep
     enough to carry them. */
  .dock {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    grid-auto-rows: 1fr;
    /* the plate is only ~99px deep on a phone; every px of padding comes
       straight off the key height, and 44 is a floor, not a target */
    gap: 2px;
    padding: 2px 3px;
  }
  .dock button {
    gap: 1px;
    font-size: clamp(6.5px, 13cqh, 10px);
    border-width: clamp(5px, 7cqh, 9px);
  }
  .dock button .ico {
    width: clamp(12px, 17cqh, 19px);
    height: clamp(12px, 17cqh, 19px);
  }
}

/* --------------------------------------------- narrow glass: shed the extras */

/* Sized against the GLASS, not the viewport: on a phone the glass width is
   derived from the viewport HEIGHT, so a media query asks the wrong question
   entirely. Last in the file and specific enough to beat each component's own
   display — an earlier copy of this lost on source order and did nothing.
   The title is the one thing that must survive: it says which app you are in. */
@container glass (max-width: 360px) {
  .appbar .garbage { display: none; }
}
@container glass (max-width: 250px) {
  .topbar .pet { display: none; }
}

/* ------------------------------------------------ composing, keyboard up */

/* The glass leaves the chassis and takes exactly the room the keyboard is not
   using. Only on a phone, only while the keyboard is actually up. The rig's
   transform has to go: a transformed ancestor becomes the containing block for
   position:fixed, and the glass would stay pinned under the keyboard. */
@media (max-width: 760px), (max-height: 620px) {
  body.composing .rig {
    position: static;
    transform: none;
    width: auto; height: auto;
    background: #000;
  }
  body.composing .screen {
    position: fixed;
    left: 0;
    top: var(--vv-top, 0px);
    width: 100vw;
    /* Whichever one actually shrank wins. With interactive-widget=resizes-content
       the layout viewport shrinks and 100dvh is right; where that is ignored the
       visual viewport is the one that knows. Taking the smaller covers both, and
       covers a browser that does neither without making things worse. */
    height: min(var(--vv-height, 100dvh), 100dvh);
    border-radius: 0;
    z-index: 5;
  }
  /* the device's own keys are no use in the middle of a sentence */
  body.composing .dock { display: none; }
  /* and the composer earns a little more room to show the line being typed */
  body.composing .composer textarea { max-height: 40dvh; }
}
