/**
 * app.css — knapp.cam
 *
 * Dark throughout, and not as a style choice: these pages are looked at in
 * dim rooms and next to night-vision footage, and a white interface beside a
 * dark picture destroys the dark adaptation that makes the picture readable.
 */

:root {
  color-scheme: dark;

  --bg:       #06101a;
  --bg-2:     #0b1724;
  --bg-3:     #10212f;
  --line:     #1c3344;
  --line-2:   #284a60;
  --ink:      #d7ecf6;
  --ink-dim:  #7d9bad;
  --ink-faint:#536a7a;
  --accent:   #62ffb0;
  --accent-2: #2bd98b;
  --warn:     #ffc861;
  --bad:      #ff6b7f;

  --gap: 8px;
  --r: 12px;
  --r-sm: 8px;
  --font: 15px/1.6 ui-sans-serif, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --mono: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: var(--font);
  -webkit-text-size-adjust: 100%;
  /* The pages are a fixed instrument panel, not a document. Rubber-banding
     the whole thing while dragging across a camera looks like a fault. */
  overscroll-behavior: none;
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { margin: 0 0 .5rem; font-weight: 620; letter-spacing: -.01em; line-height: 1.25; }
h1 { font-size: clamp(1.3rem, 3.4vw, 1.75rem); }
h2 { font-size: 1.1rem; }
h3 { font-size: .95rem; color: var(--ink-dim); text-transform: uppercase; letter-spacing: .08em; font-size: .72rem; }

/* ─────────────────────────────── chrome ─────────────────────────────── */

.top {
  display: flex; align-items: center; gap: 1rem;
  padding: .6rem clamp(.75rem, 3vw, 1.5rem);
  background: linear-gradient(#0b1724, #091420);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 30;
  padding-top: max(.6rem, env(safe-area-inset-top));
}
.brand { display: flex; align-items: center; gap: .5rem; color: var(--ink); font-weight: 650; letter-spacing: -.02em; }
.brand:hover { text-decoration: none; }
.brand .dim { color: var(--ink-faint); font-weight: 500; }
.brand .dot {
  width: .65rem; height: .65rem; border-radius: 50%;
  background: var(--accent); box-shadow: 0 0 10px #62ffb088;
}
.top-nav { display: flex; gap: .25rem; flex: 1; flex-wrap: wrap; }
.top-nav a {
  color: var(--ink-dim); padding: .3rem .6rem; border-radius: var(--r-sm); font-size: .9rem;
}
.top-nav a:hover { background: var(--bg-3); color: var(--ink); text-decoration: none; }
.top-out { display: flex; align-items: center; gap: .6rem; margin: 0; }
.who { color: var(--ink-faint); font-size: .85rem; }

.wrap { padding: clamp(.75rem, 3vw, 1.5rem); max-width: 1500px; margin: 0 auto; }
.wrap-narrow { max-width: 42rem; }

.row { display: flex; gap: .75rem; align-items: center; flex-wrap: wrap; }
.row-between { display: flex; gap: .75rem; align-items: center; justify-content: space-between; flex-wrap: wrap; }
.grow { flex: 1; }
.muted { color: var(--ink-dim); }
.faint { color: var(--ink-faint); }
.small { font-size: .85rem; }
.mono { font: var(--mono); color: var(--ink-dim); word-break: break-all; }

.card {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r); padding: clamp(.8rem, 2vw, 1.1rem);
}
.card + .card { margin-top: var(--gap); }

/* ─────────────────────────── buttons and forms ─────────────────────────── */

.btn {
  display: inline-flex; align-items: center; gap: .4rem;
  font: inherit; font-weight: 560; font-size: .9rem;
  padding: .5rem .85rem; border-radius: var(--r-sm);
  background: var(--bg-3); color: var(--ink);
  border: 1px solid var(--line-2); cursor: pointer;
  /* Thumb-sized rather than cursor-sized. These pages are used on a touch
     screen on a wall, where a 28px button is a button you miss. */
  min-height: 2.4rem;
  touch-action: manipulation;
}
.btn:hover { border-color: var(--accent-2); color: #fff; }
.btn:active { transform: translateY(1px); }
.btn-go { background: var(--accent); border-color: var(--accent); color: #04120c; }
.btn-go:hover { background: #7effc0; color: #04120c; }
.btn-quiet { background: transparent; border-color: var(--line); color: var(--ink-dim); }
.btn-quiet:hover { color: var(--ink); }
.btn-bad { border-color: #5c2733; color: #ffa8b5; background: transparent; }
.btn-bad:hover { background: #2a111a; border-color: var(--bad); color: #fff; }
.btn-bad.is-armed { background: var(--bad); border-color: var(--bad); color: #2a0008; }
.btn-sm { padding: .3rem .6rem; font-size: .82rem; min-height: 2rem; }

label.field { display: block; margin-bottom: .9rem; }
label.field > span.lab { display: block; font-size: .8rem; color: var(--ink-dim); margin-bottom: .25rem; }
label.field > span.hint { display: block; font-size: .78rem; color: var(--ink-faint); margin-top: .25rem; }

input[type=text], input[type=password], input[type=number], input[type=url], select, textarea {
  width: 100%; font: inherit; font-size: .92rem;
  padding: .55rem .7rem; border-radius: var(--r-sm);
  background: #081520; color: var(--ink); border: 1px solid var(--line-2);
  min-height: 2.5rem;
}
input:focus, select:focus, textarea:focus { outline: 2px solid var(--accent-2); outline-offset: 1px; border-color: var(--accent-2); }
input[aria-invalid=true] { border-color: var(--bad); }
.err { color: #ffa8b5; font-size: .82rem; margin: .25rem 0 0; }

.check { display: flex; align-items: center; gap: .5rem; margin-bottom: .9rem; cursor: pointer; }
.check input { width: 1.15rem; height: 1.15rem; min-height: 0; accent-color: var(--accent-2); }

.flash {
  margin: 0 0 var(--gap); padding: .6rem .8rem; border-radius: var(--r-sm);
  border: 1px solid var(--line-2); background: var(--bg-3); font-size: .9rem;
}
.flash-ok  { border-color: #1d5f41; background: #0b2419; color: #b9ffd9; }
.flash-bad { border-color: #5c2733; background: #240f16; color: #ffc3cd; }
.flash-note{ border-color: #5c4b1f; background: #221c0c; color: #ffe2a8; }

.pill {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .72rem; font-weight: 600; letter-spacing: .03em;
  padding: .15rem .5rem; border-radius: 999px;
  border: 1px solid var(--line-2); color: var(--ink-dim);
}
.pill-live { border-color: #1d5f41; color: var(--accent); }
.pill-off  { border-color: #5c2733; color: #ffa8b5; }
.pill-warn { border-color: #5c4b1f; color: var(--warn); }

/* ───────────────────────────── the camera wall ───────────────────────────── */

.wall-head { display: flex; align-items: baseline; gap: .6rem; margin-bottom: .4rem; flex-wrap: wrap; }

.camwall {
  display: grid;
  /* BOTH TRACKS ARE PINNED, and app.js chooses both. Rows left to size
     themselves from the tiles add up to whatever they add up to, which is
     almost never the height of the screen — so the wall either scrolls or
     leaves a band of black along the bottom. A camera wall that scrolls is a
     camera wall where some of the cameras are not being watched. */
  grid-template-columns: repeat(var(--cols, 2), minmax(0, 1fr));
  grid-template-rows: repeat(var(--rows, 1), minmax(0, 1fr));
  gap: var(--gap);
  /* A DEFINITE HEIGHT, ALWAYS. Everything above depends on there being a
     known amount of room to divide up. */
  height: var(--wall-h, min(72vh, 52vw));
  overflow: hidden;
}
.camwall[data-layout="cols-2"] { --cols: 2; }
.camwall[data-layout="cols-3"] { --cols: 3; }
.camwall[data-layout="cols-4"] { --cols: 4; }
@media (max-width: 760px) {
  .camwall[data-layout="cols-3"], .camwall[data-layout="cols-4"] { --cols: 2; }
}

/* Spotlight: the first camera in the view is permanently the big one, with
   the rest around it. A view, unlike the wall, can say which that is. */
.camwall[data-layout="spotlight"] { --cols: 3; }
.camwall[data-layout="spotlight"] > .tile:first-child { grid-column: span 2; grid-row: span 2; }

.camwall[data-layout="cinema"] { --cols: 1; }
.camwall[data-layout="cinema"] > .tile.is-dimmed { display: none; }

/* A tile's span, set per camera in a custom view. */
.tile[data-span="2"] { grid-column: span 2; }
.tile[data-span="3"] { grid-column: span 3; }
@media (max-width: 760px) { .tile[data-span="2"], .tile[data-span="3"] { grid-column: span 1; } }

.tile {
  position: relative;
  /* NO aspect-ratio HERE. The grid cell already has a shape, chosen by
     app.js to land near 16:9; asking the tile for a second, independent
     opinion about its own height is what made the rows stop adding up. The
     picture inside is letterboxed into whatever the cell turns out to be. */
  min-width: 0; min-height: 0;
  background: #000;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  cursor: pointer;
  /* The tile handles its own gestures in JS. Left to the browser, a one-
     finger drag across a camera scrolls the page and a two-finger pinch
     zooms the whole document, so neither pan nor pinch would ever reach
     the picture. */
  touch-action: none;
  -webkit-user-select: none; user-select: none;
  -webkit-tap-highlight-color: transparent;
  transition: outline-color .12s, opacity .18s;
  outline: 2px solid transparent; outline-offset: -2px;
}
.tile:hover { outline-color: var(--line-2); }
.tile:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }

.cam-media {
  position: absolute; inset: 0;
  transform-origin: 50% 50%;
  /* No transition: a pinch sets this on every pointermove, and an eased
     transform turns a gesture that should track the fingers into one that
     lags a fifth of a second behind them. */
  will-change: transform;
}
.cam-media kc-video, .cam-media video {
  display: block; width: 100%; height: 100%;
  /* contain, not cover. The cells are already close to 16:9, so there is
     very little letterboxing to avoid — and cropping a security camera to
     make a tile look tidy throws away the edges of the frame, which on a
     wide-angle camera is where most of what you want to see actually is. */
  object-fit: contain;
  background: #000;
}

/* ─────────────────────────────── focus ─────────────────────────────── */

/**
 * One camera over the others.
 *
 * Still a grid: the focused tile takes the whole first row and the rest
 * become a rail of thumbnails underneath, so the other cameras stay watchable
 * rather than disappearing. --thumbs is set by JS from how many there are.
 */
.camwall.has-focus {
  grid-template-columns: repeat(var(--thumbs, 4), minmax(0, 1fr));
  /* The rail takes a measured height, set by app.js from the width its
     thumbnails ended up with, and the focused camera takes everything else.
     Any thumbnails that wrap past the first rail row get a row of their own
     the same size, rather than the zero-height row an unlisted implicit row
     would collapse to. */
  grid-template-rows: minmax(0, 1fr) var(--rail, 18%);
  grid-auto-rows: var(--rail, 18%);
}
.camwall.has-focus > .tile.is-focused {
  grid-column: 1 / -1;
  grid-row: 1;
  border-color: var(--line-2);
  outline-color: var(--accent);
  cursor: zoom-in;
}
/* A span set for the grid would make one thumbnail twice the width of its
   neighbours in the rail, where the whole point is that they are a uniform
   row of small ones. Out-specifies .tile[data-span] without !important. */
.camwall.has-focus > .tile.is-dimmed[data-span] { grid-column: auto; }
.camwall.has-focus > .tile.is-focused.is-zoomed { cursor: grab; }
.camwall.has-focus > .tile.is-focused.is-zoomed:active { cursor: grabbing; }
.camwall.has-focus > .tile.is-dimmed { opacity: .62; }
.camwall.has-focus > .tile.is-dimmed:hover { opacity: 1; }

/* ─────────────────────── the furniture on a tile ─────────────────────── */

.cam-bar {
  position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: baseline; gap: .4rem;
  padding: 1.6rem .55rem .4rem;
  font-size: .78rem; line-height: 1.3;
  /* A GRADIENT, NOT A FLAT BAR. A camera's picture is whatever it is —
     white door, sunlit path, headlights — and a label with no scrim behind
     it is unreadable on half of them at some point in the day. */
  background: linear-gradient(transparent, #000000cc 55%);
  pointer-events: none;
  text-shadow: 0 1px 3px #000;
}
.cam-name { font-weight: 620; color: #fff; }
.cam-place { color: #ffffff9c; }

.cam-tools {
  position: absolute; top: .3rem; right: .3rem;
  display: flex; gap: .2rem;
  opacity: 0; transition: opacity .15s;
}
.tile:hover .cam-tools, .tile.is-focused .cam-tools, .tile:focus-within .cam-tools { opacity: 1; }
/* No hover on a touch screen, so there is nothing to reveal — the tools are
   simply always there. */
@media (hover: none) { .cam-tools { opacity: 1; } }

.cam-tool {
  font: inherit; font-size: .8rem; line-height: 1;
  width: 2rem; height: 2rem;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: #000000a8; color: #fff;
  border: 1px solid #ffffff22; cursor: pointer;
  backdrop-filter: blur(3px);
  touch-action: manipulation;
}
.cam-tool:hover { background: #000d; border-color: var(--accent-2); }

.cam-state {
  position: absolute; top: .35rem; left: .4rem;
  display: flex; align-items: center; gap: .35rem;
  font-size: .68rem; font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  padding: .18rem .45rem; border-radius: 999px;
  background: #000000a8; color: #fff; border: 1px solid #ffffff22;
  pointer-events: none;
  backdrop-filter: blur(3px);
  transition: opacity .4s .8s;
}
/* A working camera says so once and then gets out of the way. A badge that
   stays lit on every tile is eight badges nobody reads. */
.cam-state[data-state="live"] { opacity: 0; }
.tile:hover .cam-state[data-state="live"] { opacity: 1; transition-delay: 0s; }
.cam-state[data-state="live"] .cam-spin { background: var(--accent); animation: none; border-color: transparent; }
.cam-state[data-state="down"] { border-color: #ff6b7f66; color: #ffc3cd; }
.cam-state[data-state="down"] .cam-spin { background: var(--bad); animation: none; border-color: transparent; }
.cam-state[data-state="stalled"] { border-color: #ffc86166; color: #ffe2a8; }
.cam-state[data-state="stalled"] .cam-spin { background: var(--warn); animation: none; border-color: transparent; }

.cam-spin {
  width: .5rem; height: .5rem; border-radius: 50%;
  border: 1.5px solid #ffffff55; border-top-color: #fff;
  animation: kc-spin .8s linear infinite;
}
@keyframes kc-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .cam-spin { animation: none; }
  .tile, .cam-state { transition: none; }
}

/* ───────────────────────── stand-alone pages ───────────────────────── */

/**
 * `bare` is the wall on a screen on a wall: no header, no padding, cameras to
 * all four edges, and nothing that scrolls.
 */
body.bare { overflow: hidden; background: #000; }
body.bare .stage {
  position: fixed; inset: 0;
  padding: max(4px, env(safe-area-inset-top)) max(4px, env(safe-area-inset-right))
           max(4px, env(safe-area-inset-bottom)) max(4px, env(safe-area-inset-left));
  display: flex; flex-direction: column; gap: 4px;
}
body.bare .camwall { --wall-h: auto; flex: 1; min-height: 0; height: auto; }

/* The only chrome a stand-alone page has: a strip that appears when the
   pointer is near the top, so a wall display shows nothing at all until
   somebody walks up to it. */
.bare-bar {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  display: flex; align-items: center; gap: .6rem;
  padding: .45rem .7rem;
  padding-top: max(.45rem, env(safe-area-inset-top));
  /* SOLID, NOT A FADE. It is drawn over the top of the focused camera, and
     the camera has its own state badge and buttons in that same corner — a
     bar that lets them through does not look like layering, it looks like
     two labels printed on top of each other. */
  background: #000000e8;
  border-bottom: 1px solid #ffffff14;
  backdrop-filter: blur(8px);
  font-size: .85rem;
  opacity: 0; transform: translateY(-100%);
  transition: opacity .2s, transform .2s;
  pointer-events: none;
}
.bare-bar a, .bare-bar button { pointer-events: auto; }
/* `is-near` is set by app.js when the pointer is genuinely near the top of
   the screen. It replaces `body.bare:hover`, which was the obvious way to
   write this and was wrong in a way that only appears with a mouse plugged
   in: body covers the whole viewport, so it is hovered whenever the pointer
   is anywhere at all. The bar meant to appear when somebody walks up to the
   screen was in fact always on, sitting across the first camera's status
   badge and its buttons. */
.bare-bar.is-near, .bare-bar:focus-within { opacity: 1; transform: none; }

/* ON A TOUCH SCREEN THE BAR NEVER HIDES, because there is no hover to bring
   it back — so it cannot be an overlay there. It keeps its own strip and the
   cameras start underneath it. A wall screen being driven by a finger is the
   main way this page is used, so this is the case to get right, not the
   exception to tolerate. */
@media (hover: none) {
  .bare-bar { opacity: 1; transform: none; }
  body.bare .stage { padding-top: calc(2.5rem + env(safe-area-inset-top)); }
}
.bare-bar .title { font-weight: 620; color: #fff; }
.bare-bar .keys { color: #ffffff77; font-size: .76rem; }
@media (max-width: 700px) { .bare-bar .keys { display: none; } }

/* ───────────────────────────── tables / lists ───────────────────────────── */

.list { display: grid; gap: var(--gap); }

.item {
  display: flex; gap: .8rem; align-items: center; flex-wrap: wrap;
  padding: .7rem .85rem;
  background: var(--bg-2); border: 1px solid var(--line); border-radius: var(--r-sm);
}
.item .thumb {
  width: 7rem; aspect-ratio: 16/9; border-radius: 6px; background: #000;
  border: 1px solid var(--line); object-fit: cover; flex: none;
}
.item .bodytext { flex: 1; min-width: 11rem; }
.item .name { font-weight: 600; }
.item form { margin: 0; }

.empty {
  padding: 2rem 1rem; text-align: center; color: var(--ink-dim);
  border: 1px dashed var(--line-2); border-radius: var(--r);
}

/* ───────────────────────────── the editor ───────────────────────────── */

.pick-grid { display: grid; gap: var(--gap); grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr)); }
.pick {
  display: flex; gap: .6rem; align-items: center;
  padding: .55rem .7rem; border-radius: var(--r-sm);
  background: var(--bg-2); border: 1px solid var(--line); cursor: pointer;
}
.pick:hover { border-color: var(--line-2); }
.pick:has(input:checked) { border-color: var(--accent-2); background: #0c2419; }
.pick input { width: 1.15rem; height: 1.15rem; min-height: 0; accent-color: var(--accent-2); flex: none; }
.pick .bodytext { min-width: 0; }
.pick .name { font-size: .9rem; font-weight: 580; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.order-list { display: grid; gap: 6px; margin: 0; padding: 0; list-style: none; }
.order-list li {
  display: flex; gap: .5rem; align-items: center;
  padding: .45rem .6rem; background: var(--bg-2);
  border: 1px solid var(--line); border-radius: var(--r-sm); font-size: .9rem;
}
.order-list .num { color: var(--ink-faint); font: var(--mono); width: 1.4rem; flex: none; }

/* ───────────────────────────── sign in ───────────────────────────── */

.signin { min-height: 100%; display: grid; place-items: center; padding: 1.5rem; }
.signin .card { width: min(26rem, 100%); }
.signin .mark {
  width: 2.5rem; height: 2.5rem; border-radius: .8rem; margin-bottom: 1rem;
  background: linear-gradient(140deg, var(--accent), #1f8f63);
  box-shadow: 0 0 24px #62ffb033;
}
.or {
  display: flex; align-items: center; gap: .75rem;
  margin: 1.1rem 0; color: var(--ink-faint); font-size: .78rem;
  text-transform: uppercase; letter-spacing: .08em;
}
.or::before, .or::after { content: ""; flex: 1; height: 1px; background: var(--line); }

/* A live thumbnail in the camera list. Same tile machinery as a wall tile,
   but it is a picture of a camera rather than a control: no focus, no
   gestures, no furniture over the top of it. */
.item .tile.thumb {
  width: 7rem; flex: none; padding: 0;
  cursor: default; touch-action: auto;
}
.item .tile.thumb .cam-bar,
.item .tile.thumb .cam-tools { display: none; }
.item .tile.thumb .cam-state { font-size: .6rem; padding: .1rem .3rem; }
.item .tile.thumb:hover { outline-color: transparent; }
