/* functor.dev — calm, professional retro theme shared by the landing page,
   sandbox, and docs. Dark-violet identity; cyan is the primary accent, pink
   is reserved for the hero wordmark and the gradient rule. */

:root {
  --bg: #0f0c1d;
  --bg-panel: #161226;
  --bg-raised: #1e1833;
  --line: #2b2542;
  --text: #e9e6f2;
  --text-dim: #9b94b3;
  --pink: #e858b8;
  --cyan: #41d8e6;
  --amber: #eec877;
  --green: #6fdc92;
  --red: #f2637f;
  --font-display: "Orbitron", system-ui, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;

  /* Player-identity colors for multiplayer chrome (pane borders, tabs, lag
     combs, Output prefixes). One identity everywhere; p1/p2 deliberately alias
     the site accents so the multiplayer chrome reads as the same product. */
  --scrub-p1: var(--cyan);
  --scrub-p2: var(--pink);
  --scrub-p3: var(--amber);
  --scrub-p4: var(--green);
  /* The authority's ink. Deliberately the one hue in the pane vocabulary with
     no saturation: a server is not a fifth player, so it reads as absence of
     player identity rather than as another color in the set. */
  --scrub-server: #8b83ad;

  /* Spacing scale — a single rhythm the whole landing page snaps to. */
  --space-sm: 16px;
  --space-md: 24px;
  --space-lg: 40px;
  --space-xl: 64px;
  --space-2xl: 96px;
  /* Vertical gap between major landing sections. */
  --section-gap: var(--space-2xl);
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-mono);
  line-height: 1.6;
}

a {
  color: var(--cyan);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* Theme every scrollbar (page, code panels, editors) — the OS-default silver
   bars glare against the dark panels. Slim, track-less, thumb on --line. */
* {
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

*::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}

*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

*::-webkit-scrollbar-thumb:hover {
  background: #3d3560;
  background-clip: padding-box;
}

/* ------------------------------------------------------------- site header */

/* Shared top bar across the landing, sandbox, and docs pages: wordmark left,
   nav right (the sandbox slots its scene controls in between). */
.site-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 18px;
  padding: 12px 24px;
  background: var(--bg-panel);
  border-bottom: 1px solid var(--line);
}

/* Landing only: keep the bar pinned as you scroll, lifted with a soft shadow.
   Scoped away from the sandbox/docs full-height flex layouts. */
.landing-page .site-header {
  position: sticky;
  top: 0;
  z-index: 20;
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
}

/* The sandbox/IDE headers get the same lift (no sticky — they're rows in a
   full-height flex layout), floating the chrome above the editing surface. */
.sandbox-page .site-header,
.ide-page .site-header {
  position: relative;
  z-index: 20;
  box-shadow: 0 2px 14px rgba(0, 0, 0, 0.45);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: 20px;
  margin-left: auto;
}

/* The toggle checkbox is irrelevant off the mobile breakpoint (nav is always
   shown), so drop it from the layout and tab order there. */
.nav-toggle {
  display: none;
}

/* Hamburger toggle — hidden until the mobile breakpoint (landing only). */
.nav-burger {
  display: none;
  align-items: center;
  justify-content: center;
  margin-left: auto;
  padding: 4px;
  color: var(--text-dim);
  cursor: pointer;
}

.nav-burger:hover {
  color: var(--cyan);
}

@media (max-width: 620px) {
  .landing-page .nav-burger {
    display: inline-flex;
  }

  /* Keyboard-reachable but visually hidden — clip (not display:none) so it
     stays focusable; keyboard focus lands here and Space toggles the menu. */
  .landing-page .nav-toggle {
    display: block;
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
  }

  /* Surface that focus on the visible burger. */
  .landing-page .nav-toggle:focus-visible ~ .nav-burger {
    color: var(--cyan);
    outline: 2px solid var(--cyan);
    outline-offset: 3px;
    border-radius: 3px;
  }

  /* Collapse the links into a dropdown that the checkbox reveals. */
  .landing-page .site-nav {
    display: none;
    order: 3;
    flex-basis: 100%;
    flex-direction: column;
    align-items: flex-start;
    gap: 10px;
    margin-left: 0;
    padding: 6px 0 4px;
  }

  .landing-page .nav-toggle:checked ~ .site-nav {
    display: flex;
  }
}

.site-nav a {
  font-size: 0.85rem;
  color: var(--text-dim);
  letter-spacing: 0.03em;
}

.site-nav a:hover,
.site-nav a[aria-current="page"] {
  color: var(--cyan);
  text-decoration: none;
}

.site-nav a.nav-github {
  display: inline-flex;
  align-items: center;
}

.site-nav a.nav-github svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* ---------------------------------------------------------------- landing */

/* Synthwave backdrop for the landing page only. Two fixed layers behind all
   content (z-index -1), so they cost nothing to lay out and don't scroll:
   ::before  — very faint grain so the large gradients don't band.
   .site-backdrop — a slow cyan/violet/pink aurora wash that drifts.
   The body must be transparent (the base --bg stays on <html>) or its opaque
   fill would paint over the z-index:-1 layers. */
.landing-page {
  background: transparent;
}

.landing-page::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.035'/%3E%3C/svg%3E");
}

.site-backdrop {
  position: fixed;
  inset: -12%;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(40% 55% at 20% 24%, rgba(65, 216, 230, 0.09), transparent 64%),
    radial-gradient(44% 52% at 82% 14%, rgba(232, 88, 184, 0.07), transparent 64%),
    radial-gradient(56% 60% at 60% 88%, rgba(126, 86, 224, 0.08), transparent 68%);
  animation: aurora-drift 28s ease-in-out infinite alternate;
}

@keyframes aurora-drift {
  from {
    transform: translate3d(-3%, -2%, 0) scale(1.05) rotate(-2deg);
  }
  to {
    transform: translate3d(4%, 3%, 0) scale(1.15) rotate(2deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .site-backdrop {
    animation: none;
  }
}

/* Hero mini-sandbox band: copy left, a live demo card right; stacks below 900px. */
.hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: var(--space-xl);
  max-width: 1100px;
  min-height: 72vh;
  margin: 0 auto;
  padding: var(--space-xl) var(--space-md);
}

.hero-copy {
  min-width: 0;
}

.hero-demo {
  min-width: 0;
}

/* The demo card frames the live player iframe; its built-in scrubber and
   mouse-capture chrome overlay inside, clipped by the rounded card. */
.hero-card {
  position: relative;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
  background: #0d0221;
  box-shadow: 0 24px 60px -34px rgba(0, 0, 0, 0.85);
}

.hero-scene {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 10;
  border: 0;
  background: #0d0221;
}

/* A visibly labeled excerpt of the real running game. The label prevents the
   compact panel reading like a complete source file; the editor beneath it
   carries the site's shared synthwave theme. */
.hero-editor {
  border-top: 1px solid var(--line);
  /* Tall enough for the whole excerpt — the four tunables and their lead
     comment up top, the world composition below — with no line clipped. The
     budget is 16 code lines at the shared 11.5px/1.65 editor type; adding a
     line to the <editable> region means growing this too (the e2e asserts
     nothing clips). */
  height: 342px;
  overflow: hidden;
  background: #161226;
  display: flex;
  flex-direction: column;
}

.hero-editor-heading {
  flex: 0 0 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 7px 10px;
  border-bottom: 1px solid rgba(73, 231, 255, 0.16);
  background: rgba(13, 2, 33, 0.72);
  color: var(--cyan);
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.hero-editor-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}

/* The file name deep-links into the sandbox with this example loaded. It
   stays a quiet code label — the global cyan link color would make it compete
   with the tunables below — and only reads as a link on hover/focus. */
.hero-editor-source {
  min-width: 0;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
}

.hero-editor-source:hover code,
.hero-editor-source:focus-visible code {
  color: var(--cyan);
  text-decoration: underline;
}

.hero-editor-heading code {
  min-width: 0;
  overflow: hidden;
  color: var(--text-dim);
  font: inherit;
  font-weight: 400;
  letter-spacing: 0;
  text-overflow: ellipsis;
  text-transform: none;
  white-space: nowrap;
}

.hero-editor-code {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
}

.hero-editor.is-error {
  height: auto;
}

.hero-editor.is-error .hero-editor-code {
  display: none;
}

.hero-editor .cm-editor {
  height: 100%;
  /* Same type AND leading as the sandbox/IDE editors. */
  font-size: 11.5px;
}

.hero-editor .cm-content {
  line-height: 1.65;
}

/* A small status dot in the excerpt label: green live / red on a broken edit.
   The message lives in its tooltip (title) — calm, not a banner. */
.hero-status {
  flex: 0 0 auto;
  pointer-events: none;
  width: 7px;
  height: 7px;
  border-radius: 999px;
  color: var(--text-dim);
  background: currentColor;
  box-shadow: 0 0 7px currentColor;
}

.hero-status[data-state="live"] {
  color: var(--green);
}

.hero-status[data-state="busy"] {
  color: var(--amber);
}

.hero-status[data-state="error"] {
  color: var(--red);
}

/* Flourish (a): the gradient-clipped wordmark — the one big statement. */
.hero-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  letter-spacing: 0.08em;
  margin: 0;
  background: linear-gradient(180deg, var(--cyan) 0%, #a9a2c6 55%, var(--pink) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-tagline {
  font-family: var(--font-mono);
  font-size: clamp(0.8rem, 1.4vw, 1rem);
  line-height: 1.7;
  letter-spacing: 0.06em;
  color: var(--text);
  margin: 14px 0 0;
}

.hero-sub {
  max-width: 560px;
  color: var(--text-dim);
  margin: 18px 0 0;
}

.hero-actions {
  display: flex;
  gap: 16px;
  margin-top: 32px;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.button-primary,
.button-pink {
  font-family: var(--font-mono);
  font-weight: 600;
  letter-spacing: 0.04em;
  padding: 12px 24px;
  border-radius: 4px;
  font-size: 0.85rem;
  line-height: 1.4;
  transition: transform 0.15s ease, background 0.2s ease, border-color 0.2s ease,
    box-shadow 0.2s ease;
}

.button-pink {
  color: var(--bg);
  background: var(--pink);
  border: 1px solid var(--pink);
}

.button-pink:hover {
  text-decoration: none;
  background: #f06fc6;
  border-color: #f06fc6;
  transform: translateY(-2px);
  box-shadow: 0 10px 22px -10px rgba(0, 0, 0, 0.75), 0 0 26px -6px rgba(232, 88, 184, 0.7);
}

.button-primary {
  color: var(--bg);
  background: var(--cyan);
  border: 1px solid var(--cyan);
}

.button-primary:hover {
  text-decoration: none;
  background: #5ce2ee;
  border-color: #5ce2ee;
  transform: translateY(-2px);
  box-shadow: 0 10px 22px -10px rgba(0, 0, 0, 0.75), 0 0 26px -6px rgba(65, 216, 230, 0.7);
}

/* Differentiators + section titles. */
.section-title {
  font-family: var(--font-display);
  letter-spacing: 0.06em;
  color: var(--text);
  font-size: 1.15rem;
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--section-gap) var(--space-md) 0;
  text-align: center;
}

/* Feature showcase: alternating gif ↔ text rows. Full-width bands alternate a
   faint dark fill for a calm section-to-section rhythm; each media card lifts
   with a shadow as its row scrolls into focus (features.js toggles .in-view). */
.feature-rows {
  padding-top: var(--space-md);
  display: flex;
  flex-direction: column;
}

.feature-band {
  padding: var(--space-xl) var(--space-md);
}

.feature-band:nth-child(odd) {
  background: rgba(0, 0, 0, 0.2);
}

.feature-row {
  max-width: 1100px;
  margin: 0 auto;
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: center;
  gap: var(--space-xl);
}

/* Alternate the media to the right on the untinted bands. */
.feature-band:nth-child(even) .feature-media {
  order: 2;
}

.feature-media {
  position: relative;
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  background: #0d0221;
  line-height: 0;
  box-shadow: 0 18px 44px -30px rgba(0, 0, 0, 0.6);
  transition: box-shadow 0.6s ease 0.1s, border-color 0.5s ease;
}

/* A thin cyan→pink chrome line along the top edge — the site accent, echoing the
   footer rule. Brightens as the card comes into focus. */
.feature-media::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  z-index: 1;
  background: linear-gradient(90deg, var(--cyan), var(--pink));
  opacity: 0.2;
  transition: opacity 0.5s ease;
}

.feature-media img {
  display: block;
  width: 100%;
  height: auto;
}

.feature-body h3 {
  font-family: var(--font-display);
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  color: var(--text);
  margin: 0 0 14px;
}

.feature-body p {
  color: var(--text-dim);
  font-size: 1rem;
  line-height: 1.7;
  max-width: 30rem;
}

/* Scroll-into-focus reveal — gated on .reveal (added by features.js) so the rows
   stay visible if the script never runs. The media slides in from its own side
   (reinforcing the alternation); the copy fades up just behind it. */
.feature-rows.reveal .feature-media {
  opacity: 0;
  transform: translateY(14px);
  transition: box-shadow 0.6s ease 0.1s, transform 0.5s ease, opacity 0.5s ease;
}

.feature-rows.reveal .feature-body {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.45s ease 0.06s, transform 0.45s ease 0.06s;
}

.feature-rows.reveal .feature-row.in-view .feature-media,
.feature-rows.reveal .feature-row.in-view .feature-body {
  opacity: 1;
  transform: none;
}

/* Focus state: the border warms to cyan, the top accent brightens, and the card
   lifts with a deep drop + faint cyan glow. */
.feature-row.in-view .feature-media {
  border-color: rgba(65, 216, 230, 0.28);
  box-shadow: 0 34px 80px -30px rgba(0, 0, 0, 0.85), 0 0 60px -36px rgba(65, 216, 230, 0.3);
}

.feature-row.in-view .feature-media::after {
  opacity: 0.45;
}

/* Stack to one column on narrow viewports (media always on top). */
@media (max-width: 760px) {
  .feature-row {
    grid-template-columns: 1fr;
    gap: var(--space-md);
  }
  .feature-row:nth-child(even) .feature-media {
    order: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .feature-rows.reveal .feature-media,
  .feature-rows.reveal .feature-body {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Platforms — three grouped cards (Native / Browser / VR), centered like the
   feature row so a wrapped card stays centered. */
.platform-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 24px;
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--space-md) var(--space-md) 0;
}

.platform {
  flex: 1 1 220px;
  max-width: 300px;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 28px 24px;
  text-align: center;
  box-shadow: 0 16px 38px -26px rgba(0, 0, 0, 0.85);
}

.platform-icons {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
  color: var(--cyan);
  margin-bottom: 16px;
}

.platform-icons svg {
  width: 30px;
  height: 30px;
  display: block;
}

.platform h3 {
  font-family: var(--font-display);
  font-size: 0.9rem;
  letter-spacing: 0.06em;
  color: var(--text);
  margin: 0 0 6px;
}

.platform p {
  color: var(--text-dim);
  font-size: 0.9rem;
  margin: 0;
}

.how-it-works {
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--section-gap) var(--space-md) 0;
  text-align: center;
}

.how-it-works h2 {
  font-family: var(--font-display);
  letter-spacing: 0.06em;
  color: var(--text);
}

/* Keep the prose at a comfortable reading measure, left-aligned under the
   heading (not stretched across the full 1100px column). */
.how-it-works p {
  color: var(--text-dim);
  max-width: 720px;
  margin: 0 auto;
}

/* Closing call-to-action band — left-aligned to the same column as the other
   sections so all the headings share one left edge. */
.cta-band {
  max-width: 1100px;
  margin: 0 auto;
  padding: var(--section-gap) var(--space-md);
  text-align: center;
}

.cta-band h2 {
  font-family: var(--font-display);
  letter-spacing: 0.06em;
  color: var(--text);
}

.cta-band p {
  color: var(--text-dim);
  max-width: 720px;
  margin: 0 auto 24px;
}

.tok-k { color: var(--pink); }
.tok-t { color: var(--amber); }
.tok-n { color: #b7a9e0; }
.tok-c { color: #6c6685; font-style: italic; }
.tok-s { color: var(--green); }
.tok-a { color: var(--amber); }
.tok-o { color: var(--text-dim); }

/* The "put the FUN in functional programming" band between the hero and features. */
.fun-interstitial {
  text-align: center;
  padding: var(--section-gap) var(--space-md) 0;
}

.fun-interstitial p {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.05em;
  font-size: clamp(1.1rem, 2.6vw, 1.7rem);
  color: var(--text-dim);
  margin: 0;
}

.fun-interstitial .fun-accent {
  background: linear-gradient(90deg, var(--cyan), var(--pink));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.landing-footer {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
  color: var(--text-dim);
  font-size: 0.85rem;
  padding: var(--space-lg) var(--space-md);
  background: #000;
}

/* Full-width cyan→pink accent along the very top of the footer. */
.landing-footer::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--cyan), var(--pink));
}

.footer-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--text);
  font-size: 0.85rem;
}

.footer-brand .wordmark-mark {
  height: 1.5em;
  width: auto;
  color: var(--cyan);
}

.footer-links {
  display: flex;
  gap: 22px;
}

.footer-links a {
  color: var(--text-dim);
  letter-spacing: 0.03em;
}

.footer-links a:hover {
  color: var(--cyan);
  text-decoration: none;
}

.footer-tagline {
  margin: 0;
  font-size: 0.8rem;
  color: var(--text-dim);
}

/* Hero stacks to one column on narrow viewports. */
@media (max-width: 900px) {
  .hero {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
    min-height: 0;
    padding: var(--space-lg) var(--space-md);
  }
}

/* Tighten the section rhythm on phones. */
@media (max-width: 620px) {
  :root {
    --section-gap: var(--space-xl);
  }
}

/* ---------------------------------------------------------------- sandbox */

.sandbox-page,
.ide-page {
  height: 100vh;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.sandbox-controls {
  flex-wrap: wrap;
}

.wordmark {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: 0.14em;
  color: var(--text);
  font-size: 0.8rem;
}

.wordmark-mark {
  height: 1.75em;
  width: auto;
  display: block;
  color: var(--cyan);
}

.wordmark-accent {
  color: var(--pink);
}

/* The release badge next to the wordmark. The HTML ships "alpha"; the site
   build stamps in the latest release tag (see build.mjs). */
.version-badge {
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--text-dim);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
  cursor: default;
}

.sandbox-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1;
}

.picker-label {
  color: var(--text-dim);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

#example-picker,
#client-count,
#reset,
#download,
#restart {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 5px 9px;
}

/* Selects drop the OS widget chrome for the mockup's ▾ chip look. */
#example-picker,
#client-count {
  appearance: none;
  -webkit-appearance: none;
  padding-right: 24px;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0h8L4 5z' fill='%239b94b3'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 9px center;
}

#example-picker:hover,
#client-count:hover {
  border-color: var(--cyan);
  color: var(--cyan);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5'%3E%3Cpath d='M0 0h8L4 5z' fill='%2341d8e6'/%3E%3C/svg%3E");
}

#reset,
#download,
#restart {
  cursor: pointer;
}

#reset:hover,
#download:hover,
#restart:hover {
  border-color: var(--cyan);
  color: var(--cyan);
}

/* Editor configuration reads as state, not as a header action. Sandbox and
   IDE place this near the right edge of their status strip — the always-
   present view host before it carries the right-pushing auto margin. */
.editor-keybindings-toggle {
  appearance: none;
  border: 0;
  background: none;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  line-height: inherit;
  padding: 4px 8px;
  white-space: nowrap;
  cursor: pointer;
}

.editor-keybindings-toggle:hover,
.editor-keybindings-toggle:focus-visible {
  color: var(--text);
}

.editor-keybindings-toggle:focus-visible {
  outline: 1px solid rgba(65, 216, 230, 0.65);
  outline-offset: -2px;
}

.editor-keybindings-toggle[aria-pressed="true"] {
  color: var(--cyan);
}

/* The Vim mode block — airline-style: the segment's BACKGROUND takes the
   mode's color (cyan NORMAL, green INSERT, pink VISUAL, red REPLACE) with
   dark ink and a soft neon glow bleeding into the strip. */
.statusbar-vim[data-vim-mode] {
  color: var(--bg);
  font-weight: 600;
}

.statusbar-vim[data-vim-mode="normal"] {
  background: var(--cyan);
  box-shadow: 0 0 10px rgba(65, 216, 230, 0.5);
}

.statusbar-vim[data-vim-mode="insert"] {
  background: var(--green);
  box-shadow: 0 0 10px rgba(111, 220, 146, 0.5);
}

.statusbar-vim[data-vim-mode^="visual"] {
  background: var(--pink);
  box-shadow: 0 0 10px rgba(232, 88, 184, 0.5);
}

.statusbar-vim[data-vim-mode="replace"] {
  background: var(--red);
  box-shadow: 0 0 10px rgba(242, 99, 127, 0.5);
}

.editor-keybindings-toggle[aria-busy="true"] {
  cursor: progress;
}

.editor-keybindings-toggle[aria-disabled="true"] {
  opacity: 0.72;
  cursor: default;
}

.status-pill {
  font-family: var(--font-mono);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-size: 0.68rem;
  padding: 6px 12px;
  border-radius: 999px;
  border: 1px solid var(--line);
  white-space: nowrap;
}

.status-pill[data-state="live"] {
  color: var(--green);
  border-color: rgba(111, 220, 146, 0.4);
  background: rgba(111, 220, 146, 0.08);
  text-shadow: 0 0 8px rgba(111, 220, 146, 0.55);
}

.status-pill[data-state="error"] {
  color: var(--red);
  border-color: rgba(242, 99, 127, 0.5);
  background: rgba(242, 99, 127, 0.08);
  text-shadow: 0 0 8px rgba(242, 99, 127, 0.55);
}

.status-pill[data-state="busy"] {
  color: var(--amber);
  background: rgba(238, 200, 119, 0.07);
  /* No glow on purpose: busy is transient; only settled states light up. */
}

/* Shared external-runtime link for the sandbox and multi-file IDE. It stays a
   compact header control until opened, then reads like a tiny device console:
   source link above, live runtime telemetry and the raw capture below. */
.runtime-target-host {
  position: relative;
}

.runtime-target {
  position: relative;
}

.runtime-target > summary {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 6px 10px;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 4px;
  font-size: 0.75rem;
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.runtime-target > summary::-webkit-details-marker {
  display: none;
}

.runtime-target > summary:hover,
.runtime-target[open] > summary {
  color: var(--cyan);
  border-color: rgba(65, 216, 230, 0.65);
}

.runtime-target-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--text-dim);
  box-shadow: 0 0 0 3px rgba(155, 148, 179, 0.1);
}

.runtime-target-summary-state {
  color: var(--text-dim);
  font-size: 0.62rem;
}

.runtime-target > summary[data-state="busy"] .runtime-target-dot {
  background: var(--amber);
  box-shadow: 0 0 10px rgba(238, 200, 119, 0.65);
}

.runtime-target > summary[data-state="live"] .runtime-target-dot {
  background: var(--green);
  box-shadow: 0 0 10px rgba(111, 220, 146, 0.7);
}

.runtime-target > summary[data-state="error"] .runtime-target-dot {
  background: var(--red);
  box-shadow: 0 0 10px rgba(242, 99, 127, 0.65);
}

.runtime-target-panel {
  position: absolute;
  z-index: 40;
  top: calc(100% + 10px);
  left: 0;
  width: min(430px, calc(100vw - 32px));
  padding: 16px;
  background:
    linear-gradient(135deg, rgba(65, 216, 230, 0.06), transparent 42%),
    var(--bg-panel);
  border: 1px solid rgba(65, 216, 230, 0.45);
  border-radius: 6px;
  box-shadow: 0 18px 55px rgba(0, 0, 0, 0.65);
}

.runtime-target-panel::before {
  content: "";
  position: absolute;
  inset: 5px;
  border: 1px solid rgba(232, 88, 184, 0.12);
  pointer-events: none;
}

.runtime-target-heading,
.runtime-target-actions,
.runtime-target-telemetry {
  display: flex;
  align-items: center;
}

.runtime-target-heading {
  position: relative;
  gap: 10px;
  margin-bottom: 13px;
  color: var(--cyan);
  font-family: var(--font-display);
  font-size: 0.65rem;
  letter-spacing: 0.12em;
}

.runtime-target-kind {
  margin-left: auto;
  color: var(--pink);
  font-family: var(--font-mono);
  font-size: 0.58rem;
}

.runtime-target-heading button {
  flex: none;
  width: 28px;
  height: 28px;
  margin: -7px -7px -7px 0;
  padding: 0;
  color: var(--text-dim);
  background: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  font: 1rem/1 var(--font-mono);
  cursor: pointer;
}

.runtime-target-heading button:hover,
.runtime-target-heading button:focus-visible {
  color: var(--cyan);
  border-color: var(--line);
  outline: none;
}

.runtime-target-label {
  position: relative;
  display: block;
  color: var(--text-dim);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
}

.runtime-target-label input {
  display: block;
  width: 100%;
  margin-top: 5px;
  padding: 8px 10px;
  color: var(--text);
  background: rgba(15, 12, 29, 0.88);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: 0.78rem/1.4 var(--font-mono);
  outline: none;
}

.runtime-target-label input:focus {
  border-color: var(--cyan);
  box-shadow: 0 0 0 2px rgba(65, 216, 230, 0.12);
}

.runtime-target-hint {
  position: relative;
  margin: 7px 0 12px;
  color: var(--text-dim);
  font-size: 0.65rem;
}

.runtime-target-hint code {
  color: var(--amber);
}

.runtime-target-actions {
  position: relative;
  gap: 8px;
}

.runtime-target-actions button {
  padding: 7px 10px;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 4px;
  font: 0.7rem var(--font-mono);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
}

.runtime-target-actions button:first-child {
  color: var(--bg);
  background: var(--cyan);
  border-color: var(--cyan);
  font-weight: 600;
}

.runtime-target-actions button:hover:not(:disabled) {
  border-color: var(--pink);
}

.runtime-target-actions button:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}

.runtime-target-status {
  position: relative;
  margin: 12px 0 0;
  padding-top: 10px;
  color: var(--text-dim);
  border-top: 1px solid var(--line);
  font-size: 0.7rem;
  line-height: 1.45;
  max-height: 92px;
  overflow: auto;
}

.runtime-target-status[data-state="live"] {
  color: var(--green);
}

.runtime-target-status[data-state="busy"] {
  color: var(--amber);
}

.runtime-target-status[data-state="error"] {
  color: var(--red);
}

.runtime-target-telemetry {
  position: relative;
  gap: 20px;
  margin-top: 12px;
}

.runtime-target-telemetry div {
  display: grid;
  gap: 1px;
}

.runtime-target-telemetry span,
.runtime-target-capture figcaption {
  color: var(--text-dim);
  font-size: 0.55rem;
  letter-spacing: 0.11em;
}

.runtime-target-telemetry strong {
  color: var(--text);
  font-size: 0.72rem;
  font-weight: 600;
}

.runtime-target-model {
  position: relative;
  max-height: 68px;
  margin: 11px 0 0;
  padding: 8px 10px;
  overflow: auto;
  color: var(--cyan);
  background: rgba(15, 12, 29, 0.72);
  border-left: 2px solid var(--pink);
  font: 0.66rem/1.4 var(--font-mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.runtime-target-capture {
  position: relative;
  margin: 12px 0 0;
}

.runtime-target-capture img {
  display: block;
  width: 100%;
  max-height: 190px;
  object-fit: contain;
  background: #06050c;
  border: 1px solid var(--line);
}

.runtime-target-capture figcaption {
  margin-top: 4px;
  text-align: right;
}

@media (max-width: 820px) {
  .runtime-target-panel {
    position: fixed;
    top: 74px;
    right: 12px;
    left: 12px;
    width: auto;
    max-height: calc(100vh - 90px);
    overflow: auto;
  }
}

.sandbox-split {
  flex: 1;
  display: grid;
  grid-template-columns: minmax(360px, 44%) 1fr;
  min-height: 0;
}

.editor-pane {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--line);
  min-width: 0;
  min-height: 0;
}

#editor {
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

#editor .cm-editor {
  height: 100%;
  /* The design mockup's editor type: smaller glyphs with more open leading.
     Note this is a LOOK, not density — 11.5px at 1.65 shows the same line
     count as the old 13.5px at default leading. */
  font-size: 11.5px;
}

#editor .cm-content {
  line-height: 1.65;
}

.preview-pane {
  min-width: 0;
  min-height: 0;
  /* The containing block for the boot loader; the hero card is already one. */
  position: relative;
}

#player {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: var(--bg);
}

.sandbox-footer {
  padding: 8px 18px;
  border-top: 1px solid var(--line);
  background: var(--bg-panel);
}

.sandbox-footer p {
  margin: 0;
  font-size: 0.78rem;
  color: var(--text-dim);
}

@media (max-width: 820px) {
  .sandbox-split {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 1fr;
  }

  .editor-pane {
    border-right: none;
    border-bottom: 1px solid var(--line);
  }
}

/* --------------------------------------------------------------------- ide */

/* Three panes: file sidebar | editor | preview. Reuses .editor-pane,
   #editor, .preview-pane, #player, .status-pill from the sandbox rules
   above. */
.ide-split {
  flex: 1;
  display: grid;
  grid-template-columns: 200px minmax(300px, 40%) 1fr;
  min-height: 0;
}

.file-pane {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--bg-panel);
  min-height: 0;
}

.file-pane-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
}

.file-pane-title {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--text-dim);
}

#new-file {
  font-family: var(--font-mono);
  font-size: 1rem;
  line-height: 1;
  color: var(--text);
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: 4px;
  width: 24px;
  height: 24px;
  cursor: pointer;
}

#new-file:hover {
  border-color: var(--cyan);
  color: var(--cyan);
}

.file-list {
  flex: 1;
  overflow: auto;
  min-height: 0;
}

.file-row {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 8px 0 12px;
}

.file-row.active {
  background: var(--bg-raised);
  box-shadow: inset 2px 0 0 var(--pink);
}

.file-name {
  display: flex;
  align-items: center;
  gap: 7px;
  flex: 1;
  min-width: 0;
  text-align: left;
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--text);
  background: none;
  border: 0;
  padding: 8px 0;
  cursor: pointer;
}

.file-icon {
  flex: none;
  height: 1.05em;
  width: auto;
  color: var(--cyan);
}

.file-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.file-row.active .file-name {
  color: var(--cyan);
}

.file-delete {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  line-height: 1;
  color: var(--text-dim);
  background: none;
  border: 0;
  padding: 2px 6px;
  cursor: pointer;
  border-radius: 4px;
}

.file-delete:hover {
  color: var(--red);
}

.editor-tab {
  padding: 7px 14px;
  border-bottom: 1px solid var(--line);
  background: var(--bg-panel);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  color: var(--cyan);
}

@media (max-width: 900px) {
  .ide-split {
    grid-template-columns: 150px 1fr;
    grid-template-rows: 1fr 300px;
  }
  .file-pane {
    grid-row: span 2;
  }
  .preview-pane {
    border-top: 1px solid var(--line);
  }
}

/* ------------------------------------------------------------------- docs */

.docs-layout {
  display: grid;
  grid-template-columns: 200px minmax(0, 780px);
  gap: 40px;
  max-width: 1060px;
  margin: 0 auto;
  padding: 32px 24px 80px;
}

.docs-nav {
  min-width: 0;
  position: sticky;
  top: 24px;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 0.85rem;
}

/* The manual is one long page, so its nav is grouped: a label names what the
   next few links are for. Each group is one element, so it stays intact when
   the nav becomes a scrolling row on narrow screens. The spacing lives on its
   own class — `.docs-nav` is shared with the API reference page. */
.docs-nav-grouped {
  gap: 18px;
}

.docs-nav-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.docs-nav-group-label {
  color: var(--text-dim);
  font-family: var(--font-display);
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.docs-nav a {
  white-space: nowrap;
  color: var(--text-dim);
  border-left: 2px solid var(--line);
  padding-left: 12px;
}

.docs-nav a:hover {
  color: var(--cyan);
  border-left-color: var(--cyan);
  text-decoration: none;
}

/* The one link that leaves the page. */
.docs-nav a.docs-nav-out {
  color: var(--cyan);
}

.docs-main h1 {
  font-family: var(--font-display);
  letter-spacing: 0.06em;
  font-size: 1.7rem;
}

.docs-main {
  min-width: 0;
}

.docs-main h2 {
  font-family: var(--font-display);
  letter-spacing: 0.05em;
  font-size: 1.15rem;
  margin-top: 48px;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  color: var(--text);
}

.docs-main h3 {
  font-size: 0.95rem;
  color: var(--amber);
  margin-top: 28px;
}

.docs-main p,
.docs-main li {
  color: var(--text-dim);
  font-size: 0.92rem;
}

.docs-main strong,
.docs-main em {
  color: var(--text);
}

.docs-main code {
  color: var(--green);
  font-size: 0.88em;
}

.docs-main pre {
  position: relative;
  background: var(--bg-panel);
  border: 1px solid var(--line);
  border-left: 3px solid var(--cyan);
  border-radius: 6px;
  padding: 16px 18px;
  overflow-x: auto;
  font-size: 0.83rem;
  line-height: 1.55;
  color: var(--text);
}

.docs-main pre.shell {
  border-left-color: var(--amber);
  color: var(--text-dim);
}

.docs-main table {
  border-collapse: collapse;
  width: 100%;
  font-size: 0.85rem;
}

.docs-main th,
.docs-main td {
  text-align: left;
  padding: 8px 12px;
  border: 1px solid var(--line);
  color: var(--text-dim);
  vertical-align: top;
}

.docs-main th {
  color: var(--text);
  background: var(--bg-panel);
  font-family: var(--font-display);
  font-size: 0.75rem;
  letter-spacing: 0.08em;
}

.try-button {
  position: absolute;
  top: 8px;
  right: 8px;
  font-family: var(--font-mono);
  font-weight: 600;
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--bg);
  background: var(--cyan);
  border-radius: 3px;
  padding: 4px 10px;
  text-transform: uppercase;
}

.try-button:hover {
  text-decoration: none;
  background: #5ce2ee;
}

.docs-footnote {
  margin-top: 32px;
  font-size: 0.8rem;
  font-style: italic;
}

.docs-callout {
  padding: 14px 16px;
  border: 1px solid rgba(65, 216, 230, 0.25);
  border-left: 3px solid var(--cyan);
  border-radius: 6px;
  background: rgba(65, 216, 230, 0.045);
}

/* ---------------------------------------------------------- API reference */

.api-layout {
  grid-template-columns: 200px minmax(0, 860px);
  max-width: 1140px;
}

.api-nav {
  max-height: calc(100vh - 48px);
  overflow-y: auto;
}

.api-nav-heading {
  margin-bottom: 4px;
  color: var(--text);
  font-family: var(--font-display);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.api-nav > div:last-child {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.api-nav-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.api-nav-group-label {
  color: rgba(65, 216, 230, 0.78);
  font-family: var(--font-display);
  font-size: 0.58rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

/* Categories are the second level of the nav, in the same color language as
   the content dividers — cyan names a group, pink names a category — muted so
   the column stays quiet. The hairline ties each topic's modules together. */
.api-nav-category {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-left: 10px;
  border-left: 1px solid var(--line);
}

.api-nav-category-label {
  color: rgba(232, 88, 184, 0.72);
  font-family: var(--font-display);
  font-size: 0.54rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.api-intro {
  position: relative;
  overflow: hidden;
  margin-bottom: 28px;
  padding: 28px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background:
    linear-gradient(135deg, rgba(65, 216, 230, 0.07), transparent 44%),
    linear-gradient(315deg, rgba(232, 88, 184, 0.055), transparent 48%),
    var(--bg-panel);
}

.api-intro::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 2px;
  background: linear-gradient(90deg, var(--cyan), #7664d8 58%, var(--pink));
}

.api-intro h1 {
  margin: 5px 0 10px;
}

.docs-main .api-kicker {
  margin: 0;
  color: var(--cyan);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.13em;
}

.api-intro > p:last-of-type {
  max-width: 68ch;
  margin-bottom: 22px;
}

.api-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 22px;
  align-items: center;
  color: var(--text-dim);
  font-size: 0.77rem;
}

.api-meta strong {
  color: var(--text);
}

/* The manual link plus the machine-readable format chips travel together as
   one cluster pushed to the right edge of the meta row. */
.api-meta-links {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
  margin-left: auto;
}

/* Raw-format links wear the page's pill vocabulary (see .api-kind) so they
   read as artifacts to fetch, not as another prose destination. */
.api-format {
  padding: 2px 9px;
  border: 1px solid rgba(65, 216, 230, 0.25);
  border-radius: 999px;
  color: var(--cyan);
  font-size: 0.62rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.api-format:hover {
  border-color: var(--cyan);
  background: rgba(65, 216, 230, 0.08);
}

.api-search {
  display: block;
  margin-bottom: 8px;
  color: var(--text);
  font-size: 0.76rem;
  font-weight: 600;
  letter-spacing: 0.04em;
}

.api-search-box {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 7px;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--bg-panel);
}

.api-search-box:focus-within {
  border-color: var(--cyan);
  box-shadow: 0 0 0 2px rgba(65, 216, 230, 0.1);
}

.api-search-box svg {
  width: 17px;
  height: 17px;
  flex: 0 0 auto;
  color: var(--text-dim);
}

.api-search-box input {
  min-width: 0;
  flex: 1;
  border: 0;
  outline: 0;
  color: var(--text);
  background: transparent;
  font: inherit;
  font-size: 0.85rem;
}

.api-search-box input::placeholder {
  color: #6c6685;
}

.api-search-box kbd {
  padding: 1px 7px;
  border: 1px solid var(--line);
  border-radius: 3px;
  color: var(--text-dim);
  background: var(--bg-raised);
  font: inherit;
  font-size: 0.7rem;
}

.docs-main .api-results {
  min-height: 1.5em;
  margin: 0 0 6px;
  color: var(--text-dim);
  font-size: 0.75rem;
}

.api-module {
  scroll-margin-top: 24px;
}

.api-module[hidden],
.api-item[hidden],
.api-group-divider[hidden],
.api-category-divider[hidden],
.api-nav-group[hidden],
.api-nav-category[hidden],
.api-nav a[hidden] {
  display: none;
}

/* The reference has two halves — what the runner provides, and what the
   language ships. This announces the boundary while scrolling; the module
   headings below it stay the page's real structure. */
.api-group-divider {
  margin: 46px 0 26px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}

.api-group-divider:first-child {
  margin-top: 0;
}

.api-group-divider h2 {
  margin: 0;
  color: var(--cyan);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

.api-group-divider p {
  max-width: 68ch;
  margin: 6px 0 0;
  color: var(--text-dim);
  font-size: 0.8rem;
}

/* And within a half, a category names the topic the next few modules share —
   one level down from the group, encoded by color role: cyan names a group,
   pink names a category. The rule fades out from the label's own pink. */
.api-category-divider {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 44px 0 16px;
}

.api-group-divider + .api-category-divider {
  margin-top: 26px;
}

.api-category-divider::after {
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, rgba(232, 88, 184, 0.35), var(--line) 30%);
  content: "";
}

.api-category-divider h2 {
  /* `.docs-main h2` gives every heading a rule above it; the divider draws its
     own, so this one resets that. */
  margin: 0;
  padding-top: 0;
  border-top: 0;
  color: var(--pink);
  font-family: var(--font-display);
  font-size: 0.64rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
}

/* The first module under a category belongs to it, so it drops the separating
   rule `.docs-main h2` gives every other module heading. */
.api-category-divider + .api-module > h2 {
  margin-top: 22px;
  padding-top: 0;
  border-top: 0;
}

.api-module > h2 {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 18px;
}

.api-module-count {
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.67rem;
  font-weight: 400;
  letter-spacing: 0.03em;
}

.api-module > p {
  max-width: 72ch;
}

.api-items {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
  margin-top: 20px;
}

.api-item {
  min-width: 0;
  scroll-margin-top: 24px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-left: 2px solid #4a416b;
  border-radius: 5px;
  background: rgba(22, 18, 38, 0.72);
  transition: border-color 120ms ease, background 120ms ease;
}

.api-item:hover,
.api-item:target {
  border-left-color: var(--cyan);
  background: var(--bg-panel);
}

.api-item h3 {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 0 0 10px;
}

.api-item h3 a {
  color: var(--amber);
}

.api-item h3 a:hover {
  color: var(--cyan);
  text-decoration: none;
}

.api-kind {
  padding: 1px 6px;
  border: 1px solid rgba(65, 216, 230, 0.25);
  border-radius: 999px;
  color: var(--cyan);
  font-size: 0.58rem;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.api-kind-type {
  border-color: rgba(232, 88, 184, 0.28);
  color: var(--pink);
}

.docs-main pre.api-declaration {
  margin: 0;
  padding: 10px 12px;
  border-left-width: 2px;
  border-left-color: var(--amber);
  background: var(--bg);
  font-size: 0.78rem;
}

.api-item > p:last-child {
  margin-bottom: 0;
}

@media (max-width: 720px) {
  .docs-page .site-header {
    padding-inline: 16px;
  }

  .docs-page .site-nav {
    order: 3;
    flex-basis: 100%;
    justify-content: flex-start;
    gap: 16px;
    margin-left: 0;
    overflow-x: auto;
  }

  .docs-layout {
    grid-template-columns: 1fr;
    gap: 22px;
    padding: 22px 16px 60px;
  }

  .docs-nav {
    position: static;
    flex-direction: row;
    flex-wrap: nowrap;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 6px;
  }

  /* Wide enough that a group's label reads as belonging to the links after
     it, not the link before it — and no wider, since every pixel here is one
     less link reachable in the scrolling row. */
  .docs-nav-grouped {
    gap: 20px;
  }

  /* One scrolling row, so a group inlines its label with its own links. */
  .docs-nav-group {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    gap: 8px;
  }

  .docs-nav-group-label {
    flex: 0 0 auto;
  }

  .api-nav {
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    max-height: none;
    overflow: visible;
  }

  .api-nav-heading {
    flex-basis: auto;
  }

  .api-nav > div:last-child {
    width: 100%;
    flex-direction: row;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 6px;
  }

  /* On narrow screens the nav is one scrolling chip row, so each group and
     category inlines its label with its own chips instead of stacking. */
  .api-nav-group,
  .api-nav-category {
    flex: 0 0 auto;
    flex-direction: row;
    align-items: center;
    gap: 8px;
  }

  .api-nav-category {
    padding-left: 8px;
  }

  .api-nav-group-label,
  .api-nav-category-label {
    flex: 0 0 auto;
  }

  .api-category-divider {
    margin: 30px 0 14px;
  }

  .api-nav a {
    flex: 0 0 auto;
    padding: 3px 9px;
    border: 1px solid var(--line);
    border-radius: 999px;
  }

  .api-intro {
    padding: 20px;
  }

  .api-intro h1 {
    font-size: 1.45rem;
  }

  /* Narrow: the cluster drops to its own full-width row rather than pushing
     right, but the chips stay inline next to each other. */
  .api-meta-links {
    width: 100%;
    margin-left: 0;
  }

  .api-search-box kbd {
    display: none;
  }

  .api-item {
    padding: 14px;
  }

  .docs-main table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
  }
}

/* ---- Status bar (sandbox + IDE): Problems / Output panels ---------------- */

.statusbar {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--line);
  background: var(--bg-panel);
  font-family: var(--font-mono);
  /* Positioned, like the headers above: as a static block the upward shadow
     would paint beneath the preview pane / editor and never show. */
  position: relative;
  z-index: 20;
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3);
}

.statusbar-strip {
  display: flex;
  gap: 2px;
  padding: 0 8px;
  /* The Vim mode segment can push a narrow strip past its width; scroll the
     strip rather than wrapping tabs onto a second row. */
  overflow-x: auto;
}

.statusbar-tab {
  appearance: none;
  border: none;
  background: none;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  padding: 4px 10px;
  white-space: nowrap;
  cursor: pointer;
}

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

/* Inset, like the keys toggle — the strip clips overflow (it scrolls when
   too narrow), which would swallow the UA's outset focus ring. */
.statusbar-tab:focus-visible {
  outline: 1px solid rgba(65, 216, 230, 0.65);
  outline-offset: -2px;
}

.statusbar-tab.active {
  color: var(--cyan);
  box-shadow: inset 0 2px 0 var(--cyan);
}

/* The Vim mode block, pinned to the strip's right corner — the last child,
   after the vim toggle (whose auto margin pushes the pair right). The
   adapter renders into it imperatively: the clickable --MODE-- readout,
   pending keys, and the `:` / `/` command input. The per-mode background
   blocks live with the `.statusbar-vim[data-vim-mode]` rules above. */
.statusbar-vim {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  max-width: 40%;
  padding: 0 10px;
  /* Cancel the strip's right padding: the mode block sits truly flush with
     the strip's corner, like a real airline segment. */
  margin-right: -8px;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  /* On a strip too narrow for everything, the readout yields — truncating
     rather than wrapping or pushing the vim toggle off-screen. */
  white-space: nowrap;
  overflow: hidden;
  min-width: 0;
}

.statusbar-vim[hidden] {
  display: none;
}

/* The adapter still renders into the host — pending keys and the `:` / `/`
   dialog input stay — but its own `--MODE--` button and flex spacer are
   hidden: the segment's React label carries the mode without the hyphens.
   Both adapter spans are identified by their inline styles (cursor:pointer
   on the click-to-Esc button, flex:1 on the spacer); dialogs are divs and
   the pending-keys span is unstyled, so neither is caught. Coupled to the
   pinned @replit/codemirror-vim internals on purpose. */
.statusbar-vim-host {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
}

.statusbar-vim-host > span[style*="cursor"],
.statusbar-vim-host > span[style*="flex"] {
  display: none;
}

/* While a `:` / `/` dialog occupies the host, the dialog IS the readout —
   drop the mode label instead of stacking NORMAL next to the command line. */
.statusbar-vim:has(.statusbar-vim-host > div) > .statusbar-vim-mode {
  display: none;
}

.statusbar-vim input {
  min-width: 0;
  border: 0;
  outline: none;
  background: none;
  /* Inherit the mode block's dark ink — the `:` dialog sits on a colored
     background. */
  color: inherit;
  font: inherit;
}

.statusbar-tab.has-problems {
  color: var(--red);
}

.statusbar-tab.has-problems.active {
  color: var(--red);
  box-shadow: inset 0 2px 0 var(--red);
}

@media (pointer: coarse) {
  .statusbar-strip .editor-keybindings-toggle {
    min-height: 28px;
    padding-right: 10px;
    padding-left: 10px;
  }
}

.statusbar-panel {
  height: 180px;
  border-bottom: 1px solid var(--line);
  overflow: hidden;
  display: flex;
}

/* The author display:flex above would override the `hidden` attribute's UA
   display:none — without this, the closed panel still eats 180px and the
   editor/preview never get the full height back. */
.statusbar-panel[hidden] {
  display: none;
}

.statusbar-list {
  flex: 1;
  overflow-y: auto;
  padding: 6px 8px;
  font-size: 0.78rem;
  line-height: 1.5;
}

.statusbar-empty {
  color: var(--text-dim);
  font-style: italic;
}

.problem-row {
  appearance: none;
  border: none;
  background: none;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  display: flex;
  gap: 8px;
  align-items: baseline;
  width: 100%;
  text-align: left;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: 3px;
}

.problem-row:hover {
  background: var(--bg-raised);
}

.problem-icon.severity-error {
  color: var(--red);
}

.problem-icon.severity-warning {
  color: var(--amber);
}

.problem-message {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.problem-loc {
  color: var(--text-dim);
  font-size: 0.72rem;
  white-space: nowrap;
}

.output-line {
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-dim);
}

.output-preamble {
  color: #6c6685;
  font-size: 0.9em;
}

.output-line.output-warn {
  color: var(--amber);
}

.output-line.output-error {
  color: var(--red);
}

.output-line.output-info {
  color: var(--text);
}

.exec-row {
  appearance: none;
  border: none;
  background: none;
  color: var(--text-dim);
  font-family: var(--font-mono);
  font-size: 0.78rem;
  display: flex;
  align-items: baseline;
  gap: 6px;
  width: 100%;
  text-align: left;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: 3px;
}

/* The row's twisty column — a fixed width so labels align whether or not the
   execution returned something with structure to open. */
.exec-tw {
  width: 0.7em;
  flex: none;
  color: var(--text-dim);
}

/* The execution itself — never wrapped: a long provenance (an `update`
   carrying a big message) elides, so a row stays one line. */
.exec-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* What the call RETURNED, one line — the summary the tree below expands. It
   yields the row's width first: which execution this is matters more than
   what it answered. */
.exec-result {
  color: var(--text-dim);
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.exec-row.selected .exec-result {
  opacity: 0.95;
}

/* The expanded value tree, indented under its row and scrolling on its own —
   a world snapshot must not push the tab's other rows out of reach. */
.exec-tree {
  margin: 0 4px 4px 1.3em;
  max-height: 220px;
  overflow: auto;
}

.exec-row:hover {
  background: var(--bg-raised);
}

.exec-row.selected {
  color: var(--cyan);
}

/* ==== the boot loader ====================================================
   The Functor mark, milled out of nine copies of itself along Z, spinning
   over a surface that has not booted yet: the hero card, and the sandbox /
   IDE preview panes. It is STATIC markup in each page's shell — it paints on
   the first frame, before the editor bundle and the wasm runtime have
   arrived, which is the whole point. The only JS is one class toggle:

     document.querySelector('[data-fn-boot]')?.classList.add('is-done');

   which runs the 620ms evaporate and parks the node at visibility:hidden.
   The overlay is pointer-events:none from birth (not just once dismissed),
   so a fast click — or an e2e — mid-boot is never intercepted by it. */

.fn-boot {
  position: absolute;
  inset: 0;
  /* Enough to clear the iframe and the hero's status dot (z-index 2), and no
     more: .hero-card opens no stacking context, so a bigger value would paint
     the loader over the sticky site header when the hero scrolls under it. */
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  /* Sits on whatever it covers: a hair of the panel tone plus a radial lift
     so the mark reads on both #0d0221 (hero card) and --bg (preview pane). */
  background:
    radial-gradient(120% 90% at 50% 42%, rgba(65, 216, 230, 0.07), transparent 62%),
    var(--bg-panel);
  /* Purely decorative: it never takes a click, at any point in its life. */
  pointer-events: none;
  /* cqi sizing for the wordmark below scales it from the hero card down to a
     narrow preview pane. */
  container-type: inline-size;
  /* Safety valve. Dismissal is the player's readiness handshake, and a runtime
     that never boots (a 404 on the wasm, no WebGL2) would otherwise leave this
     claiming progress forever — worse than the blank pane it replaced. After
     20s it retires and lets the bare surface show through. `.is-done` replaces
     this animation outright, so a normal boot never reaches it. */
  animation: fn-boot-give-up 600ms ease-in 20s forwards;
}

/* On the hero the loader covers the whole card, and the mini-editor mounts as
   soon as hero.fun is fetched — well before the player's handshake. Keep it out
   of the hit region (and out of the tab order) while it is occluded, so nobody
   types into an editor they cannot see. */
.fn-boot:not(.is-done) ~ .hero-editor {
  visibility: hidden;
}

/* The <symbol> sprite each slice instantiates — never rendered itself. */
.fn-boot-defs {
  display: none;
}

/* Faint horizon rule under the mark — one line, keeps the composition from
   floating. The site's cyan → pink gradient rule at very low alpha. */
.fn-boot::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: min(200px, 54%);
  height: 1px;
  transform: translate(-50%, 122px);
  background: linear-gradient(
    90deg,
    transparent,
    rgba(65, 216, 230, 0.34),
    rgba(232, 88, 184, 0.24),
    transparent
  );
  animation: fn-boot-rule 2.6s ease-in-out infinite;
}

/* The faded FUNCTOR wordmark behind the mark — the brand name stays readable
   while the surface loads. Same face as the header wordmark, as a watermark.
   It sits behind the LOWER half of the mark: the F's bottom is a narrow stem,
   so only one letter is grazed (at 46% the wide head of the F parks exactly
   on the "C"). Flex items stack with plain z-index, so the stage and label
   sit above it without any extra positioning. */
.fn-boot-word {
  position: absolute;
  left: 50%;
  top: calc(50% + -22px);
  transform: translate(-50%, -50%);
  z-index: 0;
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(20px, 12cqi, 56px);
  letter-spacing: 0.34em;
  text-indent: 0.34em; /* recentre the trailing letter-space */
  white-space: nowrap;
  color: var(--text);
  opacity: 0.09;
  user-select: none;
}

.fn-boot-stage,
.fn-boot-label {
  z-index: 1;
}

.fn-boot-stage {
  perspective: 620px;
  width: 96px;
  height: 132px;
  animation: fn-boot-bob 3.4s ease-in-out infinite;
}

.fn-boot-spin {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  animation: fn-boot-spin 3.6s linear infinite;
}

/* Each slice is one copy of the mark, pushed back along Z. Nine of them read
   as a milled solid rather than a flipping card — the whole point. */
.fn-boot-slice {
  position: absolute;
  inset: 0;
  display: block;
  transform: translateZ(calc((4 - var(--i)) * 2.1px));
  /* The front face is full cyan; the tail falls off toward a deep pink-violet
     so the extrusion reads as a shadowed side wall, not a stack of stickers. */
  color: color-mix(in oklab, var(--cyan), #5b2470 calc(var(--i) * 12%));
  opacity: calc(1 - var(--i) * 0.02);
}

.fn-boot-slice svg {
  width: 100%;
  height: 100%;
  display: block;
  fill: currentColor;
}

/* Only the front slice carries the bloom — glowing all nine smears the form. */
.fn-boot-slice[style*="--i:0"] svg {
  filter: drop-shadow(0 0 12px rgba(65, 216, 230, 0.55))
    drop-shadow(0 0 30px rgba(65, 216, 230, 0.22));
}

.fn-boot-label {
  margin: 0;
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.42em;
  text-indent: 0.42em; /* offset the trailing letter-space so it stays centred */
  text-transform: uppercase;
  color: var(--text-dim);
  animation: fn-boot-label 2.6s ease-in-out infinite;
}

/* The dots cycle "" → "." → ".." → "..." without reflowing the label. */
.fn-boot-dots {
  display: inline-block;
  width: 2.4em;
  text-align: left;
  text-indent: 0;
}

.fn-boot-dots::before {
  content: "";
  animation: fn-boot-dots 1.6s steps(1) infinite;
}

/* -- the resting (loading) state ---------------------------------------- */

@keyframes fn-boot-spin {
  from {
    transform: rotateX(-11deg) rotateY(0deg);
  }
  to {
    transform: rotateX(-11deg) rotateY(360deg);
  }
}

@keyframes fn-boot-bob {
  0%,
  100% {
    transform: translateY(-3px);
  }
  50% {
    transform: translateY(3px);
  }
}

@keyframes fn-boot-label {
  0%,
  100% {
    opacity: 0.55;
  }
  50% {
    opacity: 1;
  }
}

@keyframes fn-boot-dots {
  0% {
    content: "";
  }
  25% {
    content: ".";
  }
  50% {
    content: "..";
  }
  75% {
    content: "...";
  }
}

@keyframes fn-boot-rule {
  0%,
  100% {
    opacity: 0.35;
    transform: translate(-50%, 122px) scaleX(0.86);
  }
  50% {
    opacity: 1;
    transform: translate(-50%, 122px) scaleX(1);
  }
}

/* -- the exit: fade + evaporate ------------------------------------------
   Layered so it reads as the solid coming apart, not just a div fading:
   (1) the slices spread along Z and drift up, staggered back-to-front;
   (2) the whole loader blurs, lifts and over-scales slightly;
   (3) the horizon rule and the label retire a beat earlier than the mark.
   animation-fill-mode: forwards holds the final visibility:hidden, so the
   node is parked for good; removing it afterwards is never required. */

.fn-boot.is-done {
  animation: fn-boot-out 620ms cubic-bezier(0.32, 0, 0.24, 1) forwards;
}

.fn-boot.is-done .fn-boot-spin {
  animation-play-state: paused;
}

.fn-boot.is-done .fn-boot-stage {
  animation: none;
}

.fn-boot.is-done::after {
  animation: fn-boot-rule-out 260ms ease-in forwards;
}

.fn-boot.is-done .fn-boot-label {
  animation: fn-boot-label-out 320ms ease-in forwards;
}

.fn-boot.is-done .fn-boot-word {
  animation: fn-boot-word-out 420ms ease-in forwards;
}

.fn-boot.is-done .fn-boot-slice {
  animation: fn-boot-slice-out 620ms cubic-bezier(0.32, 0, 0.24, 1) forwards;
  /* The tail evaporates first and the lit cyan face last, while the whole
     stack stretches away from the viewer. */
  animation-delay: calc((8 - var(--i)) * 22ms);
}

@keyframes fn-boot-out {
  0% {
    opacity: 1;
    filter: blur(0) saturate(1);
    transform: translateY(0) scale(1);
  }
  35% {
    opacity: 0.92;
    filter: blur(1.5px) saturate(1.15);
  }
  100% {
    opacity: 0;
    filter: blur(13px) saturate(1.25);
    transform: translateY(-14px) scale(1.055);
    visibility: hidden;
  }
}

@keyframes fn-boot-slice-out {
  0% {
    opacity: calc(1 - var(--i) * 0.02);
  }
  100% {
    opacity: 0;
    transform: translateZ(calc((4 - var(--i)) * 2.1px - var(--i) * 7px))
      translateY(calc(var(--i) * -2.6px - 8px)) scale(1.04);
  }
}

@keyframes fn-boot-label-out {
  to {
    opacity: 0;
    transform: translateY(9px);
    letter-spacing: 0.58em;
  }
}

@keyframes fn-boot-word-out {
  to {
    opacity: 0;
    letter-spacing: 0.5em;
  }
}

@keyframes fn-boot-rule-out {
  to {
    opacity: 0;
    transform: translate(-50%, 122px) scaleX(0.2);
  }
}

/* -- reduced motion ------------------------------------------------------ */

@media (prefers-reduced-motion: reduce) {
  .fn-boot-spin {
    /* Held at a 3/4 angle so the extrusion still reads. The pulse must animate
       the STAGE, not this element: opacity on a preserve-3d node flattens it
       and the shadowed back slice paints over the lit face. */
    transform: rotateX(-11deg) rotateY(-32deg);
    animation: none;
  }

  .fn-boot-stage {
    animation: fn-boot-pulse 2.4s ease-in-out infinite;
  }

  .fn-boot::after,
  .fn-boot-label {
    animation: none;
  }

  .fn-boot.is-done {
    animation: fn-boot-fade 300ms linear forwards;
  }

  .fn-boot.is-done .fn-boot-slice,
  .fn-boot.is-done .fn-boot-label,
  .fn-boot.is-done .fn-boot-word,
  .fn-boot.is-done::after {
    animation: none;
  }
}

@keyframes fn-boot-pulse {
  0%,
  100% {
    opacity: 0.6;
  }
  50% {
    opacity: 1;
  }
}

@keyframes fn-boot-give-up {
  to {
    opacity: 0;
    visibility: hidden;
  }
}

@keyframes fn-boot-fade {
  to {
    opacity: 0;
    visibility: hidden;
  }
}

/* ---------------------------------------------------------- mp pane grid */

/* Multiplayer prototype (sandbox ?clients=N > 1): the preview column becomes
   chrono bar | tab strip (tabs view) | pane grid. The chrono bar governs only
   the game side — the editor keeps its full height. Player identity comes
   from --pc (set per pane from --scrub-p1..p4). */

.preview-pane.mp {
  display: flex;
  flex-direction: column;
  background:
    radial-gradient(120% 80% at 50% 0%, #171029 0%, var(--bg) 60%);
}

.mp-chrono {
  display: flex;
  align-items: center;
  gap: 10px;
  /* The label lives inside the rail now, so the padding is symmetric and
     the transport row is truly centered. */
  padding: 12px;
  font: 12px/1 var(--font-mono);
  color: var(--text);
  background: rgba(30, 24, 51, 0.92);
  border-bottom: 1px solid var(--line);
  box-shadow:
    0 4px 12px rgba(0, 0, 0, 0.5),
    0 14px 36px rgba(0, 0, 0, 0.38);
  position: relative;
  /* Above the pane grid (pane headers are z-index 6 for their link menus), so
     the debug drawer and event tooltip never clip under the window chrome. */
  z-index: 12;
}

.mp-debug {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px 14px;
  padding: 9px 12px;
  color: var(--text);
  background: rgba(24, 18, 42, 0.96);
  border-bottom: 1px solid var(--line);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.3);
  font: 12px/1 var(--font-mono);
  position: relative;
  z-index: 11;
}

.mp-debug[hidden] {
  display: none;
}

.mp-debug strong {
  color: var(--cyan);
  letter-spacing: 0.04em;
}

.mp-debug label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
}

.mp-debug label[hidden] {
  display: none;
}

.mp-debug select,
.mp-debug button {
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 4px 6px;
  color: var(--text);
  background: rgba(65, 216, 230, 0.1);
  font: 12px/1 var(--font-mono);
}

.mp-debug button {
  cursor: pointer;
}

.mp-debug button:hover,
.mp-debug select:hover {
  border-color: var(--cyan);
}

.mp-debug input {
  accent-color: var(--cyan);
}

.mp-debug input[type="range"] {
  width: 100px;
}

.mp-debug output {
  min-width: 42px;
  color: var(--text);
}

.mp-chrono.dormant .mp-rail,
.mp-chrono.dormant .mp-rail-label {
  opacity: 0.35;
}

.mp-sbtn {
  font: 13px/1 var(--font-mono);
  color: var(--text);
  cursor: pointer;
  background: rgba(65, 216, 230, 0.1);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 5px 8px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
  transition: box-shadow 0.12s ease, border-color 0.12s ease;
}

.mp-sbtn:hover {
  border-color: var(--cyan);
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.5);
}

#mp-camera.on {
  border-color: var(--cyan);
  box-shadow: 0 0 0 1px var(--cyan), 0 2px 10px rgba(65, 216, 230, 0.35);
}

.mp-rail {
  position: relative;
  flex: 1;
  min-width: 80px;
  height: 30px;
  cursor: ew-resize;
  touch-action: none;
  user-select: none;
}

.mp-track,
.mp-recorded,
.mp-played {
  position: absolute;
  top: 12px;
  height: 6px;
  border-radius: 3px;
}

.mp-track {
  left: 0;
  right: 0;
  background: rgba(155, 148, 179, 0.18);
}

.mp-recorded {
  background: rgba(65, 216, 230, 0.3);
}

/* Striped "history unavailable" regions of a frozen viewport (a reload that
   could not carry its snapshots): the same hatch as the in-frame scrubber. */
.mp-unavailable {
  position: absolute;
  top: 12px;
  left: 0;
  height: 6px;
  width: 0;
  border-radius: 3px;
  background: repeating-linear-gradient(
    110deg,
    rgba(155, 148, 179, 0.3) 0 3px,
    rgba(8, 7, 14, 0.72) 3px 12px
  );
  pointer-events: none;
}

.mp-played {
  left: 0;
  background: var(--cyan);
  opacity: 0.62;
  box-shadow: 0 0 10px rgba(65, 216, 230, 0.45);
}

/* Event markers span the full rail (the mockup's .evt ticks): thin amber input
   lines, slightly heavier violet reload / red error lines, each with a soft
   glow so they read through the played bar. */
.mp-evt {
  position: absolute;
  top: 3px;
  height: 24px;
  transform: translateX(-50%);
  border-radius: 1px;
  pointer-events: auto;
}

/* A generous invisible hit target around the 2–3px tick (the in-frame bar
   used an 18px-wide transparent rect for the same reason). */
.mp-evt::before {
  content: "";
  position: absolute;
  left: -8px;
  right: -8px;
  top: -3px;
  bottom: -3px;
}

.mp-evt.input {
  background: #ffd166;
  width: 2px;
  opacity: 0.75;
  box-shadow: 0 0 5px rgba(255, 209, 102, 0.5);
}

.mp-evt.reload {
  background: #b994ff;
  width: 3px;
  box-shadow: 0 0 6px rgba(185, 148, 255, 0.6);
}

.mp-evt.err {
  background: #ff6b7d;
  width: 3px;
  box-shadow: 0 0 6px rgba(255, 107, 125, 0.6);
}

.mp-playhead {
  position: absolute;
  top: 15px;
  width: 14px;
  height: 20px;
  border-radius: 4px;
  transform: translate(-50%, -50%);
  background: var(--cyan);
  border: 1px solid #b9f8ff;
  box-shadow: 0 0 12px rgba(65, 216, 230, 0.6);
  pointer-events: none;
}

.mp-viewseg {
  display: flex;
  border: 1px solid var(--line);
  border-radius: 5px;
  overflow: hidden;
}

.mp-viewseg button {
  font: 10px/1 var(--font-mono);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: transparent;
  border: 0;
  border-right: 1px solid var(--line);
  padding: 6px 9px;
  cursor: pointer;
  transition: background 0.14s, color 0.14s;
}

.mp-viewseg button:last-child {
  border-right: 0;
}

.mp-viewseg button:hover {
  background: rgba(65, 216, 230, 0.07);
  color: var(--text);
}

.mp-viewseg button[aria-pressed="true"] {
  background: rgba(65, 216, 230, 0.14);
  color: var(--cyan);
}

/* ---- tab strip (tabs view) ---- */

.mp-tabs {
  display: flex;
  gap: 6px;
  padding: 8px 10px 0;
}

.mp-tab {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font: 10px/1 var(--font-mono);
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--text-dim);
  cursor: pointer;
  background: color-mix(in srgb, var(--pc) 6%, var(--bg-raised));
  border: 1px solid color-mix(in srgb, var(--pc) 28%, var(--line));
  border-radius: 5px 5px 0 0;
  border-bottom: 0;
  padding: 7px 12px;
  filter: saturate(0.45);
  transition: filter 0.18s;
}

.mp-tab.focus {
  color: var(--pc);
  background: color-mix(in srgb, var(--pc) 14%, var(--bg-raised));
  border-color: var(--pc);
  filter: none;
  box-shadow: 0 -2px 14px color-mix(in srgb, var(--pc) 25%, transparent);
}

/* ---- the grid ---- */

.mp-grid {
  flex: 1;
  min-height: 0;
  /* The header row the letterboxing views pin, and subtract to size the body
     (see the aspect block). */
  --mp-hd-h: 22px;
  display: grid;
  gap: 10px;
  padding: 10px;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  grid-auto-rows: minmax(0, 1fr);
}

/* TILED is one row, and says so: a flex row rather than auto-fit columns, which
   wrapped into a second row as soon as a fourth cell existed (three clients and
   an authority) — the one thing "tiled" promises not to do. */
.mp-grid[data-view="tiled"] {
  display: flex;
}

.mp-grid[data-view="tiled"] > * {
  flex: 1 1 0;
  min-width: 0;
}

/* ---- cells and cards ----

   Every pane sits in a CELL (mp-panes.ts) and draws itself as a CARD inside
   it. Tiled and tabs hand the whole cell to the card; GRID and NETWORK
   letterbox it (Addendum 5b.2) — see the aspect block below. Splitting the
   two is what lets the layout place a slot and the card still read as a game
   window rather than a stretched rectangle. */

.mp-cell {
  display: flex;
  min-width: 0;
  min-height: 0;
}

.mp-cell > .mp-pane {
  flex: 1;
  min-width: 0;
}

/* An open link menu drops out of its header and must paint over the pane
   beside it — but a size container (the aspect cells below) is a stacking
   context, so the cell has to be raised, not just the menu. */
.mp-cell:has(.mp-link-menu:not([hidden])) {
  z-index: 20;
}

.mp-grid[data-view="tabs"] {
  grid-template-columns: 1fr;
  padding-top: 0;
}

.mp-grid[data-view="tabs"] .mp-cell:not(.focus) {
  display: none;
}

/* ---- aspect-true cards: a game window reads as a game window ----

   Addendum 5b.2. In GRID and NETWORK a pane's BODY letterboxes to 16:9 and
   the card floats in its cell, instead of stretching to whatever shape the
   track handed it (which is why network's full-height clients read as absurdly
   tall towers). Shrink to fit: the card takes the cell's width, unless the
   cell is too short for that width's 16:9 body, in which case the height wins
   and the card narrows. Whatever slack is left over becomes margin around a
   centred card.

   The cell is a SIZE CONTAINER purely so that "too short" is expressible: the
   card's width is the smaller of the cell's width and the width whose body
   fits the cell's height, and only container units can compare the two axes.
   The header is a fixed row so that subtraction is exact — its content already
   clips by container query, so pinning its height costs nothing.

   It is a WIDE-STAGE rule. Below the breakpoint every view collapses to a
   one-column stack of short, full-width cells, so "shrink to fit" is always
   height-driven there and each card would letterbox down to a third of the
   column — a stack of stamps in a gutter of empty space. The narrow stack
   keeps stretching, as it did before. */

@media (min-width: 1281px) {
  .mp-grid[data-view="grid"] .mp-cell,
  .mp-grid[data-view="network"] .mp-cell {
    container-type: size;
  }

  .mp-grid[data-view="grid"] .mp-cell > .mp-pane,
  .mp-grid[data-view="network"] .mp-cell > .mp-pane {
    flex: none;
    /* Auto margins centre the card in its cell on both axes. */
    margin: auto;
    /* The card's own 1px borders are outside the body the ratio governs, so
       the height budget has to give them back — otherwise the card lands a
       fraction of a pixel taller than the cell it is fitting into. */
    width: min(100cqw, calc((100cqh - var(--mp-hd-h) - 2px) * 16 / 9 + 2px));
  }

  /* GRID's slack goes OUTWARD. Left centred, it would pool between the client
     row and the authority strip and read as a seam splitting the layout in
     two; so the clients sit at the foot of their cells and the strip at the
     head of its band, the two meet, and what is left over collects at the
     outside of the stage where it is just margin. (Horizontally they stay
     centred. NETWORK keeps both axes centred — there the cards are a diagram,
     and the space around each one is what the wires cross.) */
  .mp-grid[data-view="grid"] .mp-cell:not(.server) > .mp-pane {
    margin-block: auto 0;
  }

  .mp-grid[data-view="grid"] .mp-cell.server > .mp-pane {
    margin-block: 0 auto;
  }

  .mp-grid[data-view="grid"] .mp-pane-hd,
  .mp-grid[data-view="network"] .mp-pane-hd {
    height: var(--mp-hd-h);
  }

  .mp-grid[data-view="grid"] .mp-pane-body,
  .mp-grid[data-view="network"] .mp-pane-body {
    flex: none;
    width: 100%;
    aspect-ratio: 16 / 9;
  }

  /* The error strip is a THIRD row the aspect arithmetic knows nothing about
     (its height depends on how long the message is), so where the card is
     sized to fit it overlays the foot of the body instead of growing the card
     out of its cell. It keeps its own backdrop so the message stays readable
     over a game. */
  .mp-grid[data-view="grid"] .mp-pane-err,
  .mp-grid[data-view="network"] .mp-pane-err {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    background: color-mix(in srgb, var(--bg-panel) 88%, rgba(242, 99, 127, 0.5));
  }
}

/* ---- grid view: client cells over a server strip ----

   Tiled lays every pane out in one row, which at three clients plus an
   authority is four slivers. Grid wraps the CLIENTS into two columns and hands
   the server the full bottom row (design-multiplayer-ide-frontend.md §5): the
   players are peers of equal size, and the one full-width band belongs to the
   authority — the SHAPE says which is which before you read a label.

   The strip stays a thumbnail at every count, expressed as spans over one set
   of equal implicit rows rather than per-count track sizes: a client takes
   three rows (two when a third client adds a second client row), the server
   one.

   An odd LAST client under an authority (three of them: 2 + 1) keeps its
   half-width cell instead of stretching: it has peers on the row above, and
   equal cells are what makes two players comparable at a glance — the notch
   beside it is small, because the server strip closes the layout below.

   The two cases where a client would otherwise sit beside a HOLE rather than a
   notch stretch instead: a lone client (no peer to match), and an odd last
   client in a single-role example (no strip below it, so the empty cell is a
   whole quadrant).

   Each card then letterboxes inside the cell it is given, centred across it
   and anchored towards the layout's middle (see the aspect block); the cells
   themselves are what this section places. */

.mp-grid[data-view="grid"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* Client cells take three row-shares to the server strip's one, so the
   authority reads as a STRIP under the gameplay — never a peer-sized pane. */
.mp-grid[data-view="grid"] .mp-cell {
  grid-row: span 3;
}

/* At three clients the second client row would compound that share (3+3+1
   leaves the strip a 1/7 sliver), so each client backs off to two rows and
   the strip keeps the proportion it has at every other count. */
.mp-grid[data-view="grid"][data-clients="3"] .mp-cell {
  grid-row: span 2;
}

/* A lone client has no peer to match, so it takes the whole row. */
.mp-grid[data-view="grid"][data-clients="1"] .mp-cell:not(.server) {
  grid-column: 1 / -1;
}

/* Three clients and no authority: nothing closes the bottom row (no server
   strip), so the odd last client stretches rather than leaving a whole empty
   quadrant. */
.mp-grid[data-view="grid"][data-clients="3"][data-server="no"] .mp-cell:nth-child(3) {
  grid-column: 1 / -1;
}

.mp-grid[data-view="grid"] .mp-cell.server {
  grid-column: 1 / -1;
  grid-row: span 1;
}

/* ---- network view: the compact hub, the clients around it ----

   design-multiplayer-ide-frontend.md, Addendum 5 + 5a.2. Grid and tiled answer
   "what is each pane running"; NETWORK answers "who is talking to whom" — so
   the authority moves to the middle of the stage and the players sit around it,
   with a measured wire per link (mp-network.ts) drawn behind the cards.

   The SERVER IS THE SMALLEST WINDOW here, inverting v1. The gameplay is on the
   clients, and a hub that outsized them spent the stage on a pane nobody
   watches; what says "hub" is that every wire ends at it, not its acreage. So
   the authority is a compact NODE — it opts out of its cell's stretch and takes
   a clamped size instead — and the clients keep the real estate.

   The arrangement is a fixed eight-row track set, so a card's placement is a
   span rather than a computed position — the same class-change-only rule the
   other views follow, and the wires re-measure from wherever the cards land.

   The clients are STAGGERED, half the stage tall each (Addendum 5b.3, and the
   approved mockup's proportions): client 1 in the upper left, client 2 in the
   lower right, the hub in the channel between them. Two things fall out of
   that. The cards are 16:9 windows with the slack around them rather than
   towers filling a full-height column — the tall-client complaint 5b.2 names.
   And every wire leaves at its own angle, because no two cards share a
   centreline.

     1 client  · the spoke upper left, the hub across in the far column.
     2 clients · upper left and lower right, the hub in the middle channel.
     3 clients · the same two, the third filling the empty upper right.

   The middle channel is the HUB's alone. A client parked in it would be a
   gameplay pane at hub scale — the inversion 5a.2 exists to prevent — so the
   third client takes the quadrant its two peers leave open instead, at their
   size. Client 1 holds column 1, rows 1–4, and client 2 column 3, rows 5–8, at
   EVERY count: adding or dropping a client never moves a pane you are playing
   in. */

.mp-grid[data-view="network"] {
  /* The middle track is a channel, not a pane slot: it holds the hub (which is
     smaller than the track) and the wires that cross it. Its floor is the
     hub's own minimum — a channel narrower than that would squeeze the node
     into an unreadable thumbnail. */
  grid-template-columns: minmax(0, 1fr) minmax(132px, 0.52fr) minmax(0, 1fr);
  grid-template-rows: repeat(8, minmax(0, 1fr));
  gap: 8px 44px;
  padding: 12px 18px;
}

.mp-grid[data-view="network"] .mp-cell:not(.server):nth-child(1) {
  grid-column: 1;
  grid-row: 1 / span 4;
}

.mp-grid[data-view="network"] .mp-cell:not(.server):nth-child(2) {
  grid-column: 3;
  grid-row: 5 / span 4;
}

.mp-grid[data-view="network"] .mp-cell:not(.server):nth-child(3) {
  grid-column: 3;
  grid-row: 1 / span 4;
}

/* The hub: a compact node in the middle channel, level with the gap the two
   staggered clients leave. It is aspect-true like every other card — it is a
   running pane, not an icon, so it keeps a floor as well as a cap: capped it
   stays the smallest window on the stage, floored it stays a window at all. */
.mp-grid[data-view="network"] .mp-cell.server {
  grid-column: 2;
  grid-row: 3 / span 3;
}

.mp-grid[data-view="network"] .mp-cell.server > .mp-pane {
  min-width: 128px;
  max-width: 232px;
}

/* A lone client has no second flank, so the hub crosses to the far column and
   the wire gets the whole stage to run across. */
.mp-grid[data-view="network"][data-clients="1"] .mp-cell.server {
  grid-column: 3;
}

/* ---- the stage: the grid, its legend, and the wire layer under the cards ---- */

.mp-stage {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* The containing block the wire layer measures against. */
  position: relative;
}

.mp-stage .mp-grid {
  /* The grid keeps its own flex sizing now that the stage is the flex item. */
  flex: 1;
  min-height: 0;
}

/* Two layers, one on each side of the grid in the DOM. The WIRES sit before it
   so the pane cards (which are positioned) paint over them — wires run behind
   the windows, like the mockup. The CHIPS sit after it, because a label buried
   under a card is a label you cannot read. Both share the stage's box, so one
   measurement places both. */
.mp-net-layer,
.mp-net-chips {
  display: none;
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.mp-stage[data-view="network"] .mp-net-layer,
.mp-stage[data-view="network"] .mp-net-chips {
  display: block;
}

.mp-wires {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* The wire itself: the client's own ink, kept quiet. It is context — the
   packets moving along it are the thing to watch. */
.mp-wire {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  opacity: 0.42;
}

/* COLOR says who, SIZE says how much (Addendum 5a.1). Both directions are
   circles: intent is the client's own player color, lit like the player's other
   chrome; authority is the server's slate, a smaller and quieter dot, because
   it arrives every frame on every wire and a bright badge sixty times a second
   is not information — it is weather. (The retired hollow-cyan square also read
   as player 1, whose color IS cyan.) */
.mp-packet.up {
  fill: currentColor;
  filter: drop-shadow(0 0 5px currentColor);
}

.mp-packet.down {
  fill: var(--scrub-server);
  filter: drop-shadow(0 0 3px rgba(139, 131, 173, 0.75));
}

/* The mid-edge chip: this client's CONFIGURED link profile. It is a label, not
   a measurement — the coordinator's links are still perfect. */
.mp-edge-chip {
  position: absolute;
  /* The layer is click-through; the chip is not, so its title (the note that
     the profile is configured, not measured) is reachable on hover. */
  pointer-events: auto;
  transform: translate(-50%, -50%);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
  font: 9.5px/1 var(--font-mono);
  letter-spacing: 0.03em;
  color: var(--text-dim);
  background: rgba(15, 12, 29, 0.92);
  border: 1px solid var(--line);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.6);
}

.mp-edge-chip b {
  font-weight: 400;
  color: var(--text);
}

/* The packet counter rides the chip, and only under reduced motion — see the
   dots' note below. */
.mp-edge-chip i {
  display: none;
  font-style: normal;
  font-variant-numeric: tabular-nums;
}

/* Reduced motion: NOTHING flies. mp-network.ts stops spawning dots entirely
   and each edge shows a static packet count instead, so the traffic is still
   legible — a number that ticks up rather than a movement to track. */
@media (prefers-reduced-motion: reduce) {
  .mp-edge-chip i {
    display: inline;
    color: var(--cyan);
  }
}

/* ---- the pinned wire log (Addendum 5, rule 3) ----

   Clicking a wire pins that link's traffic beside it. The panel takes the
   SELECTED CLIENT'S ink (--pc, set inline by mp-network.ts) for its frame, its
   header and its intent rows, so "which link am I reading" is answered by color
   before any text is parsed — the same rule the dots follow. */

/* The click target: the visible wire is 1.4px, which is not something to aim
   at. A transparent stroke ten times that width takes the clicks, and only it
   takes them — the layer itself stays click-through. */
.mp-wire-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 18;
  pointer-events: stroke;
  cursor: pointer;
}

/* The selected wire steps forward, but the WIRE must not thicken much: its
   packets ride the same currentColor, so a bright fat stroke swallows exactly
   the traffic you pinned it to read (it did — the first attempt added a
   drop-shadow glow and the dots vanished into it). Instead the line goes to
   full ink at a hair more weight, and the depth comes from a soft halo BEHIND
   the dots — the hit path, already the right shape and already there. */
.mp-wire.selected {
  opacity: 1;
  stroke-width: 1.8;
}

.mp-wire-hit.selected {
  stroke: currentColor;
  opacity: 0.13;
}

.mp-edge-chip {
  cursor: pointer;
}

.mp-edge-chip.selected {
  border-color: color-mix(in srgb, currentColor 45%, var(--line));
  color: var(--text);
}

.mp-edge-chip:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

/* The leader line, dashed so it reads as an annotation rather than as another
   wire, with a nub where it grips the edge. */
.mp-tether path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
  stroke-dasharray: 3 4;
  opacity: 0.5;
}

.mp-tether circle {
  fill: var(--bg);
  stroke: currentColor;
  stroke-width: 1.4;
}

.mp-wirelog {
  --pc: var(--cyan);
  position: absolute;
  width: 330px;
  max-width: calc(100% - 20px);
  pointer-events: auto;
  background: rgba(17, 13, 32, 0.97);
  border: 1px solid color-mix(in srgb, var(--pc) 42%, var(--line));
  border-radius: 5px;
  box-shadow:
    0 0 24px color-mix(in srgb, var(--pc) 14%, transparent),
    0 18px 40px rgba(0, 0, 0, 0.6);
}

.mp-wirelog[hidden] {
  display: none;
}

.mp-wl-hd {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  font: 10px/1 var(--font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  background: color-mix(in srgb, var(--pc) 7%, transparent);
  border-bottom: 1px solid var(--line);
  border-radius: 4px 4px 0 0;
}

.mp-wl-hd b {
  font-weight: 500;
  color: var(--pc);
}

.mp-wl-x {
  margin-left: auto;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: var(--text-dim);
  cursor: pointer;
}

.mp-wl-x:hover {
  color: var(--text);
}

.mp-wl-rows {
  padding: 5px 0;
}

/* Four columns, fixed where the content is fixed-width (frame, direction,
   bytes) so the payload — the only column whose width is a judgement call —
   gets everything left over. */
.mp-wl {
  display: grid;
  grid-template-columns: 84px 62px 1fr 46px;
  gap: 6px;
  padding: 3px 10px;
  font: 10.5px/1.5 var(--font-mono);
  color: var(--text);
  font-variant-numeric: tabular-nums;
  border-left: 2px solid transparent;
}

.mp-wl:hover {
  background: color-mix(in srgb, var(--pc) 6%, transparent);
}

/* The row at the playhead. This is the panel's one loud moment, and it earns
   it: it is what makes the log and the rail visibly the same instrument. */
.mp-wl.at {
  border-left-color: var(--pc);
  background: color-mix(in srgb, var(--pc) 14%, transparent);
}

/* Sent frame, then delivered — the link's delay, per packet. The sent frame is
   the row's file-under number and keeps the ink; the arrival is an annotation
   on it, so it sits quieter and one notch smaller. */
.mp-wl .f {
  color: var(--text-dim);
  white-space: nowrap;
}

.mp-wl .f .dl {
  margin-left: 4px;
  font-style: normal;
  font-size: 9.5px;
  color: color-mix(in srgb, var(--text-dim) 65%, transparent);
}

/* IN FLIGHT at the playhead: sent by this frame, not yet delivered. The row is
   the one place the parked timeline can say "the receiving game has not seen
   this yet", so the arrival stamp lights up and the payload — which is the part
   that has not landed — goes ghosted. */
.mp-wl.flight .f .dl {
  color: var(--cyan);
}

.mp-wl.flight .payload,
.mp-wl.flight .n {
  opacity: 0.45;
}

/* Direction takes the ink of whoever SENT it: the client's own color going up,
   the authority's slate coming down. */
.mp-wl .d.up {
  color: var(--pc);
}

.mp-wl .d.dn {
  color: var(--scrub-server);
}

.mp-wl .payload {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Constructor first and lit, arguments after and quiet — the row is scanned for
   WHICH message this is. */
.mp-wl .payload b {
  font-weight: 500;
}

.mp-wl .payload em {
  font-style: normal;
  color: var(--text-dim);
  margin-left: 5px;
}

/* A typed payload is a disclosure control: same line, same ink, but it opens.
   The button resets to the span it replaced so the row's rhythm is unchanged
   until it is actually used. */
button.payload {
  border: 0;
  padding: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}

button.payload:hover b,
button.payload[aria-expanded="true"] b {
  color: var(--pc);
}

button.payload:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: 2px;
}

.mp-wl .n {
  text-align: right;
  color: var(--text-dim);
}

/* ---- the value tree (Addendum 8.1) ----

   An open row keeps its one line and grows a tree beneath it, across all four
   columns. Bounded height, not unbounded: a world snapshot is taller than the
   stage, and a panel that grows past the window would push its own placement
   off screen — so the deep end scrolls inside the row instead. */
.mp-wl.open {
  background: color-mix(in srgb, var(--pc) 8%, transparent);
}

.mp-wl-tree {
  grid-column: 1 / -1;
  max-height: 220px;
  overflow: auto;
  margin: 3px 0 5px;
  padding: 3px 0;
  border-top: 1px solid color-mix(in srgb, var(--pc) 20%, var(--line));
}

.mp-vt {
  font: 10.5px/1.55 var(--font-mono);
}

.mp-vt-row {
  display: flex;
  align-items: baseline;
  gap: 6px;
  width: 100%;
  padding: 0 2px;
  border: 0;
  background: none;
  font: inherit;
  text-align: left;
  color: var(--text);
}

button.mp-vt-row {
  cursor: pointer;
}

button.mp-vt-row:hover {
  background: color-mix(in srgb, var(--pc) 10%, transparent);
}

button.mp-vt-row:focus-visible {
  outline: 2px solid var(--cyan);
  outline-offset: -2px;
}

/* The twisty holds its column on leaf rows too, so names line up down the
   level rather than stepping in and out with the disclosures. */
.mp-vt-tw {
  flex: none;
  width: 9px;
  color: var(--pc);
  opacity: 0.8;
}

.mp-vt-k {
  flex: none;
  color: var(--text-dim);
}

.mp-vt-k::after {
  content: ":";
  opacity: 0.5;
}

/* Composites are dim (they are structure, and their contents are one click
   below); scalars carry the data ink. */
.mp-vt-v {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--text-dim);
}

.mp-vt-v.num {
  color: var(--cyan);
}

.mp-vt-v.str {
  color: var(--amber);
}

.mp-vt-v.bool {
  color: var(--green);
}

/* One hairline per level: the indent is measurable rather than guessed, which
   is what makes a deep snapshot readable at ten pixels. */
.mp-vt-kids {
  margin-left: 6px;
  padding-left: 8px;
  border-left: 1px solid color-mix(in srgb, var(--pc) 22%, transparent);
}

.mp-vt-kids[hidden] {
  display: none;
}

.mp-vt-text {
  display: block;
  padding: 2px 3px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  color: var(--amber);
}

.mp-vt-more {
  padding: 1px 3px;
  border: 0;
  background: none;
  font: inherit;
  font-style: italic;
  color: var(--text-dim);
  cursor: pointer;
}

.mp-vt-more:hover {
  color: var(--text);
}

.mp-wl-empty {
  margin: 0;
  padding: 6px 12px;
  font: 10px/1.5 var(--font-mono);
  color: var(--text-dim);
}

.mp-wl-ft,
.wire-ft {
  padding: 5px 10px;
  border-top: 1px solid var(--line);
  font: 9.5px/1.4 var(--font-mono);
  color: var(--text-dim);
}

/* ---- the wire tab (Addendum 8.2) ----

   The same rows, docked. The pinned panel above is a floating card tethered to
   a wire; this is an instrument bay in the status bar, so it takes the bar's
   flat chrome — no border, no shadow, no radius — and spends its one piece of
   color on the filter chips, where each link wears its client's ink. That is
   the network view's own rule (color carries identity), carried into a list
   that has to say WHICH wire each row crossed. */

.wire-list {
  /* The rows own the scroll (the header rail and footer must not leave with
     it), so the list itself is a column that clips. */
  display: flex;
  flex-direction: column;
  padding: 0;
  overflow: hidden;
}

.wire-host,
.wire-tab {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-width: 0;
  min-height: 0;
}

.wire-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  padding: 5px 10px;
  border-bottom: 1px solid var(--line);
}

.wire-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px;
}

/* The direction filter is the secondary axis — pushed to the far end so the
   links, which are what a reader picks between, hold the left. */
.wire-dirs {
  margin-left: auto;
}

.wire-chip {
  --pc: var(--cyan);
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 2px 8px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: none;
  color: var(--text-dim);
  font: 10px/1.5 var(--font-mono);
  letter-spacing: 0.04em;
  cursor: pointer;
}

/* The ink dot: whose traffic this chip selects, in that client's own color. */
.wire-chip::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--pc);
}

.wire-chip.all::before,
.wire-dirs .wire-chip::before {
  display: none;
}

.wire-chip:hover {
  color: var(--text);
  border-color: color-mix(in srgb, var(--pc) 45%, var(--line));
}

.wire-chip:focus-visible {
  outline: 1px solid var(--cyan);
  outline-offset: 1px;
}

.wire-chip[aria-pressed="true"] {
  color: var(--text);
  border-color: color-mix(in srgb, var(--pc) 60%, var(--line));
  background: color-mix(in srgb, var(--pc) 12%, transparent);
}

.wire-rows {
  --pc: var(--cyan);
  /* The positioning context the parked scroll measures a row's offset in
     (wire-tab.ts) — never `scrollIntoView`, which would move the page too. */
  position: relative;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 0;
}

/* ALL interleaves several links, so every row gains a column naming the one it
   crossed — inserted after the frame, before the direction it qualifies. */
.mp-wl.linked {
  grid-template-columns: 84px 32px 62px 1fr 46px;
}

.mp-wl .l {
  color: var(--pc);
}

/* The count on the tab itself, while the panel is closed. */
.wire-badge {
  margin-left: 6px;
  color: var(--text-dim);
  font-variant-numeric: tabular-nums;
}

.wire-badge[hidden] {
  display: none;
}

.statusbar-tab.active .wire-badge {
  color: inherit;
}

/* ---- the network status strip (Addendum 5a.3) ----

   The legend, and the readouts the pane headers drop when they clip to
   thumbnail width, ride the STATUS BAR rather than the stage: the stage is the
   topology, and a caption band above it was pushing the cards down and still
   spilling out of the headers. The bar already is the page's line of ambient
   state, so the view's own state belongs on it — scoped to the view, gone the
   moment you leave it. */

.statusbar-view-host {
  margin-left: auto;
  min-width: 0;
}

.statusbar-view {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 18px;
  min-width: 0;
  padding: 4px 4px 4px 12px;
  font: 10px/1 var(--font-mono);
  letter-spacing: 0.04em;
  color: var(--text-dim);
}

.statusbar-view[hidden] {
  display: none;
}

/* The packet legend describes dots that only fly in the NETWORK view; the pane
   readouts beside it are carried in every multi-pane view (see mp-panes.ts). */
.statusbar-view:not([data-view="network"]) .mp-vs-legend {
  display: none;
}

.mp-vs-legend {
  display: flex;
  align-items: center;
  gap: 16px;
  min-width: 0;
  overflow: hidden;
}

.mp-vs-legend b {
  font-weight: 400;
  color: var(--text);
}

.mp-vs-note {
  opacity: 0.72;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mp-sw {
  display: inline-block;
  vertical-align: middle;
  border-radius: 999px;
  margin-right: 7px;
}

/* One intent swatch per live client, each in that client's ink (mp-panes.ts). */
.mp-vs-inks {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-right: 7px;
  vertical-align: middle;
}

.mp-sw.up {
  --pc: var(--scrub-p1);
  width: 8px;
  height: 8px;
  margin: 0;
  background: var(--pc);
  box-shadow: 0 0 6px var(--pc);
}

/* The authority swatch is the authority dot: smaller, slate, unlit. */
.mp-sw.down {
  width: 5px;
  height: 5px;
  background: var(--scrub-server);
  box-shadow: 0 0 4px rgba(139, 131, 173, 0.7);
}

/* The demoted header details: one row per pane, in pane order. */
.mp-vs-panes {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: none;
}

.mp-vs-pane {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-variant-numeric: tabular-nums;
}

.mp-vs-pane b {
  font-weight: 400;
  opacity: 0.62;
}

.mp-vs-pane .mp-digit {
  font-style: normal;
}

.mp-vs-who {
  font-style: normal;
  letter-spacing: 0.12em;
  color: var(--scrub-server);
}

/* One legend per motion preference (see the pane module): the dot legend only
   under motion, the counts note only under reduce. */
.statusbar-view .still {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .statusbar-view .motion {
    display: none;
  }

  .statusbar-view .still {
    display: inline;
  }
}

/* ---- pane shell ---- */

.mp-pane {
  --pc: var(--cyan);
  position: relative;
  /* The header clips against the PANE's width, not the viewport's — a pane is
     a thumbnail at three-up on a wide screen and full width at one-up on a
     narrow one. See the header's priority rules. */
  container: mp-pane / inline-size;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
  /* The frame is the shadow, not the line: a whisper of player color on the
     border, real separation from depth. */
  border: 1px solid color-mix(in srgb, var(--pc) 12%, rgba(43, 37, 66, 0.6));
  border-radius: 5px;
  /* NOT overflow:hidden — the link menu must escape the pane. The children
     carry the rounding: the header clips its top corners, the body clips the
     iframe's bottom corners (unless the error strip is the last row). */
  background: #0b0817;
  /* Depth in three steps — contact, lift, and a wide ambient pool — so a
     client pane reads as a window floating over the surface rather than a
     boxed-off region of it. The border stays a hairline; the shadow does the
     separating. (The server tile opts out further down: see its block.) */
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 9px 22px rgba(0, 0, 0, 0.55),
    0 26px 60px rgba(0, 0, 0, 0.62);
  transition: border-color 0.18s, box-shadow 0.18s;
}

/* The active pane — the client holding the keyboard ("⌨ you") — is lit from
   its own edge: a spread glow in the player's color over the depth stack, so
   "who has my keys" is answerable from across the room, not by reading a
   header. The SERVER is excluded by name: it never owns input, and the flat
   dashed authority tile is the whole point of its styling. */
.mp-pane.focus:not(.server) {
  border-color: color-mix(in srgb, var(--pc) 60%, transparent);
  box-shadow:
    0 0 26px 5px color-mix(in srgb, var(--pc) 34%, transparent),
    0 0 72px 14px color-mix(in srgb, var(--pc) 13%, transparent),
    0 1px 2px rgba(0, 0, 0, 0.5),
    0 9px 22px rgba(0, 0, 0, 0.55),
    0 26px 60px rgba(0, 0, 0, 0.62);
}

/* The glow itself is static — nothing to stop for reduced motion — but moving
   focus crossfades three surfaces (the pane's shadow, its header, its tab).
   Under reduce, focus lands instantly on all three or on none. */
@media (prefers-reduced-motion: reduce) {
  .mp-pane,
  .mp-pane-hd,
  .mp-tab {
    transition: none;
  }
}

.mp-pane-hd {
  position: relative;
  z-index: 6; /* the link menu must paint above the pane's iframe */
  border-radius: 4px 4px 0 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 5px 9px;
  font: 10.5px/1 var(--font-mono);
  /* One row, always. NOT overflow:hidden — the link menu drops out of this
     header and must escape it (see the pane shell's note); the chip truncates
     itself instead. */
  white-space: nowrap;
  color: var(--text-dim);
  background: color-mix(in srgb, var(--pc) 8%, var(--bg-raised));
  border-bottom: 1px solid color-mix(in srgb, var(--pc) 14%, var(--line));
  filter: saturate(0.45);
  transition: filter 0.18s;
  cursor: default;
  user-select: none;
}

.mp-pane.focus .mp-pane-hd {
  filter: none;
  background: color-mix(in srgb, var(--pc) 15%, var(--bg-raised));
}

.mp-digit {
  width: 15px;
  height: 15px;
  display: grid;
  place-items: center;
  border-radius: 3px;
  flex: none;
  background: color-mix(in srgb, var(--pc) 24%, transparent);
  color: var(--pc);
  font-size: 9.5px;
}

.mp-role {
  color: var(--pc);
  letter-spacing: 0.05em;
}

.mp-hd-r {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
  font-variant-numeric: tabular-nums;
}

/* ---- header priority: what survives a thumbnail (Addendum 5a.3) ----

   A pane header is a sentence about one pane, and at network-view widths there
   is not room for the sentence. Rather than let it spill (it cannot clip: the
   link menu drops OUT of this header), the header sheds it in priority order,
   least load-bearing first:

     1. the frame counter — a number that changes 60×/s, and the one the
        status strip now carries for every pane at once;
     2. the prose — the role word, the "authority" caption, "⌨ you" (the focus
        glow already says which pane holds the keyboard), and the word beside
        the link dot;
     3. the link chip, which truncates rather than disappearing.

   What NEVER goes: the DIGIT (which pane this is) and the CONNECTION DOT
   (whether it is talking) — identity and liveness, the two things you look at
   a thumbnail's header for at all. */

@container mp-pane (max-width: 330px) {
  .mp-pf {
    display: none;
  }
}

@container mp-pane (max-width: 250px) {
  /* The SERVER keeps its word: it carries no digit, so the label is the whole
     of its identity — the one piece of prose that is load-bearing. */
  .mp-pane:not(.server) .mp-role,
  .mp-authority,
  .mp-you,
  .mp-conn-t {
    display: none;
  }
}

.mp-conn-d {
  font-weight: 400;
}

.mp-conn-t {
  margin-left: 4px;
}

.mp-you {
  color: var(--pc);
}

.mp-pf b {
  color: var(--text-dim);
  font-weight: 400;
}

.mp-st {
  width: 6px;
  height: 6px;
  border-radius: 999px;
  flex: none;
}

.mp-st[data-state="live"] {
  background: var(--green);
  box-shadow: 0 0 6px var(--green);
}

.mp-st[data-state="busy"] {
  background: var(--amber);
  box-shadow: 0 0 6px var(--amber);
}

.mp-st[data-state="error"] {
  background: var(--red);
  box-shadow: 0 0 6px var(--red);
}

.mp-pane-body {
  flex: 1;
  min-height: 0;
  position: relative;
  overflow: hidden;
}

.mp-pane-body:last-child {
  border-radius: 0 0 4px 4px;
}

.mp-pane-body iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: var(--bg);
}

.mp-pane-err {
  border-radius: 0 0 4px 4px;
  padding: 4px 9px;
  font: 10.5px/1.4 var(--font-mono);
  color: var(--red);
  background: rgba(242, 99, 127, 0.1);
  border-top: 1px solid rgba(242, 99, 127, 0.35);
}

/* ---- the server pane: the authority, not a player ----

   Clients are lit and numbered because they are a sequence you pick from. The
   server is one per session and never picked, so it drops every device that
   says "which one": no digit, no link chip (latency is a property of a
   client's link), no "⌨ you". What is left carries the identity — a hairline
   DASHED frame instead of the clients' solid glow-tinted one, and the label
   itself, tracked wide. Quiet where the clients are lit. */

.mp-pane.server {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--pc) 30%, transparent);
  /* Deliberately NOT the clients' three-step depth stack: the authority sits
     flat against the surface while the players float over it, and it never
     takes the focus glow (it owns no keyboard). Quiet where they are lit. */
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.45);
}

.mp-pane.server .mp-pane-hd {
  background: color-mix(in srgb, var(--pc) 5%, var(--bg-panel));
  border-bottom-style: dashed;
  filter: none;
}

.mp-pane.server .mp-role,
.mp-tab.server {
  letter-spacing: 0.14em;
}

/* The one-word caption that says what "SERVER" MEANS — it holds the authority
   of the session. Set as a quiet aside, not a badge. */
.mp-authority {
  font-size: 9px;
  color: var(--text-dim);
  opacity: 0.62;
  letter-spacing: 0.04em;
}

.mp-tab.server {
  filter: none;
}

/* ---- per-pane link state (the coordinator's connection table) ---- */

.mp-conn {
  font-size: 9.5px;
  color: var(--text-dim);
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.mp-conn[data-linked="true"] {
  color: var(--green);
}

/* ---- per-pane link chip + menu ---- */

.mp-link-host {
  position: relative;
  min-width: 0;
}

.mp-link-chip {
  /* Never wrap: at three tiles the pane header is narrow, and a two-line chip
     doubles the header's height. It truncates instead. */
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 10px/1 var(--font-mono);
  color: var(--text-dim);
  cursor: pointer;
  background: transparent;
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 3px 7px;
  transition: border-color 0.14s, color 0.14s;
}

.mp-link-chip:hover {
  border-color: color-mix(in srgb, var(--pc) 50%, transparent);
  color: var(--text);
}

.mp-link-menu {
  position: absolute;
  left: 0;
  top: calc(100% + 6px);
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 12px;
  font: 11px/1.4 var(--font-mono);
  color: var(--text);
  background: rgba(30, 24, 51, 0.98);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
  white-space: nowrap;
  filter: saturate(1); /* opt out of the dimmed-header filter */
}

.mp-link-presets {
  display: flex;
  border: 1px solid var(--line);
  border-radius: 4px;
  overflow: hidden;
}

.mp-link-presets button {
  font: 10.5px/1 var(--font-mono);
  color: var(--text-dim);
  background: transparent;
  border: 0;
  border-right: 1px solid var(--line);
  padding: 5px 9px;
  cursor: pointer;
}

.mp-link-presets button:last-child {
  border-right: 0;
}

.mp-link-presets button:hover {
  background: rgba(65, 216, 230, 0.08);
  color: var(--text);
}

.mp-link-presets button[aria-pressed="true"] {
  background: rgba(232, 88, 184, 0.16);
  color: var(--pink);
}

.mp-link-menu label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--text-dim);
}

.mp-link-menu input {
  width: 52px;
  font: 11px/1 var(--font-mono);
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 3px;
  padding: 3px 5px;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Loss is set here but not applied: it belongs to datagrams (Net.Udp), and
   Sub.connect is reliable-ordered. The field stays usable — it is a setting,
   not a broken control — and wears the badge that says who will read it. */
.mp-link-menu label.off {
  opacity: 0.65;
}

.mp-link-soon {
  padding: 1px 5px;
  font: 9px/1.5 var(--font-mono);
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--pink);
  background: color-mix(in srgb, var(--pink) 14%, transparent);
  border-radius: 3px;
}

.mp-link-note {
  margin: 0;
  max-width: 220px;
  white-space: normal;
  font-size: 9.5px;
  color: var(--text-dim);
  opacity: 0.8;
}

/* Narrow columns (the sandbox's preview at laptop width): stack the panes.
   Grid's two columns collapse here too — the server strip still spans them,
   so the layout keeps its shape, one column wide. */
@media (max-width: 1280px) {
  .preview-pane.mp .mp-grid {
    grid-template-columns: 1fr;
  }

  /* Tiled's flex row becomes a flex column — the same stack the grid views
     collapse to. */
  .preview-pane.mp .mp-grid[data-view="tiled"] {
    flex-direction: column;
  }

  /* Network has no room for a hub flanked by spokes in one narrow column, so
     it collapses to the same stack — the clients over the authority, in DOM
     order. The wires re-measure and run vertically down the chain, so the
     topology still reads; only the geometry changes. */
  .preview-pane.mp .mp-grid[data-view="network"] {
    grid-template-rows: none;
    grid-auto-rows: minmax(0, 1fr);
    gap: 10px;
    padding: 10px;
  }

  .preview-pane.mp .mp-grid[data-view="network"] .mp-cell {
    grid-column: 1;
    grid-row: auto;
  }

  /* The hub keeps its full width in a one-column stack (a centred node in a
     list reads as a broken cell) but stays the SHORT card — still the smallest
     window, still the thing the wires run to. The letterbox that carries that
     rank on the wide stage is off here (see the aspect block), so the stack
     states it directly. */
  .preview-pane.mp .mp-grid[data-view="network"] .mp-cell.server > .mp-pane {
    min-width: 0;
    max-width: none;
    height: clamp(76px, 62%, 132px);
  }
}

/* `hidden` must win over the display rules above. */
.mp-tabs[hidden],
.mp-link-menu[hidden] {
  display: none;
}

/* ---- under-rail frame label, future (extrapolation) segment, 🔮 state ---- */

.mp-rail-label {
  position: absolute;
  /* Tucked 2px up into the rail's bottom edge (reviewed at several offsets;
     this one read best). A fixed offset, not a percentage, so the label
     stays put if the rail's height ever changes. */
  top: calc(100% - 2px);
  left: 50%;
  transform: translateX(-50%);
  font-size: 9px;
  color: var(--text-dim);
  opacity: 0.78;
  white-space: nowrap;
  pointer-events: none;
  font-variant-numeric: tabular-nums;
}

.mp-rail-label b {
  font-weight: 400;
  opacity: 0.7;
}

.mp-viewseg button:disabled {
  opacity: 0.35;
  cursor: default;
}

.mp-viewseg button:disabled:hover {
  background: transparent;
  color: var(--text-dim);
}

/* ---- single client: same instrument, no multiplayer chrome ---- */

/* One client keeps the chrono bar (tiled/tabs disabled) but drops the pane
   header — digits, roles, and link chips are multiplayer vocabulary — and the
   focus glow, since there is nothing to disambiguate. */
.preview-pane.mp[data-count="1"] .mp-pane-hd,
.preview-pane.mp[data-count="1"] .mp-tabs {
  display: none;
}

/* One client keeps a flush, frameless game view — but the chrono bar above
   it is the same instrument at every count. */
.preview-pane.mp[data-count="1"] .mp-grid {
  padding: 0;
  gap: 0;
}

.preview-pane.mp[data-count="1"] .mp-pane {
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.preview-pane.mp[data-count="1"] .mp-pane.focus {
  border: 0;
  box-shadow: none;
}

/* ---- restored scrubber polish: handles, tooltip, overflow chip ---- */

/* Breathing room between the rail (whose playhead overhangs its right edge
   when fully caught up) and the 🔮 button. */
.mp-rail {
  margin-right: 8px;
}

.mp-evt {
  appearance: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

.mp-evt:hover,
.mp-evt:focus-visible {
  outline: 1px solid rgba(255, 255, 255, 0.75);
  outline-offset: 1px;
}

.mp-evt-tip {
  display: none;
  position: absolute;
  z-index: 5;
  top: calc(100% + 8px);
  max-width: min(280px, 60vw);
  padding: 5px 7px;
  transform: translateX(-50%);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  background: rgba(30, 24, 51, 0.98);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.45);
  font-size: 10px;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

/* 🔮 is single-client only (whole-session forward extrapolation means N
   speculative re-simulations — not v1); multiplayer gets tiled/tabs in that
   slot, and with one client the view toggle is meaningless. Hidden, not
   disabled (design-multiplayer-ide-frontend.md §1). */
.preview-pane.mp:not([data-count="1"]) #mp-extrap {
  display: none;
}

.preview-pane.mp[data-count="1"] .mp-viewseg {
  display: none;
}

/* ---- second ticks along the rail (every 60 frames; heavier each 5s) ---- */

.mp-tick {
  position: absolute;
  top: 11px;
  height: 8px;
  width: 1px;
  background: rgba(233, 230, 242, 0.28);
  pointer-events: none;
}

.mp-tick.major {
  top: 9px;
  height: 12px;
  width: 1px;
  background: rgba(233, 230, 242, 0.5);
}

/* Numeric fields are raw text entry — the native spinner arrows fight the
   theme and are unusably small; type=number keeps validation and keyboard
   arrows working. */
.mp-link-menu input[type="number"] {
  appearance: textfield;
  -moz-appearance: textfield;
}

.mp-link-menu input[type="number"]::-webkit-inner-spin-button,
.mp-link-menu input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}


/* ---- the extrapolation surface (single-client) + rail keyboard focus ---- */

.mp-playhead:focus-visible,
.mp-preview-handle:focus-visible {
  outline: 2px solid white;
  outline-offset: 2px;
}

.mp-rail-label .fut {
  color: var(--pink);
}

.mp-rail-label .out {
  color: #ffd166;
}

/* Playhead parked outside the frozen viewport: a white ring on top of its
   normal glow (mirrors the in-frame scrubber's outside state). */
.mp-playhead.outside {
  box-shadow:
    0 0 0 2px rgba(255, 255, 255, 0.55),
    0 0 12px rgba(65, 216, 230, 0.6);
}

.mp-future {
  position: absolute;
  top: 11px;
  height: 8px;
  border-radius: 3px;
  background: var(--pink);
  opacity: 0.9;
  box-shadow: 0 0 10px rgba(232, 88, 184, 0.5);
}

#mp-extrap.on {
  border-color: var(--pink);
  box-shadow: 0 0 0 1px var(--pink), 0 2px 10px rgba(232, 88, 184, 0.4);
}

.mp-preview-handle {
  display: none;
  position: absolute;
  top: 15px;
  z-index: 3;
  width: 14px;
  height: 20px;
  border-radius: 4px;
  transform: translate(-50%, -50%);
  background: var(--pink);
  border: 1px solid #ffd0ee;
  box-shadow: 0 0 12px rgba(232, 88, 184, 0.6);
  cursor: ew-resize;
  touch-action: none;
}

.mp-preview-handle.clipped {
  border-radius: 4px 0 0 4px;
}

/* Fully clipped (the endpoint sits on the playhead): perch above the rail so
   the cyan playhead stays independently visible and hittable. */
.mp-preview-handle.fully-clipped {
  top: 0;
  z-index: 5;
  height: 12px;
}

.mp-overflow {
  display: none;
  position: absolute;
  right: 0;
  top: -6px;
  z-index: 4;
  padding: 2px 4px;
  border: 1px solid var(--pink);
  border-radius: 5px;
  color: #ffd0ee;
  background: rgba(30, 24, 51, 0.96);
  font-size: 9px;
  pointer-events: none;
}

