/* ============================================================
   The web configurator — shared by Marine and Automotive.

   Full-bleed canvas with glass over it, in the studio's idiom: black,
   Inter, purple only on the thing you're acting on. Tokens come from
   design-system.css; this is the page's own vocabulary.
   ============================================================ */

:root {
  --bo-glass:      rgba(10, 12, 16, 0.66);
  --bo-glass-hi:   rgba(255, 255, 255, 0.07);
  --bo-line:       rgba(255, 255, 255, 0.12);
  --bo-line-hi:    rgba(255, 255, 255, 0.32);
  --bo-dim:        rgba(255, 255, 255, 0.56);
  --bo-faint:      rgba(255, 255, 255, 0.34);
  --bo-radius:     14px;
  --bo-radius-sm:  9px;
  --bo-panel-w:    360px;
  --bo-mono:       ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

  /* The app's own navigation bar, under the studio frame. Two bars stacked:
     the studio one is the frame every page carries, this one belongs to the
     configurator — so everything over the scene is offset by both. */
  --is-appbar-h:   54px;
  --is-chrome-h:   calc(var(--is-topbar-h) + var(--is-appbar-h));
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html, body { height: 100%; }

body {
  background: #05080c;
  color: #fff;
  font-family: 'Inter', system-ui, sans-serif;
  font-size: 14px;
  overflow: hidden;
  -webkit-font-smoothing: antialiased;
}

button, input { font-family: inherit; font-size: inherit; color: inherit; }

/* ── Canvas ─────────────────────────────────────────────── */

#scene {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  width: 100%;
  height: calc(100% - var(--is-chrome-h));
  display: block;
  touch-action: none;
}

/* The surface remote-rendered frames land in — see
   common/js/configurator/remote-render.js.

   Over the canvas rather than instead of it, and the whole fallback story is
   in that word: the canvas underneath never stops rendering the same build, so
   losing the stream is a fade rather than an interruption, and drags go
   straight through to the controls that were always handling them. */
#remote-scene {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  width: 100%;
  height: calc(100% - var(--is-chrome-h));
  display: block;
  object-fit: cover;
  background: transparent;
  opacity: 0;
  transition: opacity 240ms ease;
  pointer-events: none;
}

#remote-scene.is-showing {
  opacity: 1;
}

/* ── The app bar ────────────────────────────────────────── */

/* The configurator's navigation bar, in the shape the native apps use: a way
   back, the name of what you are looking at, and the trailing controls — the
   session badge, and one menu.

   It is a real bar rather than a title floating over the scene. The floating
   version read beautifully on the render and was the wrong idea: the apps put
   the product's name, the session's status and every action in one strip
   because they are the same subject, and spreading them over four corners of
   a canvas meant nothing was next to the thing it applied to. It also
   guarantees a legible surface for the menus, which a translucent title over
   a sunlit sea does not. */
.appbar {
  position: fixed;
  top: var(--is-topbar-h);
  left: 0;
  right: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0.8rem;
  height: var(--is-appbar-h);
  padding: 0 clamp(0.7rem, 2.4vw, 1.4rem);
  border-bottom: 1px solid var(--bo-line);
  background: rgba(6, 8, 12, 0.72);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
}

/* The chevron back to the catalog, which is where the apps put it — and the
   only way back now that "Change" has left the panel. Absent on the catalog
   itself, because there is nothing behind it. */
.appbar__back {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  flex: none;
  padding: 0.35rem 0.7rem 0.35rem 0.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: none;
  color: var(--bo-dim);
  font-size: 0.82rem;
  font-weight: 500;
  cursor: pointer;
  transition: color 0.2s, background 0.2s, border-color 0.2s;
}
.appbar__back:hover { color: #fff; background: var(--bo-glass-hi); border-color: var(--bo-line); }
.appbar__chevron { font-size: 1.15rem; line-height: 1; margin-top: -2px; }
body.is-picking .appbar__back { display: none; }

.appbar__titles { min-width: 0; flex: 1; }
.appbar__title {
  font-size: 1.02rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.appbar__subtitle {
  font-size: 0.74rem;
  color: var(--bo-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.appbar__trail {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: none;
}
.appbar__trail .revision {
  font-family: var(--bo-mono);
  font-size: 0.66rem;
  letter-spacing: 0.03em;
  color: var(--bo-faint);
  border: 1px solid var(--bo-line);
  border-radius: var(--radius-pill);
  padding: 0.2rem 0.6rem;
  white-space: nowrap;
}
/* First thing to go when the bar runs out of room: it is provenance, and the
   Saved configurations list repeats it where it decides anything. */
@media (max-width: 900px) {
  .appbar__trail .revision { display: none; }
}

/* ── Menus ──────────────────────────────────────────────── */

/* One control, and a sheet of rows under it. The apps fold every action into
   one of these per screen; see `renderMenu` for why that trade is worth the
   extra tap. */
.menu { position: relative; flex: none; }
.menu[hidden] { display: none; }

.menu__button {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.55rem;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius-sm);
  background: var(--bo-glass-hi);
  color: var(--bo-dim);
  cursor: pointer;
  transition: color 0.2s, background 0.2s, border-color 0.2s;
}
.menu__button:hover,
.menu__button[aria-expanded="true"] {
  color: #fff;
  background: rgba(255, 255, 255, 0.13);
  border-color: var(--bo-line-hi);
}
.menu__label { font-size: 0.8rem; font-weight: 500; }
/* "Something in here wants you" — the notification a control folded into a
   closed menu would otherwise have stopped being. */
.menu__button--badged::after {
  content: "";
  position: absolute;
  top: -1px;
  right: -1px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--purple-hi);
  box-shadow: 0 0 0 2px rgba(6, 8, 12, 0.9);
}

.menu__sheet {
  position: absolute;
  top: calc(100% + 0.45rem);
  right: 0;
  z-index: 40;
  width: max(240px, 18rem);
  padding: 0.35rem;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: rgba(12, 14, 19, 0.97);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}
.menu__sheet[hidden] { display: none; }

.menu__item {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  width: 100%;
  padding: 0.5rem 0.55rem;
  border: 0;
  border-radius: var(--bo-radius-sm);
  background: none;
  color: #fff;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s;
}
.menu__item:hover:not(:disabled) { background: rgba(255, 255, 255, 0.09); }
.menu__item:disabled { opacity: 0.38; cursor: default; }
.menu__item--danger { color: #FF9B9B; }
.menu__glyph { display: grid; place-items: center; width: 18px; padding-top: 1px; color: var(--bo-dim); }
.menu__item:hover:not(:disabled) .menu__glyph { color: var(--purple-hi); }
.menu__text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.menu__item-label { font-size: 0.86rem; font-weight: 500; }
.menu__hint { font-size: 0.72rem; color: var(--bo-faint); line-height: 1.35; }
.menu__rule { height: 1px; margin: 0.25rem 0.35rem; background: var(--bo-line); }

/* Icons are text-sized and take the row's own colour — see `icon` in ui.js. */
.ico-svg { flex: none; display: block; }
.btn .ico-svg { margin-right: 0.35rem; }
.btn .ico-svg:only-child { margin-right: 0; }

/* ── Controls panel ─────────────────────────────────────── */

.panel {
  position: fixed;
  top: calc(var(--is-chrome-h) + 0.9rem);
  right: 0.9rem;
  bottom: 5.4rem;
  z-index: 12;
  width: var(--bo-panel-w);
  display: flex;
  flex-direction: column;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: var(--bo-glass);
  backdrop-filter: blur(14px);
  transition: transform 0.32s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.32s;
}
body.is-panel-collapsed .panel {
  transform: translateX(calc(100% + 1.2rem));
  opacity: 0;
  /* Off-screen and transparent, but still a full-height element: without this
     it keeps swallowing clicks meant for the scene behind it. */
  pointer-events: none;
}
body.is-picking .panel { opacity: 0; pointer-events: none; }

/* Shown only while the panel is hidden.
   Hidden by a rule on `body` rather than on the element: `.btn` also declares
   a `display`, it is later in this file, and a single class can't win against
   another single class — so the plain `.panel-reopen { display: none }` this
   used to be stopped hiding anything the day the buttons grew icons. */
.panel-reopen {
  position: fixed;
  top: calc(var(--is-chrome-h) + 0.9rem);
  right: 0.9rem;
  z-index: 12;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
body:not(.is-panel-collapsed) .panel-reopen,
body.is-picking .panel-reopen { display: none; }

.panel__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--bo-line);
}
.panel__title {
  flex: 1;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--bo-faint);
}
.panel__body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.85rem;
  display: flex;
  flex-direction: column;
  /* The gap a grouped list leaves between sections — enough that a section's
     footnote reads as belonging to the card above it rather than to the
     heading below. */
  gap: 1.35rem;
}
.panel__body::-webkit-scrollbar { width: 8px; }
.panel__body::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.14);
  border-radius: 4px;
}

/* ── Groups ─────────────────────────────────────────────── */

/* An inset-grouped list, which is what the native panel is: a heading, a card
   of rows, and the catalog's own explanation under the card. Copied on
   purpose rather than reinvented — named groups of choices, each with a
   footnote, is the shape a grouped list already has, and drawing it by hand
   was a `Divider()` and a guess at the insets. */

.group { display: flex; flex-direction: column; gap: 0.42rem; }

/* Name on the left, and on the right either a value group's figure or the
   name of the colour currently picked. Baseline rather than centre, so the two
   sit on the same line of text however much their sizes differ. */
.group__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0 0.25rem;
}
.group__head h3 {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bo-dim);
}
.group__value {
  font-family: var(--bo-mono);
  font-size: 0.8rem;
  color: #fff;
  white-space: nowrap;
  /* The figure changes under a moving thumb; equal-width digits keep it from
     shuffling sideways while it does. */
  font-variant-numeric: tabular-nums;
}
/* Which colour is picked. A swatch's ring says which one; only this says what
   it is called, and the name of a colour is half of what is being chosen. */
.group__chosen {
  font-size: 0.8rem;
  color: #fff;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 55%;
}

/* The card. One surface per group, rows separated by hairlines rather than by
   gaps — so a group of six options reads as one control with six rows in it,
   which is what it is. */
.group__card {
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: rgba(255, 255, 255, 0.045);
  overflow: hidden;
}

/* The group's explanation, under the card, where a grouped list puts them. */
.group__foot {
  font-size: 0.74rem;
  color: var(--bo-faint);
  line-height: 1.45;
  padding: 0 0.35rem;
}

/* Swatches — a group where every option carries a colour */
.swatches {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  padding: 0.7rem;
}
.swatch {
  --swatch: #888;
  position: relative;
  width: 58px;
  border: 0;
  background: none;
  cursor: pointer;
  padding: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
}
.swatch__ring {
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--swatch);
  border: 1px solid rgba(255, 255, 255, 0.22);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  transition: transform 0.18s, box-shadow 0.18s, outline-color 0.18s;
  outline: 2px solid transparent;
  outline-offset: 3px;
}
.swatch:hover:not(:disabled) .swatch__ring { transform: scale(1.08); }
.swatch[aria-current="true"] .swatch__ring { outline-color: var(--purple-hi); }
.swatch__label {
  font-size: 0.66rem;
  color: var(--bo-dim);
  text-align: center;
  line-height: 1.25;
}
.swatch[aria-current="true"] .swatch__label { color: #fff; }
.swatch:disabled { cursor: not-allowed; }
.swatch:disabled .swatch__ring { opacity: 0.28; }
.swatch:disabled .swatch__label { opacity: 0.4; }
/* Struck through only when a rule forbids it. A control the whole panel has
   gone read-only for is not "unavailable", it is somebody else's. */
.swatch:disabled[data-blocked] .swatch__label { text-decoration: line-through; }

/* Options — rows, when they aren't colours. A tick on the chosen one, as the
   apps do it: a filled purple block reads as a button about to be pressed
   rather than as the state of the thing. */
.options { display: flex; flex-direction: column; }
.option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  text-align: left;
  border: 0;
  border-top: 1px solid var(--bo-line);
  background: none;
  padding: 0.6rem 0.75rem;
  cursor: pointer;
  transition: background 0.15s;
}
.option:first-child { border-top: 0; }
.option:hover:not(:disabled) { background: rgba(255, 255, 255, 0.07); }
.option:disabled { cursor: not-allowed; opacity: 0.42; }
.option__text { display: flex; flex-direction: column; gap: 0.1rem; flex: 1; min-width: 0; }
.option__name { font-size: 0.88rem; }
.option__summary { font-size: 0.74rem; color: var(--bo-faint); line-height: 1.35; }
.option__blocked {
  font-size: 0.64rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #FFB3B3;
}
.option__check {
  flex: none;
  display: grid;
  place-items: center;
  width: 16px;
  color: var(--purple-hi);
  opacity: 0;
  transition: opacity 0.15s;
}
.option[aria-current="true"] .option__check { opacity: 1; }
.option[aria-current="true"] .option__name { font-weight: 500; }

/* Toggle — the row carries the group's own name, which is why the section
   above it has no heading. */
.switch-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  cursor: pointer;
  padding: 0.62rem 0.75rem;
}
.switch-row__label { flex: 1; font-size: 0.88rem; }
.switch-row__state {
  font-size: 0.74rem;
  color: var(--bo-faint);
  font-variant-numeric: tabular-nums;
}
.switch { position: relative; width: 42px; height: 24px; flex: none; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch input:disabled { cursor: default; }
.switch__track {
  position: absolute;
  inset: 0;
  border-radius: 100px;
  background: rgba(255, 255, 255, 0.16);
  transition: background 0.22s;
  pointer-events: none;
}
.switch__track::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.22s;
}
.switch input:checked + .switch__track { background: var(--purple-mid); }
.switch input:checked + .switch__track::after { transform: translateX(18px); }
.switch input:disabled + .switch__track { opacity: 0.45; }

/* Value slider */
.slider { display: flex; flex-direction: column; gap: 0.3rem; padding: 0.7rem 0.75rem; }
.slider__row { display: flex; align-items: center; gap: 0.5rem; }
.slider__bound { font-size: 0.68rem; color: var(--bo-faint); white-space: nowrap; }
input[type="range"] {
  flex: 1;
  -webkit-appearance: none;
  appearance: none;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.2);
  cursor: pointer;
}
input[type="range"]:disabled { cursor: default; opacity: 0.5; }
input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.4);
}
input[type="range"]::-moz-range-thumb {
  width: 15px; height: 15px; border: 0; border-radius: 50%; background: #fff;
}

/* A row that does something, rather than choosing something — Reset, and the
   rows in the sheets. */
.row-action {
  display: flex;
  align-items: center;
  gap: 0.55rem;
  width: 100%;
  padding: 0.68rem 0.75rem;
  border: 0;
  background: none;
  color: var(--purple-hi);
  font-size: 0.88rem;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s;
}
.row-action:hover:not(:disabled) { background: rgba(123, 47, 255, 0.14); }
.row-action:disabled { color: var(--bo-faint); cursor: default; }
.row-action__glyph { display: grid; place-items: center; width: 16px; }

/* Read-outs */
.spec {
  display: grid;
  grid-template-columns: minmax(90px, auto) 1fr;
  gap: 0.3rem 0.7rem;
  font-size: 0.82rem;
  padding: 0.7rem 0.75rem;
}
.spec dt { color: var(--bo-faint); text-transform: capitalize; }
.spec dd { color: #fff; text-align: right; }
.spec .spec__meta { font-style: normal; }
.spec .spec__total { font-weight: 600; padding-top: 0.35rem; border-top: 1px solid var(--bo-line); }

/* ── Buttons ────────────────────────────────────────────── */

.btn {
  /* Inline-flex rather than inline-block: most buttons now carry a symbol as
     well as a word — see `icon` in ui.js — and the two have to share a
     baseline without either being pushed off centre. */
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--bo-line-hi);
  border-radius: var(--bo-radius-sm);
  background: rgba(255, 255, 255, 0.06);
  padding: 0.4rem 0.7rem;
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.18s, border-color 0.18s;
}
.btn:hover { background: rgba(255, 255, 255, 0.13); border-color: rgba(255, 255, 255, 0.5); }
.btn--primary {
  border-color: rgba(178, 100, 255, 0.5);
  background: rgba(123, 47, 255, 0.26);
  font-weight: 500;
}
.btn--primary:hover { background: rgba(123, 47, 255, 0.4); }
.btn--icon { padding: 0.32rem 0.5rem; line-height: 1; }

/* ── Viewpoint pills ────────────────────────────────────── */

.viewpoints {
  position: fixed;
  left: 50%;
  bottom: 1.5rem;
  transform: translateX(-50%);
  z-index: 12;
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  justify-content: center;
  max-width: min(680px, calc(100vw - 2rem));
  transition: opacity 0.3s;
}
/* `renderViewpoints` hides the row when there is only one framing to pick, and
   a class with `display: flex` beats the UA's `[hidden] { display: none }` —
   so the row has to say it itself, or a product with one viewpoint shows a
   lone pill that does nothing when tapped. */
.viewpoints[hidden] { display: none; }
body.is-picking .viewpoints { opacity: 0; pointer-events: none; }
.viewpoints .ui-pill { cursor: pointer; font-family: inherit; }
.viewpoints .ui-pill[aria-current="true"] {
  border-color: rgba(178, 100, 255, 0.75);
  color: #fff;
  background: rgba(123, 47, 255, 0.24);
}

/* ── Picker ─────────────────────────────────────────────── */

.picker {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 1.6rem;
  padding: clamp(1.5rem, 5vh, 4rem) clamp(1rem, 4vw, 3rem);
  background: radial-gradient(120% 80% at 50% 0%, rgba(8, 14, 24, 0.86), rgba(2, 4, 8, 0.96));
  backdrop-filter: blur(10px);
  overflow-y: auto;
}
.picker[hidden] { display: none; }
/* Centred when the catalog fits, top-aligned when it doesn't — and never
   `justify-content: center`, which on a scrolling column puts the overflow
   above the scroll origin instead of below it. A catalog of more than a few
   cars would centre the heading off the top of the box, where no amount of
   scrolling brings it back. Auto margins centre the same way and resolve to
   zero the moment the free space runs out. */
.picker > :first-child { margin-top: auto; }
.picker > :last-child { margin-bottom: auto; }
.picker__head { text-align: center; }
.picker__eyebrow {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--purple-hi);
  margin-bottom: 0.7rem;
}
.picker__head h1 {
  font-size: clamp(1.7rem, 4vw, 2.6rem);
  font-weight: 500;
  letter-spacing: -0.02em;
}
.picker__head p { color: var(--bo-dim); margin-top: 0.5rem; }

.picker__cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 1rem;
  max-width: 1080px;
  width: 100%;
  margin: 0 auto;
}

.product-card {
  display: flex;
  flex-direction: column;
  text-align: left;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: rgba(255, 255, 255, 0.03);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.25s, border-color 0.25s, background 0.25s;
}
.product-card:hover {
  transform: translateY(-3px);
  border-color: rgba(178, 100, 255, 0.5);
  background: rgba(123, 47, 255, 0.09);
}
.product-card[aria-current="true"] { border-color: rgba(178, 100, 255, 0.7); }
.product-card__art { aspect-ratio: 16 / 10; background: #0b1017; overflow: hidden; }
.product-card__art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.product-card__placeholder {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  font-size: 2rem; color: var(--bo-faint);
}
.product-card__body { padding: 0.85rem; display: flex; flex-direction: column; gap: 0.4rem; }
.product-card__name { font-size: 1.05rem; font-weight: 500; }
.product-card__summary { font-size: 0.82rem; color: var(--bo-dim); line-height: 1.45; }
.product-card__chips { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.15rem; }
/* What is configurable on this one — the difference between a catalog you
   browse and one you open three times to find the vessel with the colours. */
.product-card__axes {
  font-size: 0.7rem;
  color: var(--bo-faint);
  line-height: 1.4;
  margin-top: 0.15rem;
}
.product-card:disabled { cursor: default; opacity: 0.5; }
.chip {
  font-size: 0.68rem;
  color: var(--bo-dim);
  border: 1px solid var(--bo-line);
  border-radius: 100px;
  padding: 0.12rem 0.5rem;
}

/* ── Loading ────────────────────────────────────────────── */

.loading {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  z-index: 25;
  display: grid;
  place-items: center;
  background: rgba(3, 6, 10, 0.82);
  backdrop-filter: blur(6px);
}
.loading[hidden] { display: none; }
.loading__inner { width: min(320px, 80vw); text-align: center; }
.loading__label { font-size: 0.85rem; color: var(--bo-dim); margin-bottom: 0.7rem; }
.loading__track {
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.12);
  overflow: hidden;
}
.loading__bar {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--purple-mid), var(--pink));
  transition: width 0.25s;
}
.loading__hint { font-size: 0.72rem; color: var(--bo-faint); margin-top: 0.6rem; }

/* ── Failure ────────────────────────────────────────────── */

.failure {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  z-index: 30;
  display: grid;
  place-items: center;
  padding: 2rem;
  background: rgba(3, 6, 10, 0.94);
}
.failure[hidden] { display: none; }
.failure__card {
  width: min(460px, 100%);
  border: 1px solid var(--bo-line);
  border-radius: 18px;
  background: rgba(12, 14, 18, 0.9);
  padding: 1.8rem;
}
.failure__card h2 { font-size: 1.25rem; font-weight: 500; margin-bottom: 0.5rem; }
.failure__card p { color: var(--bo-dim); line-height: 1.6; }
.failure__card form { margin-top: 1.4rem; display: flex; flex-direction: column; gap: 0.8rem; }
.field { display: flex; flex-direction: column; gap: 0.28rem; }
.field label {
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--bo-faint);
}
.field input {
  width: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius-sm);
  background: rgba(255, 255, 255, 0.04);
  font-family: var(--bo-mono);
  font-size: 0.82rem;
}
.field input:focus { outline: none; border-color: rgba(178, 100, 255, 0.6); }

/* ── Notices ────────────────────────────────────────────── */

.notices {
  position: fixed;
  left: 50%;
  bottom: 5.2rem;
  transform: translateX(-50%);
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  align-items: center;
  max-width: min(520px, calc(100vw - 2rem));
  pointer-events: none;
}
.notice {
  border: 1px solid var(--bo-line-hi);
  border-left-width: 2px;
  border-radius: var(--bo-radius-sm);
  background: rgba(14, 14, 18, 0.94);
  backdrop-filter: blur(var(--blur));
  padding: 0.6rem 0.9rem;
  font-size: 0.85rem;
  line-height: 1.45;
  text-align: center;
  transition: opacity 0.26s;
  animation: notice-in 0.24s ease-out;
}
.notice--blocked { border-left-color: #FF6B6B; }
.notice--warn { border-left-color: #FFC466; }
.notice--info { border-left-color: var(--purple-hi); }
@keyframes notice-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

/* ── Saving and sending ─────────────────────────────────── */

/* Save, Send and Configure together used to be three buttons across the foot
   of the panel. They are menu rows now — see `renderMenus` — which is where
   the apps keep them, and which gives the panel its full height back for the
   thing it is actually for. */

.btn--small { padding: 0.28rem 0.55rem; font-size: 0.74rem; }
.btn--danger { border-color: rgba(255, 107, 107, 0.4); }
.btn--danger:hover { background: rgba(255, 107, 107, 0.2); border-color: rgba(255, 107, 107, 0.7); }
.btn:disabled { opacity: 0.4; cursor: default; }
.btn:disabled:hover { background: rgba(255, 255, 255, 0.06); border-color: var(--bo-line-hi); }

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

/* Every control on this page is a custom one — a bare button with its border
   taken off, a range with its track redrawn, a checkbox hidden under a track
   of our own — so each was falling back to the browser's default ring. It is
   visible, but it is a different shape and a different colour on each of them,
   and on the swatches it lands as a yellow rectangle in the middle of a purple
   panel. One ring, in the page's own accent, on all of them.

   `:focus-visible` rather than `:focus`, so a mouse click doesn't leave a ring
   behind on a swatch somebody has simply pressed. */
.btn:focus-visible,
.option:focus-visible,
.swatch:focus-visible,
.ui-pill:focus-visible,
.product-card:focus-visible,
.menu__button:focus-visible,
.menu__item:focus-visible,
.appbar__back:focus-visible,
.session-badge__button:focus-visible,
.row-action:focus-visible,
.sheet-action:focus-visible,
.sessions__join:focus-visible {
  outline: 2px solid var(--purple-hi);
  outline-offset: 2px;
}
/* Rows inside a card have no gap between them, so a ring drawn outside one
   overlaps its neighbours. Inset, it reads as the row it belongs to. */
.menu__item:focus-visible,
.option:focus-visible,
.row-action:focus-visible,
.sheet-action:focus-visible { outline-offset: -2px; }
/* The swatch's own selected state is already an outline on the circle, so the
   focus ring goes round the whole control — circle and name — where it reads
   as a different thing rather than a thicker version of the same one. */
.swatch:focus-visible { border-radius: 8px; }

/* The checkbox is transparent and sits on top of the track it draws, so a ring
   on the input itself would be a rectangle floating over the switch. */
.switch input:focus-visible + .switch__track {
  outline: 2px solid var(--purple-hi);
  outline-offset: 2px;
}

input[type="range"]:focus-visible {
  outline: 2px solid var(--purple-hi);
  outline-offset: 4px;
}

/* Resuming, on the picker screen.
   Ranged over the cards rather than centred in the viewport: with no heading
   above it this row is the only thing between the appbar and the grid, and a
   lone button in the middle of all that space reads as something that has come
   loose. Sharing the grid's width and sitting at its right edge makes it what
   it is — an action belonging to the list underneath. */
.picker__saved {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.45rem;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
}
/* The chips naming what you can resume read left-to-right off the label, so
   they take the free space and leave the button on the right. */
.picker__saved .resume { margin-right: auto; }
/* The sessions on the picker: the listed ones anybody may join.
   Above the cards because it is the shorter list and the more perishable one —
   a session is running now, a catalog is always there. */
.sessions {
  width: min(880px, 100%);
  margin: 0 auto 1.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}
.sessions[hidden] { display: none; }
.sessions__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--bo-dim);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.sessions__head .btn { margin-left: auto; letter-spacing: 0.04em; text-transform: none; }
.sessions__list { display: flex; flex-direction: column; gap: 0.45rem; }

.sessions__row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem 0.3rem 0.3rem;
  border-radius: 12px;
  border: 1px solid rgba(95, 217, 166, 0.24);
  background: rgba(95, 217, 166, 0.07);
  transition: border-color 0.25s, background 0.25s;
}
.sessions__row:hover {
  border-color: rgba(95, 217, 166, 0.55);
  background: rgba(95, 217, 166, 0.13);
}
/* The row itself is the join. The copy button beside it is the other thing
   you might want from a listed session — its link, which carries no key. */
.sessions__join {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 0.65rem;
  min-width: 0;
  padding: 0.4rem 0.6rem;
  border: 0;
  border-radius: 9px;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.sessions__live { display: grid; place-items: center; flex: none; color: #5FD9A6; }
.sessions__text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; }
.sessions__name { font-size: 0.95rem; font-weight: 500; }
.sessions__meta { color: var(--bo-dim); font-size: 0.78rem; }

.resume {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}
.resume[hidden] { display: none; }
.resume__label {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bo-faint);
}

/* ── Dialogs ────────────────────────────────────────────── */

.dialog {
  width: min(420px, calc(100vw - 2rem));
  max-height: calc(100vh - 4rem);
  /* A modal `<dialog>` centres itself through `margin: auto`, which the page's
     own `* { margin: 0 }` reset takes away. Put back explicitly. */
  margin: auto;
  padding: 0;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: rgba(12, 14, 19, 0.96);
  backdrop-filter: blur(18px);
  color: #fff;
  font-family: inherit;
  overflow: hidden;
}
.dialog--wide { width: min(620px, calc(100vw - 2rem)); }
.dialog::backdrop { background: rgba(3, 5, 8, 0.62); backdrop-filter: blur(3px); }

.dialog__head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 0.95rem;
  border-bottom: 1px solid var(--bo-line);
}
.dialog__title { flex: 1; font-size: 0.98rem; font-weight: 600; }
.dialog__body {
  padding: 0.95rem;
  max-height: calc(100vh - 9rem);
  overflow-y: auto;
}
.dialog__form { display: flex; flex-direction: column; gap: 0.85rem; }
.dialog__note { font-size: 0.76rem; color: var(--bo-dim); line-height: 1.45; }
.dialog__note--bad { color: #FF9B9B; }
.dialog__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.95rem;
}
.dialog__actions--split { justify-content: space-between; }

/* ── The list of saved configurations ──────────────────────────── */

.saved-configurations { display: flex; flex-direction: column; gap: 0.4rem; }
.saved-configurations__row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.6rem 0.7rem;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius-sm);
  background: var(--bo-glass-hi);
}
.saved-configurations__main { flex: 1; min-width: 0; }
.saved-configurations__name {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-weight: 500;
}
.saved-configurations__meta { font-size: 0.74rem; color: var(--bo-dim); margin-top: 0.15rem; }
.saved-configurations__actions { display: flex; gap: 0.35rem; flex-shrink: 0; }

/* A saved configuration that no longer matches what's published says so here rather than
   being hidden or quietly migrated — see ADR `common/docs/005`. */
.saved-configurations__badge {
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.1rem 0.34rem;
  border-radius: 999px;
  border: 1px solid var(--bo-line-hi);
  color: var(--bo-dim);
  white-space: nowrap;
}
.saved-configurations__badge--warn { border-color: rgba(255, 196, 102, 0.5); color: #FFC466; }

/* ── Sheets ─────────────────────────────────────────────── */

/* The vocabulary the two big sheets share — Send, and Configure together.
   Both are grouped lists inside a `<dialog>`, for the same reason the control
   panel is one: a heading, a card of rows, a footnote. Same shape as the
   panel, deliberately, so the page has one idea of what a list of things
   looks like instead of three. */

.session-sheet { display: flex; flex-direction: column; gap: 1.3rem; }

.sheet-group { display: flex; flex-direction: column; gap: 0.42rem; }
.sheet-group__head {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0 0.25rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bo-dim);
}
/* The refresh control sits at the far end of its own heading. */
.sheet-group__head > .btn { margin-left: auto; }
.sheet-group__glyph { display: grid; place-items: center; color: var(--bo-faint); }
/* A live session is the one thing on this page worth a colour of its own:
   green because it is happening, and nothing else on the page is green. */
.sheet-group__live { display: grid; place-items: center; color: #5FD9A6; }
.sheet-group--invite .sheet-group__card { border-color: rgba(95, 217, 166, 0.28); }

.sheet-group__card {
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius);
  background: rgba(255, 255, 255, 0.045);
  overflow: hidden;
}
.sheet-group__foot {
  font-size: 0.74rem;
  color: var(--bo-faint);
  line-height: 1.45;
  padding: 0 0.35rem;
}

.sheet-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.62rem 0.75rem;
  border-top: 1px solid var(--bo-line);
}
.sheet-row:first-child { border-top: 0; }
.sheet-row--stack { flex-direction: column; align-items: stretch; gap: 0.5rem; }
.sheet-row--switch { cursor: pointer; }
.sheet-row--empty { color: var(--bo-faint); font-size: 0.82rem; line-height: 1.45; display: block; }
.sheet-row__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 0.12rem; }
.sheet-row__name { font-size: 0.88rem; }
.sheet-row__meta { font-size: 0.74rem; color: var(--bo-faint); line-height: 1.4; }
.sheet-row__label {
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--bo-faint);
}
.sheet-row__buttons { display: flex; flex-wrap: wrap; gap: 0.4rem; }

/* A link, in a field rather than as text.
   `navigator.clipboard` needs a secure context and this page is served over
   plain HTTP from a laptop on the Wi-Fi as often as not — so every link is
   also somewhere it can be selected and sent by hand. */
.sheet-link {
  width: 100%;
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius-sm);
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  font-family: var(--bo-mono);
  font-size: 0.7rem;
  text-overflow: ellipsis;
}
.sheet-link:focus { outline: none; border-color: rgba(178, 100, 255, 0.6); }
.sheet-link::placeholder { font-family: 'Inter', system-ui, sans-serif; color: var(--bo-faint); }

/* A row that does something, with a symbol, a word and a line on why. This is
   the row shape both sheets are built out of — see `SendSavedConfigurationSheet` and
   `SessionView`, which are lists of exactly these. */
.sheet-action {
  display: flex;
  align-items: flex-start;
  gap: 0.7rem;
  width: 100%;
  padding: 0.68rem 0.75rem;
  border: 0;
  border-top: 1px solid var(--bo-line);
  background: none;
  color: #fff;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s;
}
.sheet-action:first-child { border-top: 0; }
.sheet-action:hover:not(:disabled) { background: rgba(255, 255, 255, 0.07); }
.sheet-action:disabled { cursor: default; }
.sheet-action:disabled .sheet-action__label { color: var(--bo-dim); }
.sheet-action__glyph { display: grid; place-items: center; width: 18px; padding-top: 2px; color: var(--purple-hi); }
.sheet-action:disabled .sheet-action__glyph { color: var(--bo-faint); }
.sheet-action__text { display: flex; flex-direction: column; gap: 0.14rem; min-width: 0; }
.sheet-action__label { font-size: 0.88rem; font-weight: 500; }
.sheet-action__hint { font-size: 0.74rem; color: var(--bo-faint); line-height: 1.4; }

/* Something the session has to say for itself — the host left, the two ends
   are on different catalogs, the server refused this one. */
.sheet-banner {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--bo-line);
  border-left-width: 2px;
  border-radius: var(--bo-radius-sm);
  background: rgba(255, 255, 255, 0.04);
  font-size: 0.82rem;
  line-height: 1.45;
  color: var(--bo-dim);
}
.sheet-banner--bad { border-left-color: #FF6B6B; }
.sheet-banner--warn { border-left-color: #FFC466; }
.sheet-banner--pending { border-left-color: #FFC466; }
.sheet-banner .ico-svg { margin-top: 2px; }

/* ── Sending ────────────────────────────────────────────── */

.send__hero {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border-radius: var(--bo-radius-sm);
  border: 1px solid var(--bo-line);
}

/* ── Live sessions ──────────────────────────────────────── */

/* In the app bar, beside the menu, which is where the apps put it — it is
   status about the whole page, so it belongs with the page's own title rather
   than floating over the middle of the scene.

   It is a control, not a read-out: pressing it opens the sheet where leaving,
   the invite and the roster live. It used to carry its own Leave button, which
   put the one irreversible thing in a session next to a status dot and nowhere
   near the explanation of what leaving does. */
.session-badge { flex: none; }
.session-badge[hidden] { display: none; }

.session-badge__button {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.34rem 0.7rem;
  border: 1px solid rgba(95, 217, 166, 0.4);
  border-radius: var(--radius-pill);
  background: rgba(95, 217, 166, 0.12);
  color: #fff;
  font-size: 0.76rem;
  white-space: nowrap;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.session-badge__button:hover {
  background: rgba(95, 217, 166, 0.22);
  border-color: rgba(95, 217, 166, 0.7);
}
.session-badge__label { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; }

.session-badge__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #5FD9A6;
  box-shadow: 0 0 0 3px rgba(95, 217, 166, 0.18);
}
/* Connecting pulses; a refusal or a catalog mismatch stops pretending. */
.session-badge[data-status="connecting"] .session-badge__button {
  border-color: rgba(255, 196, 102, 0.4);
  background: rgba(255, 196, 102, 0.12);
}
.session-badge[data-status="connecting"] .session-badge__dot {
  background: #FFC466;
  box-shadow: 0 0 0 3px rgba(255, 196, 102, 0.18);
  animation: session-pulse 1.4s ease-in-out infinite;
}
.session-badge[data-status="mismatch"] .session-badge__button,
.session-badge[data-status="refused"] .session-badge__button,
.session-badge[data-status="ended"] .session-badge__button {
  border-color: rgba(255, 107, 107, 0.45);
  background: rgba(255, 107, 107, 0.14);
}
.session-badge[data-status="mismatch"] .session-badge__dot,
.session-badge[data-status="refused"] .session-badge__dot,
.session-badge[data-status="ended"] .session-badge__dot {
  background: #FF6B6B;
  box-shadow: 0 0 0 3px rgba(255, 107, 107, 0.18);
  animation: none;
}

@keyframes session-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.35; }
}

/* In a shared session: the controls still work — a selection is a request and
   the server answers it — but they should look like they belong to a build
   everybody is in, rather than to this browser alone. Applied to every
   participant, because there is no host any more and none of them owns it. */
body.is-following .panel__body { --bo-line-hi: rgba(255, 255, 255, 0.22); }
body.is-following .panel::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: var(--bo-radius);
  border: 1px solid rgba(95, 217, 166, 0.35);
  pointer-events: none;
}
/* A viewer, though, is not making requests at all — the server refuses them by
   definition — so the panel says so rather than offering controls that move
   and snap back. The individual controls are disabled in `ui.js`; this is the
   panel admitting it as a whole. */
body.is-viewing .panel__body { opacity: 0.72; }

/* ── Spatial markers ───────────────────────────────────── */

/* The catalog's own `SpatialMarker`s, drawn on the model — `car.html`'s look,
   built from the same field the visionOS apps read. See
   common/js/configurator/markers.js.

   The overlay is matched to the canvas rather than to the window: the scene
   sits under two stacked toolbars, and a full-window layer would put every
   marker that many pixels low. `pointer-events: none` on the layer and `auto`
   on the buttons, so a drag through the gaps still turns the car. */
.markers {
  position: fixed;
  inset: var(--is-chrome-h) 0 0 0;
  z-index: 10;
  pointer-events: none;
}
body.is-picking .markers,
body.is-markers-hidden .markers { display: none; }

/* A circular glyph, glass over whatever it is floating on. No transform in the
   transition: `CSS2DRenderer` writes one every frame, and animating it makes
   every marker lag a frame behind the car it is stuck to. */
.marker {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  padding: 0;
  border-radius: 50%;
  border: 1px solid var(--bo-line);
  background: rgba(8, 10, 14, 0.7);
  color: rgba(255, 255, 255, 0.86);
  backdrop-filter: blur(12px);
  cursor: pointer;
  pointer-events: auto;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
  transition: opacity 0.2s ease, background 0.2s, border-color 0.2s, box-shadow 0.2s, color 0.2s;
}
.marker svg { width: 18px; height: 18px; display: block; }
.marker:hover,
.marker:focus-visible {
  background: rgba(16, 18, 24, 0.95);
  border-color: var(--purple-hi);
  color: #fff;
  box-shadow: 0 0 0 3px rgba(123, 47, 255, 0.24), 0 2px 12px rgba(0, 0, 0, 0.45);
  outline: none;
}
/* The one whose panel is open. The panel floats beside the car rather than on
   the glyph, so without this it would be a panel from nowhere. */
.marker.is-open {
  background: var(--purple-mid);
  border-color: var(--purple-hi);
  color: #fff;
}

/* A marker that flips a toggle group instead of opening a panel — a door, the
   lamps, the drive note. It has no panel to tint it, and it is the only thing
   on screen saying so once the Configure panel is collapsed. Lit rather than
   filled, so it doesn't read as "a panel is open here". */
.marker.is-on {
  border-color: var(--purple-hi);
  color: #fff;
  box-shadow: inset 0 0 0 1px var(--purple-hi), 0 0 14px rgba(123, 47, 255, 0.45),
    0 2px 12px rgba(0, 0, 0, 0.45);
}

/* On the far side of the car. Faded rather than removed, so it doesn't pop back
   as a new element the instant the car turns another degree — and unclickable,
   which is the part that matters: a button you can't see must not be a button
   you can press. */
.marker.is-behind {
  opacity: 0;
  pointer-events: none;
}

/* The panel a marker opens.

   The element `CSS2DRenderer` drives is this empty point, anchored beside the
   model rather than on the marker; the panel inside it is what has a size, and
   the centring is here. One transform per frame from the renderer and one
   correction from here, and neither has to know about the other. */
.marker-mount {
  width: 0;
  height: 0;
}
.marker-panel {
  position: absolute;
  left: 50%;
  top: 50%;
  /* `--marker-shift-*` is how far `markers.js` has had to nudge the panel to
     keep it inside the scene; zero when the anchor is comfortably on screen. */
  transform: translate(
    calc(-50% + var(--marker-shift-x, 0px)),
    calc(-50% + var(--marker-shift-y, 0px))
  );
  width: min(19rem, calc(100vw - 2rem));
  max-height: 46vh;
  display: flex;
  flex-direction: column;
  pointer-events: auto;
  border: 1px solid rgba(123, 47, 255, 0.3);
  border-radius: var(--bo-radius);
  background: var(--bo-glass);
  backdrop-filter: blur(16px) saturate(1.25);
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.55), inset 0 1px 0 rgba(255, 255, 255, 0.07);
  /* Opacity only. The renderer owns `transform`, so a keyframe that animated it
     would be overwritten on the next frame — mid-animation, every time. */
  animation: marker-panel-in 0.22s ease both;
}
@keyframes marker-panel-in { from { opacity: 0; } to { opacity: 1; } }

.marker-panel__head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.55rem 0.6rem 0.8rem;
  border-bottom: 1px solid var(--bo-line);
}
.marker-panel__title {
  flex: 1;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--bo-faint);
}
.marker-panel__close {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 1px solid var(--bo-line);
  background: var(--bo-glass-hi);
  color: var(--bo-dim);
  font-size: 0.72rem;
  line-height: 1;
  cursor: pointer;
}
.marker-panel__close:hover { color: #fff; border-color: var(--purple-hi); }
.marker-panel__summary {
  padding: 0.6rem 0.8rem 0;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--bo-dim);
}
.marker-panel__body {
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0.75rem 0.8rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}
/* A group heading inside a panel that already carries the group's name in its
   own header would say the same word twice. */
.marker-panel__body .group__head h3 { display: none; }
.marker-panel__body .group__head { justify-content: flex-start; }
.marker-panel__body .group__head:empty { display: none; }
/* A toggle group is one switch. It doesn't need the card's padding around it. */
.marker-panel__body--tight { padding-top: 0.55rem; }

/* ── The colour picker, the way car.html draws it ────────── */

/* Same control, different room. In the side panel a swatch carries its name
   underneath, because a scrolling list of eight groups gives you no other place
   to put it. A spatial panel is about one thing, so it does what `car.html`
   does: name the chosen colour once, large, at the top — then a row of bare
   circles under it, close together, with the ring doing the work.

   The markup is `ui.js`'s either way. Two swatch controls that happened to agree
   would be one swatch control too many. */
.marker-panel__body .group__chosen {
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: normal;
  text-transform: none;
  color: #fff;
}
.marker-panel__body .swatches {
  gap: 0.55rem;
  padding: 0.55rem 0.6rem 0.7rem;
}
/* Circles only. The name is in the heading above, and repeating it eight times
   at 0.66 rem is what makes the side panel's version a grid rather than a row. */
.marker-panel__body .swatch { width: auto; }
.marker-panel__body .swatch__label { display: none; }
.marker-panel__body .swatch__ring {
  width: 38px;
  height: 38px;
  border: 0;
  outline: 0;
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.18),
    0 2px 6px rgba(0, 0, 0, 0.35);
}
.marker-panel__body .swatch:hover:not(:disabled) .swatch__ring {
  transform: translateY(-3px) scale(1.06);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.28),
    0 7px 18px rgba(0, 0, 0, 0.45);
}
/* The chosen one: a gap ring in the panel's own colour, then a glowing accent
   ring outside it. Drawn with shadows rather than an outline so the gap can be
   the panel's dark glass instead of a hole. */
.marker-panel__body .swatch[aria-current="true"] .swatch__ring {
  transform: translateY(-1px);
  box-shadow:
    inset 0 0 0 1px rgba(255, 255, 255, 0.22),
    0 0 0 2px #0a0e1c,
    0 0 0 4px var(--purple-hi),
    0 4px 16px rgba(123, 47, 255, 0.45);
}
.marker-panel__body .swatch:disabled .swatch__ring { opacity: 0.3; }

/* An `info` marker's content: text, a picture, a clip. */
.marker-info__text {
  font-size: 0.82rem;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.86);
}
.marker-info__figure {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.marker-info__figure img {
  width: 100%;
  border-radius: var(--bo-radius-sm);
  display: block;
}
.marker-info__figure audio { width: 100%; }
.marker-info__figure figcaption {
  font-size: 0.7rem;
  color: var(--bo-faint);
}
.marker-info__missing {
  font-size: 0.74rem;
  color: var(--bo-faint);
}

/* ── The gear panel ────────────────────────────────────── */

/* Rows in a `.menu__sheet`, so it is the same sheet as every other control in
   the app bar — see common/js/configurator/scene-settings.js. */
.scene { padding: 0.45rem 0.2rem; }
.scene__row {
  display: flex;
  align-items: center;
  gap: 0.8rem;
  padding: 0.5rem 0.7rem;
  cursor: pointer;
}
.scene__row--stacked {
  flex-direction: column;
  align-items: stretch;
  gap: 0.5rem;
  cursor: default;
}
.scene__text { display: flex; flex-direction: column; gap: 0.1rem; min-width: 0; flex: 1; }
.scene__label { font-size: 0.86rem; font-weight: 500; }
.scene__hint { font-size: 0.72rem; color: var(--bo-faint); line-height: 1.35; }

/* The weather rows, on the page with a sea in it — see the sun and sea state
   sliders in common/js/configurator/scene-settings.js. The value sits on the
   label's own line rather than under it, because it is the label's other half:
   "Sun" on its own names a control and "Sun 26°" names a setting. */
.scene__head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; }
.scene__value { font-size: 0.74rem; color: var(--bo-dim); font-variant-numeric: tabular-nums; }

.scene__slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 1.1rem;
  margin: 0;
  background: none;
  cursor: pointer;
}
/* Track and thumb have to be declared twice: the two pseudo-elements are
   vendor-specific and a selector list containing an unknown one is dropped
   whole, so sharing a rule between them loses both. */
.scene__slider::-webkit-slider-runnable-track {
  height: 0.25rem;
  border-radius: 999px;
  background: var(--bo-line-hi);
}
.scene__slider::-moz-range-track {
  height: 0.25rem;
  border-radius: 999px;
  background: var(--bo-line-hi);
}
.scene__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 0.85rem;
  height: 0.85rem;
  margin-top: -0.3rem;
  border: none;
  border-radius: 50%;
  background: var(--purple-hi);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.scene__slider::-moz-range-thumb {
  width: 0.85rem;
  height: 0.85rem;
  border: none;
  border-radius: 50%;
  background: var(--purple-hi);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
.scene__slider:focus-visible { outline: 2px solid var(--purple-hi); outline-offset: 3px; }

/* The environment picker: one control, three states, and only one of them on.
   A row of buttons rather than a submenu, because the whole value of it is
   flicking between the three and watching the paint change. */
.seg { display: flex; gap: 0.3rem; }
.seg__option {
  flex: 1;
  padding: 0.4rem 0.25rem;
  font-size: 0.72rem;
  font-weight: 500;
  border: 1px solid var(--bo-line);
  border-radius: var(--bo-radius-sm);
  background: var(--bo-glass-hi);
  color: var(--bo-dim);
  cursor: pointer;
  transition: color 0.18s, background 0.18s, border-color 0.18s;
}
.seg__option:hover { color: #fff; border-color: var(--bo-line-hi); }
.seg__option[aria-checked="true"] {
  color: #fff;
  background: var(--purple-mid);
  border-color: var(--purple-mid);
}

/* ── The printed sheet ──────────────────────────────────── */

/* A second rendering of the two read-outs the panel already shows, laid out
   for paper. `window.print()` makes no file — see ADR `common/docs/005` — so this is what
   the browser's own "Save as PDF" saves.

   The sheet's own styling lives out here rather than inside `@media print`,
   and only its *visibility* is print-gated. Two reasons: the rules apply to
   nothing else on the page, and a layout that can only exist inside a print
   dialog is a layout nobody can look at while building it — add
   `.is-proofing-sheet` to <body> to see it on screen. */

#spec-sheet {
  display: none;
  background: #fff;
  color: #000;
  font-size: 10.5pt;
  line-height: 1.5;
}

.sheet__head { border-bottom: 1.5pt solid #111; padding-bottom: 0.5rem; }
.sheet__eyebrow {
  font-size: 8pt;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #555;
}
.sheet__title { font-size: 22pt; font-weight: 600; margin-top: 0.15rem; line-height: 1.15; }
.sheet__summary { color: #444; margin-top: 0.2rem; }

.sheet__hero {
  display: block;
  width: 100%;
  margin: 0.8rem 0;
  border: 0.5pt solid #bbb;
  break-inside: avoid;
}

.sheet__columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.4rem;
  align-items: start;
}
/* One column when there's no quote, so a marine catalog doesn't print a
   specification down half the page with white space beside it. */
.sheet__columns:has(> .sheet__block:only-child) { grid-template-columns: 1fr; }

.sheet__block { break-inside: avoid; }
.sheet__block h2 {
  font-size: 9pt;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #555;
  border-bottom: 0.5pt solid #ccc;
  padding-bottom: 0.2rem;
  margin-bottom: 0.35rem;
}

.sheet__list {
  display: grid;
  grid-template-columns: auto 1fr;
  row-gap: 0.16rem;
}
.sheet__list dt { color: #555; }
/* The gap between label and figure is padding rather than `column-gap`, so the
   rule above the total is one line across the block instead of two with a
   break in the middle — `border-top` on two grid items can't cross a gap. */
.sheet__list dd { text-align: right; padding-left: 0.8rem; }
.sheet__list .sheet__total {
  font-weight: 600;
  border-top: 0.5pt solid #999;
  padding-top: 0.22rem;
  margin-top: 0.22rem;
}

.sheet__note { font-size: 8.5pt; color: #666; margin-top: 0.4rem; }
.sheet__foot {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 1.4rem;
  padding-top: 0.4rem;
  border-top: 0.5pt solid #ccc;
  font-size: 8.5pt;
  color: #666;
}

/* Proofing the sheet on screen, which is also how it was verified. */
body.is-proofing-sheet { overflow: auto; background: #7a7a82; }
body.is-proofing-sheet > *:not(#spec-sheet) { display: none !important; }
body.is-proofing-sheet #spec-sheet {
  display: block;
  width: 210mm;
  min-height: 297mm;
  margin: 2rem auto;
  padding: 14mm;
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
}

@media print {
  /* Everything the screen needs and paper doesn't, including the canvas — the
     scene reaches paper as the captured hero image instead. */
  body > *:not(#spec-sheet) { display: none !important; }
  body { overflow: visible; background: #fff; color: #000; }
  #spec-sheet { display: block; }
  @page { margin: 14mm; }
}

/* ── Narrow screens ─────────────────────────────────────── */

@media (max-width: 760px) {
  /* The bar keeps the title and the menu; the subtitle and the back button's
     word are what a phone gives up. */
  :root { --is-appbar-h: 48px; }
  .appbar { gap: 0.4rem; }
  .appbar__subtitle { display: none; }
  .appbar__back span:not(.appbar__chevron) { display: none; }
  .session-badge__label { max-width: 8ch; }

  .panel {
    top: auto;
    left: 0.6rem;
    right: 0.6rem;
    bottom: 0.6rem;
    width: auto;
    max-height: 52vh;
  }
  body.is-panel-collapsed .panel { transform: translateY(calc(100% + 1rem)); }
  /* Bottom-right on a phone, where the panel slides down rather than across. */
  .panel-reopen { top: auto; bottom: 0.8rem; right: 0.8rem; }
  /* The pills move under the bar, and stop wrapping: three rows of them is
     what pushed them into the scene. One scrolling row instead. */
  .viewpoints {
    bottom: auto;
    top: calc(var(--is-chrome-h) + 0.55rem);
    left: 0.6rem;
    right: 0.6rem;
    transform: none;
    max-width: none;
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
    padding-bottom: 2px;
  }
  .viewpoints::-webkit-scrollbar { display: none; }
  .viewpoints .ui-pill { flex: none; }
  .notices { bottom: 56vh; }
  /* A menu sheet the width of its button would be unreadable; on a phone it
     spans most of the bar instead. */
  .menu__sheet { width: min(19rem, calc(100vw - 1.4rem)); }
  .saved-configurations__row { flex-wrap: wrap; }
  .saved-configurations__actions { width: 100%; justify-content: flex-end; }
  .sheet-row__buttons .btn { flex: 1; justify-content: center; }
}
