/* LivePin Dashcam Player — "Dash HUD" theme.
   Dark automotive heads-up display: near-black panels, LivePin orange as the
   brand and interactive accent, monospace telemetry. Layout is a fixed top
   strip over a rail + stage body that fills the viewport.

   The page deliberately renders no playback chrome of its own: the
   <livepin-player> element owns transport controls, volume, fullscreen,
   screenshot, DVR seeking, the buffering spinner, error toasts and the
   diagnostics panel. Everything styled here is session/camera state the
   player cannot know about. */

:root {
  --bg: #06080b;
  --panel: #0d1117;
  --panel-2: #111823;
  --line: #1d2735;
  --line-bright: #2b3a4f;
  --ink: #d7e3f0;
  --ink-dim: #7d8da0;
  /* LivePin orange is the brand *and* the interactive accent: legends, focus,
     primary buttons, the active mode. Amber is deliberately NOT folded into
     it — it stays reserved for transient state (connecting, uploading, warn)
     so "the brand" and "something is happening" never read as the same thing.
     Green and red keep their usual done/failed duties. */
  --accent: #f25d27;
  --accent-bright: #ff7440;
  --accent-dim: rgba(242, 93, 39, 0.42);
  --accent-soft: rgba(242, 93, 39, 0.12);
  --amber: #ffb300;
  --amber-dim: #7a5800;
  --red: #ff3b3b;
  --green: #3ad29f;
  --mono: "SFMono-Regular", "JetBrains Mono", "Roboto Mono", Menlo, Consolas, monospace;
  --sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --radius: 10px;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background:
    radial-gradient(1200px 600px at 70% -10%, rgba(242, 93, 39, 0.06), transparent 60%),
    radial-gradient(900px 500px at -10% 110%, rgba(255, 179, 0, 0.05), transparent 55%),
    var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}

.hud {
  height: 100vh;
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 10px;
  padding: 10px;
}

/* ---------- Top telemetry strip ---------- */
.hud-top {
  display: flex;
  align-items: center;
  /* Wraps rather than forcing the page wider than the viewport: the bar's
     contents have a min-width of their own, and without this a narrow window
     scrolls the whole layout sideways. */
  flex-wrap: wrap;
  row-gap: 8px;
  gap: 18px;
  padding: 10px 16px;
  background: linear-gradient(180deg, var(--panel-2), var(--panel));
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

/* Session state — ours, not the player's: the JT808 activation handshake and
   the device-close command have no equivalent inside the element, whose own
   badge only reports the stream it is already playing. */
.session {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  margin-left: auto;
  padding: 6px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.25);
  font-family: var(--mono);
  font-weight: 700;
  letter-spacing: 0.12em;
  font-size: 12px;
  color: var(--ink-dim);
}
.session-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #3a4456;
  box-shadow: 0 0 0 3px rgba(58, 68, 86, 0.25);
}
body.live-on .session { color: var(--red); border-color: rgba(255, 59, 59, 0.35); }
body.live-on .session-dot {
  background: var(--red);
  box-shadow: 0 0 0 3px rgba(255, 59, 59, 0.25);
  animation: pulse 1.3s ease-in-out infinite;
}
body.connecting .session { color: var(--amber); border-color: rgba(255, 179, 0, 0.35); }
body.connecting .session-dot {
  background: var(--amber);
  box-shadow: 0 0 0 3px rgba(255, 179, 0, 0.22);
  animation: pulse 1s ease-in-out infinite;
}
body.play-on .session { color: var(--accent); border-color: rgba(242, 93, 39, 0.35); }
body.play-on .session-dot {
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(242, 93, 39, 0.22);
}
@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .session-dot { animation: none !important; }
}

.clock {
  font-family: var(--mono);
  font-size: 18px;
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink);
  text-shadow: 0 0 14px rgba(242, 93, 39, 0.25);
  font-variant-numeric: tabular-nums;
}

.ident {
  display: flex;
  gap: 10px;
}
.ident-cell {
  display: inline-flex;
  flex-direction: column;
  line-height: 1.1;
  padding: 4px 12px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.25);
}
.ident-cell b {
  font: 700 9px/1.4 var(--mono);
  letter-spacing: 0.18em;
  color: var(--accent-dim);
}
.ident-cell i {
  font: 600 13px/1.3 var(--mono);
  font-style: normal;
  color: var(--ink);
}

.mode-switch {
  display: inline-flex;
  padding: 3px;
  gap: 3px;
  background: #05070a;
  border: 1px solid var(--line);
  border-radius: 9px;
}
.mode-btn {
  appearance: none;
  border: 0;
  cursor: pointer;
  padding: 7px 18px;
  border-radius: 7px;
  background: transparent;
  color: var(--ink-dim);
  font: 700 12px/1 var(--mono);
  letter-spacing: 0.14em;
  transition: background 0.15s, color 0.15s;
}
.mode-btn:hover { color: var(--ink); }
.mode-btn.active {
  color: #06080b;
  background: linear-gradient(180deg, var(--accent-bright), var(--accent));
  box-shadow: 0 0 16px rgba(255, 179, 0, 0.3);
}

/* ---------- Body: rail + stage ---------- */
.hud-body {
  display: grid;
  grid-template-columns: 324px 1fr;
  gap: 10px;
  min-height: 0;
}

/* Two panes, not one scrolling column. A laptop viewport is ~650px tall and
   the form alone is taller than that in history mode, so a single scroller put
   the clip list — the thing you actually work with — entirely below the fold.
   The form takes what it needs up to its share and scrolls within it; the clip
   panel keeps the rest and scrolls on its own, so clips are always on screen. */
.hud-rail {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 14px;
  display: grid;
  /* The clip pane is guaranteed 40% of the rail; the form takes what it needs
     from the rest and scrolls for the remainder. Without a floor here the
     form's `auto` track simply eats the pane down to a single visible row. */
  grid-template-rows: minmax(0, auto) minmax(40%, 1fr);
  gap: 14px;
  min-height: 0;
  overflow: hidden;
}
.hud-rail > form {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow-y: auto;
  /* Room for the scrollbar so it never sits on top of an input's border. */
  padding-right: 2px;
}
/* In live mode there is no clip panel to share with, so the form takes it all. */
body.mode-live .hud-rail { grid-template-rows: minmax(0, 1fr); }

/* Endpoint groups inside Advanced: same look as a rail group, one level in. */
.adv-group {
  border: 1px solid var(--line);
  border-radius: 8px;
  margin: 0 0 10px;
  padding: 10px 10px 12px;
  min-width: 0;
}
.adv-group legend {
  padding: 0 6px;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.14em;
  color: var(--ink-dim);
  text-transform: uppercase;
}

.rail-group {
  border: 1px solid var(--line);
  border-radius: 8px;
  margin: 0;
  padding: 12px 12px 14px;
  background: rgba(255, 255, 255, 0.012);
  /* A <fieldset> defaults to min-width: min-content, so a wide intrinsic child
     (the datetime-local inputs) pushed the whole group past the rail. */
  min-width: 0;
}
.rail-group legend {
  padding: 0 8px;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.18em;
  color: var(--accent);
  text-transform: uppercase;
}

.field {
  display: grid;
  grid-template-columns: 74px 1fr;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}
.field:first-of-type { margin-top: 4px; }
/* A datetime-local renders date + 12-hour time + seconds *and* its picker
   icon. That needs more than the ~188px left beside a label at rail width, and
   the overflow lands on the seconds — silently, since the control just clips.
   These get the whole row, label above. */
.field-stack {
  display: block;
}
.field-stack > span {
  display: block;
  margin-bottom: 5px;
}
.field > span {
  font: 600 11px/1.2 var(--mono);
  letter-spacing: 0.06em;
  color: var(--ink-dim);
}
.field input,
.field select {
  width: 100%;
  /* Grid items floor at min-content; datetime-local's is wider than the rail. */
  min-width: 0;
  padding: 9px 10px;
  background: #05070a;
  border: 1px solid var(--line-bright);
  border-radius: 7px;
  color: var(--ink);
  font: 500 13px/1 var(--mono);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.field input:focus,
.field select:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(242, 93, 39, 0.15);
}
.field input::placeholder { color: #46566c; }

.advanced summary {
  cursor: pointer;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.18em;
  color: var(--ink-dim);
  text-transform: uppercase;
  list-style: none;
  padding: 4px 0;
  user-select: none;
}
.advanced summary::-webkit-details-marker { display: none; }
.advanced summary::before { content: "▸ "; color: var(--accent); }
.advanced[open] summary::before { content: "▾ "; }

.rail-actions { display: flex; gap: 10px; }

.btn {
  appearance: none;
  cursor: pointer;
  border: 1px solid var(--line-bright);
  border-radius: 8px;
  padding: 11px 14px;
  font: 700 12px/1 var(--mono);
  letter-spacing: 0.1em;
  color: var(--ink);
  background: var(--panel-2);
  flex: 1;
  transition: transform 0.08s, background 0.15s, border-color 0.15s, opacity 0.15s;
}
.btn:hover { border-color: var(--line-bright); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary {
  color: #06080b;
  border-color: transparent;
  background: linear-gradient(180deg, var(--accent), #16a6bd);
  box-shadow: 0 0 18px rgba(242, 93, 39, 0.25);
}
.btn-stop {
  color: #2a0c0c;
  border-color: transparent;
  background: linear-gradient(180deg, #ff6a6a, #e23b3b);
}
.btn-ghost {
  width: 100%;
  margin-top: 12px;
  flex: none;
  background: transparent;
  color: var(--accent);
  border-color: var(--accent-dim);
}
.btn-ghost:hover { background: rgba(242, 93, 39, 0.08); }

/* Clip catalog */
.clip-panel { display: flex; flex-direction: column; min-height: 0; }
.clip-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font: 700 10px/1 var(--mono);
  letter-spacing: 0.18em;
  color: var(--accent);
  text-transform: uppercase;
  padding: 2px 2px 8px;
}
.clip-count { color: var(--ink-dim); letter-spacing: 0.06em; }
.clip-list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* The pane scrolls, now that the rail itself no longer does. */
  flex: 1;
  overflow-y: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* The row is not a control — play and upload are its own buttons — so it has
   no pointer cursor and no press state. */
.clip {
  position: relative;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-bright);
  border-radius: 7px;
  padding: 9px 11px;
  background: var(--panel-2);
  transition: border-color 0.15s, background 0.15s;
}
.clip:hover { border-left-color: var(--accent); background: #131c28; }
.clip.playing {
  border-left-color: var(--amber);
  background: #1a160a;
}
.clip.uploading { border-left-color: var(--accent); }
.clip.uploaded { border-left-color: var(--green); }
.clip.upload-failed { border-left-color: var(--red); }

/* Revealed on hover, and on keyboard focus so the buttons stay reachable
   without a pointer. An in-flight or finished upload keeps them visible, since
   the row is then showing state the viewer is watching. */
.clip-actions {
  position: absolute;
  top: 6px;
  right: 8px;
  display: flex;
  gap: 4px;
  opacity: 0;
  transition: opacity 0.12s;
}
.clip:hover .clip-actions,
.clip:focus-within .clip-actions,
.clip.uploading .clip-actions,
.clip.uploaded .clip-actions { opacity: 1; }
@media (hover: none) {
  .clip-actions { opacity: 1; } /* no hover to reveal them on touch */
}
.clip-action {
  appearance: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--line-bright);
  border-radius: 6px;
  background: rgba(5, 7, 10, 0.9);
  color: var(--ink-dim);
  transition: color 0.12s, border-color 0.12s, background 0.12s;
}
/* `display: grid` outranks the UA's `[hidden] { display: none }`, so a button
   that doesn't apply to the row's state would still show. */
.clip-action[hidden] { display: none; }
.clip-action svg { width: 14px; height: 14px; fill: currentColor; }
.clip-action:hover {
  color: var(--accent);
  border-color: var(--accent-dim);
  background: var(--accent-soft);
}
/* Replaces the upload controls while that clip's request is in flight. Keeps
   the action box so the row doesn't jump as buttons come and go. */
.clip-spinner {
  cursor: default;
  border-color: var(--line-bright);
  background: rgba(5, 7, 10, 0.9);
}
.clip-spinner::after {
  content: "";
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 2px solid var(--line-bright);
  border-top-color: var(--accent);
  animation: spin 0.8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .clip-spinner::after { animation: none; border-top-color: var(--accent); }
}
/* The armed-delete prompt takes over the progress text, so it has to read as
   a warning rather than as progress. */
.clip-armed-text {
  color: var(--red);
  font-weight: 600;
}

/* Not a button: the clip is already on the server, so this only reports. It
   keeps the action box so replacing the upload button doesn't shift the row. */
.clip-status {
  cursor: default;
  color: var(--green);
  border-color: rgba(52, 211, 153, 0.45);
  background: rgba(52, 211, 153, 0.12);
}
.clip-status:hover {
  color: var(--green);
  border-color: rgba(52, 211, 153, 0.45);
  background: rgba(52, 211, 153, 0.12);
}

/* Destructive pair: cancel stops a transfer, delete removes the stored video. */
.clip-action[data-action="cancel"]:hover,
.clip-action[data-action="delete"]:hover {
  color: var(--red);
  border-color: rgba(255, 59, 59, 0.5);
  background: rgba(255, 59, 59, 0.12);
}
/* Armed: the next click deletes for real, so it stops looking like the others. */
.clip-action.armed {
  color: #06080b;
  background: var(--red);
  border-color: var(--red);
  animation: pulse 1s ease-in-out infinite;
}
.clip-action.armed:hover { color: #06080b; background: var(--red); }
@media (prefers-reduced-motion: reduce) {
  .clip-action.armed { animation: none; }
}

.clip-progress {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 7px;
}
/* `display: flex` outranks the UA's `[hidden] { display: none }`, so a clip
   with no upload would show an empty bar. */
.clip-progress[hidden] { display: none; }
.clip-bar {
  flex: 1;
  height: 4px;
  border-radius: 3px;
  background: #05070a;
  border: 1px solid var(--line);
  overflow: hidden;
}
.clip-bar > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--accent);
  transition: width 0.3s ease-out;
}
.clip.uploaded .clip-bar > i { background: var(--green); }
.clip.upload-failed .clip-bar > i { background: var(--red); }
.clip-progress-text {
  font: 500 10px/1.2 var(--mono);
  color: var(--ink-dim);
  white-space: nowrap;
}
.clip-time {
  font: 600 12px/1.4 var(--mono);
  color: var(--ink);
}
.clip-meta {
  display: flex;
  gap: 10px;
  margin-top: 3px;
  font: 500 10px/1.2 var(--mono);
  color: var(--ink-dim);
}
.clip-alarm {
  color: var(--red);
  font-weight: 700;
}
.clip-empty {
  padding: 18px 8px;
  text-align: center;
  color: var(--ink-dim);
  font: 500 12px/1.5 var(--mono);
}

/* ---------- Video stage ---------- */
.hud-stage {
  position: relative;
  background: #000;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow), inset 0 0 90px rgba(0, 0, 0, 0.6);
  overflow: hidden;
  min-height: 0;
}
.video { position: absolute; inset: 0; }
/* An explicit height, not just `inset: 0`: the element carries
   `aspect-ratio: 16/9` on its host, and a ratio resolves any dimension left
   `auto` — so insets alone let it size itself to 16:9 and park its control bar
   mid-stage. A specified height wins over the ratio and fills the stage; the
   player letterboxes the picture inside it. */
.video livepin-player { display: block; width: 100%; height: 100%; }

/* Activation hint. A pill rather than a full-bleed scrim: the player draws its
   own controls along the bottom and its toasts above them, and a scrim over
   the whole stage hid both whenever we were not streaming. Bottom padding
   keeps it clear of that control bar; it never takes pointer events. */
.stage-hint {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding-bottom: 76px;
  pointer-events: none;
  transition: opacity 0.2s;
}
.stage-hint.hidden { opacity: 0; }
.hint-card {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  padding: 11px 18px;
  border: 1px solid var(--line-bright);
  border-radius: 999px;
  background: rgba(6, 8, 11, 0.82);
  box-shadow: var(--shadow);
  backdrop-filter: blur(6px);
}
.hint-mark {
  flex: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  border: 2px solid var(--line-bright);
  border-top-color: var(--accent);
  animation: spin 1s linear infinite;
  display: none;
}
body.connecting .hint-mark { display: block; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .hint-mark { animation: none; }
}
.hint-text {
  margin: 0;
  font: 500 12.5px/1.4 var(--mono);
  letter-spacing: 0.04em;
  color: var(--ink-dim);
  max-width: 340px;
}

/* ---------- Mode visibility ---------- */
/* These rules only ever *hide*. The obvious spelling — `.only-history {
   display: none }` plus `body.mode-history .only-history { display: block }` —
   forces `block` onto every element it shows, outranking whatever layout that
   component declared for itself: it silently turned `.clip-panel` from a flex
   column into a block box, so the clip list had no constrained height and a
   long listing was clipped instead of scrolling. Written as "hide when the
   mode doesn't match", a visible element is left with its own display. */
body:not(.mode-history) .only-history { display: none; }
body:not(.mode-live) .only-live { display: none; }

/* ---------- Toasts ---------- */
.toasts {
  position: fixed;
  right: 18px;
  bottom: 18px;
  display: flex;
  flex-direction: column;
  gap: 9px;
  z-index: 50;
}
.toast {
  min-width: 240px;
  max-width: 380px;
  padding: 12px 14px;
  border-radius: 9px;
  border: 1px solid var(--line-bright);
  background: var(--panel-2);
  color: var(--ink);
  box-shadow: var(--shadow);
  font: 500 13px/1.4 var(--sans);
  display: flex;
  gap: 10px;
  align-items: flex-start;
  animation: toast-in 0.18s ease-out;
}
.toast::before {
  content: "";
  width: 4px;
  align-self: stretch;
  border-radius: 3px;
  background: var(--accent);
}
.toast.error::before { background: var(--red); }
.toast.warn::before { background: var(--amber); }
.toast.ok::before { background: var(--green); }
.toast-title { font-weight: 700; display: block; margin-bottom: 2px; }
.toast-body { color: var(--ink-dim); font-size: 12px; }
@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: translateY(0); }
}

body.boot .hud { opacity: 0; }
.hud { transition: opacity 0.2s; }

@media (max-width: 860px) {
  .hud-body { grid-template-columns: 1fr; grid-template-rows: auto 1fr; }
  /* Enough to show a whole field group before scrolling; 38vh cut rows in half. */
  .hud-rail { max-height: 52vh; }
}

/* A landscape phone is under the breakpoint above but only ~390px tall, so
   stacking left the stage about 90px high — unusable for the one thing the
   page is for. Keep the rail beside the video and take the space from its
   width instead, which is the dimension there is plenty of. */
@media (max-width: 860px) and (orientation: landscape) {
  .hud-body {
    grid-template-columns: minmax(190px, 34%) 1fr;
    grid-template-rows: minmax(0, 1fr);
  }
  .hud-rail { max-height: none; }
  /* Reclaim vertical space: in landscape the top bar is pure overhead. */
  .hud-top { padding: 6px 12px; gap: 10px; }
  .clock { display: none; }
}

/* Below this the label column (74px) and the input no longer both fit, and the
   input clips its own text. Same treatment the time
   fields already get: label above, input across the full row. */
@media (max-width: 420px) {
  .field { display: block; }
  .field > span { display: block; margin-bottom: 5px; }
}
