/*
 * Flowi editor chrome.
 *
 * Design principles applied here, in priority order:
 *
 *  1. The canvas is the product. Chrome is quiet, low-contrast and thin so
 *     that the user's work is the only saturated thing on screen.
 *  2. Progressive disclosure. The inspector shows what applies to the current
 *     selection; there is no wall of permanently-greyed controls.
 *  3. Keyboard first. Every interactive element has a visible focus ring, and
 *     focus is never removed without a replacement.
 *  4. Respect the system. Dark mode, reduced motion and forced colours are
 *     honoured rather than overridden.
 */

:root {
  --bg: #ffffff;
  /* The canvas is a shade *below* the panels, so artwork sits on a surface
     rather than bleeding into the chrome. This is the single change that most
     makes an editor read as a canvas tool rather than a web page. */
  --bg-sunken: #ededed;
  --panel: #ffffff;
  --border: #e6e6e6;
  --border-strong: #d5d5d5;
  --text: #14161a;
  --text-muted: #6a7178;
  --text-subtle: #9aa1a8;
  --accent: #2f6bff;
  --accent-soft: rgba(47, 107, 255, 0.1);
  /* Components. Figma's purple, and it means exactly one thing everywhere it
     appears: this is a component or an instance of one. */
  --component: #8b5cf6;
  --component-soft: rgba(139, 92, 246, 0.12);
  /* The hover wash. It was referenced in three places and defined in none, so
     `var(--hover)` resolved to nothing and those three controls had no hover
     state at all — invisible to every test, since the harness has no CSS
     engine and nothing here is a layout error. */
  --hover: rgba(15, 23, 41, 0.06);
  --danger: #e5484d;
  --positive: #12a76a;
  --toolbar-height: 48px;
  --status-height: 26px;
  /* The floating tool pill is dark in both themes: it reads as an instrument
     over the artwork rather than as another panel. */
  --dock: #1c1c1e;
  --dock-text: #f2f3f5;
  --dock-muted: #9ba0a8;
  --dock-hover: rgba(255, 255, 255, 0.1);
  --sidebar-width: 248px;
  --radius: 6px;
  --shadow-panel: 0 8px 32px rgba(15, 23, 41, 0.14);
  --font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1b1d21;
    --bg-sunken: #101215;
    --panel: #1b1d21;
    --border: #2b2f36;
    --border-strong: #3a404a;
    --text: #e8eaed;
    --text-muted: #9aa3b0;
    --text-subtle: #6c7684;
    --accent: #6f95ff;
    --accent-soft: rgba(111, 149, 255, 0.16);
  --component: #a78bfa;
  --component-soft: rgba(167, 139, 250, 0.18);
    --hover: rgba(255, 255, 255, 0.07);
    --shadow-panel: 0 8px 32px rgba(0, 0, 0, 0.5);
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  height: 100%;
  overflow: hidden;
  font-family: var(--font);
  font-size: 13px;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

#app {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

/* Visible focus for every interactive element. Never `outline: none`. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-radius: 3px;
}

/* ---------------------------------------------------------------- toolbar */

.toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--toolbar-height);
  padding: 0 12px;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

/* The mark is the application menu. It reads as a wordmark and behaves as a
   button, which is the arrangement every design tool without an OS menu bar
   has arrived at. */
.brand {
  font-weight: 700;
  letter-spacing: -0.3px;
  font-size: 14px;
  padding: 4px 8px 4px 4px;
  margin-right: 2px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text);
  font-family: inherit;
  cursor: pointer;
}

.brand:hover,
.brand:focus-visible {
  background: var(--hover);
}

/* --- How much of the interface is on screen --------------------------------

   Two states beyond normal. `minimal` keeps the toolbar and the tool dock, so
   the file is still identifiable and still drawable in; `hidden` is the
   presentation state and leaves the canvas alone with the status bar.

   Panels are hidden with `display: none` rather than moved off screen: an
   off-screen panel keeps re-rendering on every document change, which is work
   nobody can see — the same reason the sidebar tabs unmount rather than hide. */
.ui-minimal .sidebar-right,
.ui-minimal .pages-section,
.ui-minimal .pages-heading {
  display: none;
}

.ui-hidden .sidebar,
.ui-hidden .toolbar,
.ui-hidden .tool-dock {
  display: none;
}

/* Interface scale, applied to the chrome and never to the canvas. Scaling the
   canvas host would change the relationship between a CSS pixel and a design
   pixel, so "make the interface bigger" would silently zoom the artwork — and
   every pointer coordinate would need the factor divided back out. */
.toolbar,
.sidebar,
.tool-dock,
.status-bar,
.context-menu {
  zoom: var(--ui-scale, 1);
}

.tool-group {
  display: flex;
  align-items: center;
  gap: 2px;
}

.spacer {
  flex: 1 1 auto;
}

/* ---------------------------------------------------------------------------
   The tool dock — a floating pill over the canvas.

   Tools live at the bottom centre rather than in the top bar for a reason
   beyond fashion: the pointer spends its time on the artwork, and the bottom
   centre is a shorter trip from anywhere on the canvas than the top-left
   corner. It also frees the top bar to be about the *document* — name, share,
   zoom — instead of mixing document and drawing controls in one strip.
   --------------------------------------------------------------------------- */

.tool-dock {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 6px;
  border-radius: 14px;
  background: var(--dock);
  color: var(--dock-text);
  box-shadow:
    0 10px 32px rgba(0, 0, 0, 0.28),
    0 1px 2px rgba(0, 0, 0, 0.3);
  /* Above the canvas, below any dialog or the command palette. */
  z-index: 20;
}

.tool-dock .dock-divider {
  width: 1px;
  height: 22px;
  margin: 0 6px;
  background: rgba(255, 255, 255, 0.16);
  flex: none;
}

/* The Actions button is not a tool — it opens a list — so it is not a
   `.tool-button`: the dock counts those, and a control that is not a tool
   sitting in that count is how "every tool has a button" stops being true
   without anything failing. It borrows the size and the states. */
.dock-actions {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--dock-muted);
  font-size: 13px;
  cursor: pointer;
}

.dock-actions:hover {
  background: var(--dock-hover);
  color: var(--dock-text);
}

.dock-actions .icon {
  font-size: 20px;
}

.tool-button {
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 9px;
  background: transparent;
  color: var(--dock-muted);
  font-size: 13px;
  cursor: pointer;
}

.tool-button:hover {
  background: var(--dock-hover);
  color: var(--dock-text);
}

.tool-button.active {
  background: var(--accent);
  color: #fff;
}

/* The dock is the one place the icon is the *only* thing identifying a
   control — every other icon in the app sits next to a label or in a row whose
   context gives it away. It gets the largest size for that reason. */
.tool-button .icon {
  font-size: 22px;
}

/* ---------------------------------------------------------------------------
   Tool groups and their flyouts.

   Figma's bar is groups rather than buttons because the shapes are
   alternatives to one another: you are picking *a shape*, and the one you
   picked last is nearly always the one you want next. So the group shows its
   most recent member and a small caret opens the rest — one click for the
   common case, two for the other four.
   --------------------------------------------------------------------------- */
.tool-cell {
  position: relative;
  display: flex;
  align-items: center;
  border-radius: 9px;
}

/* A split button: the tool takes the left of the pill, the chevron the right,
   and the two share one rounded outline so it reads as one control with two
   halves rather than as two buttons that happen to touch. */
.tool-cell.has-caret .tool-button {
  width: 32px;
  border-radius: 9px 0 0 9px;
}

.tool-caret {
  display: grid;
  place-items: center;
  width: 16px;
  height: 38px;
  padding: 0;
  border: 0;
  border-radius: 0 9px 9px 0;
  background: transparent;
  color: var(--dock-muted);
  font-size: 9px;
  line-height: 1;
  cursor: pointer;
}

.tool-caret .icon {
  font-size: 11px;
}

/* Hovering either half lights both, because pressing either one is a way of
   getting at the same group. */
.tool-cell.has-caret:hover .tool-button,
.tool-cell.has-caret:hover .tool-caret {
  background: var(--dock-hover);
  color: var(--dock-text);
}

.tool-caret:hover,
.tool-caret[aria-expanded='true'] {
  background: var(--dock-hover);
  color: var(--dock-text);
}

/* The active pill: both halves take the accent, so the chevron does not sit
   as a dark notch cut out of a blue button. */
.tool-cell.is-active .tool-caret,
.tool-cell.is-active:hover .tool-caret {
  background: var(--accent);
  color: rgba(255, 255, 255, 0.75);
}

.tool-cell.is-active .tool-caret:hover {
  color: #fff;
}

.tool-flyout {
  position: absolute;
  bottom: calc(100% + 8px);
  left: -4px;
  /* Wide enough for the longest row — "Place image…" with ⌃⇧K beside it —
     because a menu whose labels wrap is a menu that looks broken. */
  min-width: 214px;
  padding: 5px;
  border-radius: 11px;
  background: var(--dock);
  color: var(--dock-text);
  box-shadow:
    0 10px 32px rgba(0, 0, 0, 0.34),
    0 1px 2px rgba(0, 0, 0, 0.3);
  /* Above the dock it belongs to, and above the canvas — but still below a
     dialog, which is the only thing entitled to cover it. */
  z-index: 25;
}

.tool-flyout-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  height: 30px;
  padding: 0 9px 0 7px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--dock-muted);
  font-family: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.tool-flyout-item:hover,
.tool-flyout-item:focus-visible {
  background: var(--dock-hover);
  color: var(--dock-text);
}

.tool-flyout-item.is-on {
  color: var(--dock-text);
}

.tool-flyout-item .icon {
  font-size: 16px;
  width: 18px;
  text-align: center;
}

.tool-flyout-name {
  flex: 1 1 auto;
  min-width: 0;
  white-space: nowrap;
}

/* The shortcut is a reminder, not a label: dimmed and right-aligned, so the
   eye reads the names down the left and finds the key only when looking. */
.tool-flyout-key {
  flex: 0 0 auto;
  color: var(--dock-muted);
  font-size: 11px;
  opacity: 0.75;
}

/* The zoom readout rides in the dock, where the eye already is. */
.dock-zoom {
  min-width: 54px;
  height: 38px;
  padding: 0 8px;
  border: 0;
  border-radius: 9px;
  background: transparent;
  color: var(--dock-muted);
  font: inherit;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

.dock-zoom:hover {
  background: var(--dock-hover);
  color: var(--dock-text);
}

.labelled-button {
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
}

.labelled-button:hover:not(:disabled) {
  background: var(--bg-sunken);
}

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

.file-button input[type='file'] {
  display: none;
}

/* ------------------------------------------------------------------- body */

.body {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}

/* The widths come from custom properties the splitters write, so the drag
   supplies a number and the stylesheet keeps ownership of the layout. */
.sidebar {
  background: var(--panel);
  display: flex;
  flex-direction: column;
  min-height: 0;
  /* The handle is absolutely positioned against this. */
  position: relative;
}

.sidebar.left {
  width: var(--sidebar-left-width, var(--sidebar-width));
  flex: 0 0 var(--sidebar-left-width, var(--sidebar-width));
  border-right: 1px solid var(--border);
}

.sidebar.right {
  width: var(--sidebar-right-width, 264px);
  flex: 0 0 var(--sidebar-right-width, 264px);
  border-left: 1px solid var(--border);
}

/* ---------------------------------------------------------------- splitters

   Six pixels wide, with a one-pixel line drawn inside on hover. A handle only
   as wide as the line it draws is a handle nobody can grab: the target has to
   be bigger than the thing it looks like, which is why this is a transparent
   strip that straddles the border rather than the border itself. */
.splitter {
  position: absolute;
  z-index: 5;
  background: transparent;
}

.splitter::after {
  content: '';
  position: absolute;
  inset: 0;
  background: transparent;
  transition: background 100ms linear;
}

/* The divider itself, which the splitter did not draw.
 *
 * A resize handle is invisible until you hover it, and that is right for a
 * handle — but it was also the only thing between the pages list and the panel
 * below it, so two unrelated regions ran together into one column and the
 * boundary appeared only while the pointer was on it. A hairline at rest is
 * what makes them read as two panels; the accent band above still takes over
 * on hover, because `::after` is painted after this and covers it. */
.splitter::before {
  content: '';
  position: absolute;
  background: var(--border);
}

.splitter-horizontal::before {
  top: 0;
  bottom: 0;
  left: 2px;
  width: 1px;
}

.splitter-vertical::before {
  left: 0;
  right: 0;
  top: 2px;
  height: 1px;
}

.splitter:hover::after,
.splitter:focus-visible::after,
body.resizing-horizontal .splitter-horizontal:active::after,
body.resizing-vertical .splitter-vertical:active::after {
  background: var(--accent);
}

.splitter:focus-visible {
  outline: none;
}

.splitter-horizontal {
  top: 0;
  bottom: 0;
  width: 6px;
  cursor: col-resize;
}

.splitter-horizontal::after {
  left: 2px;
  right: 2px;
}

.splitter-vertical {
  left: 0;
  right: 0;
  height: 6px;
  cursor: row-resize;
}

.splitter-vertical::after {
  top: 2px;
  bottom: 2px;
}

/* `end` is the right edge for a horizontal handle and the bottom for a
   vertical one; `start` is the mirror. */
.splitter-horizontal.splitter-end { right: -3px; }
.splitter-horizontal.splitter-start { left: -3px; }
.splitter-vertical.splitter-end { bottom: -3px; }
.splitter-vertical.splitter-start { top: -3px; }

/* During a drag the cursor and the no-select apply to the whole window, or
   dragging across the layer tree highlights every row it passes. */
body.resizing-horizontal,
body.resizing-horizontal * {
  cursor: col-resize !important;
  user-select: none;
}

body.resizing-vertical,
body.resizing-vertical * {
  cursor: row-resize !important;
  user-select: none;
}

.sidebar-heading {
  height: 32px;
  display: flex;
  align-items: center;
  padding: 0 12px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-subtle);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}

.panel-body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.canvas-host {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  background: var(--bg-sunken);
}

/* The panels float over a sunken canvas, Pixso-style, rather than butting
   against it — the seam is a shadow, not a hard line. */
.sidebar.left {
  box-shadow: 1px 0 0 var(--border);
}

.sidebar.right {
  box-shadow: -1px 0 0 var(--border);
}

.flowi-scene,
.flowi-overlay {
  position: absolute;
  inset: 0;
  display: block;
}

.flowi-overlay {
  /* The overlay receives all pointer input; the scene canvas never does. */
  touch-action: none;
}

/* The focus target input methods attach to, parked under the text caret.

   Every property here is load-bearing, and most are the *opposite* of what
   "hide this element" usually means. `display: none`, `visibility: hidden` and
   the `hidden` attribute all make an element unfocusable, and an unfocusable
   proxy means no composition events at all — no Japanese, Chinese or Korean
   input and no dead-key accents — with nothing on screen to suggest why. So it
   is transparent and one pixel wide, but it is laid out, focusable, and
   positioned at the caret: the candidate window is placed relative to it, and a
   proxy parked at the origin opens the candidate list in the corner of the
   viewport rather than beside the word being typed. `pointer-events: none`
   stops it stealing clicks meant for the canvas underneath. */
.flowi-ime-proxy {
  position: absolute;
  left: 0;
  top: 0;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: 0;
  border: 0;
  outline: none;
  resize: none;
  overflow: hidden;
  opacity: 0;
  z-index: 1;
  pointer-events: none;
  background: transparent;
  color: transparent;
  /* Some platforms size the candidate window from the focused element's font;
     matching the UI default keeps it from opening at 1px type. */
  font: 13px ui-sans-serif, system-ui, sans-serif;
}

/* ----------------------------------------------------------------- layers */

.layer-list {
  padding: 4px 0;
}

.layer-row {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding-right: 8px;
  cursor: default;
  user-select: none;
  color: var(--text);
}

.layer-row:hover {
  background: var(--bg-sunken);
}

/* Hovered from the canvas. The same background as `:hover`, because it is the
   same state — the pointer just happens to be over the object rather than over
   the row. Kept below `.selected` so a selected row stays reading as selected. */
.layer-row.is-hovered {
  background: var(--bg-sunken);
}

.layer-row.selected,
.layer-row.selected.is-hovered {
  background: var(--accent-soft);
  color: var(--accent);
}

.layer-row.hidden-layer .layer-name {
  opacity: 0.45;
}

.twisty,
.twisty-spacer {
  width: 14px;
  flex: 0 0 14px;
  min-width: 0;
  border: none;
  background: none;
  color: var(--text-subtle);
  font-size: 9px;
  cursor: pointer;
  padding: 0;
}

/* Sized for a real icon, not for a box-drawing character. The old 14px slot at
   11px type was chosen for `▢`, which the system font drew hairline and small;
   a Phosphor glyph at the app's own size needs the room and gets the weight
   right for free. */
.layer-icon {
  width: 18px;
  flex: 0 0 18px;
  min-width: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
}

.layer-icon .icon {
  font-size: 16px;
}

/* Components and their instances are the one place in the tree where the icon
   carries meaning beyond the shape — they are worth finding at a glance. The
   two share this colour and differ by the glyph: a cluster of four diamonds is
   the main component, one hollow diamond is an instance of it. */
.component-icon,
.instance-icon {
  color: var(--component);
}

/* The whole row, not only the glyph. Scanning a tree means reading names, and
   a name that is purple answers "is this a component" without the eye having
   to travel to the icon and back. Selection still wins — a selected row is
   blue, because what is selected outranks what kind of thing it is. */
.layer-row.is-component .layer-name,
.layer-row.is-instance .layer-name {
  color: var(--component);
}

.layer-row.selected.is-component .layer-name,
.layer-row.selected.is-instance .layer-name {
  color: var(--accent);
}

.layer-row.selected .layer-icon {
  color: var(--accent);
}

.layer-name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
}

.layer-actions {
  display: none;
  gap: 2px;
}

.layer-row:hover .layer-actions {
  display: flex;
}

/* -------------------------------------------------------------- inspector */

.panel-section {
  padding: 10px 12px;
  border-bottom: 1px solid var(--border);
}

/* The row above Position: the layer's name, and the buttons that change what
   kind of thing it is. Not a `.panel-section` — it has no heading, because the
   name *is* the heading. */
.inspector-title {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--border);
}

.inspector-name {
  flex: 1 1 auto;
  /* `min-width: auto` on a flex item resolves to an input's min-content size,
     about 155px, and silently beats the basis — see the note on the field
     rules. Every fixed-basis input in this file resets it for that reason. */
  min-width: 0;
  height: 26px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 5px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  text-overflow: ellipsis;
}

.inspector-name:hover {
  border-color: var(--border);
}

.inspector-name:focus {
  border-color: var(--accent);
  background: var(--bg);
  outline: none;
}

.inspector-title-actions {
  display: flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
}

/* The heading is a row: title on the left, the section's own actions on the
   right. A fixed landmark, so "where do I add another one?" has the same answer
   however long the list already is. */
.section-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 8px;
  min-height: 20px;
}

.panel-section h3 {
  margin: 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.section-actions {
  display: flex;
  gap: 2px;
  flex: 0 0 auto;
}

/* The actions stay quiet until the section is under the pointer — a row of
   permanently-lit buttons beside every heading competes with the values, which
   are what the panel is for. They remain focusable throughout, so the keyboard
   is not affected by a hover rule. */
.section-actions .icon-button {
  min-width: 20px;
  min-height: 20px;
  opacity: 0.55;
}

.panel-section:hover .section-actions .icon-button,
.section-actions .icon-button:focus-visible,
.section-actions .icon-button.is-on {
  opacity: 1;
}

.section-actions .icon-button.is-on {
  color: var(--accent);
}

.field {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  min-width: 0;
}

.field-label {
  flex: 0 0 62px;
  /* Same trap as the number fields: without this, a label longer than 62px is
     never allowed to shrink to its basis, so "Corner radius" wrapped onto two
     lines and pushed its own field down with it. */
  min-width: 0;
  font-size: 11px;
  color: var(--text-muted);
}

.field input,
.field select,
.field textarea {
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
}

.field textarea {
  height: auto;
  padding: 5px 7px;
  resize: vertical;
  font-family: inherit;
}

.field input[type='checkbox'] {
  flex: 0 0 auto;
  height: auto;
  width: auto;
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--accent);
}

.field-pair {
  display: flex;
  gap: 8px;
}

/* Inside a pair the label goes *above* its field rather than beside it.
   Side by side, a two-word label ("Corner radius", "Bottom right") eats most of
   a half-width column and leaves the input too narrow to show two digits — so
   `24` rendered as `2`, which reads as the wrong value rather than as a clipped
   one. That is the worst kind of layout bug: it lies about the document.
   Stacking is also what Figma does, for the same reason. */
.field-pair .field {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  flex: 1 1 0;
  margin-bottom: 6px;
}

.field-pair .field-label {
  flex: 0 0 auto;
  min-width: 0;
}

/* A checkbox needs no room to speak of, so its label takes the rest rather
   than being squeezed into 62px and wrapping "Clip content" onto two lines. */
.field input[type='checkbox'] {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  order: -1;
}

.field:has(input[type='checkbox']) .field-label {
  flex: 1 1 auto;
}

.field.readonly .field-value {
  flex: 1 1 auto;
  font-family: var(--mono);
  font-size: 11px;
  color: var(--text-subtle);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.color-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

/* The swatch that opens the picker.

   Chequerboard behind the colour, because a fill at 20% opacity over a solid
   panel background looks like a *pale* colour rather than a transparent one —
   and the difference matters more here than anywhere else in the inspector. */
/* An image fill: swatch, scale mode, opacity, and the two buttons — one row,
   the same shape as the solid and gradient rows above it, so a fill list of
   mixed kinds still reads as a list rather than as three different controls. */
.paint-row {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

.paint-row .field {
  flex: 1 1 auto;
  min-width: 0;
}

/* The image swatch carries a word rather than a colour, so it needs to be
   legible at 26px — and it is not a button, unlike its solid-fill neighbour. */
.paint-row .color-swatch {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--text-muted);
  cursor: default;
}

.color-swatch {
  width: 26px;
  height: 26px;
  flex: 0 0 26px;
  min-width: 0;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 5px;
  cursor: pointer;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%),
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
  position: relative;
  overflow: hidden;
}

.color-swatch::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--swatch-color, #000);
  opacity: var(--swatch-alpha, 1);
}

.color-swatch[aria-expanded='true'] {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-soft);
}

/* --------------------------------------------------------------- gradients

   The gradient fill *row* is the same shape as a solid one — swatch, opacity,
   eye, minus — and everything that used to live inline in the sidebar now
   lives in the popover the swatch opens. */

.color-swatch.is-gradient::after {
  background: var(--gradient, #000);
  opacity: 1;
}

.gradient-label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 11px;
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------- the gradient editor, in the
   colour popover */

.color-popover.is-gradient {
  width: 268px;
}

.cp-pane {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.cp-pane[hidden] {
  display: none;
}

.cp-tabs {
  display: flex;
  gap: 2px;
  padding: 2px;
  border-radius: 7px;
  background: var(--bg);
}

.cp-tab {
  flex: 1 1 0;
  min-width: 0;
  height: 24px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.cp-tab.is-on {
  background: var(--panel);
  color: var(--text);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.12);
}

.cp-gradient {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cp-gradient-head {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cp-gradient-kind {
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
}

.cp-gradient-angle {
  flex: 0 0 52px;
  min-width: 0;
  height: 26px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
  text-align: center;
}

/* The ramp draws the same CSS gradient the browser would, over the same
   chequerboard as a solid swatch — a gradient fading to transparent has to
   look like it fades to transparent, not like it fades to white.

   `position: relative` is load-bearing: the chips are positioned against it,
   and without it they would place themselves against the page. */
.cp-ramp {
  position: relative;
  height: 32px;
  margin-top: 12px;
  border-radius: 6px;
  cursor: copy;
  touch-action: none;
  background-color: #fff;
  background-image:
    var(--gradient, none),
    linear-gradient(45deg, #d8dbe0 25%, transparent 25%, transparent 75%, #d8dbe0 75%),
    linear-gradient(45deg, #d8dbe0 25%, transparent 25%, transparent 75%, #d8dbe0 75%);
  background-size: auto, 10px 10px, 10px 10px;
  background-position: 0 0, 0 0, 5px 5px;
  box-shadow: inset 0 0 0 1px var(--border);
}

.cp-ramp-chips {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* A chip sitting above the ramp, pointing at the position it marks. Above
   rather than on it, so the colour underneath stays visible — a handle drawn
   over the gradient hides the exact colour being adjusted. */
.cp-chip {
  position: absolute;
  pointer-events: auto;
  top: -10px;
  left: var(--at, 0%);
  width: 14px;
  height: 14px;
  margin-left: -7px;
  padding: 0;
  border: 2px solid var(--panel);
  border-radius: 4px;
  background: var(--stop-color, #fff);
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.3);
  cursor: grab;
  touch-action: none;
}

.cp-chip:active {
  cursor: grabbing;
}

.cp-chip.is-selected {
  /* The selected chip is taller rather than a different colour: it already
     carries a colour, and recolouring the marker would be a lie about the
     stop. */
  top: -14px;
  height: 18px;
  box-shadow: 0 0 0 1px var(--accent), 0 1px 4px rgba(0, 0, 0, 0.3);
}

.cp-chip:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}

.cp-stops-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}

.cp-stops {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 132px;
  overflow-y: auto;
}

.cp-stop {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 2px;
  border-radius: 5px;
}

.cp-stop.is-selected {
  background: var(--accent-soft);
}

.cp-stop-position,
.cp-stop-opacity {
  flex: 0 0 40px;
  min-width: 0;
  height: 24px;
  padding: 0 3px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
  text-align: center;
}

.cp-stop-hex {
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;
  padding: 0 5px;
  border: 1px solid var(--border);
  border-radius: 4px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
  text-transform: uppercase;
}

.cp-stop-swatch {
  flex: 0 0 20px;
  min-width: 0;
  width: 20px;
  height: 20px;
  border-radius: 4px;
  position: relative;
  overflow: hidden;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%),
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
  box-shadow: inset 0 0 0 1px var(--border);
}

.cp-stop-swatch::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--swatch-color, #000);
  opacity: var(--swatch-alpha, 1);
}

/* ------------------------------------------------------------- library tab */

.cp-library {
  max-height: 320px;
  overflow-y: auto;
  gap: 1px;
}

.cp-library-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.cp-library-row:hover {
  background: var(--hover);
}

.cp-library-row.is-bound {
  background: var(--accent-soft);
}

.cp-library-swatch {
  flex: 0 0 18px;
  min-width: 0;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: var(--swatch-color, #000);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

.cp-library-text {
  min-width: 0;
  display: flex;
  flex-direction: column;
  line-height: 1.3;
}

.cp-library-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cp-library-origin {
  color: var(--text-muted);
  font-size: 10px;
}

/* ------------------------------------------------------- token gear + list

   The gear lives inside the field's box, so a bound property reads as one
   control with a state rather than as a number that happens to have a button
   near it. */

.input-with-gear {
  display: flex;
  align-items: center;
  gap: 2px;
}

.input-with-gear input {
  flex: 1 1 auto;
  min-width: 0;
}

/* ---------------------------------------------------------------------------
   The number field: a glyph you can drag, the value, and a gear.

   One box with a border, three things inside it that have no borders of their
   own — which is what makes it read as a single control rather than as a row
   of three. Figma's inspector is built the same way, and the reason is that
   the glyph and the gear are *about* the number; drawn as separate controls
   they look like neighbours instead.
   --------------------------------------------------------------------------- */
.number-input {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 2px 0 0;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
}

.number-input:focus-within {
  border-color: var(--accent);
}

/* The input loses its own box: the wrapper is the box now. Leaving both gives
   a border inside a border, which at 26px tall looks like a rendering fault. */
.field .number-input input {
  height: 24px;
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0 2px;
}

.field .number-input input:focus {
  outline: none;
  border: 0;
}

.scrub-handle {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 24px;
  padding: 0 3px;
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--text-subtle);
  cursor: ew-resize;
  /* Without this a horizontal drag on a touch screen scrolls the panel and the
     scrub never receives a single move event. */
  touch-action: none;
  user-select: none;
  -webkit-user-select: none;
}

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

/* Only the gear on the row under the pointer, or the one doing something.
   Thirty gears down a panel is noise, and noise is where a bound property
   stops being visible. */
.number-input .token-gear {
  opacity: 0;
}

.number-input:hover .token-gear,
.number-input:focus-within .token-gear,
.number-input .token-gear:focus-visible,
.number-input .token-gear[aria-expanded='true'],
.number-input .token-gear.is-bound {
  opacity: 1;
}

/* A field whose label is its glyph has no caption to leave room for. */
.field.glyph-only > .field-label {
  display: none;
}

.token-gear {
  flex: 0 0 20px;
  min-width: 0;
  width: 20px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.55;
}

.token-gear:hover,
.token-gear[aria-expanded='true'] {
  opacity: 1;
  background: var(--hover);
  color: var(--text);
}

.layout-gap .token-gear {
  flex: 0 0 22px;
  min-width: 0;
}

/* ---------------------------------------------------------------------------
   A bound field.

   The number and its drag handle are gone; the token is the field. Tinted with
   the accent rather than outlined, so that scanning a panel answers "what here
   is driven by the design system" in one pass, without reading a single label.

   The box keeps the number field's height and radius exactly. A bound field two
   pixels taller than the one beside it makes the panel look like it is coming
   apart, and this is a state half the rows can be in at once.
   --------------------------------------------------------------------------- */
.number-input.is-bound {
  border-color: transparent;
  background: var(--accent-soft);
}

/* The glyph a bound W or H keeps: still the label, no longer a handle. */
.number-input.is-bound .field-glyph {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 24px;
  padding: 0 3px;
  font-size: 10px;
  line-height: 1;
  color: var(--accent);
  user-select: none;
  -webkit-user-select: none;
}

/* Two controls in the slot a row reserved for one: the token gear beside the
   settings button on a stroke. */
.trailing-pair {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* A selection that does not agree. Same shape as a bound chip, in the muted ink
   the panel already uses for "not one answer" — so it cannot be read as a token
   that happens to be called Mixed. */
.token-chip.is-mixed .token-chip-name,
.token-chip.is-mixed .token-chip-detach {
  color: var(--text-muted);
}

.token-chip {
  display: flex;
  align-items: center;
  flex: 1 1 auto;
  min-width: 0;
  gap: 2px;
}

/* The name is a button because it does something: it re-opens the picker with
   this token marked, which is how you swap one token for another without
   detaching first. */
.token-chip-name {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1 1 auto;
  min-width: 0;
  height: 24px;
  padding: 0 2px;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.token-chip-name:hover,
.token-chip-name[aria-expanded='true'] {
  background: var(--hover);
}

/* One line, clipped at the end rather than wrapped: a two-line chip would make
   the row taller than every other row in the panel. */
.token-chip-text {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Detach is always visible on a bound field — unlike the gear, which hides
   until the row is hovered. It is the way out of a state the field is *in*,
   and a way out you have to discover by hovering is not one. */
.token-chip-detach {
  flex: 0 0 20px;
  min-width: 0;
  width: 20px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  color: var(--accent);
  font-size: 12px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.75;
}

.token-chip-detach:hover {
  opacity: 1;
  background: var(--hover);
}

/* In the gap row the chip stands alone, with no number-input box around it, so
   it carries the tint itself. */
.layout-gap > .token-chip {
  height: 26px;
  padding: 0 2px 0 6px;
  border-radius: 5px;
  background: var(--accent-soft);
}

.token-popover {
  position: fixed;
  z-index: 61;
  width: 252px;
  max-height: 360px;
  padding: 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.token-popover-head {
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--text-muted);
}

.token-popover .token-search {
  height: 26px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 11px;
  font-weight: 400;
}

.token-popover .token-list {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.token-popover .token-group {
  padding: 6px 6px 2px;
  font-size: 10px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.token-popover .token-row {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 5px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.token-popover .token-row:hover {
  background: var(--hover);
}

.token-popover .token-row.is-bound {
  background: var(--accent-soft);
}

.token-popover .token-row.is-deprecated .token-name {
  text-decoration: line-through;
  opacity: 0.7;
}

.token-popover .token-swatch {
  flex: 0 0 14px;
  min-width: 0;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  background: var(--swatch-color, #000);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
}

.token-popover .token-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.token-popover .token-value {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-family: var(--mono);
  font-size: 10px;
}

.token-detach {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 6px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

.token-detach:hover {
  background: var(--hover);
  color: var(--text);
}

.token-detach[hidden] {
  display: none;
}

.token-popover .token-empty {
  margin: 8px 6px;
  font-size: 11px;
  color: var(--text-muted);
}

/* The sidebar is 264px and these rows carry four controls each, so every width
   here is deliberate: nothing grows, because there is nothing that wants to —
   four small numbers in a narrow column read better aligned than stretched, and
   a single flexible field would push the rest off the edge. */
/* The hex must stay readable: a field showing `#3737` is worse than useless,
   because it looks like a value rather than like a truncation. It gets the
   flexible width and a floor; everything else on the row is fixed. */
.color-row .hex-input {
  flex: 1 1 70px;
  min-width: 62px;
}

.color-row .alpha-input {
  flex: 0 0 52px;
}

/* Every numeric field in the editor is `<input type="text">`, so there are no
   spinner arrows to hide any more — the three blocks that hid them are gone.
   The reason for the change was arithmetic: `<input type="number">` refuses to
   hold `800+24`, and the browser hands back an empty string rather than the sum
   somebody typed. `number-input.ts` carries the rest of the reasoning. What the
   spinners were costing was width — Chromium reserves about fourteen pixels for
   them inside every box — which is why nothing here needs to be replaced. */

.color-row.is-compact {
  gap: 4px;
}

.color-row.is-compact .alpha-input {
  flex: 0 0 40px;
}

/* ------------------------------------------------------------ context menu

   On <body>, like the colour picker, and for the same reason: the panels clip
   and scroll their overflow, so a menu parented inside one is cut off at the
   panel edge. Above the picker in z-order — a menu opened over a popover has to
   be the thing you are looking at. */
.context-menu {
  position: fixed;
  /* Above every surface a menu can be opened from, and the file browser is the
     highest of them at 90. It used to be 70: the browser's own context menus —
     on a card, on a project, on the account row — were rendered *behind* the
     browser, so they were invisible and every click landed on the rail behind
     them. Nothing could catch it; the harness has no stacking contexts, and the
     menu was in the DOM with the right rows in it the whole time. */
  z-index: 130;
  min-width: 208px;
  max-width: 320px;
  padding: 5px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  /* A menu taller than the viewport scrolls rather than running off it; the
     placement can flip it but cannot make the screen bigger. */
  max-height: calc(100vh - 24px);
  overflow-y: auto;
  outline: none;
}

.context-menu-item {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  height: 26px;
  padding: 0 8px 0 4px;
  border: 0;
  border-radius: 5px;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
  text-align: left;
  cursor: default;
}

/* Hover and keyboard highlight are the same state deliberately: two different
   highlights on screen at once is a menu you have to think about. */
.context-menu-item:hover:not(:disabled),
.context-menu-item.is-active:not(:disabled) {
  background: var(--accent);
  color: #fff;
}

.context-menu-item:disabled {
  color: var(--text-subtle);
  cursor: default;
}

/* A fixed-width column for the tick, so labels line up whether or not the menu
   contains any toggles — the alternative is rows that shift sideways as the
   state changes. */
.context-menu-tick {
  flex: 0 0 12px;
  min-width: 0;
  font-size: 10px;
  line-height: 1;
  text-align: center;
}

.context-menu-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.context-menu-hint {
  flex: 0 0 auto;
  color: var(--text-subtle);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.context-menu-item:hover:not(:disabled) .context-menu-hint,
.context-menu-item.is-active:not(:disabled) .context-menu-hint {
  color: rgba(255, 255, 255, 0.75);
}

.context-menu-separator {
  height: 1px;
  margin: 5px 4px;
  background: var(--border);
}

/* ------------------------------------------------------------ colour picker

   Positioned on <body> rather than inside the inspector: the inspector clips
   its overflow and scrolls, so a popover parented there is cut off at the panel
   edge and scrolls away from its own swatch. */
.color-popover {
  position: fixed;
  z-index: 60;
  width: 232px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* The saturation/value square: hue underneath, white left-to-right, black
   bottom-to-top. Two gradients over a flat colour is the whole thing — no
   canvas, no image, and sharp at any pixel density. */
.cp-area {
  position: relative;
  height: 148px;
  border-radius: 6px;
  cursor: crosshair;
  touch-action: none;
  background-color: var(--cp-hue-color, #f00);
  background-image:
    linear-gradient(to top, #000, transparent),
    linear-gradient(to right, #fff, transparent);
}

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

.cp-tracks {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.cp-track {
  position: relative;
  height: 10px;
  border-radius: 5px;
  cursor: pointer;
  touch-action: none;
}

.cp-hue {
  background: linear-gradient(
    to right,
    #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%
  );
}

.cp-alpha {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%),
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
}

.cp-alpha-fill {
  position: absolute;
  inset: 0;
  border-radius: 5px;
  pointer-events: none;
}

/* One handle class for all three controls. The white ring plus the dark outer
   ring is what keeps it visible over both ends of every gradient it sits on —
   a plain white dot disappears in the top-left corner of the square. */
.cp-handle {
  position: absolute;
  left: 0;
  top: 50%;
  width: 12px;
  height: 12px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.35), 0 1px 3px rgba(0, 0, 0, 0.4);
  pointer-events: none;
}

.cp-preview {
  width: 26px;
  height: 26px;
  flex: 0 0 26px;
  min-width: 0;
  border-radius: 50%;
  position: relative;
  overflow: hidden;
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%),
    linear-gradient(45deg, #d9d9d9 25%, transparent 25%, transparent 75%, #d9d9d9 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
  box-shadow: inset 0 0 0 1px var(--border);
}

.cp-preview::after {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--cp-preview-color, #000);
  opacity: var(--cp-preview-alpha, 1);
}

.cp-dropper {
  flex: 0 0 26px;
  min-width: 0;
}

.cp-fields {
  display: flex;
  align-items: center;
  gap: 6px;
}

.cp-group {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  gap: 4px;
}

.cp-group[hidden] {
  display: none;
}

.cp-mode,
.cp-hex,
.cp-channel,
.cp-opacity {
  height: 26px;
  min-width: 0;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-size: 11px;
  font-family: var(--mono);
}

.cp-mode {
  flex: 0 0 58px;
  min-width: 0;
  font-family: inherit;
}

.cp-hex {
  flex: 1 1 auto;
  text-transform: uppercase;
}

.cp-channel {
  flex: 1 1 0;
  width: 0;
  text-align: center;
}

.cp-opacity {
  flex: 0 0 44px;
  min-width: 0;
  text-align: center;
}

.cp-area:focus-visible,
.cp-track:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.hex-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: var(--mono);
  font-size: 11px;
}

.alpha-input {
  flex: 0 0 46px;
  /* `min-width: auto` on a flex item resolves to its *min-content* size, and
     for an `<input>` that is the width of its `size` attribute — twenty
     characters, about 155px. That silently beats `flex-basis`, so every number
     field in a row came out three times its declared width and pushed the rest
     of the row off the panel. The declared basis is the whole point of these
     rows; this is what makes it apply. */
  min-width: 0;
  height: 26px;
  padding: 0 4px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-size: 11px;
  font-family: inherit;
}

/* Alignment: segmented groups of icon buttons.

   The groups are separated by a gap rather than a divider line, and each group
   is a single rounded shape, so "which of these three is the same kind of
   thing" is answered by shape before it is answered by reading. */
.align-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 4px;
}

.align-group {
  display: flex;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
  background: var(--bg);
}

.align-group .icon-button {
  width: 32px;
  height: 28px;
  border: 0;
  border-radius: 0;
  background: transparent;
}

/* The seam between buttons inside a group, drawn once so it cannot double up
   on the group's own border. */
.align-group .icon-button + .icon-button {
  box-shadow: inset 1px 0 0 var(--border);
}

.align-group .icon-button:hover {
  background: var(--border);
  color: var(--text);
}

.align-group .icon-button:active {
  background: var(--accent-soft);
  color: var(--accent);
}

/* A field with a small control pinned to its right — the aspect-ratio lock and
   the independent-corners toggle. The control changes what the neighbouring
   fields *mean*, so it sits beside them rather than on its own row. */
.field-with-trailing {
  display: flex;
  align-items: center;
  gap: 6px;
}

.field-with-trailing-main {
  flex: 1 1 auto;
  min-width: 0;
}

.field-with-trailing-group {
  display: flex;
  gap: 2px;
  flex: 0 0 auto;
}

.field-with-trailing .icon-button.is-on,
.field-with-trailing-group .icon-button.is-on {
  background: var(--accent-soft);
  color: var(--accent);
}

/* The inspector while points are selected. The title is a label rather than an
   input, because a point has no name to rename — and the count beside it is
   what says the fields below read across more than one of them. */
.inspector-points-name {
  flex: 1 1 auto;
  min-width: 0;
  padding: 0 8px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text);
}

.inspector-points-count {
  flex: 0 0 auto;
  padding-right: 4px;
  font-size: 11px;
  color: var(--text-muted);
}

/* Handle mirroring, as a segmented control like the alignment row above it.
   Drawn rather than named: the three states are arrangements of a point and its
   two handles, no icon set has them, and the text glyphs that stood in for them
   (`⋮ ∠ ↔`) were pictures of nothing — the row could only be read by hovering
   each button in turn. `mirror-control.ts` builds the SVG; it inherits
   `currentColor`, so the pressed state below is the only place a colour is
   named. */
.point-mirror {
  width: 44px;
  height: 28px;
  line-height: 1;
}

.point-mirror svg,
.vector-mirror-button svg {
  display: block;
  margin: 0 auto;
}

.align-group .point-mirror.is-on {
  background: var(--accent-soft);
  color: var(--accent);
}

.effect-header {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 6px;
}

.effect-header .field {
  flex: 1 1 auto;
  min-width: 0;
}

/* A hidden paint stays legible — it is still the colour you will bring back —
   but reads as switched off rather than as merely pale. */
.color-row.is-hidden-paint .color-swatch,
.color-row.is-hidden-paint .hex-input,
.color-row.is-hidden-paint .alpha-input {
  opacity: 0.45;
}

.button-row {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin-top: 4px;
}

.button-row button,
.text-button {
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 11px;
  cursor: pointer;
}

.button-row button:hover,
.text-button:hover {
  background: var(--bg-sunken);
  border-color: var(--border-strong);
}

.text-button {
  width: 100%;
  color: var(--accent);
  border-style: dashed;
}

/* ----------------------------------------------------------- empty states */

.empty-state {
  padding: 24px 16px;
  text-align: center;
}

.empty-title {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 600;
  color: var(--text-muted);
}

.empty-hint {
  margin: 0;
  font-size: 11px;
  line-height: 1.5;
  color: var(--text-subtle);
}

/* ------------------------------------------------------------ status bar */

.status-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  height: var(--status-height);
  padding: 0 12px;
  background: var(--panel);
  border-top: 1px solid var(--border);
  font-size: 11px;
  color: var(--text-muted);
  flex: 0 0 auto;
}

.status-message[data-tone='error'] {
  color: var(--danger);
}

.status-message[data-tone='success'] {
  color: var(--positive);
}

.status-metric {
  font-variant-numeric: tabular-nums;
  color: var(--text-subtle);
}

.status-metric.warn {
  color: var(--danger);
}

/* -------------------------------------------------------- command palette */

.palette-overlay {
  position: fixed;
  inset: 0;
  background: rgba(10, 14, 20, 0.4);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding-top: 14vh;
  z-index: 100;
}

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

.palette {
  width: min(560px, 92vw);
  background: var(--panel);
  border: 1px solid var(--border-strong);
  border-radius: 10px;
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.palette-input {
  width: 100%;
  height: 46px;
  padding: 0 14px;
  border: none;
  border-bottom: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: 15px;
}

.palette-input:focus {
  outline: none;
}

.palette-results {
  max-height: 46vh;
  overflow-y: auto;
  padding: 4px;
}

.palette-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 6px;
  cursor: pointer;
}

.palette-row.active {
  background: var(--accent-soft);
}

.palette-row.disabled {
  opacity: 0.38;
  cursor: default;
}

.palette-title {
  flex: 1 1 auto;
  font-size: 13px;
}

.palette-section {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: var(--text-subtle);
}

.palette-shortcut {
  font-family: var(--mono);
  font-size: 10px;
  color: var(--text-subtle);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

.palette-empty {
  padding: 20px;
  text-align: center;
  font-size: 12px;
  color: var(--text-subtle);
}

/* ------------------------------------------------------------ boot states */

.booting {
  flex: 1 1 auto;
  display: grid;
  place-content: center;
  gap: 10px;
  text-align: center;
  color: var(--text-muted);
}

.booting-mark {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.5px;
  color: var(--text);
}

.boot-error {
  max-width: 520px;
  margin: 12vh auto;
  padding: 24px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
}

.boot-error h1 {
  margin: 0 0 8px;
  font-size: 17px;
}

.boot-error p {
  margin: 0 0 8px;
  color: var(--text-muted);
  line-height: 1.55;
}

.boot-hint {
  font-size: 12px;
  color: var(--text-subtle);
}

.boot-error button {
  margin-top: 8px;
  height: 30px;
  padding: 0 14px;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-family: inherit;
  font-size: 13px;
  cursor: pointer;
}

/* ------------------------------------------------------- system respect */

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }
}

@media (forced-colors: active) {
  .tool-button.active,
  .palette-row.active,
  .layer-row.selected {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }
}

/* ------------------------------------------------------- lint findings */

.finding {
  padding: 7px 9px;
  margin-bottom: 6px;
  border-left: 3px solid var(--border-strong);
  border-radius: 0 5px 5px 0;
  background: var(--bg-sunken);
}

.finding-error {
  border-left-color: var(--danger);
}

.finding-warning {
  border-left-color: #d99114;
}

.finding-info {
  border-left-color: var(--text-subtle);
}

.finding-message {
  font-size: 11px;
  line-height: 1.45;
  color: var(--text);
}

.finding-fix {
  margin-top: 3px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--text-muted);
}

/* ---------------------------------------------------------------------------
   Icons — Phosphor (https://phosphoricons.com), loaded as a webfont.

   The font is an enhancement, not a dependency. Until the root carries
   `icons-ready` the text fallback is what renders, so a blocked CDN or an
   offline session degrades to distinct letters rather than to a row of
   identical empty boxes.
   --------------------------------------------------------------------------- */

/* Icon size is set once, here, and every button sizes itself around it.

   Outline glyphs need more room than filled ones to read: a filled shape is
   legible from its silhouette, an outline from its interior detail, and that
   detail is the first thing to go as the glyph shrinks. The weight changed to
   regular, so the size had to follow — at the old size the toolbar read as a
   row of faint marks rather than recognisable objects.

   **In px, not rem.** `html` here sets `font-size: 13px`, so `1rem` is 13px and
   not the 16px everyone reads it as — the old `1.05rem` looked like 17px in the
   source and rendered at 13.65px. An icon's box is its font-size (`.icon` is
   1em square), so this number is the one thing in the file that must be
   literal. */
.icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  font-size: 18px;
  line-height: 1;
  flex: none;
}

.icon .icon-glyph {
  display: none;
}

.icon-fallback {
  font-size: 0.78em;
  font-weight: 600;
  letter-spacing: -0.02em;
}

.icons-ready .icon .icon-glyph {
  display: inline-block;
}

.icons-ready .icon-fallback {
  display: none;
}

/* The fill weight is a second font from a second stylesheet, so it gets a
   second gate. Without these rules `icons-ready` would reveal a fill glyph
   whose family had not loaded — a blank box, which is exactly the state the
   text fallback exists to prevent. Each weight reveals only its own icons. */
.icons-ready .icon[data-weight='fill'] .icon-glyph {
  display: none;
}

.icons-ready .icon[data-weight='fill'] .icon-fallback {
  display: inline;
}

.icons-fill-ready .icon[data-weight='fill'] .icon-glyph {
  display: inline-block;
}

.icons-fill-ready .icon[data-weight='fill'] .icon-fallback {
  display: none;
}

/* There used to be two rules for this class, seven hundred lines apart, one
   asking for 18px and one for a 26px minimum. The minimum won, so the 18px was
   dead code that nonetheless read as the real size to anyone changing it — the
   kind of quiet disagreement that makes a later "why is this not 18px?" take an
   afternoon. One rule now. */
.icon-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 26px;
  min-height: 26px;
  padding: 0;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.icon-button:hover {
  background: var(--bg-sunken);
  color: var(--text);
}

.labelled-button .icon {
  margin-right: 6px;
}

/* ---------------------------------------------------------------------------
   Document bar — the top strip, now about the document rather than drawing.
   --------------------------------------------------------------------------- */

/* A button, not a div: it is the way back to the file browser, which is where
   Figma puts it and where anyone who has used a design tool looks for it. */
.doc-crumb {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  padding: 3px 6px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.doc-crumb:hover {
  background: var(--hover);
}

.doc-crumb .crumb-parent {
  color: var(--text-subtle);
}

.doc-crumb .crumb-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 40ch;
}

/* ---------------------------------------------------------------------------
   Pages
   --------------------------------------------------------------------------- */

/* Height comes from the splitter, falling back to the old viewport-relative
   slice when nothing has been dragged yet. `position: relative` because the
   handle is positioned against this element. */
/* The wrapper carries the height and the resize handle; the panel inside it
   scrolls. Splitting the two is what keeps the handle alive across the panel's
   `replaceChildren`. */
.pages-section {
  position: relative;
  height: var(--pages-height, 26vh);
  flex: 0 0 var(--pages-height, 26vh);
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.pages-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 4px 6px 6px;
}

.page-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.page-row {
  display: flex;
  align-items: center;
  gap: 2px;
  border-radius: var(--radius);
  padding-right: 2px;
}

.page-row:hover {
  background: var(--bg-sunken);
}

.page-row.current {
  background: var(--accent-soft);
}

.page-open {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1;
  min-width: 0;
  /* Comfortably hittable: this is a row you also rename by double-clicking. */
  min-height: 30px;
  padding: 0 6px;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.page-row.current .page-open {
  font-weight: 600;
}

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

.page-count {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-subtle);
}

.page-name-field {
  flex: 1;
  min-width: 0;
  min-height: 30px;
  padding: 0 6px;
  border: 1px solid var(--accent);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.page-add {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 30px;
  margin-top: 4px;
  padding: 0 6px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.page-add:hover {
  background: var(--bg-sunken);
  color: var(--text);
}

/* ---------------------------------------------------------------------------
   Sidebar tabs — Layers and Tokens are two views of one file.
   --------------------------------------------------------------------------- */

/* Scrolls rather than overflowing. With four tabs `flex: 1` fitted; the fifth
   pushed the strip past the sidebar's edge, and the part that stuck out sat
   *under the canvas*, where it was visible and could not be clicked — the same
   `min-width: auto` flexbox trap that is documented further down, arriving as a
   tab that appeared broken rather than as a squashed one. */
/* ------------------------------------------------------------- panel rail

  The five panels of the left sidebar, as a vertical rail of icons. They were a
  horizontal strip of text tabs and five did not fit: the fifth was off the edge
  at the default sidebar width, so the comments panel was reachable only by
  keyboard. A rail costs a fixed width and does not care how many entries it
  holds. */

.panel-rail {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  gap: 2px;
  width: 72px;
  padding: 8px 6px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  overflow-y: auto;
  scrollbar-width: none;
}

.panel-rail::-webkit-scrollbar {
  display: none;
}

.panel-rail-item {
  display: flex;
  flex: 0 0 auto;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 0;
  padding: 8px 2px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  cursor: pointer;
}

.panel-rail-item:hover {
  background: var(--bg-sunken);
  color: var(--text);
}

.panel-rail-item.active {
  background: var(--bg-sunken);
  color: var(--accent);
}

.panel-rail-label {
  overflow: hidden;
  max-width: 100%;
  font-size: 9px;
  letter-spacing: 0.2px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

.tokens-body {
  padding: 8px 6px;
}

.token-section {
  margin-bottom: 10px;
}

.token-heading {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 0 4px 4px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.token-hint {
  margin-left: auto;
  font-size: 10px;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  opacity: 0.8;
}

.theme-row {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.theme-chip {
  min-height: 26px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 13px;
  background: var(--panel);
  color: var(--text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.theme-chip:hover {
  background: var(--bg-sunken);
  color: var(--text);
}

.theme-chip.active {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  font-weight: 600;
}

.set-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.set-row {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 0 4px;
  border-radius: var(--radius);
  font-size: 12px;
}

.set-row:hover {
  background: var(--bg-sunken);
}

.set-row.state-disabled .set-name,
.set-row.state-disabled .set-count {
  opacity: 0.45;
}

.set-state {
  min-width: 34px;
  height: 18px;
  padding: 0 4px;
  border: 0;
  border-radius: 4px;
  font: inherit;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.3px;
  cursor: pointer;
  flex: none;
}

.state-enabled .set-state {
  background: var(--accent);
  color: #fff;
}

.state-source .set-state {
  background: var(--accent-soft);
  color: var(--accent);
}

.state-disabled .set-state {
  background: var(--bg-sunken);
  color: var(--text-subtle);
}

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

.set-count {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-subtle);
}

.set-actions {
  display: none;
  gap: 0;
}

.set-row:hover .set-actions,
.set-row:focus-within .set-actions {
  display: flex;
}

.token-search {
  width: 100%;
  min-height: 28px;
  margin-bottom: 8px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.token-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.token-row {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-height: 28px;
  padding: 0 4px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.token-row:hover {
  background: var(--bg-sunken);
}

.token-swatch {
  display: grid;
  place-items: center;
  width: 16px;
  height: 16px;
  border-radius: 4px;
  background: var(--bg-sunken);
  color: var(--text-subtle);
  font-size: 10px;
  flex: none;
}

.token-swatch.is-colour {
  /* A border, because a white swatch on a white panel is invisible. */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.16);
}

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

.token-value {
  font-variant-numeric: tabular-nums;
  font-size: 11px;
  color: var(--text-subtle);
  max-width: 40%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.token-value.is-broken {
  color: var(--danger);
}

.token-impact {
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--accent-soft);
  color: var(--accent);
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  flex: none;
}

/* How many layers wear this token — and the way to see them. A button rather
   than a badge, because it does something; muted rather than accent, so the
   count does not compete with the impact badge beside it, which answers a
   different question ("what other tokens depend on this"). */
.token-usage {
  flex: none;
  min-width: 18px;
  height: 16px;
  padding: 0 4px;
  border: 0;
  border-radius: 8px;
  background: var(--hover);
  color: var(--text-muted);
  font: inherit;
  font-size: 10px;
  font-weight: 600;
  line-height: 16px;
  text-align: center;
  cursor: pointer;
}

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

.token-form-detach {
  flex: none;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.token-empty {
  margin: 8px 4px;
  font-size: 12px;
  color: var(--text-subtle);
}

/* --- authoring a token ----------------------------------------------------

   The row's primary action is "apply this to the selection"; editing is a
   separate button beside it, because a row whose click sometimes applies and
   sometimes opens a form is a row nobody clicks confidently. */

.token-row-wrap {
  display: flex;
  align-items: center;
  gap: 2px;
}

.token-row-wrap .token-row {
  flex: 1 1 auto;
  min-width: 0;
}

.token-row-wrap .icon-button {
  flex: none;
  opacity: 0;
}

.token-row-wrap:hover .icon-button,
.token-row-wrap .icon-button:focus-visible {
  opacity: 1;
}

.token-form-popover {
  width: 240px;
  padding: 10px;
}

.token-form {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.token-form-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.token-form-label {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.4px;
  text-transform: uppercase;
  color: var(--text-subtle);
}

.token-form-input {
  height: 28px;
  min-width: 0;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.token-form-actions {
  display: flex;
  gap: 6px;
  margin-top: 2px;
}

.token-form-save {
  flex: 1 1 auto;
  min-width: 0;
  height: 28px;
  border: 0;
  border-radius: 5px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.token-form-delete {
  flex: none;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: transparent;
  color: var(--danger);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* --- Anchored popovers ---------------------------------------------------

   Shared by the stroke settings, the blend-mode menu and the effect editors.
   `position: fixed` because the inspector scrolls and an absolutely-positioned
   popover scrolls away from the button that opened it. */
.popover {
  position: fixed;
  z-index: 60;
  width: 232px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.popover .field {
  margin: 0;
}

/* ---------------------------------------------------------------------------
   Image settings: the crop rectangle and the seven corrections.

   Wider than the standard popover because a row here is a label, a slider and a
   number — at 232px the slider is 60px wide, which is a control you cannot aim
   with. The sliders are the reason this is a panel rather than a fill row: seven
   of them in the fill list would make an image four times the height of every
   other paint.
   --------------------------------------------------------------------------- */
.popover.image-settings {
  width: 288px;
}

.image-settings-heading {
  margin: 4px 0 0;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* The gear on an image fill row, lit when the image has actually been changed.
   A row of identical grey gears tells nobody which photograph has been adjusted,
   and "did I already fix that one" is the question this answers at a glance. */
.image-gear.is-on {
  color: var(--accent);
}

.adjust-row {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}

.adjust-slider {
  flex: 1 1 auto;
  min-width: 0;
  height: 18px;
  accent-color: var(--accent);
  /* A horizontal drag on a touch screen scrolls the panel otherwise — the same
     reason the scrub handle sets it. */
  touch-action: none;
}

.adjust-number {
  flex: 0 0 46px;
  width: 46px;
  /* A fixed flex-basis is a *suggestion* until `min-width` is reset: the
     default `min-width: auto` lets the input's own content size win, so a
     three-digit value would push the slider beside it out of the row. */
  min-width: 0;
  padding: 3px 6px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 11px;
  text-align: right;
}

.adjust-number:focus {
  border-color: var(--accent);
  outline: none;
}

/* The aspect presets. They wrap rather than scroll: six short labels on two
   lines read as a set, and a horizontal scroller hides half of them behind a
   gesture nobody knows is there. */
.crop-ratios {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.crop-ratio {
  padding: 3px 8px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 10px;
  white-space: nowrap;
  cursor: pointer;
}

.crop-ratio:hover {
  border-color: var(--accent);
  color: var(--accent);
}

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

/* A menu-shaped popover: full-width rows, no field labels. */
.popover-menu {
  width: 200px;
  padding: 6px;
  gap: 0;
}

.popover-menu button {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 5px 8px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.popover-menu button:hover,
.popover-menu button:focus-visible {
  background: var(--hover);
}

.popover-menu button[aria-checked='true'] {
  font-weight: 600;
}

/* The tick column is reserved on every row, so the labels line up whether or
   not a row is the selected one. Without it the list shifts sideways as the
   selection moves, which reads as the menu twitching. */
.popover-menu .menu-tick {
  width: 12px;
  flex: 0 0 12px;
  min-width: 0;
  text-align: center;
  color: var(--accent);
}

.popover-menu hr {
  height: 1px;
  margin: 4px 2px;
  border: 0;
  background: var(--border);
}

/* "Mixed" reads as a value, not as a button — it sits in the same slot as a
   number input and must not look like an action. It is clickable so the four
   real values are one press away. */
.mixed-value {
  flex: 1 1 0;
  min-width: 0;
  padding: 4px 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--input-bg, transparent);
  color: var(--text-muted);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.mixed-value:hover {
  color: var(--text);
  border-color: var(--border-strong, var(--border));
}

/* --- Dragging layer rows -------------------------------------------------- */

.layer-row.is-dragging {
  opacity: 0.45;
}

/* The line sits *between* rows, so it needs to take no vertical space of its
   own — otherwise every row below the pointer jumps down by two pixels as the
   indicator moves, and the tree appears to shudder during the drag. */
.drop-line {
  height: 0;
  margin: 0;
  border-top: 2px solid var(--accent);
  pointer-events: none;
}

/* "Into this container" is a different answer from "next to it", and a line
   cannot express the difference. */
.layer-row.drop-into {
  box-shadow: inset 0 0 0 2px var(--accent);
  border-radius: 4px;
}

.layer-name-field {
  flex: 1 1 auto;
  min-width: 0;
  padding: 1px 4px;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
}

/* --- Keyboard reference --------------------------------------------------- */

.shortcuts-sheet {
  position: fixed;
  z-index: 80;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(640px, 90vw);
  max-height: 78vh;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  overflow: auto;
}

.shortcuts-sheet h3 {
  margin: 0 0 12px;
  font-size: 14px;
}

/* Two columns, because the list is long and a single column turns the sheet
   into a scroll no one reads to the end of. */
.shortcuts-body {
  column-count: 2;
  column-gap: 24px;
}

.shortcut-section {
  margin: 12px 0 6px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
  break-after: avoid;
}

.shortcut-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
  break-inside: avoid;
}

/* A fixed key column so the descriptions line up: the eye scans down the keys,
   and a ragged left edge makes that scan impossible. */
.shortcut-keys {
  flex: 0 0 96px;
  min-width: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: var(--text);
  white-space: nowrap;
}

.shortcut-title {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-muted);
  font-size: 12px;
}

/* --- Assets --------------------------------------------------------------- */

.assets-body {
  padding: 8px;
}

.asset-search {
  width: 100%;
  min-width: 0;
  height: 26px;
  margin-bottom: 8px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.asset-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.asset-row {
  display: flex;
  align-items: center;
  gap: 4px;
  /* Indent comes from the folder depth the panel writes onto the row. A nested
     list of real elements would be tidier markup and worse behaviour: the rows
     have to stay one flat sequence for the search results, which cut across
     folders. */
  padding-left: calc(var(--asset-depth, 0) * 12px);
}

/* A folder in the library. It is a button, not a heading with a button inside:
   the whole strip toggles, and a 12px twisty is a target nobody hits. */
.asset-group {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  padding: 5px 6px;
  padding-left: calc(6px + var(--asset-depth, 0) * 12px);
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-align: left;
  cursor: pointer;
}

.asset-group:hover,
.asset-group:focus-visible {
  background: var(--hover);
  color: var(--text);
}

.asset-group .icon {
  font-size: 13px;
}

.asset-group-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The whole row is the place-an-instance button, so the target is the width of
   the panel rather than a small icon. Placing is what this list is for. */
.asset-place {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
  padding: 5px 6px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.asset-place:hover,
.asset-place:focus-visible {
  background: var(--hover);
}

.asset-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Zero instances is what distinguishes a component in use from one left over,
   so the count is always shown rather than hidden when it is zero. */
.asset-count {
  flex: 0 0 auto;
  min-width: 0;
  font-size: 11px;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

/* The family name is the longest value in the section and gets the full width;
   everything else in Typography pairs off. */
.font-family {
  flex: 1 1 auto;
  min-width: 0;
  height: 26px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
}

/* --- Auto layout ---------------------------------------------------------- */

.stacked-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
  margin-bottom: 6px;
}

/* `.field-label` carries `flex: 0 0 62px` for the side-by-side case. In a
   column that basis is read as a *height*, so every label floated 62px above
   the control it names and the block grew to twice its size. */
.stacked-field > .field-label {
  flex: 0 0 auto;
  min-width: 0;
}

.icon-choice {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 6px;
}

.icon-choice .icon-button {
  flex: 1 1 0;
  min-width: 0;
}

/* Alignment and gap sit side by side because they are adjusted together while
   looking at the result. */
.layout-align-row {
  display: flex;
  gap: 8px;
  align-items: flex-start;
}

.layout-align-row > .stacked-field {
  flex: 1 1 0;
  min-width: 0;
}

/* Nine dots, in a square, because the control is a picture of the frame. */
.align-pad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0;
  aspect-ratio: 1;
  width: 100%;
  padding: 4px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
}

.align-dot {
  position: relative;
  border: 0;
  border-radius: 4px;
  background: none;
  cursor: pointer;
}

/* The dot is drawn with a pseudo-element so the hit target stays the whole
   ninth of the pad — a 4px target is unusable, and nine of them in a row is
   the worst version of that. */
.align-dot::after {
  content: '';
  position: absolute;
  inset: 50%;
  width: 4px;
  height: 4px;
  margin: -2px 0 0 -2px;
  border-radius: 50%;
  background: var(--text-muted);
  opacity: 0.5;
}

.align-dot:hover::after {
  opacity: 1;
}

.align-dot.is-on::after {
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 2px;
  background: var(--accent);
  opacity: 1;
}

.align-dot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

.layout-gap {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bare-number {
  width: 100%;
  min-width: 0;
  height: 26px;
  padding: 0 7px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  font-size: 12px;
}

/* The paint-type strip at the top of the picker. */
.cp-kinds {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 6px;
}

.cp-kind {
  flex: 1 1 0;
  min-width: 0;
  height: 24px;
  border: 0;
  border-radius: 4px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}

.cp-kind:hover {
  background: var(--hover);
  color: var(--text);
}

.cp-kind.is-on {
  background: var(--accent);
  color: #fff;
}

/* --- Avatars ---------------------------------------------------------------
   One monogram, used by the account row, the sign-in screen and the file
   cards. The colour is an *index* stored on the account rather than a hex
   value, so the palette can change with the theme — a hex baked into the
   record at sign-up would still be the 2026 palette in the 2027 build, and
   would not adapt to a light theme at all. */

.avatar {
  --avatar-size: 28px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--avatar-size);
  height: var(--avatar-size);
  border-radius: 50%;
  background: var(--avatar-bg, #5b6470);
  color: #fff;
  font-size: calc(var(--avatar-size) * 0.4);
  font-weight: 600;
  letter-spacing: 0.02em;
  line-height: 1;
  user-select: none;
}

.avatar[data-colour='0'] { --avatar-bg: #6c5cff; }
.avatar[data-colour='1'] { --avatar-bg: #0f9d76; }
.avatar[data-colour='2'] { --avatar-bg: #e5484d; }
.avatar[data-colour='3'] { --avatar-bg: #d97706; }
.avatar[data-colour='4'] { --avatar-bg: #2f6bff; }
.avatar[data-colour='5'] { --avatar-bg: #c026d3; }
.avatar[data-colour='6'] { --avatar-bg: #0891b2; }
.avatar[data-colour='7'] { --avatar-bg: #4d5560; }

/* --- Sign in, sign up, account settings ------------------------------------
   Over everything, including the file browser: it is the one screen where the
   rest of the application is not usable yet. */

.auth-screen {
  position: fixed;
  inset: 0;
  z-index: 120;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--bg-sunken);
  overflow-y: auto;
}

/* Opened over a working editor rather than in front of a locked one, so the
   backdrop dims instead of replacing. */
.auth-screen.is-dialog {
  background: rgba(10, 12, 16, 0.55);
}

.auth-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 380px;
  margin: auto;
  padding: 28px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 700;
}

.auth-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 7px;
  background: var(--accent);
  color: #fff;
  font-size: 14px;
}

.auth-card h1 {
  margin: 8px 0 0;
  font-size: 20px;
}

.auth-sub {
  margin: 0 0 4px;
  color: var(--text-muted);
  font-size: 12px;
}

.auth-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.auth-label {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
}

.auth-field input,
.auth-profile input {
  width: 100%;
  min-width: 0;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}

.auth-field input:focus-visible,
.auth-profile input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* Four segments and a word. The word is what people read; the bars are what
   they notice. */
.auth-meter {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: 6px;
}

.auth-meter-bar {
  flex: 1 1 auto;
  min-width: 0;
  height: 3px;
  border-radius: 2px;
  background: var(--border-strong);
}

.auth-meter-bar.is-on {
  background: var(--positive);
}

.auth-meter-label {
  flex: 0 0 auto;
  min-width: 44px;
  color: var(--text-muted);
  font-size: 11px;
  text-align: right;
}

.auth-error {
  padding: 8px 10px;
  border: 1px solid var(--danger);
  border-radius: 6px;
  color: var(--danger);
  font-size: 12px;
}

.auth-error.is-ok {
  border-color: var(--positive);
  color: var(--positive);
}

.auth-submit {
  height: 36px;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.auth-submit:disabled {
  opacity: 0.6;
  cursor: default;
}

.auth-secondary {
  height: 32px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.auth-secondary.is-danger {
  border-color: var(--danger);
  color: var(--danger);
}

.auth-swap,
.auth-cancel {
  border: 0;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.auth-cancel {
  color: var(--text-muted);
}

.auth-known {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.auth-known-label {
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
}

.auth-known-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.auth-known-row:hover {
  background: var(--hover);
}

.auth-known-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.auth-known-name {
  font-size: 13px;
  font-weight: 500;
}

.auth-known-email {
  color: var(--text-muted);
  font-size: 11px;
}

.auth-note {
  margin: 4px 0 0;
  color: var(--text-subtle);
  font-size: 11px;
  line-height: 1.5;
}

.auth-profile {
  max-width: 440px;
}

.auth-profile-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.auth-colours {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.auth-swatches {
  display: flex;
  gap: 6px;
}

.auth-swatch {
  width: 22px;
  height: 22px;
  border: 2px solid transparent;
  border-radius: 50%;
  cursor: pointer;
}

.auth-swatch.is-on {
  border-color: var(--text);
}

.auth-swatch[data-colour='0'] { background: #6c5cff; }
.auth-swatch[data-colour='1'] { background: #0f9d76; }
.auth-swatch[data-colour='2'] { background: #e5484d; }
.auth-swatch[data-colour='3'] { background: #d97706; }
.auth-swatch[data-colour='4'] { background: #2f6bff; }
.auth-swatch[data-colour='5'] { background: #c026d3; }
.auth-swatch[data-colour='6'] { background: #0891b2; }
.auth-swatch[data-colour='7'] { background: #4d5560; }

.auth-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.auth-section summary {
  color: var(--text-muted);
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.auth-section.is-danger summary {
  color: var(--danger);
}

/* --- File browser --------------------------------------------------------- */

/* Full-screen rather than a panel. Choosing a file is a mode, not a detail of
   the file you are in — the canvas behind it is a distraction from the only
   decision on screen. */
.file-browser {
  position: fixed;
  inset: 0;
  z-index: 90;
  background: var(--bg);
}

.fb-shell {
  display: flex;
  height: 100%;
  min-height: 0;
}

.fb-rail {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 0 0 244px;
  min-width: 0;
  padding: 12px;
  border-right: 1px solid var(--border);
  overflow-y: auto;
}

.fb-brand {
  margin-bottom: 14px;
  padding: 0 8px;
  font-size: 15px;
  font-weight: 700;
}

/* The account row: who you are, above everything that belongs to you. */
.fb-account {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  margin-bottom: 10px;
  padding: 6px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.fb-account:hover {
  background: var(--hover);
}

.fb-account-text {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.fb-account-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
}

.fb-account-email {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
  font-size: 11px;
}

.fb-search {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  margin-bottom: 10px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-sunken);
  color: var(--text-muted);
}

/* `flex: 1 1 auto` alone is not enough: `min-width: auto` on a flex item
   resolves to an input's min-content size, about 155px, and the field would
   push the rail wider than the rail is. */
.fb-search-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 100%;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.fb-search-input:focus {
  outline: none;
}

.fb-nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.fb-rail-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin: 16px 0 4px;
  padding: 0 8px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.fb-rail-hint {
  margin: 2px 8px;
  color: var(--text-subtle);
  font-size: 11px;
  line-height: 1.5;
}

.fb-scope {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 6px 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.fb-scope:hover {
  background: var(--hover);
}

.fb-scope.is-on {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 600;
}

.fb-scope-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fb-scope-count {
  flex: 0 0 auto;
  min-width: 0;
  color: var(--text-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

.fb-main {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  padding: 16px 24px 24px;
  overflow-y: auto;
}

.fb-topbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding-bottom: 12px;
}

/* What an import had to say, said inside this screen. The editor's status bar
   sits behind an opaque `.file-browser`, so a message sent only there is a
   message nobody can read — which is how a refused drop came to look like
   nothing happening at all. */
.fb-notice {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin-bottom: 14px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  font-size: 12px;
  line-height: 1.5;
}

.fb-notice[data-tone='error'] {
  border-left-color: var(--danger);
}

.fb-notice[data-tone='success'] {
  border-left-color: var(--positive);
}

.fb-notice-text {
  flex: 1 1 auto;
  min-width: 0;
}

.fb-notice-action {
  flex: 0 0 auto;
  align-self: center;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg-sunken);
  color: var(--text);
  font-size: 11px;
  white-space: nowrap;
  cursor: pointer;
}

.fb-notice-action:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.fb-notice-dismiss {
  flex: 0 0 auto;
  border: 0;
  padding: 0 2px;
  background: none;
  color: var(--text-subtle);
  font-size: 12px;
  line-height: 1.5;
  cursor: pointer;
}

.fb-notice-dismiss:hover {
  color: var(--text);
}

.fb-crumbs {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-muted);
  font-size: 12px;
}

.fb-crumb.is-current {
  color: var(--text);
}

.fb-crumb-sep {
  color: var(--text-subtle);
}

.fb-topbar-actions {
  display: flex;
  gap: 8px;
}

.fb-header {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 14px;
}

.fb-title {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: 0;
}

.fb-title h2 {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 20px;
}

.fb-header-note {
  flex: 1 1 100%;
  margin: -6px 0 0;
  color: var(--text-muted);
  font-size: 12px;
}

.fb-primary,
.fb-secondary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.fb-primary {
  border-color: var(--accent);
  background: var(--accent);
  color: #fff;
}

.fb-danger {
  border-color: var(--danger);
  color: var(--danger);
}

/* The template strip, dismissible and staying dismissed. */
.fb-templates {
  margin-bottom: 18px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--bg-sunken);
}

.fb-templates-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}

.fb-templates-head h3 {
  margin: 0;
  font-size: 13px;
  font-weight: 600;
}

.fb-templates-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
}

.fb-template {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

/* Drawn in CSS rather than shipped as four images: a thumbnail of a template
   is a picture of a document that does not exist yet, and a gradient that
   hints at the layout is honest where a screenshot of an old build is not. */
.fb-template-art {
  aspect-ratio: 16 / 10;
  margin-bottom: 6px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: linear-gradient(135deg, #2f6bff22, #6c5cff22);
}

.fb-template-art[data-template='blank'] {
  background: var(--panel);
}

.fb-template-art[data-template='dashboard'] {
  background:
    linear-gradient(90deg, #1f2430 0 24%, transparent 24%),
    linear-gradient(180deg, #6c5cff33, #2f6bff22);
}

.fb-template-art[data-template='mobile'] {
  background:
    radial-gradient(closest-side, #0f9d7644 60%, transparent 62%),
    linear-gradient(180deg, #0f9d7622, #2f6bff22);
}

.fb-template:hover .fb-template-art {
  border-color: var(--accent);
}

.fb-template-name {
  font-size: 12px;
  font-weight: 600;
}

.fb-template-desc {
  color: var(--text-muted);
  font-size: 11px;
}

.fb-filters {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 12px;
}

.fb-count {
  color: var(--text-muted);
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}

.fb-filters-spacer {
  flex: 1 1 auto;
  min-width: 0;
}

.fb-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  height: 28px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.fb-chip:disabled {
  color: var(--text-subtle);
  cursor: default;
}

.fb-viewtoggle {
  display: flex;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--border);
  border-radius: 8px;
}

.fb-viewtoggle .is-on {
  background: var(--accent-soft);
  color: var(--accent);
}

.fb-results {
  flex: 1 1 auto;
  min-height: 0;
}

/* Sized by the thumbnails, not by a fixed column count: at 1200px this is five
   across and at 700px it is two, with no breakpoint to maintain. */
.fb-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: 16px;
}

.fb-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

/* Always in the DOM, revealed on hover or when it is on — a star that appears
   only on hover is one a touch device can never reach. */
.fb-star {
  position: absolute;
  top: 6px;
  right: 6px;
  z-index: 1;
  border-radius: 6px;
  background: var(--panel);
  opacity: 0;
}

.fb-card:hover .fb-star,
.fb-star:focus-visible,
.fb-star.is-on {
  opacity: 1;
}

.fb-star.is-on {
  color: #d97706;
}

.fb-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  width: 100%;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--panel);
  cursor: pointer;
  overflow: hidden;
}

.fb-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.fb-thumb:hover {
  border-color: var(--accent);
}

.fb-card.is-open .fb-thumb {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}

.fb-card-foot {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}

.fb-card-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 12px;
  font-weight: 500;
}

.fb-card-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}

.fb-card-when {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
  font-size: 11px;
}

/* The list layout. A table, so the dates line up — which is the only reason
   to want a list instead of the cards. */
.fb-list {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
}

.fb-list th {
  padding: 0 8px 8px;
  border-bottom: 1px solid var(--border);
  color: var(--text-muted);
  font-size: 11px;
  font-weight: 600;
  text-align: left;
}

.fb-list td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--border);
}

.fb-row:hover {
  background: var(--hover);
}

.fb-row.is-open .fb-row-name {
  color: var(--accent);
  font-weight: 600;
}

.fb-row-open {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  min-width: 0;
  padding: 4px 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.fb-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.fb-row-star {
  color: #d97706;
}

.fb-row-where,
.fb-row-when {
  color: var(--text-muted);
  white-space: nowrap;
}

.fb-row-actions {
  width: 32px;
  text-align: right;
}

.fb-rename {
  flex: 1 1 auto;
  min-width: 0;
  height: 22px;
  padding: 0 5px;
  border: 1px solid var(--accent);
  border-radius: 4px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.fb-empty {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 40px 0;
  color: var(--text-muted);
  font-size: 13px;
}

/* --- Swapping an instance's component ------------------------------------- */

.swap-target {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1 1 auto;
  /* The flexbox trap again: without this the button's min-content width is its
     longest component name, and a long one pushes the label out of the panel. */
  min-width: 0;
  height: 24px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.swap-target:hover {
  border-color: var(--accent);
}

.swap-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: left;
}

.swap-popover {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 260px;
  padding: 8px;
}

.swap-search {
  width: 100%;
  min-width: 0;
  height: 26px;
  padding: 0 8px;
  border: 1px solid var(--border-strong);
  border-radius: 6px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

/* Bounded, and scrolling: a library of two hundred components would otherwise
   make a popover taller than the screen, and the clamp would put the search
   field off the top of it. */
.swap-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  max-height: 260px;
  overflow-y: auto;
}

.swap-option {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-width: 0;
  padding: 5px 6px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.swap-option:hover {
  background: var(--hover);
}

.swap-option.is-on {
  color: var(--accent);
}

.swap-folder {
  flex: 0 0 auto;
  color: var(--text-subtle);
}

.swap-leaf {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.swap-tick {
  flex: 0 0 auto;
}

.swap-empty {
  margin: 6px 4px;
  color: var(--text-muted);
  font-size: 12px;
}

/* --- Sharing --------------------------------------------------------------
   The corner of the toolbar Figma reserves for "who else is here" and "let
   somebody else in", in that order: the avatars answer a question you have
   while working, the button answers one you have deliberately. */

.toolbar-presence {
  display: flex;
  align-items: center;
  gap: -4px;
  margin-right: 8px;
}

/* Overlapped, like every collaborator row: four avatars in the width of three,
   and the overlap is what makes it read as a group rather than as four
   unrelated dots. */
.presence-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-left: -6px;
  border: 2px solid var(--panel);
  border-radius: 50%;
  background: var(--peer-colour, #6c5cff);
  color: #fff;
  font-size: 10px;
  font-weight: 600;
  user-select: none;
}

.presence-avatar.is-more {
  background: var(--border-strong);
  color: var(--text);
}

.share-button {
  height: 28px;
  padding: 0 14px;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

/* A live session is the one state the button itself has to carry: the avatars
   disappear when you are connected and alone, and "sharing is on" is not
   something to find out by opening the dialog. */
.share-button.is-live {
  box-shadow: 0 0 0 2px var(--accent-soft);
}

.share-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 80px 24px 24px;
  background: rgba(10, 12, 16, 0.55);
  overflow-y: auto;
}

.share-dialog {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 520px;
  padding: 20px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

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

.share-header h2 {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 16px;
}

.share-copy {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 28px;
  padding: 0 10px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--accent);
  font: inherit;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
}

.share-copy:disabled {
  color: var(--text-subtle);
  cursor: default;
}

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

/* `min-width: 0`, as everywhere: an input's min-content width is about 155px
   and it would push the Join button off the dialog on a narrow window. */
.share-input {
  flex: 1 1 auto;
  min-width: 0;
  height: 34px;
  padding: 0 10px;
  border: 1px solid var(--accent);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 13px;
}

.share-primary {
  height: 34px;
  padding: 0 16px;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.share-secondary {
  height: 28px;
  padding: 0 12px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* The link itself, shown rather than only copyable. */
.share-link {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
}

/* `min-width: 0` and `text-overflow`, because a share link is long: without
   them the input is as wide as its content and stretches the whole dialog past
   the edge of the window, taking the Copy button with it. Read-only rather
   than disabled — a disabled input cannot be selected, and selecting the text
   is the fallback for every browser that refuses the clipboard. */
.share-link-url {
  min-width: 0;
  height: 32px;
  padding: 0 10px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-overflow: ellipsis;
}

.share-link-detail {
  margin: 0;
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.5;
}

/* The drop target over the file browser.
   Present at all times and hidden by opacity rather than inserted on drag: an
   element added during a drag changes what is under the pointer, and the
   browser answers that with a `dragleave` that cancels the drag. */
.fb-drop {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: color-mix(in srgb, var(--bg) 82%, transparent);
  opacity: 0;
  pointer-events: none;
  transition: opacity 120ms ease;
  z-index: 40;
}

.file-browser.is-dropping .fb-drop,
.fb-drop.is-working {
  opacity: 1;
}

.fb-drop-sheet {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: center;
  padding: 28px 40px;
  border: 2px dashed var(--accent);
  border-radius: 16px;
  background: var(--panel);
  color: var(--text);
  font-size: 15px;
  text-align: center;
}

.fb-drop-sheet span {
  color: var(--text-muted);
  font-size: 12px;
}

/* The caveat beside an import's score: what the number could not count. */
.import-caveat {
  color: var(--warning, #b26a00);
}

/* Shown only when the account has more than one project to choose between. */
.share-project {
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.share-hint,
.share-note {
  margin: 0;
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.5;
}

.share-note {
  color: var(--text-subtle);
  padding-top: 4px;
  border-top: 1px solid var(--border);
}

/* What the last invite did. Two tones, because "Dana can now edit this file"
   and "nobody here has that address" must not look the same at a glance. */
.share-note[data-tone='good'] {
  color: var(--positive);
}

.share-note[data-tone='bad'] {
  color: var(--danger);
}

/* Inviting somebody. One row: address, what they may do, and the button. */
.share-invite {
  display: flex;
  align-items: center;
  gap: 8px;
}

.share-invite-email {
  flex: 1 1 auto;
  min-width: 0;
  height: 30px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: 12px;
}

.share-invite-email:focus {
  outline: none;
  border-color: var(--accent);
}

.share-invite-role {
  flex: 0 0 auto;
  height: 30px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font-size: 12px;
}

.share-dialog h3 {
  margin: 4px 0 0;
  font-size: 12px;
  font-weight: 600;
}

.share-access {
  display: flex;
  flex-direction: column;
}

.share-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

.share-row:last-child {
  border-bottom: 0;
}

.share-row-text {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
}

.share-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 13px;
}

.share-row-detail {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-muted);
  font-size: 11px;
}

.share-row-right {
  flex: 0 0 auto;
  color: var(--text-muted);
  font-size: 12px;
}

/* The peer's own colour, the same one their cursor is drawn in on the canvas.
   Two different colours for one person would make the list useless for the
   thing it is for: matching a name to the cursor that is moving. */
.share-dot {
  flex: 0 0 auto;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--peer-colour, #6c5cff);
}

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

.share-status {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--text-muted);
  font-size: 12px;
}

.share-status[data-state='connected'] {
  color: var(--positive);
}

.share-status[data-state='reconnecting'],
.share-status[data-state='connecting'] {
  color: var(--text);
}

/* The avatar group is one control, not four decorations: it opens the people
   menu, which is where following and the spotlight live. */
.presence-group {
  display: inline-flex;
  align-items: center;
  height: 32px;
  padding: 0 6px 0 10px;
  border: 0;
  border-radius: 16px;
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.presence-group:hover {
  background: var(--hover);
}

.presence-avatar.is-me {
  background: var(--border-strong);
  color: var(--text);
}

/* The ring says "your screen is following this person" — on the avatar, not
   only in the menu, because the menu is shut while you are being moved. */
.presence-avatar.is-followed {
  box-shadow: 0 0 0 2px var(--accent);
}

.presence-caret {
  margin-left: 4px;
  font-size: 11px;
  line-height: 1;
}

/* --- The vector toolbar ---------------------------------------------------
   Figma's, at the bottom of the canvas while points are being edited, and
   with only the modes that do something: a row offering five things of which
   two are unimplemented is worse than a row offering three that work. */

.vector-toolbar {
  position: absolute;
  bottom: 76px;
  left: 50%;
  z-index: 12;
  display: flex;
  align-items: center;
  /* One row, always. Without this the mirroring group and the hint wrap onto
     lines of their own and the bar becomes a paragraph. */
  flex-wrap: nowrap;
  white-space: nowrap;
  gap: 2px;
  padding: 4px;
  border-radius: 12px;
  background: var(--dock);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.35);
  transform: translateX(-50%);
}

.vector-tool {
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--dock-text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.vector-tool:hover {
  background: var(--dock-hover);
}

.vector-tool.is-on {
  background: var(--accent);
  color: #fff;
}

.vector-hint {
  margin: 0 8px;
  color: var(--dock-muted);
  font-size: 11px;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   The handle-mirroring group.

   Figma's three settings, separated from the modes by a rule because they are
   a different kind of thing: the modes decide what a click does, these decide
   what a *point* is. Square buttons rather than the modes' pill shape, for the
   same reason.
   --------------------------------------------------------------------------- */
.vector-mirror {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 2px 0 6px;
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}

.vector-mirror-button {
  width: 28px;
  min-width: 28px;
  padding: 0;
  font-size: 14px;
  line-height: 1;
  text-align: center;
}

/* ---------------------------------------------------------------------------
   The subpath group.

   Present only on a shape that has more than one run, unlike the mirroring
   group beside it — a subpath control on a path with no subpaths could never
   do anything, and a permanently dead button teaches people the row is
   decoration. The reading is a count rather than a name because runs have no
   names: a Subtract made them and nobody typed anything.
   --------------------------------------------------------------------------- */
.vector-subpath {
  display: flex;
  align-items: center;
  gap: 2px;
  margin: 0 2px;
  padding-left: 8px;
  border-left: 1px solid rgba(255, 255, 255, 0.16);
}

.vector-subpath-label {
  margin-right: 4px;
  color: var(--dock-muted);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.vector-subpath-button {
  width: 28px;
  min-width: 28px;
  padding: 0;
  font-size: 13px;
  line-height: 1;
  text-align: center;
}

/* Disabled, not hidden: a control that comes and goes with the selection is
   one nobody learns is there. It has to *look* unavailable, though, or the
   first press reads as the button being broken. */
.vector-tool:disabled {
  opacity: 0.38;
  cursor: default;
}

.vector-tool:disabled:hover {
  background: none;
}

.vector-done {
  color: var(--dock-muted);
}

/* ---------------------------------------------------------------------------
   The comment thread popover.

   DOM rather than canvas, and positioned absolutely inside the canvas host so
   it moves with a pan without anybody re-laying-out the page. The pins are
   painted; this is the one thread you have open, and it is a panel because you
   type into it.
   --------------------------------------------------------------------------- */
.comment-popover {
  position: absolute;
  z-index: 14;
  display: flex;
  width: 268px;
  max-height: 60vh;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.22);
  color: var(--text);
  font-size: 12px;
}

.comment-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 8px 6px 12px;
  border-bottom: 1px solid var(--border);
}

.comment-title {
  overflow: hidden;
  flex: 1;
  color: var(--text-muted);
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.comment-resolve,
.comment-close {
  height: 24px;
  padding: 0 8px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.comment-resolve:hover,
.comment-close:hover {
  background: var(--accent-soft);
  color: var(--text);
}

/* Scrolls rather than growing: a thread of forty replies must not push the
   composer off the bottom of the window, which is where the reply goes. */
.comment-messages {
  overflow-y: auto;
  flex: 1;
  padding: 6px 12px;
}

.comment-message + .comment-message {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

/* Replies indent, so the first message reads as the subject and the rest as
   the conversation about it. */
.comment-message.is-reply {
  padding-left: 10px;
  border-left: 2px solid var(--border-strong);
}

.comment-meta {
  display: flex;
  align-items: baseline;
  gap: 6px;
}

.comment-author {
  font-weight: 600;
}

.comment-time,
.comment-edited {
  color: var(--text-subtle);
  font-size: 10px;
}

.comment-body {
  margin: 3px 0 0;
  /* Somebody else's text, of unknown shape: a pasted URL with no spaces in it
     must wrap rather than widen the panel past the edge of the screen. */
  overflow-wrap: anywhere;
  white-space: pre-wrap;
}

.comment-reactions {
  display: flex;
  gap: 4px;
  margin-top: 5px;
}

.comment-reaction {
  height: 22px;
  padding: 0 6px;
  border: 1px solid transparent;
  border-radius: 11px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
  opacity: 0.55;
}

.comment-reaction:hover {
  background: var(--accent-soft);
  opacity: 1;
}

.comment-reaction.is-on {
  border-color: var(--accent);
  background: var(--accent-soft);
  color: var(--text);
  opacity: 1;
}

.comment-composer {
  display: flex;
  align-items: flex-end;
  gap: 6px;
  padding: 8px 10px 10px;
  border-top: 1px solid var(--border);
}

.comment-input {
  min-height: 34px;
  flex: 1;
  padding: 6px 8px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  resize: vertical;
}

.comment-input:focus {
  border-color: var(--accent);
  outline: none;
}

.comment-send {
  height: 30px;
  padding: 0 10px;
  border: 0;
  border-radius: 8px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.comment-send:hover {
  filter: brightness(1.08);
}

/* ---------------------------------------------------------------------------
   The multi-selection header.

   A count, the boolean split button, and the overflow — Figma's row. The split
   button is one pill with a hairline down the middle rather than two buttons,
   so it reads as "one control with a menu" instead of two unrelated icons.
   --------------------------------------------------------------------------- */
.inspector-count {
  overflow: hidden;
  flex: 1;
  font-size: 12px;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inspector-split {
  display: inline-flex;
  align-items: center;
  overflow: hidden;
  border-radius: 6px;
}

.inspector-split .icon-button {
  border-radius: 0;
}

.inspector-split-caret {
  width: 18px;
  min-width: 18px;
  padding: 0;
  border-left: 1px solid var(--border);
}

/* Disabled, not hidden. Two shapes that cannot be combined is a fact about the
   selection, and a button that vanishes is one nobody learns is there. */
.inspector-split-face:disabled,
.inspector-split-caret:disabled {
  opacity: 0.4;
  cursor: default;
}

/* ---------------------------------------------------------------------------
   The file's colour styles, in the empty inspector.

   A dense list rather than the inspector's usual field rows: this is something
   to scan, not something to fill in, and forty styles at field spacing would
   be a page and a half of scrolling.
   --------------------------------------------------------------------------- */
.style-group {
  padding: 2px 12px 4px;
  color: var(--text-muted);
  font-size: 11px;
}

.style-list {
  display: flex;
  flex-direction: column;
}

.style-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 12px;
  border: 0;
  background: none;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.style-row:hover {
  background: var(--bg-sunken);
}

/* A ring rather than a bare square: a white style on a white panel is
   otherwise an empty gap with a name beside it. */
.style-swatch {
  width: 16px;
  min-width: 16px;
  height: 16px;
  flex: 0 0 16px;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
}

.style-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
   A component's declared properties.

   One row per property: its name (editable in place), its kind, and — for a
   variant — the values it may take. Renaming is a click rather than a dialog,
   because the name is the only part of a new property that cannot be guessed.
   --------------------------------------------------------------------------- */
.property-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px 12px;
}

.property-name,
.property-options {
  min-width: 0;
  flex: 1 1 auto;
  padding: 4px 6px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.property-name:focus,
.property-options:focus {
  border-color: var(--accent);
  outline: none;
}

/* The kind is a label, not a control: a property's type is decided when it is
   created and changing it would invalidate every instance's answer. */
.property-kind {
  flex: 0 0 auto;
  color: var(--text-subtle);
  font-size: 10px;
  text-transform: uppercase;
  white-space: nowrap;
}

/* What a property drives. A count rather than a list of layer names, so the
   chip keeps one width whether the property drives one layer or eleven and the
   name field beside it does not move as targets are added. */
.property-targets {
  flex: 0 0 auto;
  padding: 3px 7px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 10px;
  white-space: nowrap;
  cursor: pointer;
}

.property-targets:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* While the layers are being picked. The lead reads as instruction rather than
   as a label, so it is full width and wraps; the count is what changes as you
   click, so it is the emphatic line of the three. */
.picker-lead,
.picker-count,
.picker-warning {
  margin: 0;
  padding: 2px 12px;
  font-size: 11px;
  line-height: 1.45;
}

.picker-lead {
  color: var(--text-muted);
}

.picker-count {
  color: var(--text);
  font-weight: 600;
}

.picker-warning {
  color: var(--danger);
}

/* The documentation link: an address you can edit and, when it is one the
   editor will follow, a way to follow it. `Open` is a link rather than a button
   because it navigates — middle-click and "copy link address" should both do
   what they do anywhere else. */
.docs-field {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  flex: 1 1 auto;
}

.docs-url {
  min-width: 0;
  flex: 1 1 auto;
}

.docs-open {
  flex: 0 0 auto;
  color: var(--accent);
  font-size: 11px;
  text-decoration: none;
  white-space: nowrap;
}

.docs-open:hover {
  text-decoration: underline;
}

/* ---------------------------------------------------------------------------
   Version history
   --------------------------------------------------------------------------- */

/* A history that cannot be rebuilt. Tinted with `--danger` rather than shown as
   an ordinary note: every row below it is a state the file cannot be taken back
   to, and a person who reads past this will click one and get an error about
   node ids. */
.versions-damaged {
  margin: 0 12px 8px;
  padding: 8px 10px;
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: rgba(229, 72, 77, 0.08);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.versions-damaged-text {
  margin: 0;
  color: var(--text);
  font-size: 11px;
  line-height: 1.45;
}

.versions-repair {
  align-self: flex-start;
  padding: 4px 10px;
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: transparent;
  color: var(--danger);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.versions-repair:hover {
  background: var(--danger);
  color: #ffffff;
}

.versions-panel {
  padding: 0;
  overflow-y: auto;
}

.versions-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.versions-header {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 8px 8px 6px;
  background: var(--bg);
}

.versions-save {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 28px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.versions-save:hover:not(:disabled) {
  background: var(--bg-hover, var(--bg-sunken));
  border-color: var(--border-strong, var(--border));
}

.versions-save:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The name field replaces the button in place, so the header must not jump. */
.versions-name-field {
  width: 100%;
  min-width: 0;
  min-height: 28px;
  padding: 0 8px;
  border: 1px solid var(--accent, #2f6bff);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.versions-empty {
  margin: 4px 10px 10px;
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.5;
}

.versions-list {
  display: flex;
  flex-direction: column;
  padding: 0 4px 8px;
}

.versions-day {
  padding: 8px 6px 4px;
  color: var(--text-subtle);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.version-row {
  display: flex;
  align-items: center;
  gap: 2px;
  border-radius: var(--radius);
}

.version-row:hover {
  background: var(--bg-sunken);
}

.version-main {
  display: flex;
  flex: 1;
  align-items: baseline;
  gap: 8px;
  min-width: 0;
  min-height: 26px;
  padding: 0 6px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

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

/* A named version is the one somebody meant to come back to. */
.version-row.is-named .version-title {
  font-weight: 600;
}

.version-meta {
  flex: none;
  color: var(--text-subtle);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
}

/* Hidden until the row is under the pointer, so a long list stays a list. */
.version-more {
  flex: none;
  opacity: 0;
}

.version-row:hover .version-more,
.version-more:focus-visible {
  opacity: 1;
}

.versions-more {
  margin: 0 8px 10px;
  min-height: 26px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

/* ---------------------------------------------------------------------------
   Branches and merging
   --------------------------------------------------------------------------- */

.branch-bar {
  margin-bottom: 6px;
}

.branch-current {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 26px;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.branch-current:hover {
  background: var(--bg-sunken);
}

.branch-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-align: left;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.branch-caret {
  flex: none;
  color: var(--text-subtle);
  font-size: 9px;
}

.merge-sheet {
  position: fixed;
  z-index: 90;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(560px, 92vw);
  max-height: 80vh;
  display: flex;
  flex-direction: column;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

.merge-sheet h3 {
  margin: 0 0 10px;
  font-size: 14px;
}

.merge-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-bottom: 12px;
}

.merge-count {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 11px;
}

.merge-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.merge-clean {
  margin: 0;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.merge-conflict {
  padding: 10px 0;
  border-top: 1px solid var(--border);
}

.merge-detail {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.5;
}

.merge-choices {
  display: flex;
  gap: 6px;
}

.merge-choice {
  min-height: 26px;
  padding: 0 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

/* The chosen side, which is the only state that says the conflict is answered. */
.merge-choice.active {
  border-color: var(--accent, #2f6bff);
  background: var(--accent, #2f6bff);
  color: #fff;
}

.merge-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

.merge-cancel,
.merge-confirm {
  min-height: 28px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.merge-confirm {
  border-color: var(--accent, #2f6bff);
  background: var(--accent, #2f6bff);
  color: #fff;
}

.merge-confirm:disabled {
  opacity: 0.5;
  cursor: default;
}

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

.comments-panel {
  padding: 0;
  overflow-y: auto;
}

.comments-body {
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.comments-filters {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  gap: 4px;
  padding: 8px;
  background: var(--bg);
}

.comments-filter {
  display: flex;
  align-items: center;
  gap: 5px;
  min-height: 24px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: var(--bg-sunken);
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  cursor: pointer;
}

.comments-filter.active {
  border-color: var(--border);
  background: var(--bg);
  color: var(--text);
  font-weight: 600;
}

.comments-unread {
  min-width: 15px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent, #2f6bff);
  color: #fff;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.comments-empty {
  margin: 4px 10px 10px;
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.5;
}

.comments-list {
  display: flex;
  flex-direction: column;
  padding: 0 4px 8px;
}

.comment-row {
  display: flex;
  align-items: flex-start;
  gap: 2px;
  border-radius: var(--radius);
}

.comment-row:hover {
  background: var(--bg-sunken);
}

.comment-main {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  padding: 6px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.comment-head {
  display: flex;
  align-items: baseline;
  gap: 6px;
  min-width: 0;
}

.comment-author {
  overflow: hidden;
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.comment-when {
  flex: none;
  color: var(--text-subtle);
  font-size: 10px;
}

/* Two lines, then an ellipsis: the list is for finding a thread, not reading it. */
.comment-excerpt {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-muted);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
}

.comment-row-meta {
  color: var(--text-subtle);
  font-size: 10px;
}

/* Unread is a bar down the left edge, not a colour on the text: the text has to
   stay as readable as every other row. */
.comment-row.is-unread {
  box-shadow: inset 2px 0 0 var(--accent, #2f6bff);
}

.comment-row.is-resolved .comment-author,
.comment-row.is-resolved .comment-excerpt {
  opacity: 0.6;
}

.comment-row.is-orphaned .comment-row-meta {
  color: #d97706;
}

.comment-more {
  flex: none;
  margin-top: 4px;
  opacity: 0;
}

.comment-row:hover .comment-more,
.comment-more:focus-visible {
  opacity: 1;
}

/* The @ picker, over the composer rather than pushing it down: a list that
   reflows the textarea moves the caret out from under the pointer. */
.mention-list {
  position: absolute;
  z-index: 3;
  bottom: 100%;
  left: 8px;
  right: 8px;
  max-height: 148px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

.mention-option {
  display: block;
  width: 100%;
  min-height: 26px;
  padding: 0 8px;
  border: 0;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  text-align: left;
  cursor: pointer;
}

.mention-option.active,
.mention-option:hover {
  background: var(--bg-sunken);
}

.comment-composer {
  position: relative;
}

/* ---------------------------------------------------------------------------
   Notifications — the bell and its panel
   --------------------------------------------------------------------------- */

.toolbar-bell {
  display: flex;
  align-items: center;
}

.bell-button {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 28px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.bell-button:hover,
.bell-button.has-unread {
  color: var(--text);
}

/* The count sits on the bell's shoulder, as every product with one puts it. */
.bell-badge {
  position: absolute;
  top: 0;
  right: 0;
  min-width: 15px;
  padding: 0 4px;
  border-radius: 999px;
  background: #e5484d;
  color: #fff;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  line-height: 15px;
  text-align: center;
}

.bell-panel {
  position: fixed;
  z-index: 85;
  left: var(--bell-left, auto);
  top: var(--bell-top, 48px);
  display: flex;
  flex-direction: column;
  width: 360px;
  max-height: min(560px, 70vh);
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.bell-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 16px 10px;
}

.bell-head h3 {
  margin: 0;
  font-size: 15px;
}

.bell-clear {
  border: 0;
  background: transparent;
  color: var(--accent, #2f6bff);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.bell-clear:disabled {
  color: var(--text-subtle);
  cursor: default;
}

.bell-tabs {
  display: flex;
  gap: 4px;
  padding: 0 12px;
  border-bottom: 1px solid var(--border);
}

.bell-tab {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 6px 10px;
  border: 0;
  background: transparent;
  color: var(--text-muted);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.bell-tab.active {
  color: var(--text);
  font-weight: 600;
}

/* The underline is the tab marker, drawn inside the strip so the border below
   it is not doubled. */
.bell-tab.active::after {
  content: '';
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: var(--accent, #2f6bff);
}

.bell-tab-count {
  min-width: 16px;
  padding: 0 4px;
  border-radius: 4px;
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.bell-list {
  flex: 1;
  min-height: 120px;
  overflow-y: auto;
  padding: 6px;
}

.bell-empty {
  margin: 0;
  padding: 48px 0;
  color: var(--text-subtle);
  font-size: 12px;
  text-align: center;
}

.bell-row {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  width: 100%;
  padding: 10px 10px 10px 18px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.bell-row:hover {
  background: var(--bg-sunken);
}

/* Unread is a dot, not a colour on the words: the text has to stay as readable
   as every other row's. */
.bell-dot {
  position: absolute;
  top: 16px;
  left: 7px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: transparent;
}

.bell-row.is-unread .bell-dot {
  background: #e5484d;
}

.bell-avatar {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--accent, #2f6bff);
  color: #fff;
  font-size: 12px;
  font-weight: 600;
}

.bell-avatar.is-system {
  background: var(--bg-sunken);
  color: var(--text-muted);
}

.bell-text {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.bell-title {
  font-size: 12px;
  font-weight: 600;
}

.bell-body {
  display: -webkit-box;
  overflow: hidden;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.45;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
}

.bell-when {
  color: var(--text-subtle);
  font-size: 10px;
}

/* ------------------------------------------------------- the file identity

  The name of the file and the menu that acts on it, together in the top-left
  corner — the one place a person looks to answer "what am I in". The caret is
  a separate target from the name because "go back to my files" and "do
  something to this file" are different intentions, and one button cannot be
  both. */

.doc-identity {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
}

.doc-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text-subtle);
  cursor: pointer;
}

.doc-menu:hover,
.doc-menu[aria-expanded='true'] {
  background: var(--hover);
  color: var(--text);
}

/* --------------------------------------------------- panel header controls

  A heading that carries a control needs room for it; the plain `.sidebar-heading`
  is a label and packs to the left. Both of these push their trailing control to
  the far edge, which is where a list's "add" and a view's zoom belong. */

.pages-heading,
.inspector-heading {
  justify-content: space-between;
  gap: 8px;
  padding-right: 6px;
}

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

/* The zoom. Moved here from the tool dock: this is the corner every design
   tool puts it in, and it is the one number in the chrome that is about the
   view rather than about the selection. */
.inspector-zoom {
  min-width: 48px;
  height: 22px;
  padding: 0 6px;
  border: 0;
  border-radius: 5px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}

.inspector-zoom:hover {
  background: var(--hover);
  color: var(--text);
}

/* ------------------------------------------------------------- libraries

  Publishing this file, and consuming other files. A dialog rather than a
  panel: connecting a library is decided once and changes what every other
  panel contains, so it takes the screen for the moment it needs and leaves. */

.libraries-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 64px 24px 24px;
  background: rgba(10, 12, 16, 0.55);
  overflow-y: auto;
}

.libraries-dialog {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 860px;
  max-height: min(720px, calc(100vh - 96px));
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.libraries-header {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}

.libraries-header h2 {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 16px;
}

.libraries-close {
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  cursor: pointer;
}

.libraries-close:hover {
  background: var(--hover);
  color: var(--text);
}

.libraries-body {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
}

.libraries-rail {
  display: flex;
  flex: 0 0 220px;
  /* The reset a fixed flex basis always needs: `min-width: auto` lets a long
     library name push the rail wider than its own basis. */
  min-width: 0;
  flex-direction: column;
  gap: 2px;
  padding: 12px 10px;
  border-right: 1px solid var(--border);
  overflow-y: auto;
}

.libraries-rail-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border: 0;
  border-radius: 6px;
  background: none;
  color: var(--text-muted);
  font: inherit;
  font-size: 13px;
  text-align: left;
  cursor: pointer;
}

.libraries-rail-item:hover {
  background: var(--hover);
  color: var(--text);
}

.libraries-rail-item.active {
  background: var(--hover);
  color: var(--text);
  font-weight: 600;
}

/* The count rides on the tab, because "is anything out of date" is the
   question the dialog is reopened for and a number you must click to see
   cannot answer it. */
.libraries-badge {
  margin-left: auto;
  min-width: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: var(--accent);
  color: #fff;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  line-height: 18px;
  text-align: center;
}

.libraries-panel {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 4px;
  padding: 16px 20px;
  overflow-y: auto;
}

.libraries-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-bottom: 14px;
}

.libraries-group h3 {
  margin: 6px 0 2px;
  font-size: 13px;
  font-weight: 600;
}

.libraries-note {
  display: flex;
  flex-direction: column;
  gap: 3px;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.libraries-note strong {
  color: var(--text);
  font-size: 12px;
}

.libraries-actions {
  display: flex;
  gap: 8px;
  padding-top: 6px;
}

.libraries-footer {
  display: flex;
  justify-content: flex-end;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.libraries-primary {
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.libraries-primary:disabled {
  opacity: 0.4;
  cursor: default;
}

.libraries-plain {
  height: 28px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.libraries-plain:hover {
  background: var(--bg-sunken);
}

.library-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
  border-bottom: 1px solid var(--border);
}

.library-main {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.library-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
}

.library-meta {
  color: var(--text-subtle);
  font-size: 11px;
}

.library-connected {
  color: var(--text-subtle);
  font-size: 11px;
}

/* The assets of one library, indented under it: they belong to the row above
   and an unindented list reads as a second, unrelated one. */
.library-assets {
  display: flex;
  flex-direction: column;
  padding-left: 12px;
}

.library-asset {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
}

.library-asset-name {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* What the publication contains, with a tick per asset. Scrolls at about eight
   rows: a file with two hundred components must not push the Publish button off
   the bottom of the dialog, which is the one control this list qualifies. */
.library-contents {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 220px;
  margin-top: 8px;
  padding: 6px 8px;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-sunken);
}

.library-contents-head {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 0 2px;
}

.library-contents-title {
  flex: 1 1 auto;
  color: var(--text-subtle);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.library-check {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 4px 2px;
  color: var(--text-muted);
  font-size: 12px;
  cursor: pointer;
}

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

.library-check input {
  flex: 0 0 auto;
  margin: 0;
  accent-color: var(--accent);
}

/* ---------------------------------------------------------- style picker

  The list that hangs off every section heading, and the dialog that names what
  is on the node. One of each for Fill, Stroke, Typography and Effects — see
  `style-picker.ts` for why they are not four. */

.style-button.is-styled {
  color: var(--accent);
}

.style-picker {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 260px;
  max-height: 420px;
  padding: 8px 0;
  overflow-y: auto;
}

.style-picker-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 8px 4px 12px;
}

.style-picker-title {
  font-size: 12px;
  font-weight: 600;
}

.style-search {
  height: 26px;
  margin: 0 8px 4px;
  padding: 0 8px;
  /* The reset every fixed-basis field needs; see the note on `min-width` in
     `stylesheet.test.ts`. */
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

/* The style a node is already wearing. Marked rather than moved to the top:
   a list that reorders itself under the pointer is a list you cannot learn. */
.style-row.active {
  background: var(--hover);
  font-weight: 600;
}

.style-row .style-detail {
  margin-left: auto;
  color: var(--text-subtle);
  font-size: 11px;
  font-variant-numeric: tabular-nums;
}

/* A style with no colour to show — a text or effect style — gets a neutral
   chip rather than an empty ring that reads as "white". */
.style-swatch.is-abstract {
  background: repeating-linear-gradient(
    45deg,
    var(--bg-sunken),
    var(--bg-sunken) 3px,
    var(--border) 3px,
    var(--border) 6px
  );
}

.style-detach {
  margin: 6px 8px 0;
  padding: 6px 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.style-detach:hover {
  background: var(--bg-sunken);
}

.style-empty {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 12px;
  color: var(--text-muted);
  font-size: 11px;
  line-height: 1.5;
}

.style-empty strong {
  color: var(--text);
  font-size: 12px;
}

/* --- the create dialog --- */

.style-dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 96px 24px 24px;
  background: rgba(10, 12, 16, 0.45);
  overflow-y: auto;
}

.style-dialog {
  display: flex;
  flex-direction: column;
  gap: 12px;
  width: 100%;
  max-width: 360px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

.style-dialog-title {
  margin: 0;
  font-size: 14px;
}

/* The preview is the reason the dialog exists: a style is named after the
   value looks right, so the value has to be on screen while it is named. */
.style-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 120px;
  border: 1px solid var(--border);
  border-radius: 8px;
  overflow: hidden;
}

.style-preview.is-chequered {
  background-image:
    linear-gradient(45deg, var(--bg-sunken) 25%, transparent 25%),
    linear-gradient(-45deg, var(--bg-sunken) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--bg-sunken) 75%),
    linear-gradient(-45deg, transparent 75%, var(--bg-sunken) 75%);
  background-position: 0 0, 0 6px, 6px -6px, -6px 0;
  background-size: 12px 12px;
}

.style-preview-colour {
  width: 100%;
  height: 100%;
}

.style-preview-text {
  font-size: 28px;
}

.style-preview-effect {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
}

.style-dialog-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: 11px;
  color: var(--text-muted);
}

.style-dialog-field input {
  height: 30px;
  min-width: 0;
  padding: 0 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.style-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

.style-dialog-confirm {
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius);
  background: var(--accent);
  color: #fff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.style-dialog-cancel {
  height: 30px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--panel);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

/* The row and the button that edits it. The row is a button, so the menu
   cannot be inside it — nested buttons are invalid and the inner one does not
   get the click everywhere. The wrapper carries both. */
.style-row-wrapper {
  display: flex;
  align-items: center;
}

.style-row-wrapper .style-row {
  flex: 1 1 auto;
  min-width: 0;
}

/* Shown on hover or focus only: a column of ⋯ down a list of forty styles is
   noise that competes with the swatches, which are what the list is read by. */
.style-row-more {
  opacity: 0;
  margin-right: 6px;
}

.style-row-wrapper:hover .style-row-more,
.style-row-more:focus-visible {
  opacity: 1;
}

/* ---------------------------------------------------------- the server dialog

  Sign in, open a file from the server, send this one to it. Sized narrower than
  the libraries dialog: it is a form and two lists, not a catalogue. */

.server-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 64px 24px 24px;
  background: rgba(10, 12, 16, 0.55);
  overflow-y: auto;
}

.server-dialog {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  max-height: min(720px, calc(100vh - 96px));
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.server-header {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}

.server-header h2 {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 16px;
}

.server-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.server-close:hover {
  background: var(--hover);
  color: var(--text);
}

.server-body {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 10px;
  padding: 16px 20px 20px;
  overflow-y: auto;
}

.server-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-top: 6px;
}

.server-group h3 {
  margin: 0;
  color: var(--text-subtle);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.server-note {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.45;
}

/* A label above its input rather than beside it: four fields side by side in a
   560px dialog leaves an address field too narrow to read a URL in. */
.server-field {
  display: flex;
  flex-direction: column;
  gap: 4px;
  color: var(--text-muted);
  font-size: 11px;
}

.server-field input {
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--panel);
  color: var(--text);
  font-size: 13px;
}

.server-field input:focus {
  border-color: var(--accent);
  outline: none;
}

.server-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding-top: 8px;
}

.server-primary {
  padding: 7px 14px;
  border: none;
  border-radius: 6px;
  background: var(--accent);
  color: #fff;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.server-primary:disabled {
  opacity: 0.5;
  cursor: default;
}

.server-plain {
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: transparent;
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
}

.server-plain:hover {
  background: var(--hover);
}

.server-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
}

.server-main {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.server-name {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
}

.server-meta {
  color: var(--text-subtle);
  font-size: 11px;
}

/* A project is a button, because clicking it loads its files — a row that looks
   like text and behaves like a control is one nobody clicks. */
.server-project {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}

.server-project:hover {
  background: var(--hover);
}

.server-project.active {
  border-color: var(--border);
  background: var(--bg-sunken);
}

/* Indented under the project they belong to — the libraries dialog's reasoning:
   an unindented list reads as a second, unrelated one. */
.server-files {
  display: flex;
  flex-direction: column;
  padding-left: 14px;
}

.server-file {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 5px 0;
}

.server-file-name {
  display: flex;
  flex: 1 1 auto;
  align-items: center;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
  color: var(--text-muted);
  font-size: 12px;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.server-open {
  color: var(--text-subtle);
  font-size: 11px;
}

/* A colour under a token: the swatch keeps showing the value and stops being a
   control, matching `.number-input.is-bound` beside it. The cursor is the tell
   — a swatch that still looked clickable would be the same invitation the
   picker used to be. */
.color-row.is-bound .color-swatch.is-bound {
  cursor: default;
  border-color: transparent;
  box-shadow: 0 0 0 1px var(--border);
}

.color-row.is-bound .token-chip {
  flex: 1 1 auto;
  min-width: 0;
}

/* ---------------------------------------------------------------------------
   Comparing two versions

   The same sheet shape as the merge dialog, widened for two canvases. The
   canvases carry a chequerboard behind them for the same reason a colour
   swatch does: a design on a transparent page and one on a white page are
   different pictures, and a white pane would show both as white.
   --------------------------------------------------------------------------- */

.compare-sheet {
  position: fixed;
  z-index: 90;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(820px, 94vw);
  max-height: 86vh;
  display: flex;
  flex-direction: column;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
}

.compare-sheet h3 {
  margin: 0 0 10px;
  font-size: 14px;
}

.compare-body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.compare-panes {
  display: flex;
  gap: 12px;
  align-items: flex-start;
}

.compare-pane {
  flex: 1 1 0;
  min-width: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.compare-picker {
  width: 100%;
  min-height: 26px;
  padding: 0 6px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 11px;
}

.compare-canvas {
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%),
    linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%);
  background-size: 12px 12px;
  background-position: 0 0, 6px 6px;
}

.compare-caption {
  color: var(--text-muted);
  font-size: 11px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.compare-summary {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 12px;
}

.compare-count {
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--bg-sunken);
  color: var(--text-muted);
  font-size: 11px;
}

.compare-list {
  width: 100%;
  margin-top: 8px;
}

.compare-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 4px 0;
  border-top: 1px solid var(--border);
  font-size: 11px;
}

.compare-row-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The kind of change, colour-coded the way the layer list marks state: the
   word is the label, the colour is what makes a list of forty scannable. */
.compare-row-kind {
  flex: 0 0 auto;
  color: var(--text-muted);
}

.compare-row[data-change='added'] .compare-row-kind {
  color: var(--ok, #2ea043);
}

.compare-row[data-change='removed'] .compare-row-kind {
  color: var(--danger, #d93636);
}

.compare-clean,
.compare-more,
.compare-empty {
  margin: 8px 0 0;
  color: var(--text-muted);
  font-size: 12px;
  line-height: 1.5;
}

.compare-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 14px;
}

.compare-cancel,
.compare-restore {
  min-height: 28px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.compare-restore {
  border-color: var(--accent, #2f6bff);
  background: var(--accent, #2f6bff);
  color: #fff;
}

.compare-restore:disabled {
  opacity: 0.5;
  cursor: default;
}

/* The page both panes show, when the file has more than one. Hidden entirely
   for a single-page file rather than shown disabled: a control with one choice
   in it is furniture. */
.compare-page-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
}

.compare-page-label {
  color: var(--text-muted);
  font-size: 11px;
}

.compare-page-picker {
  width: auto;
  min-width: 140px;
}

/* A change-list row is a button — clicking it frames that layer in both panes.
   The appearance is reset back to the row it replaced, because the affordance
   here is the hover and the pressed state, not a button's chrome. */
button.compare-row {
  width: 100%;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 11px;
  text-align: left;
  cursor: pointer;
}

button.compare-row:hover {
  background: var(--bg-sunken);
}

/* The framed layer. Its own colour, matching the outline drawn on the canvas,
   so the row and the box on the picture read as the same thing. */
button.compare-row.is-focused {
  background: var(--accent-soft);
}

button.compare-row.is-focused .compare-row-name {
  font-weight: 600;
}

/* ---------------------------------------------------------------------------
   Importing a design from another tool.

   A dialog rather than a panel, and for the same reason the server one is:
   an import is a decision taken once, it changes what the document contains,
   and it has nothing to say while somebody is drawing.
   --------------------------------------------------------------------------- */
.import-backdrop {
  position: fixed;
  inset: 0;
  z-index: 110;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 64px 24px 24px;
  background: rgba(10, 12, 16, 0.55);
  overflow-y: auto;
}

.import-dialog {
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 560px;
  max-height: min(720px, calc(100vh - 96px));
  border: 1px solid var(--border);
  border-radius: 12px;
  background: var(--panel);
  box-shadow: var(--shadow-panel);
  overflow: hidden;
}

.import-header {
  display: flex;
  flex: 0 0 auto;
  align-items: center;
  gap: 10px;
  padding: 16px 20px;
  border-bottom: 1px solid var(--border);
}

.import-header h2 {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  font-size: 16px;
}

.import-close {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: none;
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
}

.import-close:hover:not(:disabled) {
  background: var(--hover);
  color: var(--text);
}

.import-close:disabled {
  opacity: 0.4;
  cursor: default;
}

.import-body {
  flex: 1 1 auto;
  min-height: 0;
  padding: 16px 20px 20px;
  overflow-y: auto;
}

/* Each source is its own block with a rule between, because the two ways in are
   genuinely different — one is a file and the other is an API — and running
   them together would read as one form with too many fields. */
.import-group + .import-group {
  margin-top: 20px;
  padding-top: 20px;
  border-top: 1px solid var(--border);
}

.import-group h3,
.import-report h3 {
  margin: 0 0 6px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.import-note {
  margin: 0 0 12px;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-muted);
}

/* The "what is coming next" line. Quieter than the rest, because it is context
   rather than an instruction, and a person reading the screen to do something
   should reach the buttons before they reach the roadmap. */
.import-note-quiet {
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
  color: var(--text-subtle);
  font-size: 11px;
}

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

.import-field span {
  font-size: 11px;
  color: var(--text-muted);
}

.import-field input {
  min-width: 0;
  padding: 7px 9px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-sunken);
  color: var(--text);
  font: inherit;
  font-size: 12px;
}

.import-field input:focus {
  border-color: var(--accent);
  outline: none;
}

.import-primary {
  padding: 7px 14px;
  border: 1px solid var(--accent);
  border-radius: 6px;
  background: var(--accent);
  color: #ffffff;
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}

.import-primary:hover {
  filter: brightness(1.06);
}

.import-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  margin-top: 16px;
}

/* The headline number: how much of the file came across. It is the question
   somebody arrives at this screen with, so it is the largest thing on it. */
.import-score {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin-bottom: 12px;
}

.import-score strong {
  font-size: 32px;
  line-height: 1;
  color: var(--positive);
}

.import-score[data-status='completed-with-warnings'] strong {
  color: var(--text);
}

.import-score[data-status='failed'] strong {
  color: var(--danger);
}

.import-score span {
  font-size: 12px;
  color: var(--text-muted);
}

/* Grouped, not listed: two hundred identical warnings about layer blur are one
   fact, and two hundred lines are a list nobody reads. */
.import-list {
  margin: 0 0 12px;
  padding-left: 18px;
  font-size: 12px;
  line-height: 1.6;
  color: var(--text);
}

.import-list li[data-support='unavailable'] {
  color: var(--danger);
}

.import-list li[data-support='partial'],
.import-list li[data-support='converted'] {
  color: var(--text-muted);
}

.import-error {
  margin: 0 0 12px;
  padding: 10px 12px;
  border: 1px solid var(--danger);
  border-radius: var(--radius);
  background: rgba(229, 72, 77, 0.08);
  font-size: 12px;
  line-height: 1.5;
  color: var(--text);
}
