/*
 * Styling for the target-tracking 3D viewer.
 *
 * Dark instrument-panel theme so the wireframe scene stays legible. Layout is
 * a three-row grid (header / stage / controls) with the stage filling the
 * remaining viewport height; the canvas is sized by CSS and the renderer
 * matches its backing store to that size and the device pixel ratio.
 */

:root {
  --bg: #070b14;
  /* Floating stage overlays. Deliberately quite transparent so the scene
     stays readable through them; legibility comes from the backdrop blur and
     the text contrast rather than from an opaque fill. */
  --panel: rgba(10, 16, 29, 0.52);
  --panel-solid: #0c1321;
  --border: #1f2c46;
  --text: #dbe6f7;
  --text-dim: #8fa3c4;
  --accent: #38bdf8;
  --truth: #4ade80;
  --estimate: #fca311;
  --maneuver: #ef4444;
  --infrared: #fb7185;
  --uncertainty: #a78bfa;
  --error: #f87171;
  /* Filter over-confidence. Distinct from --maneuver so cause and symptom are
     never confused on screen. */
  --inconsistent: #e11d74;
  --focus: #7dd3fc;
}

* {
  box-sizing: border-box;
}

/*
 * A class rule such as `.custom-panel { display: flex }` outranks the user
 * agent's `[hidden] { display: none }`, which would leave elements toggled via
 * the `hidden` property still on screen. Restating it here keeps `hidden`
 * authoritative for every element regardless of its own display rule.
 */
[hidden] {
  display: none !important;
}

html,
body {
  height: 100%;
  margin: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 14px;
  overflow: hidden;
}

.app {
  display: grid;
  grid-template-rows: auto auto auto 1fr auto;
  height: 100%;
}

/* ---------- Header ---------- */

/*
 * Three columns rather than a flex row: the equal 1fr side tracks make the
 * middle column sit at the true centre of the bar, independent of how wide
 * the wordmark and the trailing group happen to be.
 */
.app__header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  /* Also kept shallow; the stage gets the height instead. */
  padding: 8px 14px;
  background: var(--panel-solid);
  border-bottom: 1px solid var(--border);
}

.app__header-center {
  display: flex;
  align-items: center;
  gap: 14px;
  justify-self: center;
}

/* ---- Estimator segmented control ---- */

.segmented {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  /* Fieldsets default to min-width: min-content, which would stop this
     shrinking sensibly inside a flex row. */
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: 7px;
  background: #0a1120;
  overflow: hidden;
}

/* The radio itself is hidden but still focusable and still the thing that
   holds the checked state; the adjacent label is what gets painted. */
.segmented__input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.segmented__label {
  padding: 5px 14px;
  color: var(--text-dim);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  user-select: none;
}

.segmented__label:hover {
  background: rgba(56, 189, 248, 0.09);
  color: var(--text);
}

.segmented__input:checked + .segmented__label {
  background: #1d4ed8;
  color: #fff;
}

/* The hidden input takes focus, so the focus ring has to be drawn on the
   label instead or keyboard users would see nothing. */
.segmented__input:focus-visible + .segmented__label {
  outline: 2px solid var(--focus);
  outline-offset: -2px;
}

.segmented__input:disabled + .segmented__label {
  opacity: 0.4;
  cursor: not-allowed;
}

.segmented__input:disabled + .segmented__label:hover {
  background: none;
  color: var(--text-dim);
}

.app__header-end {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  /* Lets a long status message shrink instead of forcing the centre column
     off-centre. */
  min-width: 0;
}

/*
 * Below this width there is not enough room for a centred middle column, so
 * the header falls back to the wrapping flex row it used before.
 */
@media (max-width: 780px) {
  .app__header {
    display: flex;
    flex-wrap: wrap;
  }

  .app__header-end {
    margin-left: auto;
  }
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
}

/* ---- Radar mark ---- */

.brand__mark {
  width: 30px;
  height: 30px;
  flex: none;
  overflow: visible;
}

.brand__ring,
.brand__cross,
.brand__sweep {
  fill: none;
  stroke: var(--accent);
  stroke-linecap: round;
}

.brand__ring {
  stroke-width: 1.1;
  opacity: 0.45;
}

.brand__cross {
  stroke-width: 0.9;
  opacity: 0.22;
}

.brand__sweep {
  stroke-width: 1.6;
  /* Rotates about the dial centre rather than the element's own box. */
  transform-origin: 16px 16px;
  animation: radar-sweep 4s linear infinite;
}

.brand__blip {
  fill: var(--truth);
  animation: radar-blip 4s ease-in-out infinite;
}

@keyframes radar-sweep {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

/* Pulses as the sweep passes over it, which is what sells the mark as a radar
   rather than a static logo. */
@keyframes radar-blip {
  0%, 100% { opacity: 0.25; }
  14%      { opacity: 1; }
  45%      { opacity: 0.25; }
}

/*
 * Motion is decorative here and carries no information, so it is dropped
 * entirely for anyone who has asked for reduced motion. The mark still reads
 * as a radar dial from the rings and blip alone.
 */
@media (prefers-reduced-motion: reduce) {
  .brand__sweep,
  .brand__blip {
    animation: none;
  }
  .brand__blip { opacity: 0.9; }
}

/* ---- Wordmark ---- */

.app__title {
  display: grid;
  margin: 0;
  line-height: 1.05;
}

/* Small caps line above the name. Splitting the title across two weights is
   what stops it reading as default browser heading text; the <h1> still
   exposes the full "Multi-Sensor Target Tracking" as one accessible name. */
.app__title-kicker {
  color: var(--accent);
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.19em;
  text-transform: uppercase;
  opacity: 0.9;
}

.app__title-main {
  color: #a5e4ff;
  font-size: 17px;
  font-weight: 650;
  letter-spacing: -0.01em;
}

.status {
  margin: 0;
  color: var(--text-dim);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/*
 * Removed from the visual layout but left in the accessibility tree.
 *
 * `.status--quiet` uses it so routine detail such as "3001 frames over 60.0 s"
 * is still announced by the role="status" live region while taking no space.
 * The estimator fieldset's <legend> uses it to name the radio group without
 * the legend consuming a whole line in the header. Clipped rather than
 * `display: none`, which would remove it from the accessibility tree too.
 */
.visually-hidden,
.status--quiet {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.custom-panel {
  display: flex;
  gap: 22px;
  flex-wrap: wrap;
  padding: 10px 18px;
  background: #0a1120;
  border-bottom: 1px solid var(--border);
}

.error-banner {
  margin: 0;
  padding: 10px 18px;
  background: #3b1216;
  border-bottom: 1px solid #7f1d1d;
  color: #fecaca;
  font-size: 13px;
}

/* ---------- Form controls ---------- */

.field {
  display: flex;
  align-items: center;
  gap: 8px;
}

.field--grow {
  flex: 1 1 240px;
  min-width: 160px;
}

.field label,
.fieldset legend {
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.field__hint {
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.75;
}

select,
input[type='file'] {
  background: #111a2c;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 5px 8px;
  font: inherit;
  font-size: 13px;
}

input[type='range'] {
  width: 100%;
  accent-color: var(--accent);
}

.button {
  background: #16213a;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 5px 11px;
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}

.button:hover {
  background: #1e2c4a;
}

.button--primary {
  min-width: 84px;
  background: #14532d;
  border-color: #22c55e;
  font-weight: 600;
}

.button--primary:hover {
  background: #166534;
}

/* Header action: present but visually subordinate to the scenario picker,
   which is the control people reach for most. */
.button--ghost {
  background: transparent;
  white-space: nowrap;
  border-color: rgba(96, 126, 173, 0.45);
  color: var(--text-dim);
}

.button--ghost:hover {
  background: rgba(56, 189, 248, 0.1);
  border-color: var(--accent);
  color: var(--text);
}

/* Camera preset currently applied. Marked with aria-pressed as well, so the
   state is not communicated by colour alone. */
.button[aria-pressed='true'] {
  background: #1d4ed8;
  border-color: var(--focus);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.fieldset {
  display: flex;
  align-items: center;
  gap: 9px;
  flex-wrap: wrap;
  margin: 0;
  padding: 3px 10px 6px;
  border: 1px solid var(--border);
  border-radius: 6px;
}

/* Camera presets, sitting inline alongside the transport controls. */
.camera-group {
  display: flex;
  align-items: center;
  gap: 6px;
}

.camera-group__label {
  color: var(--text-dim);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  white-space: nowrap;
}

.fieldset--layers {
  /* Tighter row-gap so that when the checkboxes do wrap on a narrow window,
     the second line costs as little height as possible. */
  row-gap: 2px;
}

.fieldset--layers label {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--text-dim);
  font-size: 11.5px;
  cursor: pointer;
}

.fieldset--layers input {
  accent-color: var(--accent);
}

.readout {
  min-width: 74px;
  color: var(--text);
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ---------- Stage ---------- */

.app__stage {
  position: relative;
  min-height: 0;
  overflow: hidden;
}

.stage__canvas {
  display: block;
  width: 100%;
  height: 100%;
  cursor: grab;
  touch-action: none;
}

.stage__canvas.is-dragging {
  cursor: grabbing;
}

.hud {
  position: absolute;
  background: var(--panel);
  border: 1px solid rgba(96, 126, 173, 0.34);
  border-radius: 10px;
  padding: 9px 12px;
  /* Carries most of the legibility now that the fill is largely transparent:
     the blur removes the high-frequency wireframe detail behind the text
     while still letting the scene's colour and motion through. Saturation is
     lifted slightly so the scene does not look washed out through the panel. */
  backdrop-filter: blur(13px) saturate(135%);
  -webkit-backdrop-filter: blur(13px) saturate(135%);
  /* Lifts the panels off the scene so they read as an overlay rather than
     as part of the geometry behind them. */
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.38);
  pointer-events: none;
}

/*
 * Fallback for engines without backdrop-filter. Without the blur, a 52%
 * transparent panel would leave text sitting directly on the wireframe, so
 * the fill goes back to being nearly opaque.
 */
@supports not (backdrop-filter: blur(1px)) {
  .hud {
    background: rgba(10, 16, 29, 0.88);
  }
}

.hud--telemetry {
  top: 14px;
  left: 14px;
  min-width: 232px;
}

.hud--legend {
  top: 14px;
  right: 14px;
}

/* A dark halo on overlay text, so a bright trajectory line passing behind the
   panel cannot erode the contrast of a label sitting on top of it. */
.hud,
.hud * {
  text-shadow: 0 1px 2px rgba(4, 8, 16, 0.85);
}

.hud__heading {
  margin: 0 0 6px;
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}

.telemetry {
  margin: 0;
  display: grid;
  gap: 1px;
}

/* Zebra striping makes it much easier to track a label across to its value
   than relying on the gap alone. */
.telemetry__row {
  display: flex;
  justify-content: space-between;
  gap: 18px;
  padding: 2px 5px;
  border-radius: 3px;
}

/* Slightly stronger than it would need to be on an opaque panel, since the
   scene showing through competes with a very faint stripe. */
.telemetry__row:nth-child(odd) {
  background: rgba(255, 255, 255, 0.06);
}

.telemetry dt {
  color: var(--text-dim);
  font-size: 12px;
}

.telemetry dd {
  margin: 0;
  color: #eaf1fb;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}

.badge {
  margin: 9px 0 0;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-align: center;
  text-transform: uppercase;
}

.badge--maneuver {
  background: rgba(239, 68, 68, 0.18);
  border: 1px solid var(--maneuver);
  color: #fca5a5;
}

.legend {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 4px;
  font-size: 12px;
  color: var(--text-dim);
}

.legend li {
  display: flex;
  align-items: center;
  gap: 8px;
}

.swatch {
  width: 16px;
  height: 3px;
  border-radius: 2px;
  flex: none;
}

.swatch--truth { background: var(--truth); }
.swatch--estimate { background: var(--estimate); }
.swatch--maneuver { background: var(--maneuver); }
.swatch--radar { background: var(--accent); }
.swatch--ir { background: var(--infrared); }
.swatch--uncertainty { background: var(--uncertainty); }
.swatch--error { background: var(--error); }
.swatch--inconsistent { background: var(--inconsistent); }

/* IMM model-probability row: the meter and its text reading sit side by side
   so the value is legible without interpreting the bar. */
.telemetry__model {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.model-meter {
  width: 4.5rem;
  height: 0.55rem;
}

/* Meter styling has to be set per-engine; the element still carries the
   correct value and text either way, so an unstyled fallback stays usable. */
.model-meter::-webkit-meter-bar {
  background: rgba(255, 255, 255, 0.12);
  border-radius: 999px;
}

.model-meter::-webkit-meter-optimum-value {
  background: var(--inconsistent);
  border-radius: 999px;
}

.model-meter::-moz-meter-bar {
  background: var(--inconsistent);
  border-radius: 999px;
}

/*
 * Telemetry values that carry a verdict rather than a measurement. The text
 * itself also states the condition, so this styling reinforces the alarm
 * rather than being the only signal of it.
 */
.telemetry dd.is-alarm {
  color: #ff9ec4;
  font-weight: 600;
}

.legend__hint {
  margin: 7px 0 0;
  color: var(--text-dim);
  font-size: 11px;
  opacity: 0.8;
}

/* The two hint lines read as one block, so they sit closer together than the
   first one sits to the legend list above it. */
.legend__hint + .legend__hint {
  margin-top: 3px;
}

/* ---------- Controls ---------- */

/*
 * Kept as shallow as possible: every pixel here is taken from the 3D stage.
 * Two rows rather than three, with the camera presets folded into the
 * transport row and the keyboard hint onto the end of the layers row.
 */
.app__controls {
  padding: 7px 14px 8px;
  background: var(--panel-solid);
  border-top: 1px solid var(--border);
  display: grid;
  gap: 6px;
}

.controls__row {
  display: flex;
  align-items: center;
  gap: 12px;
}

.controls__row--wrap {
  flex-wrap: wrap;
}

kbd {
  background: rgba(22, 33, 58, 0.72);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 4px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 10px;
}

/* Hide the floating panels on narrow screens where they would cover the scene. */
@media (max-width: 860px) {
  .hud--legend {
    display: none;
  }
}

/* ---------- About dialog ---------- */

/*
 * A native <dialog>. The user agent handles the top-layer placement, focus
 * trap, Esc-to-close and returning focus to the invoking button, so none of
 * that is reimplemented here.
 */
.about {
  width: min(720px, calc(100vw - 32px));
  max-height: min(84vh, 900px);
  padding: 0;
  border: 1px solid rgba(96, 126, 173, 0.4);
  border-radius: 14px;
  background: #0d1526;
  color: var(--text);
  box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
  overflow: hidden;
}

.about::backdrop {
  background: rgba(4, 8, 16, 0.72);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
}

.about__body {
  max-height: inherit;
  overflow-y: auto;
  padding: 26px 30px 22px;
  /* Long-form prose, so the measure is capped nearer 70 characters than the
     full dialog width. */
  font-size: 13.5px;
  line-height: 1.62;
}

.about__header {
  border-bottom: 1px solid var(--border);
  padding-bottom: 14px;
  margin-bottom: 16px;
}

.about__eyebrow {
  margin: 0 0 5px;
  color: var(--accent);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.19em;
  text-transform: uppercase;
}

.about__title {
  margin: 0;
  font-size: 21px;
  font-weight: 650;
  line-height: 1.25;
  letter-spacing: -0.015em;
}

.about__lede {
  margin: 0 0 4px;
  color: #e6eefc;
  font-size: 14.5px;
  line-height: 1.6;
}

.about__section {
  margin-top: 22px;
}

.about__section h3 {
  margin: 0 0 7px;
  color: var(--accent);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.about__section p {
  margin: 0 0 9px;
  color: #c6d4ea;
}

.about__section ul {
  margin: 0 0 9px;
  padding-left: 18px;
  color: #c6d4ea;
}

.about__section li {
  margin-bottom: 5px;
}

/* Legend rows inside the dialog: the swatch is decorative, so each item also
   names what it refers to in prose. */
.about__keys {
  list-style: none;
  padding-left: 0;
}

.about__keys li {
  display: flex;
  gap: 10px;
}

/* flex-start plus an explicit offset rather than baseline alignment: an empty
   swatch has no text to derive a baseline from, so its position would depend
   on the browser's fallback. */
.about__keys .swatch {
  width: 18px;
  margin-top: 9px;
}

.about__table-wrap {
  /* Scrolls rather than overflowing on a narrow window. */
  overflow-x: auto;
  margin: 12px 0;
}

.about__table {
  width: 100%;
  border-collapse: collapse;
  font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}

.about__table caption {
  color: var(--text-dim);
  font-size: 11px;
  text-align: left;
  padding-bottom: 7px;
}

.about__table th,
.about__table td {
  padding: 6px 10px;
  text-align: right;
  border-bottom: 1px solid rgba(31, 44, 70, 0.75);
}

.about__table thead th {
  color: var(--text-dim);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}

.about__table tbody th[scope='row'] {
  color: #c6d4ea;
  font-weight: 400;
  text-align: left;
}

.about__note {
  color: var(--text-dim);
  font-size: 11px;
}

/* The improved column. Weight and colour together, never colour alone --
   the row header already says which metric it is and lower/higher is
   explained in the surrounding prose. */
.about__win {
  color: #86efac;
  font-weight: 650;
}

.about__caveat {
  margin-top: 12px;
  padding: 10px 12px;
  border-left: 2px solid var(--border);
  background: rgba(255, 255, 255, 0.03);
  border-radius: 0 5px 5px 0;
  color: var(--text-dim);
  font-size: 12.5px;
}

.about__footer {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 24px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

/* The GitHub link is styled as a button, so it needs the padding a real
   button gets from its user-agent styles. */
.about__footer .button {
  text-decoration: none;
  line-height: 1.5;
}

.about__footer #about-close {
  margin-left: auto;
}
