:root {
  --gold:#b0894f;
  --gold-deep:#97713a;
  --gold-soft:#f3ebdc;
  --gold-bright:#d8b676;
  --navy:#1f2a3c;
  --navy-2:#26344a;
  --navy-soft:#e7ebf1;
  --ink:#1b1f2a;
  --ivory:#faf8f4;
  --bg:#faf8f4;
  --bg-2:#f3efe8;
  --surface:#ffffff;
  --surface-2:#fbfaf7;
  --line:#e8e3da;
  --line-soft:#f0ece4;
  --ink-soft:#555c6b;
  --ink-faint:#8a909c;
  --ok:#2f8f5b;
  --ok-soft:#e7f3ec;
  --warn:#c98a25;
  --warn-soft:#faf0db;
  --danger:#c0492f;
  --danger-soft:#f8e8e3;
  --info:#2f6f9f;
  --info-soft:#e6eef5;
  --shadow-1:0 1px 2px rgba(27,31,42,.05);
  --shadow-2:0 18px 50px rgba(27,31,42,.12);
  --shadow-3:0 30px 80px rgba(31,42,60,.18);
  --radius-sm:10px;
  --radius:18px;
  --radius-lg:26px;
  /* Fluid layout: scales continuously with the window, no breakpoint jumps. */
  --sidebar:clamp(228px, 19vw, 292px);
  --topbar:clamp(52px, 8vh, 76px);
  --pad:clamp(12px, 2.2vh, 24px);
  --gap:clamp(10px, 1.7vh, 16px);
  --card-pad:clamp(12px, 1.9vh, 18px);
  --font-serif:Georgia, Cambria, "Times New Roman", serif;
  --font-sans:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --font-mono:"SFMono-Regular", Consolas, "Liberation Mono", monospace;
}

* { box-sizing:border-box; }

html, body, #app { margin:0; min-height:100vh; }

body {
  background:
    radial-gradient(circle at 10% 10%, rgba(176,137,79,.13), transparent 32%),
    linear-gradient(135deg, var(--ivory), var(--bg-2));
  color:var(--ink);
  font-family:var(--font-sans);
  line-height:1.5;
  overflow:hidden;
}

button, input, select, textarea { font:inherit; }
button { border:0; cursor:pointer; }

.boot { display:grid; min-height:100vh; place-items:center; padding:24px; }

.boot-card {
  display:flex;
  gap:18px;
  align-items:center;
  background:rgba(255,255,255,.82);
  border:1px solid var(--line);
  border-radius:28px;
  box-shadow:var(--shadow-3);
  padding:24px 28px;
}

.boot-card h1 { font-family:var(--font-serif); margin:2px 0 0; font-size:1.4rem; }

.gate-logo { width:72px; height:72px; border-radius:20px; display:block; }

/* THE SIDEBAR IS GONE.

   It was permanent furniture: ~280 px of every screen, on every view, forever — so that a list
   of twenty destinations could be read at a glance roughly twice a day. On a 32-camera wall that
   column is video the operator paid for and never saw.

   Navigation is a MOMENT, not a state. It gets the space while it is being used and none of it
   afterwards. What replaces it is one button and a panel that, when open, is far more readable
   than the cramped rail ever was. */
.shell {
  display:grid;
  grid-template-columns:1fr;
  height:100vh;
  overflow:hidden;
}

.sidebar {
  background:linear-gradient(180deg, var(--navy), #172030);
  color:#f8f4ee;
  padding:22px 16px;
  display:flex;
  flex-direction:column;
  height:100vh;
  min-height:0;
  overflow-y:auto;
  box-shadow:14px 0 40px rgba(31,42,60,.18);
  position:relative;
  isolation:isolate;
}
.sidebar::-webkit-scrollbar { width:8px; }
.sidebar::-webkit-scrollbar-thumb { background:rgba(255,255,255,.14); border-radius:8px; }

.sidebar::before {
  content:"";
  position:absolute;
  inset:0;
  background:
    radial-gradient(circle at 20% 0%, rgba(216,182,118,.20), transparent 35%),
    linear-gradient(180deg, rgba(255,255,255,.04), transparent 45%);
  z-index:-1;
}

.brand {
  display:flex;
  align-items:center;
  gap:14px;
  padding:4px 10px 18px;
  border-bottom:1px solid rgba(255,255,255,.10);
}

.brand-mark {
  width:48px;
  height:48px;
  border-radius:15px;
  background:linear-gradient(145deg, var(--gold), var(--gold-deep));
  display:grid;
  place-items:center;
  color:white;
  font-family:var(--font-serif);
  font-size:1.45rem;
  font-weight:700;
  box-shadow:0 12px 28px rgba(151,113,58,.35);
  flex:0 0 auto;
}

.brand-name { font-family:var(--font-serif); font-size:1.20rem; line-height:1.05; letter-spacing:.01em; }
.brand-subtitle { color:#c9d0db; font-size:.78rem; margin-top:2px; }

.nav { display:flex; flex-direction:column; gap:5px; margin-top:18px; }

.nav-button {
  color:#e7ebf1;
  display:flex;
  align-items:center;
  gap:12px;
  width:100%;
  padding:11px 12px;
  border-radius:14px;
  background:transparent;
  text-align:left;
  transition:background .16s ease, color .16s ease, transform .16s ease;
}

.nav-button:hover { background:rgba(255,255,255,.08); transform:translateX(2px); }

.nav-button.active {
  background:linear-gradient(145deg, rgba(176,137,79,.95), rgba(151,113,58,.95));
  color:white;
  box-shadow:0 12px 26px rgba(0,0,0,.16);
}

.nav-ico {
  width:28px;
  height:28px;
  border-radius:10px;
  display:grid;
  place-items:center;
  background:rgba(255,255,255,.08);
}

.nav-button.active .nav-ico { background:rgba(255,255,255,.18); }

.sidebar-footer { margin-top:auto; padding:16px 10px 4px; }

.mini-status {
  background:rgba(255,255,255,.08);
  border:1px solid rgba(255,255,255,.10);
  border-radius:18px;
  padding:14px;
}

.mini-status .label {
  color:#c9d0db;
  font-size:.74rem;
  text-transform:uppercase;
  letter-spacing:.12em;
}

.mini-status .value { font-weight:700; margin-top:5px; }

.workspace {
  display:grid;
  grid-template-rows:var(--topbar) 1fr;
  min-width:0;
  min-height:0;
}

.topbar {
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:18px;
  padding:clamp(8px, 1.4vh, 14px) var(--pad);
  background:rgba(250,248,244,.86);
  border-bottom:1px solid var(--line);
  backdrop-filter:blur(18px);
}

.view-title { display:flex; align-items:center; gap:12px; min-width:0; }

.topbar-mark, .topbar-logo {
  width:40px; height:40px; border-radius:12px; font-size:1.15rem; flex:0 0 auto;
}

.view-title h1 {
  font-family:var(--font-serif);
  margin:0;
  font-size:clamp(1.2rem, 2.9vh, 1.55rem);
  line-height:1.1;
  white-space:nowrap;
}

.view-title p {
  margin:3px 0 0;
  color:var(--ink-soft);
  font-size:.88rem;
}

.top-actions { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }

.content { min-height:0; overflow-y:auto; overflow-x:hidden; padding:var(--pad); }

.grid { display:grid; gap:var(--gap); }
.grid.cols-2 { grid-template-columns:repeat(2, minmax(0, 1fr)); }
.grid.cols-3 { grid-template-columns:repeat(3, minmax(0, 1fr)); }
.grid.cols-4 { grid-template-columns:repeat(4, minmax(0, 1fr)); }

.card {
  background:rgba(255,255,255,.86);
  border:1px solid var(--line);
  border-radius:var(--radius);
  box-shadow:var(--shadow-1);
  overflow:hidden;
}

.card.pad { padding:var(--card-pad); }

.card-header {
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:12px;
  padding:var(--card-pad) var(--card-pad) 0;
}

.card-title { font-weight:750; margin:0; }
.card-subtitle { color:var(--ink-soft); font-size:.86rem; margin:4px 0 0; }

.eyebrow {
  font-size:.72rem;
  letter-spacing:.17em;
  text-transform:uppercase;
  color:var(--gold-deep);
  font-weight:750;
}

.hero {
  position:relative;
  overflow:hidden;
  background:
    radial-gradient(circle at 8% 0%, rgba(216,182,118,.22), transparent 38%),
    linear-gradient(135deg, var(--surface), var(--gold-soft));
  border:1px solid var(--line);
  border-radius:28px;
  padding:clamp(16px, 2.6vh, 28px);
  box-shadow:var(--shadow-2);
  margin-bottom:var(--gap);
}

.hero h2 {
  font-family:var(--font-serif);
  font-size:clamp(1.4rem, 3.4vh, 2.08rem);
  line-height:1.06;
  letter-spacing:-.02em;
  margin:6px 0 10px;
  max-width:880px;
}

.hero p { max-width:860px; color:var(--ink-soft); margin:0; }

.hero-actions { display:flex; flex-wrap:wrap; gap:10px; margin-top:clamp(12px, 2vh, 20px); }

.btn {
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  min-height:39px;
  padding:9px 14px;
  border-radius:12px;
  color:var(--ink);
  background:var(--surface);
  border:1px solid var(--line);
  box-shadow:var(--shadow-1);
  font-weight:720;
  transition:transform .14s ease, box-shadow .14s ease, background .14s ease;
}

.btn:hover { transform:translateY(-1px); box-shadow:0 10px 24px rgba(27,31,42,.10); }

.btn.primary {
  color:white;
  background:linear-gradient(145deg, var(--gold), var(--gold-deep));
  border-color:rgba(151,113,58,.30);
}

.btn.dark {
  color:white;
  background:linear-gradient(145deg, var(--navy), var(--navy-2));
  border-color:rgba(31,42,60,.20);
}

.btn.danger {
  color:var(--danger);
  background:var(--danger-soft);
  border-color:rgba(192,73,47,.18);
}

.btn.ghost { background:transparent; }
.btn.small { min-height:31px; padding:6px 10px; border-radius:10px; font-size:.82rem; }

.metric { padding:var(--card-pad); }
.metric .num { font-family:var(--font-mono); font-size:clamp(1.25rem, 2.9vh, 1.75rem); line-height:1; font-weight:750; color:var(--ink); }
.metric .label { color:var(--ink-soft); margin-top:clamp(4px, .8vh, 8px); font-size:.86rem; }
.metric .trend { margin-top:clamp(6px, 1.2vh, 12px); }

.pill {
  display:inline-flex;
  align-items:center;
  gap:6px;
  border-radius:999px;
  padding:4px 9px;
  font-size:.76rem;
  font-weight:750;
  border:1px solid transparent;
}

.pill.ok { background:var(--ok-soft); color:var(--ok); border-color:rgba(47,143,91,.14); }
.pill.warn { background:var(--warn-soft); color:var(--warn); border-color:rgba(201,138,37,.18); }
.pill.danger { background:var(--danger-soft); color:var(--danger); border-color:rgba(192,73,47,.18); }
.pill.info { background:var(--info-soft); color:var(--info); border-color:rgba(47,111,159,.18); }
.pill.gold { background:var(--gold-soft); color:var(--gold-deep); border-color:rgba(176,137,79,.20); }
.pill.navy { background:var(--navy-soft); color:var(--navy); border-color:rgba(31,42,60,.12); }

.split { display:grid; grid-template-columns:minmax(0, 1.15fr) minmax(320px, .85fr); gap:var(--gap); align-items:start; }

.form-grid { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:clamp(8px, 1.3vh, 12px); }

.field { display:flex; flex-direction:column; gap:clamp(3px, .6vh, 6px); }
.field.full { grid-column:1 / -1; }

.field label {
  font-size:.76rem;
  color:var(--ink-soft);
  text-transform:uppercase;
  letter-spacing:.08em;
  font-weight:800;
}

.field input, .field select, .field textarea {
  width:100%;
  border:1px solid var(--line);
  border-radius:12px;
  padding:clamp(7px, 1.2vh, 10px) 12px;
  outline:0;
  background:var(--surface);
  color:var(--ink);
  transition:border .14s ease, box-shadow .14s ease;
}

.field input:focus, .field select:focus, .field textarea:focus {
  border-color:rgba(176,137,79,.56);
  box-shadow:0 0 0 4px rgba(176,137,79,.14);
}

/* A checkbox is not a text field, and `width:100%` above was stretching it across the whole row —
   which is why the box sat alone in the middle and its label was flung to the far right edge. It
   looked like a broken layout because it WAS one: one rule written for one kind of input, applied
   to all of them. */
.field input[type="checkbox"],
.field input[type="radio"] {
  width:auto;
  flex:0 0 auto;
  margin:0;
  padding:0;
  accent-color:var(--gold);
  transform:scale(1.15);
}

/* And the caption beside a checkbox is a SENTENCE, not a field name. Uppercase + letter-spacing is
   for the tiny labels above inputs; applied to "HTTPS (encrypt the connection)" it shouts. */
.field label:has(input[type="checkbox"]),
.field label:has(input[type="radio"]) {
  flex-direction:row;
  text-transform:none;
  letter-spacing:normal;
  font-size:.92rem;
  font-weight:600;
  color:var(--ink);
}

.table-wrap { overflow:auto; border:1px solid var(--line); border-radius:18px; background:var(--surface); }

table { width:100%; border-collapse:collapse; min-width:940px; }

th, td {
  padding:12px 14px;
  text-align:left;
  border-bottom:1px solid var(--line-soft);
  vertical-align:middle;
  font-size:.9rem;
}

th { color:var(--ink-faint); text-transform:uppercase; letter-spacing:.07em; font-size:.70rem; }
tr:last-child td { border-bottom:0; }

.mono { font-family:var(--font-mono); font-size:.82rem; word-break:break-all; }
.footer-note { color:var(--ink-faint); font-size:.82rem; margin-top:8px; }

.source-grid { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:14px; }

.source-card {
  border:1px solid var(--line);
  background:var(--surface);
  border-radius:18px;
  padding:16px;
  box-shadow:var(--shadow-1);
}

.source-card h3 { margin:.25rem 0; }
.source-meta { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }

.tile-grid { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:14px; }
.tile-grid.wall-4 { grid-template-columns:repeat(2, minmax(0,1fr)); }
.tile-grid.wall-9 { grid-template-columns:repeat(3, minmax(0,1fr)); }
.tile-grid.wall-16 { grid-template-columns:repeat(4, minmax(0,1fr)); }

/* ---------- control-room mode ----------

   The SEHL identity has two faces, and they are not a style choice — they are a function
   of what the screen is FOR.

     Console (add cameras, storage, audit, users) -> ivory. It is a document. You read it.
     Wall (watch)                                 -> deep navy. It is a control room.

   Why the wall must be dark: on an ivory background the interface is brighter than the
   video, so the eye is pulled toward the chrome and away from the scene — the exact
   opposite of what a camera wall is for. Dark surroundings also let real luminance
   differences in the footage register, which is how a human notices movement in a shadow
   at 3 a.m. Every serious VMS is dark, and not because designers like it.

   Gold survives only as the brand mark and the active focus ring. It never competes with
   red and amber, which are reserved for threat. */
body.control-room {
  --bg:#0d1219;
  --bg-2:#131a24;
  --surface:#161e29;
  --surface-2:#1b2431;
  --line:#243040;
  --line-soft:#1e2836;
  --ink:#e7ecf3;
  --ink-soft:#9fb0c3;
  --ink-faint:#6d7f93;
  --navy-soft:#1b2431;
  --gold-soft:#2a2418;
  background:var(--bg);
  color:var(--ink);
}

body.control-room .content { background:var(--bg); }

/* The topbar was the last strip of daylight on the wall — a bright band across the top of a
   dark room, which is exactly what a control room is designed to avoid. The subtitle goes
   too: "Operator wall prepared for gateway playback" is a sentence for a brochure, not for
   someone watching 32 cameras. */
body.control-room .topbar {
  background:var(--bg);
  border-bottom:1px solid var(--line);
  color:var(--ink);
}
body.control-room .view-title h1 { color:var(--ink); }
body.control-room .view-title p { display:none; }
body.control-room .top-actions select {
  background:var(--surface-2);
  color:var(--ink);
  border-color:var(--line);
}

/* The wall's own control row: layout switch only, no panel around it. */
.wall-bar {
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:8px;
  margin-bottom:12px;
}
body.control-room .card { background:var(--surface); border-color:var(--line); }
body.control-room .hero { background:linear-gradient(135deg, #16202c, #0f1720); border-color:var(--line); }
body.control-room .btn { background:var(--surface-2); color:var(--ink); border-color:var(--line); }
body.control-room .btn:hover { background:#222e3d; }
body.control-room .btn.primary { background:var(--gold); color:#12161d; border-color:transparent; }
body.control-room .btn.danger { background:rgba(179,38,30,.16); color:#ff8a80; border-color:rgba(179,38,30,.4); }
body.control-room .video-tile { border-color:#1f2937; }
body.control-room .tile-bar { background:rgba(9,13,18,.96); border-top-color:#1f2937; }
body.control-room .pill { background:var(--surface-2); color:var(--ink-soft); }

/* On dark, the alert must own the screen: solid fill, not a tint. */
body.control-room .attention {
  background:linear-gradient(180deg, rgba(179,38,30,.16), rgba(179,38,30,.02) 70%);
  border-color:rgba(255,120,110,.35);
}
body.control-room .attention-item { background:#1a2330; border-color:#26313f; }
body.control-room .attention-threat.high   { background:rgba(255,120,110,.16); color:#ff8a80; }
body.control-room .attention-threat.medium { background:rgba(226,168,64,.16); color:#e2a840; }
body.control-room .attention-msg { color:var(--ink); }

/* ---------- attention strip ----------

   Colour here is information, not decoration. In the rest of the app gold is the brand
   accent and it is everywhere; on the wall that would be fatal, because if everything
   glows, a real alarm glows no louder than a Save button. So: RED and AMBER appear
   nowhere else in the wall, and they mean exactly one thing — this needs a human. */
.attention {
  border-radius:16px;
  background:linear-gradient(180deg, rgba(179,38,30,.06), transparent 60%);
  border:1px solid rgba(179,38,30,.22);
  padding:12px 14px 14px;
}

.attention-head {
  display:flex; align-items:center; gap:8px;
  margin-bottom:10px;
  font-size:.9rem;
}

/* A slow pulse, not a strobe. It must catch a wandering eye without becoming the thing
   the operator learns to tune out. */
.attention-dot {
  width:9px; height:9px; border-radius:50%;
  background:var(--danger, #b3261e);
  box-shadow:0 0 0 0 rgba(179,38,30,.6);
  animation:attention-pulse 2.4s ease-out infinite;
}
@keyframes attention-pulse {
  0%   { box-shadow:0 0 0 0 rgba(179,38,30,.55); }
  70%  { box-shadow:0 0 0 10px rgba(179,38,30,0); }
  100% { box-shadow:0 0 0 0 rgba(179,38,30,0); }
}

.attention-list {
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(260px, 1fr));
  gap:10px;
}

.attention-item {
  display:flex; gap:10px; align-items:stretch;
  background:var(--surface);
  border:1px solid var(--line);
  border-left:4px solid var(--warn, #c98a25);
  border-radius:12px;
  overflow:hidden;
  cursor:pointer;
  transition:transform .12s ease, box-shadow .12s ease;
}
.attention-item:hover { transform:translateY(-1px); box-shadow:var(--shadow-1); }
.attention-item.is-critical { border-left-color:var(--danger, #b3261e); }

/* The frame the model actually judged. An alert without the picture is a rumour: the
   operator cannot decide anything from a sentence, and he will not walk to the camera. */
.attention-item img,
.attention-noimg {
  width:112px; min-height:76px; object-fit:cover; background:#000; flex:0 0 112px;
}

.attention-body { padding:8px 10px 8px 0; min-width:0; display:flex; flex-direction:column; gap:4px; justify-content:center; }
.attention-meta { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.attention-cam { font-weight:800; font-size:.86rem; }
.attention-time { color:var(--ink-faint); font-size:.74rem; font-variant-numeric:tabular-nums; }

.attention-threat {
  font-size:.68rem; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
  padding:2px 6px; border-radius:999px;
}
.attention-threat.high   { background:rgba(179,38,30,.12); color:#b3261e; }
.attention-threat.medium { background:rgba(201,138,37,.14); color:#8a5d12; }
.attention-threat.low    { background:var(--bg-2); color:var(--ink-soft); }

.attention-msg {
  font-size:.84rem; line-height:1.35; color:var(--ink);
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}

/* Tile = video area + control bar underneath. The controls never sit on the image:
   on a dense wall every pixel of the scene matters, and a pill over a doorway is a
   blind spot. The decorative shimmer/grid live on .tile-video, so they only paint
   the placeholder area before the stream is up — never the bar. */
/* Edge to edge: card, border, shadow and rounded corner are gone. Each of them ate a strip of
   every tile, and across 32 tiles those strips add up to several rows of video the operator is
   paying for and never sees. A 2px gap is enough to tell two cameras apart; more is
   decoration. */
.video-tile {
  position:relative;
  border-radius:2px;
  overflow:hidden;
  background:#000;
  color:white;
  display:flex;
  flex-direction:column;
  border:0;
  box-shadow:none;
}

/* Name + Expand float ON the image instead of in a bar below it. The gradient keeps the name
   readable over a bright scene without a solid bar stealing pixels. */
.tile-overlay {
  position:absolute;
  left:0; right:0; bottom:0;
  z-index:3;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  padding:14px 10px 8px;
  background:linear-gradient(to top, rgba(0,0,0,.62), rgba(0,0,0,0));
  pointer-events:none;   /* the gradient must never swallow a click meant for the tile */
}

.tile-name {
  font-size:.8rem;
  font-weight:800;
  text-shadow:0 1px 4px rgba(0,0,0,.85);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

/* Expand appears on hover. It is needed for a fraction of a second, a few times a day — it has
   not earned permanent residence on 32 tiles. */
.tile-expand {
  pointer-events:auto;
  opacity:0;
  transform:translateY(4px);
  transition:opacity .12s ease, transform .12s ease;
  flex:0 0 auto;
}
.video-tile:hover .tile-expand,
.tile-expand:focus-visible { opacity:1; transform:none; }

/* The grid gap was sized for cards that no longer exist. */
.tile-grid { gap:2px; }

/* ---------- the menu button + panel ---------- */
.menu-btn { display:grid; place-items:center; width:38px; padding:0; }
.menu-glyph { position:relative; display:block; width:16px; height:2px; border-radius:2px; background:currentColor; }
.menu-glyph::before, .menu-glyph::after {
  content:""; position:absolute; left:0; width:16px; height:2px; border-radius:2px; background:currentColor;
}
.menu-glyph::before { top:-5px; }
.menu-glyph::after  { top:5px; }

.menu-scrim {
  position:fixed; inset:0; z-index:60;
  background:rgba(9,13,19,.55);
  backdrop-filter:blur(3px);
  display:flex; justify-content:flex-end;
  animation:menu-fade .12s ease;
}
@keyframes menu-fade { from { opacity:0; } }

.menu-panel {
  width:min(420px, 92vw);
  height:100%;
  background:linear-gradient(180deg, var(--navy), #172030);
  color:#f8f4ee;
  box-shadow:-24px 0 60px rgba(9,13,19,.45);
  display:flex; flex-direction:column;
  padding:18px 16px;
  overflow-y:auto;
  animation:menu-slide .16s ease;
}
@keyframes menu-slide { from { transform:translateX(24px); opacity:.6; } }

.menu-head {
  display:flex; align-items:center; gap:12px;
  padding-bottom:16px; margin-bottom:14px;
  border-bottom:1px solid rgba(255,255,255,.10);
}
.menu-head .brand-name { font-family:var(--font-serif); font-size:1.15rem; }
.menu-head .brand-subtitle { color:#c9d0db; font-size:.78rem; margin-top:2px; }
.menu-x { margin-left:auto; background:rgba(255,255,255,.08); color:#f8f4ee; }

/* Two columns: twenty destinations in one glance, each one big enough to hit without aiming. */
.menu-grid { display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:6px; }
.menu-item {
  display:flex; align-items:center; gap:10px;
  padding:11px 12px; border-radius:12px;
  background:rgba(255,255,255,.05); color:#e7ebf1;
  text-align:left; font-size:.88rem;
  transition:background .14s ease;
}
.menu-item:hover { background:rgba(255,255,255,.12); }
.menu-item.active {
  background:linear-gradient(145deg, rgba(176,137,79,.95), rgba(151,113,58,.95));
  color:#fff;
}
.menu-item .nav-ico { width:26px; height:26px; flex:0 0 auto; }

.menu-foot { margin-top:auto; padding-top:16px; border-top:1px solid rgba(255,255,255,.10); }
.menu-lang { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:12px; }
.menu-lang span { color:#c9d0db; font-size:.82rem; }
.menu-lang select { min-width:140px; }
.menu-user { display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap; }
.menu-user .footer-note { color:#c9d0db; }

@media (max-width: 640px) {
  .menu-panel { width:100%; }
  .menu-grid { grid-template-columns:minmax(0,1fr); }
}

/* ---------- FIT: the whole building on one screen ----------

   In 4/9/16 the grid only sets COLUMNS, so 32 cameras always spill below the fold and the
   operator scrolls. A wall you scroll is a wall nobody watches: the camera that matters is
   always the one currently off-screen. FIT computes rows AND columns from the camera count and
   the window's shape (applyFitGrid in app.js), then hands them here as variables.

   The critical part is that the grid must FILL its box rather than grow past it. That means
   explicit `1fr` rows — with `grid-auto-rows` the rows keep their content height and the wall
   overflows exactly as before. */
.tile-grid.wall-fit {
  grid-template-columns:repeat(var(--fit-cols, 4), minmax(0, 1fr));
  grid-template-rows:repeat(var(--fit-rows, 2), minmax(0, 1fr));
  height:100%;
  min-height:0;
}

/* The tiles stop dictating their own height (no aspect-ratio): the grid gives them one, and the
   video covers it. Some cropping at extreme window shapes is the correct trade — seeing all 32
   cameras slightly cropped beats seeing 9 of them perfectly and missing the one that matters. */
.tile-grid.wall-fit .video-tile { aspect-ratio:auto; height:100%; min-height:0; }
.tile-grid.wall-fit .tile-slot,
.tile-grid.wall-fit .tile-slot video,
.tile-grid.wall-fit guardian-stream,
.tile-grid.wall-fit guardian-stream video { height:100%; width:100%; object-fit:cover; }

/* At 32-up the name label is bigger than the face it sits on. Shrink it, and let it out of the
   way entirely on the smallest tiles. */
.tile-grid.wall-fit .tile-name { font-size:.7rem; }

/* The wall view must not scroll. Only in this mode: every other panel is a document and scrolls
   normally. */
.content:has(> .tile-grid.wall-fit) { overflow:hidden; display:flex; flex-direction:column; }
.content:has(> .tile-grid.wall-fit) > .tile-grid.wall-fit { flex:1; min-height:0; }

/* ============================================================================
   RESPONSIVE — one build, every screen.

   Until now this interface assumed the screen it was born on. Below ~1280px the
   sidebar was eating a fifth of the window and the two-column forms were crushing
   themselves; on a phone it was unusable. A security console is opened on whatever
   glass is nearest — the reception laptop, the manager's tablet, a 4K wall in the
   office — and it has to be honest on all of them.

   The rule applied here: SPACE IS EARNED, NOT CLAIMED. When width gets scarce, the
   first thing to go is the chrome that only labels (nav text, brand line, footer
   note) — never the video. The wall keeps its pixels the longest, because the wall
   is the product.
   ============================================================================ */

/* --- Tablet / small laptop / half-screen window: the sidebar becomes an icon rail.
       Navigation survives (icons + active state), it just stops being a paragraph. */
@media (max-width: 1180px) {
  .shell { grid-template-columns:76px 1fr; }
  .sidebar { padding:16px 10px; align-items:center; }
  .brand { padding:2px 0 14px; justify-content:center; gap:0; }
  .brand > div,
  .nav-button > span:not(.nav-ico),
  .sidebar-footer .label,
  .sidebar-footer .value,
  .sidebar-footer .footer-note { display:none; }
  .brand-mark, .brand-logo { width:40px; height:40px; font-size:1.15rem; }
  .nav { width:100%; align-items:center; }
  .nav-button { justify-content:center; padding:11px 0; gap:0; width:56px; }
  .nav-button:hover { transform:none; }
  .sidebar-footer { width:100%; padding:12px 0 0; }
  .mini-status { padding:8px; background:transparent; border:0; }
  .mini-status .btn { width:100%; padding:8px 0; font-size:.72rem; }
  .split { grid-template-columns:minmax(0,1fr); }
  .grid.cols-4 { grid-template-columns:repeat(2, minmax(0,1fr)); }
  .source-grid { grid-template-columns:minmax(0,1fr); }
}

/* --- Narrow laptop / large phone landscape: the rail moves to the top.
       A horizontal strip costs height, which we have; it stops costing width, which
       we do not. Below this width a vertical sidebar is simply theft. */
@media (max-width: 860px) {
  .shell { grid-template-columns:1fr; grid-template-rows:auto 1fr; }
  .sidebar {
    height:auto; flex-direction:row; align-items:center; gap:8px;
    padding:8px 10px; overflow-x:auto; overflow-y:hidden;
    box-shadow:0 14px 40px rgba(31,42,60,.18);
  }
  .brand { border-bottom:0; border-right:1px solid rgba(255,255,255,.10); padding:0 10px 0 2px; flex:0 0 auto; }
  .nav { flex-direction:row; margin-top:0; width:auto; gap:4px; }
  .nav-button { width:44px; padding:8px 0; border-radius:12px; flex:0 0 auto; }
  .sidebar-footer { margin-top:0; margin-left:auto; width:auto; flex:0 0 auto; }
  .mini-status .btn { width:auto; padding:8px 12px; }
  .workspace { min-height:0; }
  .topbar { flex-wrap:wrap; gap:10px; }
  .grid.cols-2, .grid.cols-3, .grid.cols-4 { grid-template-columns:minmax(0,1fr); }
  .form-grid { grid-template-columns:minmax(0,1fr); }
  .content { padding:12px; }
}

/* --- Phone: the wall goes to a single column and stacks.
       Two 16:9 tiles side by side on a 390px screen are 195px wide — you cannot tell
       a person from a shadow at that size, so showing four of them is a lie. One
       readable camera beats four decorative ones. */
@media (max-width: 640px) {
  .tile-grid,
  .tile-grid.wall-4,
  .tile-grid.wall-9,
  .tile-grid.wall-16 { grid-template-columns:minmax(0,1fr); }
  .wall-bar { flex-wrap:wrap; gap:8px; }
  .view-title p { display:none; }
  .top-actions { width:100%; }
  /* Touch: there is no hover, so the expand affordance must be permanent. */
  .tile-expand { opacity:1; transform:none; }
}

/* --- Coarse pointers (touch screens of any size): hover-only UI does not exist.
       This is a separate axis from width — a 27" touch monitor at reception is wide
       AND has no cursor. Tying "show the control" to width would leave it invisible. */
@media (hover: none) {
  .tile-expand { opacity:1; transform:none; }
  .nav-button, .btn { min-height:40px; }
}

/* --- Big screens: on a 4K wall the fluid clamps top out and the console starts to
       look like a phone app blown up. Let the type and the grid breathe. */
@media (min-width: 1800px) {
  :root { --sidebar:clamp(260px, 15vw, 320px); }
  .grid.cols-2 { grid-template-columns:repeat(3, minmax(0,1fr)); }
  .content { padding:clamp(20px, 2.2vw, 34px); }
}

/* --- Short windows (a maximised window on a 768px-tall laptop, or a wall in a
       landscape strip): reclaim vertical chrome so the video is not squeezed. */
@media (max-height: 700px) {
  :root { --topbar:clamp(44px, 7vh, 56px); --pad:12px; }
  .brand { padding-bottom:10px; }
  .sidebar-footer .footer-note { display:none; }
}

/* Everything above assumed a mouse-sized window. The wall itself must also stop
   pretending: a 16-up layout inside a 900px-wide window is 4 columns of 225px. */
@media (max-width: 1180px) and (min-width: 641px) {
  .tile-grid.wall-16 { grid-template-columns:repeat(3, minmax(0,1fr)); }
}
@media (max-width: 900px) and (min-width: 641px) {
  .tile-grid.wall-9,
  .tile-grid.wall-16 { grid-template-columns:repeat(2, minmax(0,1fr)); }
}

/* ---------- live signalling ----------

   A ring around the tile, not a badge over the scene: the operator must be able to spot WHICH
   camera from the corner of his eye, without anything covering the thing he then needs to look
   at. Colour is meaning, and it is the same meaning as everywhere else in this app:

     gold  = something moved (routine — this happens all night)
     amber = the model saw something worth a second glance
     red   = the model saw something serious

   Motion pulses gently; a HIGH threat does not pulse at all. That is deliberate: a flashing
   red border is the fastest way to teach someone to ignore red borders. It just sits there,
   solid, until acted on. */
.video-tile.is-motion,
.video-tile.is-low { outline:2px solid var(--gold); outline-offset:-2px; animation:tile-breathe 2s ease-in-out infinite; }
.video-tile.is-medium { outline:3px solid #e2a840; outline-offset:-3px; }
.video-tile.is-high { outline:3px solid #ff5449; outline-offset:-3px; box-shadow:inset 0 0 40px rgba(255,84,73,.28); }

@keyframes tile-breathe {
  0%, 100% { outline-color:rgba(216,182,118,.95); }
  50%      { outline-color:rgba(216,182,118,.35); }
}

/* The label rides in the top-left corner, where no camera ever puts anything important. */
.video-tile[data-flag]::before {
  content:attr(data-flag);
  position:absolute;
  top:6px;
  left:6px;
  z-index:4;
  padding:2px 8px;
  border-radius:999px;
  font-size:.62rem;
  font-weight:900;
  letter-spacing:.06em;
  text-transform:uppercase;
  background:rgba(6,10,16,.78);
  color:var(--gold-bright, #d8b676);
  pointer-events:none;
}
.video-tile.is-medium[data-flag]::before { color:#ffcf7a; }
.video-tile.is-high[data-flag]::before   { background:#ff5449; color:#12161d; }

/* ---------- NO SIGNAL ----------
   A tile that is not receiving frames must be unmistakable. Not subtle, not tasteful: a frozen
   picture that looks live is the failure that loses a night of evidence, and every failure we
   found today wore exactly that disguise. */
.video-tile.is-dead { outline:3px solid #ff5449; outline-offset:-3px; }
.video-tile.is-dead .tile-slot { filter:grayscale(1) brightness(.45); }
.video-tile.is-dead::after {
  content:"NO SIGNAL";
  position:absolute;
  inset:0;
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:5;
  font-size:.82rem;
  font-weight:900;
  letter-spacing:.16em;
  color:#ff5449;
  background:rgba(6,10,16,.45);
  pointer-events:none;
}

/* Match the camera's own aspect (16:9) instead of forcing a squarer box: otherwise
   the player letterboxes and you lose vertical pixels of the actual scene. */
.tile-video {
  position:relative;
  flex:1;
  aspect-ratio:16 / 9;
  min-height:clamp(120px, 18vh, 190px);
  background:#000;
  overflow:hidden;
}

.tile-video::before {
  content:"";
  position:absolute;
  inset:0;
  background:
    linear-gradient(120deg, transparent 0%, rgba(255,255,255,.08) 16%, transparent 33%),
    radial-gradient(circle at 72% 32%, rgba(216,182,118,.16), transparent 28%),
    linear-gradient(135deg, #10141d, #253147 54%, #10141d);
  background-size:240% 100%, auto, auto;
  animation:shimmer 5s linear infinite;
  z-index:0;
}

.tile-video::after {
  content:"";
  position:absolute;
  inset:0;
  background:
    linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255,255,255,.035) 1px, transparent 1px);
  background-size:28px 28px;
  opacity:.24;
  z-index:0;
}

/* The live feed must cover the decoration, not sit under it. */
.tile-video .tile-feed,
.tile-video .video-copy { position:absolute; inset:0; z-index:1; }

/* The slot the cached player is moved into, and the player itself. No controls, no
   scrollbar, no iframe: object-fit:cover fills the tile edge to edge, so a 16:9 stream in a
   16:9 tile loses nothing and a 4:3 camera is cropped at the sides rather than letterboxed
   with dead black bars that waste the operator's screen. */
.tile-slot { position:absolute; inset:0; z-index:1; background:#000; overflow:hidden; }

/* The last known frame, painted instantly while the live stream connects. It sits ON TOP of
   the player and fades out the moment real video plays — so the wall is never a grid of black
   rectangles, and it is never a stale image sitting over a live camera either. */
.tile-poster {
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  z-index:2;
  opacity:1;
  transition:opacity .35s ease;
  /* Slightly dimmed: it must read as "this is the last frame", not as live video. */
  filter:brightness(.82);
}
.tile-poster.is-gone { opacity:0; }

/* ---------- digital zoom ----------

   The slot clips the magnified image, and the transform runs on the GPU: the stream itself is
   never touched, so zooming costs nothing until it is used. */
.tile-slot.is-zoomed { cursor:grab; }
.tile-slot guardian-stream { transition:transform .08s linear; will-change:transform; }

/* Scaling is where a browser quietly ruins a picture. Chromium's default is a bilinear blur,
   which is the worst possible choice for surveillance: it smears exactly the edges an operator
   is trying to read — a face, a plate, a hand. `high-quality` asks Chromium for its best
   resampler (Lanczos-class), which reconstructs those edges from the pixels that are actually
   there. It does not invent detail, and that distinction matters when the footage is evidence:
   an AI upscaler would hallucinate a plausible face, and a plausible face is a lie in court. */
.tile-slot.is-zoomed guardian-stream video,
#gv-tile-modal guardian-stream video {
  image-rendering:high-quality;
  image-rendering:-webkit-optimize-contrast;
}

/* The wall's substream, blown up instantly while the main stream negotiates behind it. Sits
   above the player and cross-fades out the moment real full-quality video plays. */
.expand-standin {
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
  background:#000;
  z-index:2;
  opacity:1;
  transition:opacity .35s ease;
  image-rendering:high-quality;
  pointer-events:none;
}
.expand-standin.is-gone { opacity:0; }

.zoom-readout {
  position:absolute;
  left:12px;
  bottom:12px;
  z-index:5;
  padding:4px 10px;
  border-radius:999px;
  background:rgba(6,10,16,.72);
  color:#e7ecf3;
  font-size:.78rem;
  font-weight:800;
  font-variant-numeric:tabular-nums;
  pointer-events:none;
}

guardian-stream {
  display:block;
  width:100%;
  height:100%;
  background:#000;
  overflow:hidden;   /* the scrollbar the go2rtc page used to draw dies here */
}
guardian-stream video {
  width:100%;
  height:100%;
  display:block;
  outline:none;
}
guardian-stream video::-webkit-media-controls { display:none !important; }

/* .tile-bar is dead: the tile is edge-to-edge now and the label floats over the image. Kept
   only as a no-op so any stale markup cannot reintroduce a solid strip. */
.tile-bar { display:none; }

.tile-meta { min-width:0; }
.tile-name {
  font-weight:820;
  font-size:.9rem;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.tile-sub {
  color:rgba(255,255,255,.6);
  font-size:.72rem;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

.tile-controls {
  display:flex;
  align-items:center;
  gap:6px;
  flex:0 0 auto;
}

/* A 16-up wall has no room for word buttons; keep the bar readable. */
.tile-grid.wall-16 .tile-sub { display:none; }
.tile-grid.wall-16 .tile-bar { padding:6px 8px; }

@keyframes shimmer { to { background-position:-240% 0, 0 0, 0 0; } }

.video-overlay {
  position:absolute;
  inset:0;
  display:flex;
  flex-direction:column;
  justify-content:space-between;
  padding:14px;
  z-index:2;
}

.video-title { font-weight:820; text-shadow:0 2px 8px rgba(0,0,0,.32); }
.video-copy { color:rgba(255,255,255,.72); font-size:.78rem; }

.event-row {
  display:grid;
  grid-template-columns:100px 1fr auto;
  gap:12px;
  align-items:center;
  padding:12px;
  background:var(--surface-2);
  border:1px solid var(--line-soft);
  border-radius:14px;
}

.timeline { display:grid; gap:10px; padding:16px 18px 18px; }
.event-time { font-family:var(--font-mono); font-size:.76rem; color:var(--ink-faint); }
.event-title { font-weight:760; }
.event-copy { color:var(--ink-soft); font-size:.84rem; margin-top:2px; }

.progress { position:relative; overflow:hidden; height:10px; background:var(--line-soft); border-radius:999px; }
.progress span { position:absolute; inset:0 auto 0 0; background:linear-gradient(90deg, var(--gold), var(--gold-deep)); border-radius:999px; }

.kv { display:grid; grid-template-columns:1fr auto; gap:12px; padding:9px 0; border-bottom:1px solid var(--line-soft); }
.kv:last-child { border-bottom:0; }
.kv strong { font-family:var(--font-mono); }

.discovery-list { display:grid; gap:10px; padding:16px 18px 18px; }

.discovery-item {
  border:1px solid var(--line-soft);
  background:var(--surface-2);
  border-radius:16px;
  padding:13px;
  display:grid;
  grid-template-columns:1fr auto;
  gap:14px;
  align-items:center;
}

.empty { display:grid; place-items:center; min-height:clamp(72px, 11vh, 170px); color:var(--ink-soft); text-align:center; padding:clamp(12px, 2vh, 24px); }

/* Hard ceiling on the strip itself, so even if something ever bypasses the JS limit the toasts
   cannot climb the screen: they stop at three heights and the newest wins. */
.toast-zone {
  position:fixed; right:24px; bottom:24px;
  display:flex; flex-direction:column; justify-content:flex-end; gap:10px;
  max-height:min(46vh, 340px);
  overflow:hidden;
  pointer-events:none;
  z-index:50;
}
.toast-zone > * { pointer-events:auto; }

.toast {
  position:relative;
  width:min(380px, calc(100vw - 48px));
  flex:0 0 auto;
  border:1px solid var(--line);
  background:rgba(255,255,255,.95);
  backdrop-filter:blur(12px);
  border-radius:18px;
  box-shadow:var(--shadow-2);
  padding:13px 14px;
  display:flex;
  gap:12px;
  align-items:flex-start;
}

.toast .dot { width:10px; height:10px; border-radius:99px; margin-top:6px; background:var(--gold); flex:0 0 auto; }
.toast.ok .dot { background:var(--ok); }
.toast.warn .dot { background:var(--warn); }
.toast.danger .dot { background:var(--danger); }
.toast-title { font-weight:800; }
.toast-body { color:var(--ink-soft); font-size:.86rem; }

/* "×7" instead of seven identical cards. The repetition IS the information. */
.toast-count {
  position:absolute; top:10px; right:12px;
  font-size:.74rem; font-weight:800; color:var(--ink-soft);
  background:var(--bg-2); border:1px solid var(--line);
  border-radius:99px; padding:1px 7px;
}

.codebox {
  background:#111723;
  color:#e7ebf1;
  border-radius:16px;
  border:1px solid rgba(255,255,255,.10);
  padding:14px;
  overflow:auto;
  font-family:var(--font-mono);
  font-size:.78rem;
  line-height:1.55;
  white-space:pre-wrap;
}

.tabs { display:flex; gap:8px; flex-wrap:wrap; margin:14px 0; }
.tab { padding:8px 12px; border-radius:999px; border:1px solid var(--line); background:var(--surface); font-weight:760; }
.tab.active { background:linear-gradient(145deg, var(--gold), var(--gold-deep)); color:white; border-color:transparent; }

@media (max-width: 1220px) {
  :root { --sidebar:254px; }
  .grid.cols-4 { grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .split { grid-template-columns:1fr; }
  .source-grid { grid-template-columns:1fr; }
  .tile-grid.wall-9, .tile-grid.wall-16 { grid-template-columns:repeat(2, minmax(0,1fr)); }
}

@media (max-width: 900px) {
  body { overflow:auto; }
  .shell { grid-template-columns:1fr; height:auto; min-height:100vh; }
  .sidebar { min-height:auto; position:relative; }
  .nav { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .workspace { display:block; }
  .topbar { position:sticky; top:0; z-index:20; align-items:flex-start; flex-direction:column; }
  .content { overflow:visible; }
  .grid.cols-2, .grid.cols-3, .grid.cols-4, .form-grid, .tile-grid, .tile-grid.wall-4, .tile-grid.wall-9, .tile-grid.wall-16 { grid-template-columns:1fr; }
}

/* Sidebar chrome scales fluidly with viewport height (continuous, no jumps),
   sized so all 11 nav items fit without scrolling on a typical laptop. */
.sidebar { padding:clamp(12px, 1.8vh, 22px) clamp(12px, 1vw, 16px); }
.brand { padding:clamp(2px, .5vh, 4px) 10px clamp(8px, 1.5vh, 18px); gap:clamp(9px, 1.2vw, 14px); }
.brand-mark {
  width:clamp(34px, 4.6vh, 48px);
  height:clamp(34px, 4.6vh, 48px);
  font-size:clamp(1rem, 2.2vh, 1.45rem);
}
.brand-name { font-size:clamp(.98rem, 2.1vh, 1.20rem); }
.brand-subtitle { font-size:clamp(.68rem, 1.3vh, .78rem); }
.nav { gap:clamp(2px, .5vh, 5px); margin-top:clamp(8px, 1.6vh, 18px); }
.nav-button { padding:clamp(6px, 1.1vh, 11px) 12px; }
.nav-ico { width:clamp(22px, 3vh, 28px); height:clamp(22px, 3vh, 28px); }
.sidebar-footer { padding:clamp(8px, 1.4vh, 16px) 10px 4px; }

/* Very short viewports: drop the hero paragraph and the sidebar status card
   so the primary controls always stay on screen. */
@media (max-height: 760px) {
  .sidebar-footer { display:none; }
}
@media (max-height: 680px) {
  .hero p { display:none; }
}

/* ============================================================
   Enhanced responsive — phones & small tablets (adapts to any size).
   These override the earlier breakpoints (later rules win).
   ============================================================ */

/* Tablets / small laptops: 3-up walls become 2-up, wide metric rows wrap. */
@media (max-width: 1024px) {
  .tile-grid.wall-16 { grid-template-columns:repeat(2, minmax(0,1fr)); }
}

/* Phones & narrow windows: sidebar behaves like a compact top nav bar with a
   single horizontally-scrollable row, so ANY number of sections fits. */
@media (max-width: 900px) {
  body { overflow:auto; }
  .shell { grid-template-columns:1fr; height:auto; min-height:100vh; }
  .sidebar {
    height:auto; min-height:auto; position:sticky; top:0; z-index:30;
    padding:10px 12px; box-shadow:0 8px 24px rgba(31,42,60,.22);
  }
  .brand { padding:2px 6px 10px; }
  .brand-subtitle { display:none; }
  .nav {
    display:flex; flex-direction:row; flex-wrap:nowrap;
    gap:8px; overflow-x:auto; -webkit-overflow-scrolling:touch;
    margin-top:10px; padding-bottom:4px;
  }
  .nav::-webkit-scrollbar { height:6px; }
  .nav::-webkit-scrollbar-thumb { background:rgba(255,255,255,.18); border-radius:6px; }
  .nav-button { width:auto; flex:0 0 auto; white-space:nowrap; padding:8px 12px; }
  .nav-button:hover { transform:none; }
  .sidebar-footer { display:none; }
  .workspace { display:block; }
  .topbar { position:static; flex-direction:column; align-items:flex-start; gap:10px; }
  .top-actions { width:100%; }
  .top-actions select, .top-actions .btn { flex:1 1 auto; }
  .content { overflow:visible; }
  .split, .source-grid, .form-grid,
  .tile-grid, .tile-grid.wall-4, .tile-grid.wall-9, .tile-grid.wall-16 { grid-template-columns:1fr; }
}

/* Small phones: tighter spacing, stacked headers/rows, bigger tap targets. */
@media (max-width: 600px) {
  :root { --pad:12px; --card-pad:13px; --gap:12px; }
  .hero { padding:16px; border-radius:20px; }
  .hero p { display:block; }
  .card-header { flex-direction:column; align-items:flex-start; }
  .kv { grid-template-columns:1fr; }
  .kv > *:last-child { justify-self:start; }
  .video-tile { min-height:200px; }
  .toast-zone { right:12px; left:12px; bottom:12px; }
  .toast { width:100%; }
  .btn { min-height:42px; }
  .btn.small { min-height:34px; }
  table { min-width:640px; }
}

/* Very small screens. */
@media (max-width: 400px) {
  .brand-name { font-size:1rem; }
  .nav-button { padding:8px 10px; }
}
