/* Light is the default (no attribute needed) — chosen so a diagram
   exported to Google Docs, which always lands on a white page, matches
   what the exporter was looking at while drawing it. Dark is opt-in via
   Settings (see ui/AppMenu.js / src/theme.js), applied by setting
   data-theme="dark" on <html>.
   FAB buttons (the floating add/delete/color/parent-nav controls) and the
   color swatch palette they open are deliberately left out of this token
   system — they stay a fixed dark, high-contrast "glass" chrome in both
   themes, the same way a floating toolbar often does regardless of the
   page behind it. */
:root {
  /* Not a color token — kept alongside them so #app's grid row and the
     drawer's own top offset (see .topbar-menu) can't drift apart. */
  --topbar-height: 52px;
  --bg-canvas: #f7f8fa;
  --bg-surface: #ffffff;
  --bg-surface-2: #ffffff;
  --bg-well: #f3f5f8;
  --bg-hover: #eef1f5;
  --bg-hover-strong: #e2e7ee;
  --border: #e1e5eb;
  --border-strong: #c9d0da;
  --text-primary: #1c2431;
  --text-strongest: #000000;
  --text-secondary: #48505c;
  --text-muted: #6b7280;
  --text-faint: #99a1ad;
  --text-faint-2: #b0b7c3;
  --accent: #2f6fed;
  --accent-strong: #2f6fed;
  --accent-strong-hover: #1d5fe0;
  --success: #3ecf5d;
  --danger: #e5484d;
  --danger-tint: rgba(229, 72, 77, 0.08);
  --warning: #ffb454;
  --warning-tint: rgba(255, 180, 84, 0.12);
  --warning-text: #8a5a12;
  --warning-border: #f0c988;
  --backdrop: rgba(15, 18, 24, 0.35);
}

:root[data-theme='dark'] {
  --bg-canvas: #12161d;
  --bg-surface: #1e2530;
  --bg-surface-2: #1a1f28;
  --bg-well: #10151c;
  --bg-hover: #2c3644;
  --bg-hover-strong: #3a4556;
  --border: #2c3644;
  --border-strong: #3a4556;
  --text-primary: #e6e9ef;
  --text-strongest: #ffffff;
  --text-secondary: #c3c9d4;
  --text-muted: #8b93a3;
  --text-faint: #6b7889;
  --text-faint-2: #4a5568;
  --accent: #4f8cff;
  --accent-strong: #2f6fed;
  --accent-strong-hover: #3f7bf5;
  --success: #3ecf5d;
  --danger: #e5484d;
  --danger-tint: rgba(229, 72, 77, 0.12);
  --warning: #ffb454;
  --warning-tint: rgba(255, 180, 84, 0.08);
  --warning-text: #d8a760;
  --warning-border: #4a3a1c;
  --backdrop: rgba(0, 0, 0, 0.35);
}

* {
  box-sizing: border-box;
  -webkit-tap-highlight-color: transparent;
}

html, body {
  margin: 0;
  height: 100%;
  font-family: -apple-system, Segoe UI, Roboto, sans-serif;
  overflow: hidden;
  background: var(--bg-canvas);
}

#app {
  display: grid;
  /* A single column always — the inspector is a fixed-position panel that
     slides in over the canvas (see #inspector below) rather than a grid
     column that pushes it aside, so it doesn't need one of its own here. */
  grid-template-columns: 1fr;
  grid-template-rows: var(--topbar-height) 1fr;
  grid-template-areas:
    "topbar"
    "canvas";
  height: 100%;
  height: 100dvh;
}

#topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0 16px;
  background: var(--bg-surface);
  color: var(--text-primary);
  border-bottom: 1px solid var(--border);
}

#topbar .brand {
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.02em;
  white-space: nowrap;
}

.doc-sync {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* Opens the sliding app menu — visible on every screen size now, always
   the leftmost thing in the header. */
.menu-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  flex-shrink: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-secondary);
  cursor: pointer;
  /* The open drawer sits at the same left edge as this button (see
     .topbar-menu) — without its own stacking position it would be
     covered and unclickable, the only way to close the drawer being the
     backdrop or Escape. */
  position: relative;
  z-index: 31;
}

.menu-toggle:hover:not(:disabled) {
  color: var(--text-primary);
  border-color: var(--border-strong);
}

.menu-toggle:disabled {
  opacity: 0.4;
  cursor: default;
}

.topbar-menu-backdrop:not([hidden]) {
  display: block;
  position: fixed;
  inset: 0;
  background: var(--backdrop);
  z-index: 29;
}

/* A drawer fixed to the left edge and off-screen until opened — the same
   panel and behavior on every screen size, rather than a desktop inline
   row plus a separate mobile dropdown. Starts below the header rather
   than at the very top of the viewport, so its own first row (New)
   doesn't sit underneath the header's hamburger/brand/breadcrumb. */
.topbar-menu {
  position: fixed;
  top: var(--topbar-height);
  bottom: 0;
  left: 0;
  width: min(280px, 85vw);
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--bg-surface);
  border-right: 1px solid var(--border);
  box-shadow: 12px 0 32px rgba(0, 0, 0, 0.5);
  padding: 16px 12px;
  overflow-y: auto;
  transform: translateX(-100%);
  transition: transform 0.22s ease;
  z-index: 30;
}

.topbar-menu.open {
  transform: translateX(0);
}

/* The doc-sync cluster is flag-gated (see config.js) and, unlike the
   app-menu rows above it, was never designed as a vertical list — a
   compact row still reads fine inside the drawer. */
.topbar-menu .doc-sync {
  padding: 10px;
  flex-wrap: wrap;
}

/* Always-visible header actions: undo/redo, Share, Start Session. */
.header-actions {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-left: auto;
  flex-shrink: 0;
}

/* Who else is here right now — a small avatar stack, top-right, present
   only once someone actually has joined (see ui/OnlineUsers.js). Overlaps
   each avatar slightly, the same "stack of coins" layout most
   presence-avatar rows use, rather than a wide row that grows without
   bound as more people join. */
.online-users {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  margin-left: 4px;
}

.online-users[hidden] {
  display: none;
}

.online-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  margin-left: -8px;
  border-radius: 50%;
  border: 2px solid var(--bg-surface);
  color: #0b0e13;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.online-avatar:first-child {
  margin-left: 0;
}

.header-icon-button {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background: none;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-secondary);
  cursor: pointer;
}

.header-icon-button:hover:not(:disabled) {
  color: var(--text-primary);
  border-color: var(--border-strong);
}

.header-icon-button:disabled {
  opacity: 0.4;
  cursor: default;
}

/* Live-session state — a mode the whole app is in, so the icon itself
   changes color rather than relying on a label. */
.session-button.active {
  color: var(--success);
  border-color: var(--success);
}

.session-button.connecting {
  color: var(--warning);
  border-color: var(--warning);
}

.session-peer-badge {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 15px;
  height: 15px;
  padding: 0 3px;
  border-radius: 8px;
  background: var(--success);
  color: #0b1017;
  font-size: 9px;
  font-weight: 700;
  line-height: 15px;
  text-align: center;
}

/* Bottom-right, stacked oldest-on-top, so a second toast while one is
   already showing doesn't cover it up. Kept clear of the FAB stack
   (bottom-right too) by sitting above it with margin, not overlapping. */
.app-toast-stack {
  position: fixed;
  right: 16px;
  bottom: 96px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(360px, calc(100vw - 32px));
  z-index: 40;
}

.app-toast {
  padding: 12px 14px;
  background: var(--bg-surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.35);
  color: var(--text-secondary);
  font-size: 13px;
  line-height: 1.5;
}

.app-toast-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 10px;
}

.app-toast-button {
  padding: 5px 12px;
  background: var(--bg-hover);
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  color: var(--text-primary);
  font-size: 12px;
  cursor: pointer;
}

.app-toast-button:hover {
  background: var(--bg-hover-strong);
}

/* The app-menu drawer's own rows (New/Open/Save/Export/Settings). */
.app-menu {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.app-menu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 10px;
  background: none;
  border: none;
  border-radius: 6px;
  color: var(--text-secondary);
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.app-menu-item:hover:not(:disabled) {
  background: var(--bg-hover);
  color: var(--text-primary);
}

.app-menu-item:disabled {
  opacity: 0.4;
  cursor: default;
}

.app-menu-item svg {
  flex-shrink: 0;
}

/* The dot marks edits not yet written into the page address — same idea
   as before, just living on the Save row instead of a header button. */
.app-menu-item.unsaved::after {
  content: '';
  display: inline-block;
  width: 6px;
  height: 6px;
  margin-left: auto;
  border-radius: 50%;
  background: var(--warning);
  flex-shrink: 0;
}

.app-menu-divider {
  height: 1px;
  margin: 8px 6px;
  background: var(--border);
}

.app-menu-chevron {
  margin-left: auto;
  transition: transform 0.15s ease;
  flex-shrink: 0;
}

.app-menu-item[aria-expanded="true"] .app-menu-chevron {
  transform: rotate(90deg);
}

/* Shared by every expandable row (Settings, Export — see AppMenu.js's
   expandable()), which each toggle via the plain `hidden` attribute. The
   `display: flex` below is what actually needs overriding while hidden:
   same specificity as the bare class rule, so without this the browser's
   own default `[hidden] { display: none }` loses to it and the body never
   visually collapses at all, whatever `.hidden` says. */
.app-menu-settings-body {
  display: flex;
  flex-direction: column;
}

.app-menu-settings-body[hidden] {
  display: none;
}

.app-menu-toggle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 10px 8px 34px;
  color: var(--text-secondary);
  font-size: 13px;
  cursor: pointer;
}

.app-menu-toggle-row input[type='checkbox'] {
  width: 15px;
  height: 15px;
  accent-color: var(--accent);
}

/* A sub-item inside an expandable body (see AppMenu.js's expandable/
   subItem) — same indentation as a toggle row, so Export's JSON/YAML
   choices line up under "Export" the same way Animate/Dark mode line up
   under Settings. */
.app-menu-subitem {
  padding-left: 34px;
}

.doc-sync-status {
  font-size: 11px;
  color: var(--text-muted);
  white-space: nowrap;
}

.doc-sync-status[data-state="synced"] {
  color: var(--success);
}

.doc-sync-status[data-state="error"] {
  color: var(--danger);
}

.doc-sync-status[data-state="saving"],
.doc-sync-status[data-state="loading"] {
  color: var(--warning);
}

.doc-sync-button {
  background: var(--accent-strong);
  color: white;
  border: none;
  border-radius: 5px;
  padding: 5px 12px;
  font-size: 12px;
  cursor: pointer;
}

.doc-sync-button:hover {
  background: var(--accent-strong-hover);
}

.doc-sync-button:disabled {
  background: var(--bg-hover);
  color: var(--text-faint);
  cursor: default;
}

.doc-sync-settings {
  background: none;
  border: 1px solid var(--border);
  border-radius: 5px;
  width: 26px;
  height: 26px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
}

.doc-sync-settings:hover {
  color: var(--text-primary);
  border-color: var(--border-strong);
}

/* The navigate-up FAB, mirroring the add FAB in the opposite corner.
   Muted rather than accent-blue so "add a block" stays the one primary
   action on the canvas. */
/* Two-class selector on purpose: the base .fab rules are defined further
   down the file, so a single .fab-left would lose the background on
   source order alone. Top-left rather than bottom — a navigation action
   ("go up a level") reads as its own zone up by the breadcrumb it
   mirrors, separate from the selection controls now anchored at the
   bottom of this same edge (see .fab-stack). */
.fab.fab-left {
  right: auto;
  left: 20px;
  top: calc(var(--topbar-height) + 20px);
  bottom: auto;
  background: #2c3644;
}

.fab.fab-left:hover {
  background: #3a4556;
}

/* Floats over the canvas exactly where the name it's replacing is drawn,
   so renaming reads as editing the label in place. */
.name-editor {
  position: fixed;
  z-index: 30;
  box-sizing: border-box;
  padding: 2px 6px;
  background: var(--bg-well);
  border: 1px solid var(--accent);
  border-radius: 4px;
  color: var(--text-primary);
  font-family: -apple-system, Segoe UI, Roboto, sans-serif;
  font-size: 13px;
  text-align: center;
  outline: none;
}

.name-editor[hidden] {
  display: none;
}

.breadcrumb {
  display: flex;
  align-items: center;
  gap: 4px;
  overflow-x: auto;
  white-space: nowrap;
  scrollbar-width: none;
}

.breadcrumb::-webkit-scrollbar {
  display: none;
}

.breadcrumb .crumb {
  flex-shrink: 0;
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 12px;
  padding: 2px 4px;
  cursor: pointer;
  border-radius: 3px;
}

.breadcrumb .crumb:hover {
  color: var(--text-secondary);
  background: var(--bg-hover);
}

/* Clickable too (opens rename), so it keeps the pointer cursor and hover
   feedback of an ordinary crumb — only the color marks it as "you are
   here" rather than "go here". */
.breadcrumb .crumb.current {
  color: var(--text-primary);
}

.breadcrumb .crumb.current:hover {
  color: var(--text-strongest);
}

.breadcrumb .crumb-sep {
  flex-shrink: 0;
  color: var(--text-faint-2);
  font-size: 11px;
}

#scene-canvas {
  grid-area: canvas;
  display: block;
  width: 100%;
  height: 100%;
  background: var(--bg-canvas);
  cursor: default;
  touch-action: none;
}

/* Floating action button — the app's single primary action, following the
   mobile FAB convention so the same control works for touch and mouse.
   Sits at the viewport edge while the inspector is hidden; the desktop
   media rule below shifts it left of the inspector column when that's
   open, so it stays over the canvas rather than inside the panel. */
.fab {
  position: fixed;
  right: 20px;
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--accent-strong);
  color: white;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4), 0 2px 4px rgba(0, 0, 0, 0.3);
  cursor: pointer;
  z-index: 20;
  transition: transform 0.15s ease, background 0.15s ease, opacity 0.15s ease;
}

.fab:hover:not(:disabled) {
  background: var(--accent-strong-hover);
  transform: translateY(-1px);
}

.fab:active:not(:disabled) {
  transform: translateY(0) scale(0.94);
}

/* Once something is selected, the mini-FABs above take over that corner
   (recolor/fill/font/delete) — the add button has nothing to do with a
   selection. Disabled rather than hidden (main.js's draw loop sets the
   actual `disabled` attribute, matching selection count) — the same
   "present but inert" treatment as the mini-FABs it sits below (see
   .fab.fab-mini:disabled), so neither stack pops in and out as the
   selection comes and goes. */
.fab:disabled {
  opacity: 0.35;
  cursor: default;
}

/* Selection controls: delete/colour/font for whatever is currently
   selected, docked bottom-left — its own edge, separate from the add
   FAB(s) on the right (see .fab-stack-right below). */
.fab-stack {
  position: fixed;
  left: 20px;
  right: auto;
  bottom: calc(20px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: 10px;
  z-index: 20;
}

/* The "add text" mini-FAB, stacked directly above the primary add-block
   FAB in its corner — same relationship .fab-stack used to have with the
   add button before the selection controls moved to the opposite edge. */
.fab-stack-right {
  left: auto;
  right: 20px;
  bottom: calc(20px + 56px + 12px + env(safe-area-inset-bottom, 0px));
}

/* Wraps a mini-FAB and its own popover as siblings (see SelectionFabs.js's
   miniFabWithPopover) — positioned so the popover (see .fab-palette)
   anchors to this pair rather than to the stack as a whole, with four
   mini-FABs now sharing the column. */
.fab-mini-wrap {
  position: relative;
}

/* Smaller and muted: these act on an existing selection, so they must not
   compete with "add a block" for the eye. Positioned (not the base .fab
   rule's fixed) so it participates in the .fab-stack column layout rather
   than jumping to a fixed corner of the viewport. */
.fab.fab-mini {
  position: relative;
  /* The base .fab rule's right/bottom are meant for its own `position:
     fixed` (an offset from the viewport edge) — for `position: relative`
     they don't just get dropped, they apply *on top of* this button's
     normal flex position in the stack, silently shoving every mini-FAB an
     extra 20px+ away from the edge the stack is already docked to. This
     button's placement comes entirely from the flex column, so it needs
     none of that. */
  right: auto;
  bottom: auto;
  width: 44px;
  height: 44px;
  background: #2c3644;
}

.fab.fab-mini:hover:not(:disabled) {
  background: #3a4556;
}

/* Nothing selected: present but inert, rather than gone — a fixed
   landmark in the corner instead of controls that pop in and out as the
   selection comes and goes. */
.fab.fab-mini:disabled {
  opacity: 0.35;
  cursor: default;
}

.fab.fab-danger:hover:not(:disabled) {
  background: #a52a2f;
}

/* Delete mode armed (see main.js's toggleDeleteMode): the one mini-FAB
   that's clickable with nothing selected, so it needs its own "this is
   live" signal — solid red plus a glow, rather than just the ordinary
   idle background it shares with the other three the rest of the time. */
.fab.fab-danger.fab-danger-armed {
  background: #c0392b;
  box-shadow: 0 0 0 3px rgba(192, 57, 43, 0.35);
}

.fab.fab-danger.fab-danger-armed:hover {
  background: #a52a2f;
}

/* A host page's own selection-dependent button (see SelectionFabs.js's
   getExtraFab / main.js's window.nodigraphSelectionFab) — its default
   class, used whenever a host doesn't supply its own `className`. Amber
   rather than the neutral slate the built-in mini-FABs use, so a button
   that isn't one of nodigraph's own four reads as visually distinct at a
   glance, not just a fifth of the same thing. */
.fab.fab-extra {
  background: #8a5a12;
}

.fab.fab-extra:hover:not(:disabled) {
  background: #a86e17;
}

/* Opens to the left of its own button, vertically centered on it, rather
   than above: above is where the canvas content usually is, and the
   stack already sits near the bottom edge on a phone. */
.fab-palette {
  position: absolute;
  left: calc(100% + 12px);
  top: 50%;
  transform: translateY(-50%);
  display: grid;
  grid-template-columns: repeat(3, 28px);
  gap: 8px;
  padding: 10px;
  background: #1e2530;
  border: 1px solid #2c3644;
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.45);
}

.fab-palette[hidden] {
  display: none;
}

.fab-swatch {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, 0.18);
  border-radius: 6px;
  cursor: pointer;
  background: none;
}

.fab-swatch:hover {
  border-color: #e6e9ef;
}

/* "Back to the default" is the absence of a colour, so it is drawn as an
   absence — an outline, not a sample of the default blue, which would
   read as just another choice. */
.fab-swatch-default {
  position: relative;
  background: #12161d;
}

.fab-swatch-default::after {
  content: '';
  position: absolute;
  inset: 6px;
  border: 1px dashed #8b93a3;
  border-radius: 3px;
}

/* A checkerboard, the standard way of drawing "no color here" — plain
   `background: transparent` would just show the popover's own dark
   background through and look indistinguishable from an empty swatch. */
.fab-swatch-transparent {
  background-image:
    linear-gradient(45deg, #6b7383 25%, transparent 25%, transparent 75%, #6b7383 75%),
    linear-gradient(45deg, #6b7383 25%, transparent 25%, transparent 75%, #6b7383 75%);
  background-size: 10px 10px;
  background-position: 0 0, 5px 5px;
  background-color: #1e2530;
}

.fab-swatch-custom {
  -webkit-appearance: none;
  appearance: none;
  background: none;
}

.fab-swatch-custom::-webkit-color-swatch-wrapper {
  padding: 2px;
}

.fab-swatch-custom::-webkit-color-swatch {
  border: none;
  border-radius: 4px;
}

/* The font picker's own popover: a small font dialog (family, size, bold,
   italic) rather than a single-pick swatch grid — a block label needed the
   same handful of controls any other piece of styled text does. */
.fab-font-panel {
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 190px;
  padding: 12px;
}

.fab-font-panel select,
.fab-font-panel input[type='number'] {
  background: #12161d;
  border: 1px solid #2c3644;
  color: #e6e9ef;
  border-radius: 6px;
  padding: 6px 8px;
  font-size: 13px;
}

.fab-font-family {
  width: 100%;
}

.fab-font-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.fab-font-row label {
  color: #8b93a3;
  font-size: 12px;
}

.fab-font-size {
  width: 64px;
}

.fab-font-style-row {
  justify-content: flex-start;
}

.fab-font-style-btn {
  width: 34px;
  height: 30px;
  background: #12161d;
  border: 1px solid #2c3644;
  border-radius: 6px;
  color: #e6e9ef;
  font-size: 14px;
  cursor: pointer;
}

.fab-font-style-btn:hover {
  border-color: #3a4556;
}

.fab-font-style-btn.active {
  background: #2f6fed;
  border-color: #2f6fed;
  color: #fff;
}

/* A fixed panel over the canvas — on desktop it slides in from the right
   (see the min-width media query below), the same overlay treatment the
   left-hand app menu already uses (see .topbar-menu), rather than a grid
   column that pushed the canvas aside and resized it on every open/close.
   Mobile overrides this into a bottom sheet instead (see the max-width
   media query further down). */
#inspector {
  position: fixed;
  top: var(--topbar-height);
  right: 0;
  bottom: 0;
  width: 300px;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  border-left: 1px solid var(--border);
  box-shadow: -12px 0 32px rgba(0, 0, 0, 0.3);
  padding: 14px;
  overflow-y: auto;
  font-size: 13px;
  z-index: 25;
}

/* Scoped to desktop rather than living in the base rule above: the mobile
   bottom sheet (see the max-width media query) sets its own transform/
   transition for a translateY slide, and `body.inspector-open #inspector`
   would out-specificity `#inspector.open` (an extra type selector beats an
   extra class) and win regardless of source order, sliding sideways
   there too if this weren't kept out of its way entirely. */
@media (min-width: 769px) {
  #inspector {
    transform: translateX(100%);
    transition: transform 0.22s ease;
  }

  body.inspector-open #inspector {
    transform: translateX(0);
  }
}

/* The inspector now overlays the same corner the FABs are docked in
   (rather than pushing the canvas, which used to leave them plainly
   visible beside it) — faded out and inert while it's open, the same
   treatment the mobile bottom sheet already used before this was also
   true on desktop. */
body.inspector-open .fab,
body.inspector-open .fab-stack {
  opacity: 0;
  pointer-events: none;
}

#inspector h3 {
  margin: 0 0 12px;
  font-size: 13px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

#inspector h4 {
  margin: 18px 0 10px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

#inspector .sheet-header {
  display: none;
}

#inspector .tab-bar {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

#inspector .tab-button {
  flex: 1;
  background: none;
  border: none;
  color: var(--text-muted);
  padding: 8px 4px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

#inspector .tab-button.active {
  color: var(--text-primary);
  border-bottom-color: var(--accent-strong);
}

#inspector .tab-button:hover {
  color: var(--text-secondary);
}

#inspector .hint-text {
  margin: 10px 0 0;
  font-size: 11px;
  color: var(--text-faint);
  line-height: 1.4;
}

#inspector select {
  background: var(--bg-well);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 4px;
  padding: 5px 7px;
  font-size: 13px;
}

#inspector select:focus {
  outline: 1px solid var(--accent-strong);
}

#inspector .description-editor {
  width: 100%;
  background: var(--bg-well);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 6px;
  padding: 8px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.5;
  resize: vertical;
}

#inspector .description-editor:focus {
  outline: 1px solid var(--accent-strong);
}

#inspector .description-editor::placeholder {
  color: var(--text-faint-2);
}

#inspector .apply-row {
  margin-top: 8px;
  display: flex;
  justify-content: flex-end;
}

#inspector .apply-row button {
  background: var(--bg-hover);
  color: var(--text-primary);
  border: none;
  border-radius: 4px;
  padding: 6px 12px;
  font-size: 12px;
  cursor: pointer;
}

#inspector .apply-row button:hover {
  background: var(--bg-hover-strong);
}

#inspector .port-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 4px;
}

#inspector .port-dir-select {
  flex-shrink: 0;
  width: 58px;
  padding: 5px 4px;
  font-size: 11px;
}

#inspector .port-name-input {
  flex: 1;
  min-width: 0;
}

/* How many pins (see BlockDescription's module doc) a logical port's one
   row actually has on the canvas — only shown past a single pin, since a
   plain unlisted count would just be visual noise for the common case. */
#inspector .port-pin-count {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--text-secondary);
}

#inspector .port-delete-button {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  background: none;
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text-muted);
  font-size: 14px;
  line-height: 1;
  cursor: pointer;
}

#inspector .port-delete-button:hover {
  color: var(--danger);
  border-color: var(--danger);
}

#inspector .port-desc-input {
  width: 100%;
  margin-bottom: 12px;
  font-size: 11px;
  color: var(--text-secondary);
}

#inspector .delete-row {
  margin-top: 20px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

#inspector .delete-button {
  width: 100%;
  background: transparent;
  border: 1px solid var(--danger);
  color: var(--danger);
  border-radius: 4px;
  padding: 8px;
  font-size: 12px;
  cursor: pointer;
}

#inspector .delete-button:hover {
  background: var(--danger-tint);
}

#inspector .field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}

#inspector label {
  color: var(--text-muted);
  font-size: 11px;
}

#inspector input {
  background: var(--bg-well);
  border: 1px solid var(--border);
  color: var(--text-primary);
  border-radius: 4px;
  padding: 5px 7px;
  font-size: 13px;
}

#inspector input:focus {
  outline: 1px solid var(--accent-strong);
}

#inspector .empty {
  color: var(--text-muted);
  font-style: italic;
}

#inspector .row {
  display: flex;
  gap: 8px;
}

#inspector .row .field {
  flex: 1;
}

/* App-like layout below the mobile breakpoint: the inspector becomes a
   bottom sheet that slides up over the full-bleed canvas instead of eating
   permanent screen width. */
@media (max-width: 768px) {
  /* The brand name is the one thing here that isn't a control, so it's
     the first to go on a narrow screen — the hamburger, breadcrumb, and
     header-action icons all need the room more than it does. */
  #topbar {
    gap: 8px;
    padding: 0 10px;
    position: relative;
  }

  #topbar .brand {
    display: none;
  }

  #inspector {
    position: fixed;
    /* Reset against the desktop base rule above, which pins `top` to
       anchor its own right-edge slide-in — this sheet anchors to `bottom`
       instead, with `max-height` (not `top`) capping how tall it grows. */
    top: auto;
    width: auto;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 65vh;
    border-left: none;
    border-top: 1px solid var(--border);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 24px rgba(0, 0, 0, 0.45);
    padding: 8px 16px 20px;
    padding-bottom: calc(20px + env(safe-area-inset-bottom, 0px));
    transform: translateY(100%);
    transition: transform 0.25s ease;
    z-index: 15;
  }

  #inspector.open {
    transform: translateY(0);
  }

  #inspector .sheet-header {
    display: flex;
    justify-content: center;
    padding: 6px 0 10px;
  }

  #inspector .sheet-header .grabber {
    width: 36px;
    height: 4px;
    border-radius: 2px;
    background: var(--border-strong);
  }

  #inspector h3 {
    display: none;
  }
}

/* --- Share dialog -------------------------------------------------- */

.share-dialog {
  width: min(560px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-surface-2);
  color: var(--text-primary);
  font-family: -apple-system, Segoe UI, Roboto, sans-serif;
  font-size: 13px;
}

.share-dialog::backdrop {
  background: rgba(0, 0, 0, 0.55);
}

.share-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 14px 16px 10px;
}

.share-header h2 {
  margin: 0;
  font-size: 15px;
}

.share-close {
  background: none;
  border: none;
  color: var(--text-muted);
  font-size: 15px;
  cursor: pointer;
  padding: 4px 6px;
  line-height: 1;
}

.share-close:hover {
  color: var(--text-primary);
}

.share-body {
  padding: 16px;
  max-height: 70vh;
  overflow-y: auto;
}

.share-field {
  margin-bottom: 14px;
}

.share-field label {
  display: block;
  margin-bottom: 5px;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

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

.share-row input {
  flex: 1;
  min-width: 0;
  background: var(--bg-well);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 7px 9px;
  color: var(--text-primary);
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.share-row input:focus {
  outline: 1px solid var(--accent-strong);
}

/* A text field standing alone in a .share-field, rather than sharing a
   .share-row with a Copy button — GitHubConnectDialog's repo-path and
   token inputs. Same look as .share-row input, but full width since
   there's no sibling button to leave room for. */
.share-input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-well);
  border: 1px solid var(--border);
  border-radius: 5px;
  padding: 7px 9px;
  color: var(--text-primary);
  font-size: 12px;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
}

.share-input:focus {
  outline: 1px solid var(--accent-strong);
}

.share-button {
  flex-shrink: 0;
  background: var(--bg-hover);
  border: none;
  border-radius: 5px;
  padding: 7px 12px;
  color: var(--text-primary);
  font-size: 12px;
  cursor: pointer;
}

.share-button:hover {
  background: var(--bg-hover-strong);
}

.share-button-primary {
  background: var(--accent-strong);
  color: #fff;
}

.share-button-primary:hover {
  background: var(--accent-strong-hover);
}

.share-hint {
  margin: 6px 0 0;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-faint);
}

.share-checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: 6px;
  margin: 10px 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--text-muted);
  cursor: pointer;
}

.share-checkbox-row input {
  margin-top: 2px;
}

.share-hint a {
  color: var(--accent);
}

/* A field revealed on demand rather than always shown — see
   GitHubConnectDialog's collapsed token field for a public repo, where
   most opens need no credential at all. */
.share-link-button {
  display: block;
  margin: 0 0 14px;
  padding: 0;
  border: none;
  background: none;
  color: var(--accent);
  font-size: 12px;
  text-decoration: underline;
  cursor: pointer;
}

.share-link-button:hover {
  color: var(--accent-strong);
}

.share-warning {
  margin: 14px 0 0;
  padding: 9px 11px;
  border-radius: 6px;
  border: 1px solid var(--warning-border);
  background: var(--warning-tint);
  color: var(--warning-text);
  font-size: 11px;
  line-height: 1.45;
}

.share-preview {
  margin-bottom: 12px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-canvas);
  text-align: center;
}

.share-preview img {
  max-width: 100%;
  max-height: 220px;
}

