/* eyes.z.je — Traffic Scotland LEV camera wall.
 *
 * Committed dark theme rather than a light/dark pair: the content is 581 dusk-
 * and-night road scenes, and a light chrome around them turns every thumbnail
 * into a glare box. Status hues are the reserved status palette (good/warning/
 * serious/critical) and are never asked to carry meaning alone — every status
 * appears as colour + glyph + word.
 */

:root {
  color-scheme: dark;

  --bg:          #111110;
  --surface-1:   #1a1a19;
  --surface-2:   #232321;
  --surface-3:   #2d2d2a;
  --line:        #383835;
  --line-soft:   #2a2a27;

  --ink:         #f5f5f2;
  --ink-2:       #c3c2b7;
  --ink-3:       #8b8a80;

  /* Reserved status palette — fixed, never themed. */
  --good:        #0ca30c;
  --warning:     #fab219;
  --serious:     #ec835a;
  --critical:    #d03b3b;
  --accent:      #3987e5;

  --r:  10px;
  --r-s: 6px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--accent); }

/* ── header ─────────────────────────────────────────────────────────────── */

header {
  position: sticky; top: 0; z-index: 900;
  background: color-mix(in srgb, var(--bg) 92%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
  padding: 10px 16px;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
}

.brand { display: flex; align-items: baseline; gap: 10px; margin-right: auto; }
.brand h1 { font-size: 16px; margin: 0; letter-spacing: -0.01em; font-weight: 650; }
.brand .sub { font-size: 12px; color: var(--ink-3); }

.stamp {
  font-family: var(--mono); font-size: 11px; color: var(--ink-3);
  display: flex; align-items: center; gap: 6px; white-space: nowrap;
}
.pulse {
  width: 7px; height: 7px; border-radius: 50%; background: var(--good);
  box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 70%, transparent);
  animation: pulse 2.6s ease-out infinite;
}
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--good) 70%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) { .pulse { animation: none; } }

/* ── stat strip ─────────────────────────────────────────────────────────── */

.stats {
  display: grid; gap: 1px; background: var(--line-soft);
  grid-template-columns: repeat(auto-fit, minmax(132px, 1fr));
  border-bottom: 1px solid var(--line);
}
.stat {
  background: var(--bg); padding: 12px 16px;
  display: flex; flex-direction: column; gap: 2px;
  border: none; text-align: left; font: inherit; color: inherit;
  cursor: pointer; transition: background .12s;
  /* A <button> flex container does not hand its width down the way a <div>
     does, so the long status labels were escaping the tile and scrolling the
     whole page sideways on a phone. The ellipsis below is the intended
     behaviour; this is the guarantee that nothing gets out regardless. */
  min-width: 0; overflow: hidden;
}
.stat:hover { background: var(--surface-1); }
.stat[aria-pressed="true"] { background: var(--surface-2); box-shadow: inset 0 -2px 0 var(--accent); }
.stat .n {
  font-family: var(--mono); font-size: 21px; font-weight: 600;
  letter-spacing: -0.02em; line-height: 1.15;
}
.stat .l {
  font-size: 11px; color: var(--ink-3); text-transform: uppercase;
  letter-spacing: .07em; display: flex; align-items: center; gap: 5px; min-width: 0;
}
/* The label needs to be a real element, not a bare text node: an anonymous flex
   item cannot be given overflow, so without this wrapper "CURRENTLY
   UNAVAILABLE" widens its grid column and scrolls the whole page sideways. */
.stat .l .t {
  min-width: 0; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.stat .pct { font-size: 11px; color: var(--ink-3); font-family: var(--mono); }
.stat .abbr { display: none; }

.dot { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.dot.live        { background: var(--good); border-radius: 50%; }
.dot.unavailable { background: var(--critical); }
.dot.operational { background: var(--warning); transform: rotate(45deg); width:7px; height:7px; }
.dot.stale, .dot.frozen { background: var(--serious); clip-path: polygon(50% 0,100% 100%,0 100%); }
.dot.missing     { background: var(--ink-3); border-radius: 50%; }
.dot.placeholder { background: var(--critical); }

/* ── toolbar ────────────────────────────────────────────────────────────── */

.toolbar {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  padding: 10px 16px; border-bottom: 1px solid var(--line);
  background: var(--surface-1);
}
.toolbar input[type=search], .toolbar select {
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-s);
  padding: 6px 10px; font: inherit; font-size: 13px;
}
.toolbar input[type=search] { min-width: 210px; flex: 1 1 210px; max-width: 320px; }
.toolbar input:focus-visible, .toolbar select:focus-visible,
button:focus-visible, .card:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}

.seg { display: flex; border: 1px solid var(--line); border-radius: var(--r-s); overflow: hidden; }
.seg button {
  background: var(--surface-2); color: var(--ink-2); border: none;
  padding: 6px 13px; font: inherit; font-size: 13px; cursor: pointer;
}
.seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--accent); color: #fff; }

.count { margin-left: auto; font-family: var(--mono); font-size: 12px; color: var(--ink-3); }

.linkish {
  background: none; border: none; color: var(--accent); font: inherit;
  font-size: 12px; cursor: pointer; padding: 4px 2px; text-decoration: underline;
  text-underline-offset: 2px;
}

/* ── views ──────────────────────────────────────────────────────────────── */

/* The map fills whatever the header, stat strip and toolbar leave, rather than
   subtracting a magic number that drifts the moment a stat tile wraps. */
main { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
#map { flex: 1 1 auto; min-height: 400px; background: var(--surface-1); }

.grid {
  display: grid; gap: 10px; padding: 14px 16px 40px;
  grid-template-columns: repeat(auto-fill, minmax(232px, 1fr));
}

.card {
  background: var(--surface-1); border: 1px solid var(--line);
  border-radius: var(--r); overflow: hidden; cursor: pointer;
  display: flex; flex-direction: column; text-align: left;
  padding: 0; font: inherit; color: inherit;
  transition: border-color .12s, transform .12s;
}
.card:hover { border-color: var(--ink-3); transform: translateY(-1px); }

.shot { position: relative; aspect-ratio: 4 / 3; background: var(--surface-3); }
.shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card.is-card .shot img { opacity: .55; filter: saturate(.4); }
.gone {
  width: 100%; height: 100%; display: grid; place-items: center;
  font-size: 11px; color: var(--ink-3); font-family: var(--mono);
  background: repeating-linear-gradient(45deg, var(--surface-2) 0 8px, var(--surface-3) 8px 16px);
}

.badge {
  position: absolute; top: 6px; left: 6px;
  font-family: var(--mono); font-size: 10px; letter-spacing: .04em;
  padding: 2px 6px; border-radius: 4px; text-transform: uppercase;
  display: flex; align-items: center; gap: 4px;
  background: color-mix(in srgb, #000 68%, transparent); color: #fff;
  border: 1px solid color-mix(in srgb, #fff 22%, transparent);
}
/* Sits beside the status badge, in the "serious" status hue: the camera is not
   down, but what it is showing cannot be used. */
.badge.odd {
  left: auto; right: 6px;
  background: color-mix(in srgb, #ec835a 86%, #000);
  border-color: color-mix(in srgb, #fff 30%, transparent); color: #1a1a19;
  font-weight: 600;
}
.age {
  position: absolute; bottom: 6px; right: 6px;
  font-family: var(--mono); font-size: 10px; padding: 2px 5px; border-radius: 4px;
  background: color-mix(in srgb, #000 68%, transparent); color: var(--ink-2);
}

.meta { padding: 8px 10px 10px; display: flex; flex-direction: column; gap: 2px; }
.meta .nm { font-size: 13px; font-weight: 550; line-height: 1.3; }
.meta .lo { font-size: 11px; color: var(--ink-3); display: flex; gap: 6px; flex-wrap: wrap; }

/* ── region health bars ─────────────────────────────────────────────────── */

.panel { padding: 16px 16px 44px; max-width: 900px; }
.panel h2 { font-size: 14px; margin: 22px 0 4px; font-weight: 600; }
.panel h2:first-child { margin-top: 0; }
.panel p.note { font-size: 12px; color: var(--ink-3); margin: 0 0 14px; max-width: 65ch; }

table.health { width: 100%; border-collapse: collapse; font-size: 13px; }
table.health th {
  text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-3); font-weight: 500; padding: 6px 8px; border-bottom: 1px solid var(--line);
}
table.health th.num, table.health td.num { text-align: right; font-family: var(--mono); }
table.health td { padding: 7px 8px; border-bottom: 1px solid var(--line-soft); vertical-align: middle; }
table.health tr:hover td { background: var(--surface-1); }

/* Stacked availability bar: 2px surface gap between segments, 4px rounded ends. */
.bar { display: flex; gap: 2px; height: 9px; min-width: 120px; }
.bar span { display: block; height: 100%; }
.bar span:first-child { border-radius: 4px 0 0 4px; }
.bar span:last-child  { border-radius: 0 4px 4px 0; }
.bar span:only-child  { border-radius: 4px; }
.bar .s-live        { background: var(--good); }
.bar .s-operational { background: var(--warning); }
.bar .s-unavailable { background: var(--critical); }
.bar .s-stale, .bar .s-frozen { background: var(--serious); }
.bar .s-missing     { background: var(--ink-3); }
/* Unpainted remainder in the cadence histogram — holds the bar's full width so
   every row shares one scale, without drawing anything. */
.bar span:not([class]) { background: var(--line-soft); }

/* ── detail dialog ──────────────────────────────────────────────────────── */

dialog#detail {
  border: 1px solid var(--line); border-radius: var(--r); padding: 0;
  background: var(--surface-1); color: var(--ink);
  max-width: min(760px, 94vw); width: 100%;
}
dialog#detail::backdrop { background: rgba(0,0,0,.72); }
.dh { display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px 10px; }
.dh h3 { margin: 0; font-size: 16px; font-weight: 600; }
.dh .sub { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.dh button {
  margin-left: auto; background: var(--surface-3); border: 1px solid var(--line);
  color: var(--ink-2); border-radius: var(--r-s); width: 28px; height: 28px;
  cursor: pointer; font-size: 15px; line-height: 1; flex: none;
}
dialog#detail img { width: 100%; display: block; background: #000; }
.dl {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px; background: var(--line-soft); border-top: 1px solid var(--line);
}
.dl div { background: var(--surface-1); padding: 9px 14px; }
.dl dt { font-size: 10px; text-transform: uppercase; letter-spacing: .07em; color: var(--ink-3); }
.dl dd { margin: 1px 0 0; font-family: var(--mono); font-size: 12.5px; }

/* ── leaflet skin ───────────────────────────────────────────────────────── */

.leaflet-container { background: var(--surface-1); font-family: var(--sans); }
/* OSM tiles are bright; damping them keeps the status markers the loudest
   thing on the map rather than the basemap's motorway shields. */
.leaflet-tile-pane { filter: brightness(.68) saturate(.72) contrast(1.06); }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
  background: var(--surface-2); color: var(--ink);
  border: 1px solid var(--line); box-shadow: 0 8px 26px rgba(0,0,0,.5);
}
.leaflet-popup-content { margin: 0; width: 248px !important; }
.leaflet-popup-content img { width: 100%; display: block; border-radius: var(--r-s) var(--r-s) 0 0; }
.pop { padding: 8px 10px 10px; }
.pop .nm { font-size: 13px; font-weight: 600; }
.pop .lo { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
.pop button {
  margin-top: 7px; width: 100%; background: var(--surface-3); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--r-s); padding: 5px;
  font: inherit; font-size: 12px; cursor: pointer;
}
.leaflet-control-attribution {
  background: color-mix(in srgb, var(--bg) 84%, transparent) !important;
  color: var(--ink-3) !important; font-size: 10px !important;
}
.leaflet-control-attribution a { color: var(--ink-2) !important; }
.leaflet-bar a {
  background: var(--surface-2); color: var(--ink); border-color: var(--line);
}
.leaflet-bar a:hover { background: var(--surface-3); color: var(--ink); }

.legend {
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  border: 1px solid var(--line); border-radius: var(--r-s);
  padding: 8px 10px; font-size: 11px; line-height: 1.7; backdrop-filter: blur(8px);
}
.legend div { display: flex; align-items: center; gap: 7px; color: var(--ink-2); }

.empty { padding: 48px 16px; text-align: center; color: var(--ink-3); }

/* Sits below the map rather than over it: the derivation disclaimer is the part
   that matters and it should not be something you have to dismiss. */
footer {
  border-top: 1px solid var(--line); padding: 12px 16px;
  font-size: 11.5px; color: var(--ink-3); line-height: 1.6; max-width: 90ch;
}
footer a { color: var(--ink-2); }
[hidden] { display: none !important; }

/* ── narrow screens ─────────────────────────────────────────────────────────
 * On a phone the header, four stat tiles and a wrapped toolbar consumed the
 * entire viewport, pushing the map below the fold — the one thing the page
 * exists to show. Everything above the map is tightened so it clears in ~45% of
 * the screen instead of 110%.
 */
@media (max-width: 560px) {
  header { padding: 8px 12px; gap: 8px; }
  .brand h1 { font-size: 15px; }
  .brand .sub { display: none; }

  /* minmax(0, 1fr), not 1fr: a bare 1fr track is minmax(auto, 1fr), and that
     auto floor is the item's min-content width -- so the longest label widens
     its column no matter what min-width the item itself carries. */
  .stats { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  /* Grid items default to min-width:auto, so the longest label ("CURRENTLY
     UNAVAILABLE") widens its column past 1fr and pushes the page into
     horizontal scroll. Both the tile and the flex row inside it must be allowed
     to shrink before the ellipsis can do its job. */
  .stat { padding: 7px 8px; gap: 0; min-width: 0; }
  .stat .l, .stat .n, .stat .pct { min-width: 0; }
  .stat .n { font-size: 16px; }
  .stat .l { font-size: 9.5px; letter-spacing: .03em; gap: 3px; }
  /* Swap to the short wording rather than letting the ellipsis cut mid-word. */
  .stat .full { display: none; }
  .stat .abbr { display: inline; }
  .stat .pct { font-size: 9.5px; }

  .toolbar { padding: 8px 12px; gap: 6px; }
  .toolbar input[type=search] { flex: 1 1 100%; max-width: none; min-width: 0; }
  .toolbar select { flex: 1 1 0; min-width: 0; font-size: 12px; padding: 5px 6px; }
  .seg button { padding: 5px 11px; font-size: 12px; }
  .count { flex: 1 1 100%; margin-left: 0; text-align: right; }

  #map { min-height: 320px; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 8px; padding: 10px 12px 32px; }
  .panel { padding: 12px 12px 32px; }
  table.health td, table.health th { padding: 6px 5px; font-size: 12px; }
}

/* ── suss view ──────────────────────────────────────────────────────────────
 * The last real frame from cameras that have since gone to a placeholder.
 */
.panel.wide { max-width: none; }
.suss-grid { padding: 0; margin-top: 10px; }

/* An operator grab reads as an event, so it gets a card that looks like one
   rather than another row in a table. */
.incident {
  border: 1px solid var(--line); border-left: 3px solid var(--warning);
  border-radius: var(--r-s); padding: 9px 12px; margin-bottom: 8px;
  background: var(--surface-1);
}
.ihead { font-size: 13px; }
.ihead .muted { color: var(--ink-3); font-size: 12px; }
.inames { font-size: 11.5px; color: var(--ink-3); margin-top: 3px; line-height: 1.5; }

/* The Suss wall shows historic frames, so the age chip has to carry a phrase
   ("dark since before this site started watching"), not just "9m ago". */
.suss-grid .age {
  max-width: calc(100% - 12px); white-space: normal; text-align: right; line-height: 1.35;
}

/* ── rewind strip ───────────────────────────────────────────────────────────
 * Arrow keys step through a camera's recent frames. The buttons exist as well
 * as the keys because a phone has no arrow keys, and the strip is the only
 * thing telling you a rewind is available at all.
 */
.rewind {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 12px; background: var(--surface-2);
  border-top: 1px solid var(--line);
}
.rewind button {
  background: var(--surface-3); color: var(--ink); border: 1px solid var(--line);
  border-radius: var(--r-s); width: 34px; height: 26px; cursor: pointer;
  font-size: 11px; line-height: 1; flex: none;
}
.rewind button:hover:not(:disabled) { background: var(--line); }
.rewind button:disabled { opacity: .35; cursor: default; }
#d-when {
  flex: 1; text-align: center; font-family: var(--mono); font-size: 11.5px;
  color: var(--ink-2);
}
#d-when .live { color: var(--good); }
#d-when .past { color: var(--warning); }
