/* Dark instrument-panel look: the canvas is the product, the chrome gets out of
   the way. Phone-first — the panel is a bottom sheet under 900px, a sidebar over
   it. This is harness styling; the apps ship native UI. */

/* The brand palette, mirrored from the Android theme
   (`android/app/src/main/java/eu/disclab/app/ui/theme/Color.kt`), which is where
   these colours are named and owned. The logo is the same two colours: cyan on
   #121A22. Keep them in step — a harness in a different accent reads as a
   different product. */
:root {
  --bg: #121a22;
  --bg-sunk: #0c1218;
  --panel: #18232d;
  --line: #1f2c38;
  --text: #f8fafc;
  --muted: #8296a8;
  --accent: #4dd8ff;
  --accent-dim: #0e7490;
  /* Channels of --accent, for the tinted fills below. CSS cannot take a hex
     apart, and a second literal is how the accent went stale here last time. */
  --accent-rgb: 77, 216, 255;
  --radius: 10px;
  --font: system-ui, -apple-system, "Segoe UI", Inter, Roboto, "Helvetica Neue", sans-serif;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

button, input { font: inherit; color: inherit; }

/* `minmax(0, 1fr)`, not `1fr`: a `1fr` track floors at the *min-content* of what
   is in it, and the segmented controls are deliberately nowrap. A plain `1fr`
   therefore lets the top bar's buttons set the width of the whole page — the
   viewport stops being the limit, and every row overflows sideways rather than
   the one that could not fit. The track absorbs the pressure instead. */
#app {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  grid-template-rows: 52px 1fr;
  grid-template-areas: "top panel" "stage panel";
  height: 100dvh;
}

/* ---------- top bar ---------- */

.topbar {
  grid-area: top;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 14px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-sunk);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: inherit;
  font-weight: 650;
  letter-spacing: 0.2px;
  text-decoration: none;
}

/* The logo itself, from the one copy the site serves (`/logo.svg` — `logo/icon.svg`,
   the same art the disc's flight plate is drawn from). Relative, so the harness
   stays movable. The SVG carries its own #121A22 tile, which is a shade lighter
   than the sunk top bar: that is the mark reading as an app icon, not a seam.

   The zoom crops that icon's safe-area padding, which is the launcher's business
   and not the top bar's — the glyph is only 80% of the tile across and 62% down,
   and it is off-centre in it (centre 52.8%, 47.9%), so the positions straddle 50%
   to centre the glyph rather than the artboard. Not fixable at the source: that
   file is also the launcher icon, which needs the padding. `inkscape --query-all`
   re-derives all three. */
.brand .mark {
  width: 32px;
  height: 32px;
  border-radius: 7px;
  flex: none;
  background: var(--bg) url(../logo.svg) 71.5% 34.1% / 115% no-repeat;
}

.brand .sub {
  color: var(--muted);
  font-weight: 400;
  font-size: 12px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
}

/* ---------- segmented controls ---------- */

.toggles {
  display: flex;
  align-items: center;
  gap: 10px;
}

.viewtoggle, .throwtypes, .unittoggle {
  display: flex;
  gap: 2px;
  padding: 2px;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.throwtypes { width: 100%; }

.seg {
  flex: 1;
  padding: 6px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  /* Keep every label on one line so "Top-down" never wraps and all buttons
     stay the same height. */
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}

.seg:hover { color: var(--text); }

.seg.on {
  background: rgba(var(--accent-rgb), 0.14);
  color: var(--accent);
  font-weight: 600;
}

/* ---------- stage ---------- */

.stage {
  grid-area: stage;
  position: relative;
  min-height: 0;
  background: var(--bg-sunk);
}

#canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The orbit controller owns every gesture; the page must not scroll under it. */
  touch-action: none;
  cursor: grab;
}

#canvas:active { cursor: grabbing; }

.status {
  position: absolute;
  top: 10px;
  left: 12px;
  padding: 4px 9px;
  border-radius: 999px;
  background: rgba(12, 18, 24, 0.75);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: 11px;
  letter-spacing: 0.4px;
  pointer-events: none;
}

.error-screen {
  /* Fatal: nothing in the app works, so cover the whole viewport rather than
     leaving the non-functional sidebar and top bar showing behind. */
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: rgba(9, 14, 19, 0.97);
}

.error-screen[hidden] { display: none; }

.error-card { max-width: 420px; text-align: center; }

.error-card h2 {
  margin: 0 0 10px;
  font-size: 18px;
  font-weight: 650;
  color: var(--text);
}

.error-card p {
  margin: 0 0 8px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.5;
}

.error-detail {
  margin-top: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12px;
  color: #64748b;
  word-break: break-word;
}

.share-trigger {
  position: absolute;
  z-index: 2;
  top: 10px;
  left: 12px;
  padding: 8px 12px;
  border: 1px solid var(--accent-dim);
  border-radius: var(--radius);
  background: rgba(18, 26, 34, 0.82);
  color: var(--accent);
  font-size: 12px;
  font-weight: 650;
  cursor: pointer;
  backdrop-filter: blur(6px);
}

.share-trigger:hover { background: rgba(var(--accent-rgb), 0.14); }

/* Native dialog supplies focus trapping and Escape-to-close. Its card carries
   the visual surface so a backdrop click can still target the dialog itself. */
.share-dialog {
  width: min(520px, calc(100vw - 24px));
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text);
}

.share-dialog::backdrop {
  background: rgba(9, 14, 19, 0.78);
  backdrop-filter: blur(3px);
}

.share-card {
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: 14px;
  background: var(--panel);
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.55);
}

.share-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.share-heading h2 {
  margin: 0;
  font-size: 18px;
  font-weight: 650;
}

.dialog-close {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 24px;
  line-height: 1;
  cursor: pointer;
}

.dialog-close:hover { color: var(--text); background: rgba(255, 255, 255, 0.05); }

.share-card > p {
  margin: 8px 0 14px;
  color: var(--muted);
  font-size: 13px;
  line-height: 1.45;
}

.share-card label {
  display: block;
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.7px;
  text-transform: uppercase;
}

.share-mode {
  display: flex;
  width: fit-content;
  gap: 2px;
  margin-bottom: 14px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-sunk);
}

.share-mode .seg { min-width: 70px; }

.share-mode .seg:disabled {
  color: var(--line);
  cursor: default;
}

.share-copy { display: flex; gap: 8px; }

.share-copy input {
  min-width: 0;
  flex: 1;
  padding: 9px 11px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  outline: none;
  background: var(--bg-sunk);
  color: var(--text);
  font-size: 12px;
}

.share-copy input:focus { border-color: var(--accent-dim); }

.share-copy button {
  flex: none;
  padding: 8px 14px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: var(--bg);
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}

.share-copy button:disabled {
  opacity: 0.45;
  cursor: wait;
}

.share-card .share-status {
  min-height: 18px;
  margin: 7px 0 0;
  color: var(--accent);
  font-size: 12px;
}

.stats {
  position: absolute;
  top: 10px;
  right: 12px;
  display: grid;
  gap: 6px;
  padding: 10px 12px;
  min-width: 148px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(18, 26, 34, 0.82);
  backdrop-filter: blur(6px);
}

.stat {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  font-size: 12px;
}

.stat .k { color: var(--muted); }
.stat .v { font-variant-numeric: tabular-nums; font-weight: 600; }

/* ---------- transport ---------- */

.transport {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: rgba(18, 26, 34, 0.82);
  backdrop-filter: blur(6px);
}

.play {
  width: 34px;
  height: 34px;
  flex: none;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
  color: var(--bg);
  font-size: 13px;
  cursor: pointer;
}

.ghost {
  flex: none;
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: transparent;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
}

.ghost:hover { color: var(--text); border-color: var(--accent-dim); }

/* Full path is on. Same accent tint the segmented toggles use, so an active
   state reads the same wherever it appears. */
.ghost.on {
  background: rgba(var(--accent-rgb), 0.14);
  border-color: var(--accent-dim);
  color: var(--accent);
  font-weight: 600;
}

/* Playback rate: the scrub keeps the slack, this group takes what it needs. */
.ratetoggle {
  flex: none;
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--line);
  border-radius: 8px;
}

.ratetoggle .seg {
  flex: none;
  padding: 4px 8px;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

/* Flight behind the playhead, lit; ahead of it, the bare track. `--fill` is a
   percentage the tick loop writes each frame. */
.scrub {
  flex: 1;
  min-width: 0;
  appearance: none;
  height: 4px;
  border-radius: 2px;
  background: linear-gradient(to right, var(--accent) var(--fill, 0%), var(--line) var(--fill, 0%));
  cursor: pointer;
}

input[type="range"] { appearance: none; }

input[type="range"]::-webkit-slider-thumb {
  appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--accent);
  border: 0;
}

input[type="range"]::-moz-range-thumb {
  width: 15px;
  height: 15px;
  border: 0;
  border-radius: 50%;
  background: var(--accent);
}

/* ---------- panel ---------- */

.panel {
  grid-area: panel;
  min-height: 0;
  border-left: 1px solid var(--line);
  background: var(--bg);
}

.panel-scroll {
  height: 100%;
  overflow-y: auto;
  padding: 14px;
  display: grid;
  gap: 18px;
  align-content: start;
}

/* The sheet handle is a phone control (see the media query). At sidebar widths
   the settings are simply there, so there is nothing to open. */
.sheet-handle { display: none; }

/* A hairline above every group but the first. Gap alone stops separating a
   header from the sliders above it once a run of them is all sliders — Release
   and Environment are the same control in the same panel and only the label
   says where one ends. The rule goes on every seam rather than that one: a
   divider in a single place among four groups reads as something being wrong
   there, not as a section break. */
.group + .group {
  border-top: 1px solid var(--line);
  padding-top: 14px;
}

.group h2 {
  margin: 0 0 8px;
  font-size: 11px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 600;
}

.picker { position: relative; }

#disc-search {
  width: 100%;
  padding: 9px 11px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  outline: none;
}

#disc-search:focus { border-color: var(--accent-dim); }

.results {
  position: absolute;
  z-index: 5;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  max-height: 260px;
  overflow-y: auto;
  margin: 0;
  padding: 4px;
  list-style: none;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
}

.results li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 9px;
  border-radius: 8px;
  cursor: pointer;
}

.results li:hover, .results li[aria-selected="true"] { background: rgba(var(--accent-rgb), 0.1); }
.results .r-name { font-size: 13px; }
.results .r-brand { color: var(--muted); font-size: 11px; }
.results .r-nums { color: var(--muted); font-size: 11px; font-variant-numeric: tabular-nums; }
.results .empty { color: var(--muted); font-size: 12px; cursor: default; }

.current {
  margin-top: 10px;
  padding: 11px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
}

/* The catalog sweep. The list shares the search dropdown's row styling but
   sits in flow: it is a result the user asked for, not a transient overlay. */
.longest { margin-top: 10px; }
/* `.seg` supplies font and hover; the border/background a segmented group's
   container would supply, a standalone button carries itself. */
.longest #longest-run {
  width: 100%;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--panel);
}
.longest-list {
  position: static;
  margin-top: 6px;
  box-shadow: none;
}
.longest-list .r-col {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.longest-list .r-out { align-items: flex-end; }
.longest-list .r-dist {
  font-variant-numeric: tabular-nums;
  font-size: 13px;
  white-space: nowrap;
}
.longest-list .r-lat {
  color: var(--muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Four class rows, never a scroll — and the popover below must not be clipped
   by an inherited dropdown scroll box. */
.longest-list {
  max-height: none;
  overflow: visible;
}

.fairway-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-top: 7px;
  padding: 0 2px;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  user-select: none;
}
.fairway-toggle[hidden] { display: none; }
.fairway-toggle input { accent-color: var(--accent); cursor: pointer; }

/* The tuple-mates popover. The whole row is the hover surface; the chip is
   the visual hint that there is something to hover. */
.longest-list li { position: relative; }
.longest-list .r-more {
  color: var(--muted);
  font-size: 11px;
  border-bottom: 1px dotted var(--muted);
  cursor: help;
  white-space: nowrap;
}
.longest-list .r-pop {
  display: none;
  position: absolute;
  z-index: 6;
  right: 8px;
  top: calc(100% - 2px);
  flex-direction: column;
  gap: 3px;
  min-width: 170px;
  padding: 8px 10px;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  background: var(--panel);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.5);
  cursor: default;
}
.longest-list li:hover .r-pop { display: flex; }
.longest-list .r-pop > span { white-space: nowrap; }
.longest-list .p-head {
  color: var(--muted);
  font-size: 10px;
  letter-spacing: 0.6px;
  text-transform: uppercase;
  margin-bottom: 2px;
}
.longest-list .p-name { font-size: 12px; }
.longest-list .p-brand { color: var(--muted); font-size: 11px; }

.disc-name { font-weight: 650; }
.disc-brand { color: var(--muted); font-size: 12px; margin-top: 1px; }

.flight-numbers {
  display: flex;
  gap: 6px;
  margin-top: 9px;
}

/* The weight slider is inside the disc card, below the numbers. */
#weight { margin-top: 11px; }

.number {
  flex: 1;
  text-align: center;
  padding: 3px 0 5px;
  border-radius: 7px;
  background: var(--bg-sunk);
  border: 1px solid var(--line);
}

.number b {
  display: block;
  font-size: 16px;
  font-variant-numeric: tabular-nums;
}

.number span {
  font-size: 9px;
  letter-spacing: 0.7px;
  color: var(--muted);
  text-transform: uppercase;
}

/* The steppers frame the value they change, so up is above it and down below.
   Big enough to hit with a thumb without making the cell a button-stack. */
.number .step {
  display: block;
  width: 100%;
  padding: 2px 0;
  border: 0;
  background: transparent;
  color: var(--muted);
  font-size: 9px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.12s;
}

.number .step:hover { color: var(--accent); }

/* At the end of the range there is nowhere to step: the arrow stays put and
   stops responding rather than vanishing and shuffling the row. */
.number .step:disabled {
  color: var(--line);
  cursor: default;
}

/* ---------- sliders ---------- */

.sliders { display: grid; gap: 12px; }

.slider .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 5px;
  font-size: 12px;
}

.slider .name { color: var(--muted); }
.slider .val { font-variant-numeric: tabular-nums; font-weight: 600; }
.slider input { width: 100%; }

/* ---------- phone ---------- */

@media (max-width: 900px) {
  /* The shut sheet is its handle, and that height is the one number the stage's
     floating chrome needs in order to sit clear of it. */
  :root { --sheet-handle: 46px; }

  #app {
    grid-template-columns: minmax(0, 1fr);
    /* The top bar takes two rows on a phone (below), so it measures itself. The
       panel row collapses: the sheet is out of flow (below), so the stage has
       the screen under the top bar whether the sheet is up or not. */
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-areas: "top" "stage" "panel";
  }

  /* Brand and units on one row, the view toggle across the next. A phone cannot
     hold all three side by side, and the labels are one-liners by design — so
     the row breaks between the groups, where there is a seam, rather than
     shrinking the buttons until "Follow disc" is unreadable. */
  .topbar {
    flex-wrap: wrap;
    gap: 8px;
    padding: 8px 12px;
  }

  /* The wrap has to happen between the two toggle groups, so they have to be
     the top bar's own flex items — otherwise `.toggles` wraps as one 427px
     lump and nothing has been solved. */
  .toggles { display: contents; }

  .viewtoggle { flex-basis: 100%; }

  /* "Flight simulator" is what the tab title is for. Spelled out here it is
     wide enough to push the units toggle onto a row of its own, and a third row
     of chrome is a worse trade than a subtitle on a page you are already
     looking at. */
  .brand .sub { display: none; }

  /* The sheet floats over the flight instead of shrinking it. At the height a
     phone needs to actually show the settings, an in-flow panel leaves a stage
     that is more chrome than flight — and it would resize the surface on every
     open, re-framing the shot the user aimed. Anchored to the bottom with no
     height of its own, so it *is* its handle when shut and grows upward when
     opened; DGFV's sheet is the same shape at 75vh
     (`tools/flight-sim-calibration/vendor/dgfv-app.html`). */
  .panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    max-height: 75dvh;
    border-left: 0;
    border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0;
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -10px 34px rgba(0, 0, 0, 0.45);
  }

  .panel:not(.open) .panel-scroll { display: none; }

  .panel-scroll {
    height: auto;
    flex: 1;
    min-height: 0;
    padding: 2px 12px 14px;
  }

  /* Fixed height, because `--sheet-handle` above is what the transport and the
     stats hold themselves clear of — a handle that measured itself would drift
     out from under them. */
  .sheet-handle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    height: var(--sheet-handle);
    padding: 0 12px;
    border: 0;
    background: transparent;
    text-align: left;
    cursor: pointer;
  }

  .sheet-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--muted);
  }

  /* Which disc is in the air is the one thing worth knowing with the sheet shut,
     and the card that says it is inside the sheet. */
  .sheet-disc {
    flex: 1;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 13px;
    font-weight: 650;
  }

  .chev {
    color: var(--muted);
    font-size: 11px;
    transition: transform 0.15s;
  }

  .panel.open .chev { transform: rotate(180deg); }

  /* Above the shut sheet, and under the open one — which is what a sheet is for.
     The stack from the bottom: handle, transport, stats. */
  .stats {
    top: auto;
    bottom: calc(66px + var(--sheet-handle) + env(safe-area-inset-bottom));
    right: 12px;
    left: 12px;
    grid-template-columns: 1fr 1fr;
    padding: 8px 10px;
  }

  .transport { bottom: calc(12px + var(--sheet-handle) + env(safe-area-inset-bottom)); }

  .results { max-height: 42dvh; }

  /* Thumbs, not a pointer: the arrows get a hit area a finger can find. */
  .number .step { padding: 8px 0; font-size: 10px; }
}
