/* The application chrome: one screen, five panes, no scrolling page.
 *
 * Macromedia-era, deliberately. Light grey panels, hairline rules, small type,
 * and a timeline whose keyframes are DOTS on a frame grid — Flash's vocabulary,
 * because this tool's model is Flash's model (a library of timelines, instances
 * placed on a stage, a layer list against a ruler) and borrowing the look is the
 * cheapest way to make that legible to anyone who has used one. What is not
 * borrowed is the chrome of the period: no bevels, no gradients on buttons, no
 * inset wells. Flat, clean, one accent.
 *
 * Raw CSS on purpose. This file styles a fixed grid of panes whose class names
 * are the domain's own — `.tl-row`, `.pool-item`, `.swatch` — so a utility
 * framework would buy naming we want anyway, at the cost of a third process in
 * a dev loop the repo keeps at two. Tokens are custom properties; everything
 * else is a handful of grids.
 *
 * It lives under static/ rather than inline in the template because it is now
 * long enough that "the styles" and "the page" are two things. */

:root {
  color-scheme: light;

  /* The stage's background is the PALETTE's index 0, not a theme colour. The
     two agreeing is what makes the preview honest, so it is written here as the
     same value arthur.domain.palette declares and nowhere else in this file.
     It is the one dark surface on the page, and it should be: the picture is
     320x200 of indexed colour and the UI is not allowed to compete with it. */
  --stage: #12141c;

  --desk:   #8e8e8e;   /* the work area the stage floats on */
  --pane:   #f2f2f0;
  --chrome: #e4e4e1;
  --sunk:   #ebebe8;
  --line:   #b0b0ac;
  --hair:   #d2d2ce;
  --fg:     #1f1f1f;
  --dim:    #6e6e6a;

  /* One accent, used for selection and for nothing else. */
  --sel:    #2f6fc0;
  --sel-bg: #cfe0f5;

  /* A SECOND accent because selection and effective creation target are two
     facts that are often true of one node at the same time.
     Violet rather than another blue: in the chrome it has to be told apart
     from --sel, and on the stage from the warm gold the handles are drawn in.
     Colour is not the only channel either — the target outline is SOLID where the
     selection's box is dashed, and it carries no handles at all, because there
     is nothing about being a creation parent that you could drag. --target-stage is the
     same hue lifted to read against --stage, which is the one dark surface. */
  --target:       #8d4bd6;
  --target-stage: #c79bf2;

  /* A THIRD, and the last. SELECTED IN THE TIMELINE had been --sel-bg, which is
     #cfe0f5 against spans drawn in #dfe8f4: two pale blues a hair apart, in the
     one place where which row and which clip is selected decides what the pane
     is showing — an expanded lane opens exactly the selected clip. The fix is
     not a stronger blue, which stays in the family the spans already own, but a
     different hue entirely. Ochre: warm against every cool surface in the pane,
     clear of --target's violet, --live's green and the playhead's red, and dark
     enough at full strength to carry white type on a 21px block. */
  --pick:    #a85c00;
  --pick-bg: #ffd9a0;
  --pick-fg: #4a2800;

  /* Auto-key is a recording state, distinct from selection and creation target. */
  --live:    #239447;
  --live-bg: #e2f5e7;

  /* A keyframe is a dot and a dot is ink. Flash draws them black and so does
     this; the frames a node exists over are a pale tint behind them. */
  --key:      #1f1f1f;
  --span:     #dfe8f4;
  --span-line:#a9bdd8;
  --grid:     #dcdcd8;
  --grid-5:   #c6c6c2;
  --playhead: #c8322b;
  --warn:     #b4502a;

  --row:   21px;
  --ruler: 19px;
  --label: 210px;
}

* { box-sizing: border-box; }

html, body { margin: 0; height: 100%; overflow: hidden; }

body {
  background: var(--desk);
  color: var(--fg);
  font: 11px/1.45 "Lucida Grande", "Segoe UI", system-ui, sans-serif;
}

#app { height: 100%; }

/* The preview is nearest-neighbour everywhere. A browser that smoothed the
   upscale would misrepresent the exact look the tool exists to judge, so this
   rule is load-bearing rather than cosmetic. */
canvas { image-rendering: pixelated; }

audio { display: none; }

/* --------------------------------------------------------------------------
   the frame */

/* THE TRACKS ARE CUSTOM PROPERTIES, NOT LENGTHS. `ui/layout` writes all six on
   this element, which is what makes the three edges between the panes draggable:
   a drag is one `assoc-in` and one property, and no pane's contents re-render.
   The defaults here are the same numbers `ui/layout`'s `normal` holds, written
   twice so that this file still describes a window on its own. */
.app {
  display: grid;
  height: 100%;
  --pool-w: 210px;
  --params-w: 250px;
  --time-h: 232px;
  --grip-pool: 4px;
  --grip-params: 4px;
  --grip-time: 4px;
  grid-template-columns:
    var(--pool-w) var(--grip-pool) minmax(0, 1fr) var(--grip-params) var(--params-w);
  /* The location bar is its own row and takes its height from the stage, not
     from the timeline: it exists to explain what the timeline is showing, so
     paying for it in timeline rows would be the wrong trade. */
  grid-template-rows: 30px minmax(0, 1fr) 21px var(--grip-time) var(--time-h);
  grid-template-areas:
    "top  top   top  top   top"
    "pool gpool view gparm params"
    "loc  loc   loc  loc   loc"
    "gtime gtime gtime gtime gtime"
    "time time  time time  time";
  gap: 1px;
  background: var(--line);
}

.top    { grid-area: top; }
.pool   { grid-area: pool; }
.view   { grid-area: view; }
.params { grid-area: params; }
.loc    { grid-area: loc; }
.time   { grid-area: time; }

/* The draggable edge between two panes. Four pixels of chrome that lights up
   under the pointer: a hairline is honest about where the boundary is and
   impossible to hit, and a wide gutter would be furniture. */
.grip { touch-action: none; background: var(--chrome); }
.grip:hover, .grip:active { background: var(--sel); }
.grip.col { cursor: col-resize; }
.grip.row { cursor: row-resize; }
.grip-pool   { grid-area: gpool; }
.grip-params { grid-area: gparm; }
.grip-time   { grid-area: gtime; }

/* Which panes are open, in the top bar. Lower-case words rather than icons: the
   three panes have names and the names are what the rest of the chrome calls
   them. */
.panes > button { color: var(--dim); }
.panes > button.on { color: var(--fg); }

/* A zoom readout that is also the way back to 1:1, so it is a button that looks
   like the number it shows. Tabular figures, because the + beside it must not
   move as the percentage passes 100. */
.zoom-at { min-width: 44px; text-align: center; font-variant-numeric: tabular-nums; }

/* Every pane is its own scroll container. `min-height: 0` is what lets a grid
   row shrink below its content instead of pushing the layout taller than the
   viewport — the single line that separates "a page that scrolls" from "an
   application window". */
.pane {
  background: var(--pane);
  min-height: 0;
  min-width: 0;
  overflow: auto;
  display: flex;
  flex-direction: column;
}

.pane-head {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 0 7px;
  height: 21px;
  flex: 0 0 21px;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
  color: var(--dim);
  letter-spacing: .03em;
  /* One line, always. A strip of controls that reflows to two lines resizes the
     pane under it, and the pane under this one is the timeline. */
  white-space: nowrap;
}

.pane-head .readout { color: var(--fg); }

/* A pick-one that looks like the other pick-ones. A native select in this strip
   draws a tall white well and a chevron of the browser's choosing, which reads
   as a form field among buttons; the app's own idiom for "choose one of these"
   is `label ▾`, and this IS one of those. So: the select keeps the behaviour and
   the keyboard, and borrows the look. The arrow is the wrapper's, because an
   option list cannot carry one. */
.pick { position: relative; display: inline-flex; align-items: center; }
.pick > select {
  appearance: none;
  background: var(--pane);
  padding: 1px 15px 1px 6px;
  cursor: pointer;
}
.pick > select:hover { background: #fff; }
.pick::after {
  content: "▾";
  position: absolute;
  right: 5px;
  color: var(--dim);
  pointer-events: none;
}

.pane-head .spacer { flex: 1; }
.pane-body { padding: 5px; }

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

button {
  font: inherit;
  color: var(--fg);
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 1px 7px;
  cursor: pointer;
}

button:hover:not(:disabled) { background: #fff; }
button:active:not(:disabled) { background: var(--sunk); }
button:disabled { opacity: .38; cursor: default; }

button.on {
  background: var(--sel-bg);
  border-color: var(--sel);
  color: #14395f;
}

select, input[type="text"] {
  font: inherit;
  color: var(--fg);
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 1px 3px;
  max-width: 100%;
}

input[type="range"] { width: 100%; accent-color: var(--sel); }

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

.dim  { color: var(--dim); }
.warn { color: var(--warn); }

/* Buttons that are one control: a transport, a stepper, a mode picker. They
   share their borders, so the group reads as a single object with parts rather
   than as several things that happen to be adjacent — which is the whole claim
   a segmented control makes. `.seg` is `.group` with that meaning; both are drawn
   because the difference is what the buttons do, not how they look.

   The negative margin collapses the doubled border between two buttons into
   one hairline. A pressed button then has to be raised, or the neighbour's
   plain border covers half of its accent one. */
.group, .seg { display: flex; align-items: center; }
.group > button, .seg > button { border-radius: 0; margin-left: -1px; }
.group > button:first-child, .seg > button:first-child { margin-left: 0; border-radius: 2px 0 0 2px; }
.group > button:last-child, .seg > button:last-child { border-radius: 0 2px 2px 0; }
.group > button.on, .seg > button.on { position: relative; z-index: 1; }

/* The rule between two groups of unrelated controls. Shorter than the strip, so
   it reads as a division of the row rather than as a wall across it. */
.sep { flex: 0 0 1px; width: 1px; height: 13px; background: var(--line); }

/* An icon button. The glyph is inked in `currentColor`, so `.on` colours it
   with the same rule that colours a word. 11px to sit on 11px type. */
button.ico { padding: 2px 5px; line-height: 0; }
button.ico > svg { display: block; width: 11px; height: 11px; }
/* Play is the one control in the strip you aim at without looking. */
button.ico-play { padding-left: 8px; padding-right: 8px; }

/* A number that changes every frame. Tabular figures stop it twitching, and
   stop the controls after it being nudged about as the count passes 9 and 99. */
.readout { font-variant-numeric: tabular-nums; }

/* The answer to a refusal rather than a standing command — `lane-retry` only
   exists because something was declined and named this as the way through. */
button.retry { border-color: var(--sel); color: var(--sel); }

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

.top {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0 8px;
  background: var(--chrome);
}

.top .brand { color: var(--dim); letter-spacing: .14em; text-transform: uppercase; }
.top .status {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--dim);
}

.project-title { max-width: 22vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.project-name { width: 180px; }
.export-scrim { position: fixed; inset: 0; z-index: 50; background: rgba(0, 0, 0, .45); }
.export-dialog {
  position: fixed; z-index: 51; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(360px, calc(100vw - 32px)); padding: 14px;
  background: var(--pane); border: 1px solid var(--line); border-radius: 3px;
  box-shadow: 0 8px 30px rgba(0, 0, 0, .45);
}
.export-dialog h2 { margin: 0 0 12px; font: inherit; font-weight: 600; }
.export-field { display: grid; gap: 4px; margin: 8px 0; color: var(--dim); }
.export-field select { color: var(--fg); }

/* --------------------------------------------------------------------------
   the open menu */

.menu-wrap { position: relative; }

/* A full-page catcher behind the panel, so a click anywhere else dismisses it.
   More predictable than a document listener that has to be added, removed, and
   told to ignore the click that opened the menu. */
.menu-scrim { position: fixed; inset: 0; z-index: 40; }

.menu {
  position: absolute;
  top: calc(100% + 3px);
  right: 0;
  z-index: 41;
  min-width: 250px;
  max-height: 72vh;
  overflow: auto;
  padding: 5px;
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .3);
}

.menu > h2 {
  margin: 6px 0 3px;
  font: inherit;
  font-weight: 600;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .07em;
}

.menu > h2:first-child { margin-top: 0; }
.menu > h2 + .dim { margin-bottom: 4px; }

.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 2px 6px;
  border: 1px solid transparent;
  border-radius: 2px;
  background: none;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.menu-item:hover:not(:disabled) { background: var(--sel-bg); }

/* A fixture is not a document. Italic is the whole of the distinction, and it is
   enough: the heading above already says what these are. */
.menu-item.example { font-style: italic; }
.menu-item .sub { display: block; color: var(--dim); }

/* Rows about the document rather than choices in it: who can edit. */
.menu-item.static { display: flex; align-items: center; gap: 6px; cursor: default; }
.menu-item.static:hover { background: none; }
.menu-item.static .sub { display: inline; margin-left: auto; }
.menu-item.static button { margin-left: auto; }
.menu .row { margin: 4px 0; flex-wrap: nowrap; }
.menu .row input { flex: 1; min-width: 0; }
.menu p { margin: 4px 0; }
.account { display: grid; gap: 4px; }
button.link { border: 0; background: none; padding: 0; color: var(--sel); cursor: pointer; }

/* `/`: your projects, over the editor. */
.index { position: fixed; inset: 0; z-index: 30; display: flex; flex-direction: column;
         background: var(--pane); }
.index-body { width: min(640px, 100%); margin: 0 auto; padding: 24px 16px; overflow: auto; }
.index-body h1 { font: inherit; font-size: 18px; font-weight: 600; margin: 0 0 12px; }
.index-head { display: flex; align-items: baseline; justify-content: space-between; }
.index-list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--hair); }
.index-list li { display: flex; justify-content: space-between; gap: 12px;
                 padding: 7px 2px; border-bottom: 1px solid var(--hair); }
.index-list a { color: var(--fg); text-decoration: none; font-weight: 600; }
.index-list a:hover { color: var(--sel); }
a.brand { text-decoration: none; }

/* Undo, its list, redo: one group. */
.undo { display: flex; gap: 0; }
.undo > button { border-radius: 0; }
.undo > button:first-child { border-radius: 2px 0 0 2px; }
.undo > .undo-list { padding: 0 4px; margin-right: 4px; border-left: 0; border-radius: 0 2px 2px 0; }
.menu.menu-left { left: 0; right: auto; }

/* A menu dropping out of a PANE HEAD. Fixed and placed by `ui/menu` against the
   button, because a pane clips its own overflow and an absolutely positioned
   panel would be cut off at the pane's edge — invisibly, and more of it the
   longer the menu gets. `top`/`bottom`/`left`/`max-height` all come from there.

   Wide enough that a command's one-line explanation is one line. At 215px every
   `.sub` in the cel menu wrapped, which doubled the panel's height for no gain:
   the explanations are the reason these are menu rows and not buttons, so they
   get the width to be read in. */
.menu.menu-drop {
  position: fixed;
  /* BOTH offsets on each axis are reset, and `ui/menu` sets exactly one of each
     inline. Leaving `.menu`'s own `top` standing alongside an inline `bottom`
     over-constrains the box, and a fixed box given both offsets takes its HEIGHT
     from them — which, from a toolbar near the foot of the window, computes
     negative and collapses the panel to its padding with the commands still
     inside it. */
  top: auto;
  bottom: auto;
  left: auto;
  right: auto;
  width: 272px;
  overflow: auto;
}
.menu-note { padding: 1px 6px 5px; }

/* A disabled row still says what the command is FOR. That is the whole trade:
   a greyed button hides its reason in a `title`, a greyed row prints it. */
.menu-item:disabled { opacity: .5; cursor: default; }
.menu-item:disabled:hover { background: none; }
.menu-item { padding: 3px 6px; }
.menu-item .sub { font-size: 10px; line-height: 1.3; white-space: normal; }

/* The one way to hand the document to somebody else, so it looks like one. */
button.share-button { background: var(--sel); color: #fff; border-color: var(--sel); font-weight: 600; padding: 1px 12px; }
button.share-button:hover, button.share-button.on { filter: brightness(1.1); }

/* Who else has this document open. */
.roster { display: flex; gap: 2px; align-items: center; }
.peer {
  width: 18px; height: 18px; border-radius: 50%;
  display: inline-grid; place-items: center;
  background: var(--sel); color: #fff; font-size: 10px; font-weight: 600;
}
.peer.guest { background: var(--dim); }

/* --------------------------------------------------------------------------
   media pool

   A FILE LIST, AND IT IS 210px WIDE. Every row is one line — a picture at the
   stage's own proportions, a name, one number — because the pane under this one
   is the timeline and a row that costs two lines costs timeline rows. The row is
   --row tall, which is a timeline row, so a pool of symbols and the rows they
   end up on are the same size object.

   One exception, deliberately: the main timeline. See `.pool-row.main`. */

/* The search strip, between the pane head and the tabs. Its own band rather
   than crowded into the head, which is one line and already holds the + . */
.pool-find {
  position: sticky;
  top: 21px;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 3px 5px;
  background: var(--chrome);
  border-bottom: 1px solid var(--hair);
}

.pool-search {
  flex: 1;
  min-width: 0;
  height: 17px;
  padding: 0 5px;
  font: inherit;
  color: var(--fg);
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 2px;
}

/* Safari draws its own clear button inside a search input, in its own idiom and
   at its own size. The one next to it is this file's. */
.pool-search::-webkit-search-decoration,
.pool-search::-webkit-search-cancel-button { -webkit-appearance: none; }

.pool-clear {
  flex: 0 0 auto;
  width: 17px;
  height: 17px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: none;
  color: var(--dim);
  cursor: pointer;
}
.pool-clear:hover { background: var(--hair); color: var(--fg); }

/* The scope tabs, in the vocabulary `ui/tabs` already established for symbols —
   the same object, so two rows of tabs on one screen read as one idea. */
.pool > .tabs { top: 45px; position: sticky; z-index: 2; }

/* A tally, shown only on the tab you are NOT looking at and only while a search
   is running: it says "what you asked for is over here". */
.tab .count {
  padding: 0 4px;
  border-radius: 7px;
  background: var(--sel-bg);
  color: var(--sel);
  font-variant-numeric: tabular-nums;
}

/* MEDIA, SOUNDS, TIMELINES. A heading is one line and it sticks, so scrolling a
   long list never leaves you looking at rows with no idea what kind they are. */
.pool-section { margin-bottom: 7px; }

.pool-section > h2 {
  position: sticky;
  top: 69px;
  z-index: 1;
  margin: 0 0 2px;
  padding: 1px 5px;
  font: inherit;
  font-weight: 600;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .07em;
  background: var(--pane);
}

.pool-section > .dim { padding: 0 5px 2px; }

.pool-rows { display: flex; flex-direction: column; }

/* What a search found nothing to say. */
.pool-empty { padding: 6px 5px; color: var(--dim); }

.pool-project { margin: 0 0 1px 5px; }
.pool-project > summary { cursor: pointer; color: var(--dim); padding: 1px 0; }
.pool-project > .pool-rows { margin-left: 8px; }

/* THE ROW IS THE BUTTON. Click selects, double-click opens, and drag starts
   anywhere across it — the whole strip, not the few characters of the name. The
   wrapper carries the state classes and the frame; the button inside it fills
   that frame edge to edge, so there is no dead strip anywhere along the row. */
.pool-row {
  display: flex;
  align-items: center;
  gap: 2px;
  min-width: 0;
  height: var(--row);
  padding: 0 4px;
  border: 1px solid transparent;
  border-radius: 2px;
}

.pool-row:hover:not(.disabled) { background: #fff; }
.pool-row.on { background: var(--sel-bg); border-color: var(--sel); }

/* WHICH ONE IS OPEN, as a mark and not as the word "open" — a list this narrow
   cannot spend twenty pixels of every name on a word that is true of one row.
   The accent, because selection is the only thing it is ever used for and the
   open symbol is what the rest of the screen is showing. */
.pool-row.open { box-shadow: inset 2px 0 0 var(--sel); }

.pool-item {
  display: flex;
  align-items: center;
  gap: 5px;
  flex: 1;
  min-width: 0;
  padding: 0;
  text-align: left;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  cursor: grab;
}

/* Shrinks to the name, so the pencil sits against the end of the word rather
   than out at the edge of the pane — and ellipses rather than pushing the
   number off the row. */
.pool-item .text { display: flex; flex-direction: column; flex: 0 1 auto; min-width: 0; }
.pool-item .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* The number, at the far edge and never moving: `auto` on the left is what
   pushes it there, and tabular figures are what stop it shuffling as the rows
   scroll past. */
.pool-item > .sub {
  flex: 0 0 auto;
  margin-left: auto;
  padding-left: 4px;
  color: var(--dim);
  font-variant-numeric: tabular-nums;
}

/* Every row leads with a picture, and they are all this box, so the names line
   up down the list whatever is beside them. 16:10 is the stage's own ratio at
   320x200 — a thumbnail that is not the picture's shape is a thumbnail you have
   to think about. */
.pool-item .thumb {
  flex: 0 0 32px;
  width: 32px;
  height: 20px;
  object-fit: cover;
  background: var(--stage);
  border-radius: 1px;
  pointer-events: none;
}

.pool-item .thumb.sound {
  display: flex;
  align-items: center;
  justify-content: center;
  color: #d9d9d9;
}

/* THE MAIN TIMELINE, under the PROJECT heading and at the top of the pane.
   `clip/opens-on`'s answer: the longest symbol nothing else places, which is
   where the work happens. The one row in the pane given size, a second line and
   a surface of its own — spent here rather than spread over every row, which is
   what keeps the list below it a list. */
.pool-row.main {
  height: auto;
  margin-bottom: 5px;
  padding: 4px;
  background: var(--sunk);
  border-color: var(--hair);
}

.pool-row.main .thumb {
  flex: 0 0 64px;
  width: 64px;
  height: 40px;
}

.pool-row.main .name { font-weight: 600; }

/* Both facts on one line under the name. Beside a 64px picture there is room,
   and this is the one row where a person wants more than the length before
   they click. */
.pool-row.main .detail {
  color: var(--dim);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The pencil. A span and not a button — see `ui/pool`'s `row` — so it needs its
   own centring and cursor rather than inheriting a control's.

   Hidden until the row is under the pointer or holds focus, because a column of
   twenty pencils is a column of twenty pencils; `visibility` rather than
   `display`, so it keeps its width and the name beside it does not reflow as the
   pointer crosses the list. */
.pool-rename {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 15px;
  width: 15px;
  height: 15px;
  border-radius: 2px;
  color: var(--dim);
  line-height: 1;
  cursor: pointer;
  visibility: hidden;
}

.pool-row:hover .pool-rename,
.pool-row:focus-within .pool-rename { visibility: visible; }
.pool-rename:hover { background: var(--hair); color: var(--fg); }

/* Renaming. The input REPLACES the row at the same height, so a list does not
   jump while you type in it. */
.pool-name {
  flex: 1;
  min-width: 0;
  height: 17px;
  padding: 0 4px;
  font: inherit;
  color: var(--fg);
  background: #fff;
  border: 1px solid var(--sel);
  border-radius: 2px;
}

/* The whole pane is the drop target, so the cue has to be the pane and not a
   zone inside it — a drop that lands two pixels outside a dashed rectangle and
   silently does nothing is the failure this avoids. */
.pool.dropping {
  outline: 2px dashed var(--sel);
  outline-offset: -3px;
  background: var(--sel-bg);
}

/* --------------------------------------------------------------------------
   the viewer: palette above, stage below */

.view {
  display: flex;
  flex-direction: column;
  background: var(--desk);
  min-height: 0;
  overflow: hidden;
}

/* One tab per open symbol: flat, on the chrome, with the open one marked by the
   selection colour underneath rather than by a raised shape. */
.tabs {
  display: flex;
  align-items: stretch;
  height: 24px;
  flex: 0 0 24px;
  padding: 0 4px;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
}

.tab {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 0 6px 0 10px;
  color: var(--dim);
  border-right: 1px solid var(--hair);
  cursor: pointer;
  white-space: nowrap;
}

.tab:hover { color: var(--fg); background: var(--sunk); }
.tab.on { color: var(--fg); background: var(--pane); box-shadow: inset 0 -2px 0 var(--sel); }

.tab .close {
  width: 16px;
  height: 16px;
  padding: 0;
  border: 0;
  border-radius: 2px;
  background: none;
  color: var(--dim);
  line-height: 16px;
  visibility: hidden;
}

.tab:hover .close, .tab.on .close { visibility: visible; }
.tab .close:hover { background: var(--hair); color: var(--fg); }

.options-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 4px 8px;
  background: var(--chrome);
  border-bottom: 1px solid var(--line);
}

.auto-key.on {
  color: #145c2b;
  background: var(--live-bg);
  border-color: var(--live);
  box-shadow: 0 0 7px rgba(35, 148, 71, .72), inset 0 0 0 1px rgba(255, 255, 255, .7);
}

.swatches { display: flex; gap: 3px; }
.palette-slot { display: flex; align-items: center; }
.palette-picker { display: none; }
.thumb i { display: block; min-width: 1px; min-height: 1px; }

/* Circles. A palette entry is one indivisible tone, not an area of coverage, and
   a row of dots says that where a row of tiles says "swatch book". */
.swatch {
  width: 15px;
  height: 15px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  cursor: pointer;
}

.swatch:hover:not(:disabled) { border-color: var(--fg); }

/* A swatch that cannot be picked still has to SHOW ITS COLOUR: the generic
   disabled rule fades it towards the pane, which turned the background slot —
   the darkest tone in the palette — into pale grey, i.e. into a lie about what
   index 0 is. Slot 0 says what it is with a ring instead. */
.swatch:disabled { opacity: 1; }
.swatch.bg { cursor: default; box-shadow: inset 0 0 0 2px var(--pane); }

.swatch.on {
  border-color: var(--fg);
  box-shadow: 0 0 0 2px var(--pane), 0 0 0 3px var(--sel);
}

/* An empty slot has to read as "no colour here" rather than as a pale colour,
   which a flat fill at this size cannot do. */
.swatch.empty {
  cursor: default;
  background: #fff;
  border-style: dashed;
  border-color: var(--hair);
}

.stage-area {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* `safe`, because the stage zooms: a centred item larger than its scroll
     container puts its top-left edge out of reach on the start side, which at
     8x is most of the picture. Safe alignment falls back to start when it does
     not fit, and centring is only ever about where a SMALL stage sits. */
  align-items: safe center;
  justify-content: safe center;
  overflow: auto;
  padding: 14px;
}

.stage-target {
  flex: 0 0 auto;
  margin-top: -1px;
  padding: 3px 9px 4px;
  border: 1px solid var(--line);
  border-top: 0;
  border-radius: 0 0 3px 3px;
  background: var(--pane);
  color: var(--dim);
}

.stage-target strong { color: var(--fg); font-weight: 600; }

/* The stage sits ON the work area rather than filling it, as it does in every
   tool of this shape: the picture has edges and they are part of what you are
   judging. */
.stage-wrap {
  position: relative;
  line-height: 0;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .35);
  outline: 1px solid rgba(0, 0, 0, .45);
}

.stage { display: block; background: var(--stage); }

.paint-overlay { position: absolute; inset: 0; touch-action: none; }
.stage-overlay-wrap {
  position: absolute;
  left: -100%; top: -100%;
  width: 300%; height: 300%;
}
.stage-shade {
  position: absolute;
  inset: 0;
  pointer-events: none;
  outline: 1px solid #aaa;
}
/* The footage traced over: a reference above the picture, never part of it. */
.tracing { position: absolute; inset: 0; pointer-events: none; }

/* Where a drag out of the pool would land. Dashed and unfilled, so it reads as
   "not there yet" over whatever is already drawn. */
/* Never a hit target: it is drawn under the pointer, and anything under the
   pointer that changes mid-drag gets a dragenter of its own. */
.paint-overlay .ghost { pointer-events: none; }
.paint-overlay .ghost > * {
  fill: none;
  stroke: #fff1be;
  stroke-width: 1;
  stroke-dasharray: 3 2;
  vector-effect: non-scaling-stroke;
}

/* --------------------------------------------------------------------------
   params */

.section { border-bottom: 1px solid var(--hair); padding: 6px; }
.section:last-child { border-bottom: 0; }

/* The footage slider shares its row with the word "opacity", so it takes what is
   left rather than the full width the generic range rule gives it. */
.trace-opacity { flex: 1; min-width: 0; }

.section > h2 {
  margin: 0 0 5px;
  font: inherit;
  font-weight: 600;
  color: var(--dim);
  text-transform: uppercase;
  letter-spacing: .07em;
}

.inspector-form { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin-top: 7px; }
.inspector-field { display: grid; gap: 2px; color: var(--dim); }
.inspector-field input { width: 100%; color: var(--fg); }

.correction-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; }
.correction-grid select, .correction-grid input { width: 100%; color: var(--fg); }
.correction-list, .correction-conflicts { display: grid; gap: 4px; margin-top: 7px; }
.correction-item { display: flex; align-items: center; gap: 4px; min-width: 0; }
.correction-item > span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* Label / value, once, for every read-only fact in the pane. */
.facts { display: grid; grid-template-columns: auto 1fr; gap: 2px 8px; margin: 0; }
.facts dt { color: var(--dim); }
.facts dd { margin: 0; overflow: hidden; text-overflow: ellipsis; }

.knob { display: grid; gap: 1px; margin-bottom: 6px; }
.knob .top-line { display: flex; justify-content: space-between; gap: 8px; }
.knob .top-line .name { color: var(--dim); }

.regeneration-debug {
  padding: 4px 5px;
  background: var(--sunk);
  border: 1px solid var(--hair);
  border-radius: 2px;
}

/* --------------------------------------------------------------------------
   the location bar

   Where you are, above the timeline that draws it. Chrome-coloured like a pane
   head, because that is what it is to the two temporal views below it — but it
   is not one of their heads, since it says the same thing whichever is showing. */

.loc {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0 7px;
  min-width: 0;
  background: var(--chrome);
  color: var(--dim);
  white-space: nowrap;
  overflow: hidden;
}

.crumbs { display: flex; align-items: center; gap: 1px; min-width: 0; overflow: hidden; }

/* A crumb is a place, not a command: no border and no fill until it is pointed
   at. The trail has to read as one sentence, and five outlined buttons in a row
   read as five things to press. */
.crumb {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  padding: 1px 4px;
  border: 1px solid transparent;
  border-radius: 2px;
  background: none;
  color: var(--dim);
}
.crumb:hover:not(:disabled) { background: var(--sel-bg); color: var(--fg); }
/* The last crumb is the selection itself. Weight and full contrast say so; the
   fill and accent border `button.on` would otherwise give it are undone here,
   because a filled crumb reads as a pressed control and the thing it marks is
   where you ARE, not something switched on. */
.crumb.on { color: var(--fg); font-weight: 600; background: none; border-color: transparent; }
.crumb.lane::before { content: "≡ "; color: var(--dim); font-weight: 400; }
.crumb-sep { flex: 0 0 auto; color: var(--line); }

.loc-fact { flex: 0 0 auto; }
.loc-fact::before, .loc-shared::before { content: "· "; color: var(--line); }
.loc-shared { flex: 0 0 auto; display: inline-flex; align-items: baseline; gap: 5px; }
.loc .spacer { flex: 1; }
.creation-controls button { display: inline-flex; align-items: center; gap: 4px; }
.creation-controls svg { width: 11px; height: 11px; }

/* --------------------------------------------------------------------------
   timeline */

.time { overflow: hidden; }

.tl-body {
  flex: 1;
  min-height: 0;
  display: flex;
  /* Not `stretch`: that sizes both columns to the body's height, the rows then
     overflow them, and the sticky ruler goes out with its column. Each column is
     as tall as its rows instead, and at least as tall as the body. */
  align-items: flex-start;
  overflow: auto;
  /* THE GUTTER IS ALWAYS THERE, whether or not there is a scrollbar in it. Every
     mark in this pane is a percentage of the tracks column, and the column is
     what is left of the body after the labels -- so a vertical scrollbar
     appearing took 15px out of the width and moved the ruler, the frame grid,
     every clip and the playhead sideways, mid-gesture. Expanding a lane's portal
     adds seven rows at once, which is exactly when a person is looking at the
     frame they are aiming for. */
  scrollbar-gutter: stable;
}

.tl-labels,
.tl-tracks { min-height: 100%; }

.tl-labels {
  flex: 0 0 var(--label);
  /* A flex item's `min-width` defaults to `auto`, which floors its used size at
     its MIN-CONTENT width — so a row labelled with a uuid widens the whole
     column and pushes the tracks out of alignment with the ruler above them.
     `0` is what makes the declared basis the actual width. */
  min-width: 0;
  position: sticky;
  left: 0;
  /* Over the ruler and the playhead, which both pass under it when the tracks
     scroll sideways. */
  z-index: 4;
  background: var(--pane);
  border-right: 1px solid var(--line);
}

.tl-tracks {
  flex: 1;
  /* The timeline's zoom, and the whole of it: every mark in here is positioned
     as a percentage of this element's width, so one multiplier on the width
     spreads the frame grid, the spans, the keys, the ruler and the playhead
     apart together, and `.tl-body` was already a scroll container. 1 is the
     width that exactly fills the body, which is what `flex: 1` alone gave. */
  min-width: max(340px, calc((100% - var(--label)) * var(--tl-zoom, 1)));
  position: relative;
  background: #fff;
  /* The major frame grid, using the same whole-second interval as the numbered
     ruler. It is a background rather than an element per mark; `--tick` is set
     by the component because only it knows the open symbol's length and FPS. */
  background-image:
    repeating-linear-gradient(90deg,
      var(--grid-5) 0 1px, transparent 1px var(--tick, 10%));
}

.tl-label,
.tl-track {
  height: var(--row);
  border-bottom: 1px solid var(--hair);
}

/* The row is the containing block for its own span and keys. Without this they
   resolve against `.tl-tracks` instead and every row's marks land stacked on the
   first one, full height — which reads as "the timeline draws nothing" rather
   than as a positioning bug. */
.tl-track { position: relative; }

.tl-label {
  /* Scrolled to from the stage: clear of the sticky ruler above. */
  scroll-margin-top: var(--ruler);
  display: flex;
  align-items: center;
  gap: 3px;
  padding-right: 6px;
  cursor: default;
  white-space: nowrap;
  overflow: hidden;
}

/* Selected, in the timeline's own colour. The bar down the left edge is the
   half that survives at a glance: a row 21px tall scrolled past in a column of
   twenty reads its left edge before it reads its fill. */
.tl-label.on {
  background: var(--pick-bg);
  box-shadow: inset 3px 0 0 var(--pick);
  color: var(--pick-fg);
}
.tl-label.on .kind { color: var(--pick); }
.tl-label.target { box-shadow: inset 0 0 0 2px var(--target); }
/* Both at once, and they must still be two readable facts: the target ring sits
   inside the pick's edge rather than replacing it. */
.tl-label.on.target { box-shadow: inset 3px 0 0 var(--pick), inset 0 0 0 2px var(--target); }
.tl-track.target::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 2px solid var(--target);
  pointer-events: none;
  z-index: 4;
}
.tl-label:hover:not(.on) { background: #fff; }
.tl-label .name { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.tl-label .kind { color: var(--dim); }

/* A lane's row: the strip glyph, a faint warm ground under the name, and a
   track behind its blocks — enough that a lane is told from the instance rows
   around it while scanning, and not so much that it shouts. */
.tl-ico {
  flex: 0 0 12px;
  line-height: 0;
  color: var(--dim);
}
.tl-ico > svg { display: block; width: 11px; height: 11px; }
.tl-label.lane { background: #edeae2; }
.tl-label.lane .name { font-weight: 600; }
.tl-label.lane.on { background: var(--pick-bg); }
.tl-label.lane.on .tl-ico { color: var(--pick); }
.tl-label.lane:hover:not(.on) { background: #f8f6f0; }
.tl-track.lane { background: rgba(0, 0, 0, 0.035); }
.tl-name-input { min-width: 0; flex: 1; font: inherit; }
.tl-rename { margin-left: auto; padding: 0 3px; border: 0; background: none; color: var(--dim); }
.tl-rename:hover { color: var(--fg); }

/* A fixed-width cell whether or not there is a triangle in it, so names at the
   same depth line up down the column. */
.tl-twist {
  flex: 0 0 13px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--dim);
  cursor: pointer;
  text-align: center;
}

.tl-twist:hover:not(:disabled) { background: none; color: var(--fg); }
.tl-twist:disabled { opacity: 0; cursor: default; }
.tl-delete { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); }
.tl-delete:hover { color: var(--fg); }
.tl-solo, .tl-trace { margin-left: auto; padding: 0 4px; border: 0; background: none; color: var(--dim); font-size: 10px; }
.tl-solo:hover, .tl-trace:hover { color: var(--fg); }
.tl-solo.on, .tl-trace.on { color: var(--sel); font-weight: 600; }
.tl-solo + .tl-delete, .tl-trace + .tl-solo { margin-left: 0; }

/* The ruler and the corner above the labels stay at the top of the body while
   the rows scroll under them: the ruler is the scrubber, and scrolling down to
   a row is no reason to lose it. */
.tl-corner,
.tl-ruler {
  position: sticky;
  top: 0;
  z-index: 2;
  height: var(--ruler);
  border-bottom: 1px solid var(--line);
  background: var(--chrome);
}

.tl-ruler {
  cursor: pointer;
  user-select: none;
}

.tl-ruler .tick {
  position: absolute;
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--line);
  padding-left: 3px;
  color: var(--dim);
  font-size: 10px;
  line-height: var(--ruler);
}

/* The heading between the picture's rows and the sounds', as an editor puts
   its audio tracks under its video tracks. */
.tl-label.tl-section,
.tl-track.tl-section { background: var(--chrome); border-top: 1px solid var(--line); }
.tl-label.tl-section { padding-left: 6px; color: var(--dim); font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; }

/* A sound's bar, told apart from the picture's at a glance. */
.tl-span.sound { background: #e3efdf; border-color: #a6c49b; }
/* A tracing layer is a reference, never exported: hatched and dashed so it
   cannot be read as a drawing. */
.tl-span.trace, .tl-cel.trace {
  background: repeating-linear-gradient(135deg, transparent 0 4px, rgba(0, 0, 0, 0.07) 4px 8px);
  border: 1px dashed var(--span-line);
}
.pool-item .thumb.sound { display: flex; align-items: center; justify-content: center; color: #d9d9d9; }

/* The span the node exists over. Drawn under the keys so a dot on the first
   frame of a span is not half-hidden by its own bar. */
.tl-span {
  position: absolute;
  top: 3px;
  bottom: 3px;
  background: var(--span);
  border: 1px solid var(--span-line);
  border-radius: 1px;
}

.tl-span.dense {
  /* Generated, one value per frame: hatched, because ticking every frame would
     be a solid block that says less than the bar behind it does. */
  background: repeating-linear-gradient(
    -45deg, var(--span) 0 3px, #c3d4ea 3px 6px);
}

/* The preview row of a drop in flight: its own length, where it would start. */
.tl-span.ghost, .tl-label.ghost, .tl-cel.ghost { pointer-events: none; }
.tl-cel.ghost { background: transparent; border: 1px dashed var(--sel); color: var(--sel); }

/* A row being dragged over another: into an instance, or grouped with a node,
   by its middle; in front of it or behind it, by its top or bottom edge. */
.tl-label.drop-into { box-shadow: inset 0 0 0 2px var(--sel); background: var(--sel-bg); }
.tl-label.drop-group { outline: 2px dashed var(--sel); outline-offset: -2px; }
.tl-label.drop-front { box-shadow: inset 0 2px 0 var(--sel); }
.tl-label.drop-back { box-shadow: inset 0 -2px 0 var(--sel); }

/* Selected: the clip block and the plain bar alike, in the pick colour. A clip
   block had no selected state at all, which is why the portal under an expanded
   lane looked like it opened at random. */
.tl-cel.on {
  background: var(--pick);
  border-color: var(--pick);
  color: #fff;
  font-weight: 600;
}
.tl-cel.on:hover:not(:disabled) { background: var(--pick); filter: brightness(1.12); }
.tl-span.on { background: var(--pick-bg); border-color: var(--pick); }
.tl-cel.target, .tl-span.target { outline: 2px solid var(--target); outline-offset: -2px; }
.tl-span.on.dense {
  background: repeating-linear-gradient(
    -45deg, var(--pick-bg) 0 3px, #f0bf77 3px 6px);
}

/* A node's bar slides it along its symbol's time. */
.tl-span.movable { cursor: ew-resize; touch-action: none; }
.tl-span.movable.sliding { border-color: var(--sel); }
.tl-edge {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 7px;
  cursor: e-resize;
  touch-action: none;
  z-index: 2;
}
.tl-edge.out { right: -3px; }
.tl-edge.in { left: -3px; }
.tl-junction {
  position: absolute;
  top: -1px;
  /* Centred on the CUT, which is the clips' shared border-box edge. A clip block
     is a button with a 1px border, so `left: 0` is one pixel inside it and the
     handle sat a pixel to the right of the line it drags. */
  left: -1px;
  bottom: -1px;
  width: 12px;
  transform: translateX(-50%);
  cursor: col-resize;
  touch-action: none;
  z-index: 3;
}
/* `nowrap` is load-bearing. A block narrower than its name wrapped it at the
   hyphen — "symbol-" then "8" — and the button is `overflow: visible` so that
   second line spilled out below the track as a row of loose digits under the
   lane, which read as corrupt timing rather than as a long name in a short
   block. One line, clipped. */
.tl-cel-label {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  pointer-events: none;
}
.tl-span.ghost {
  background: transparent;
  border: 1px dashed var(--sel);
}
.tl-label.ghost { color: var(--sel); font-style: italic; }

/* Passive animation ticks leave the clip underneath available to grab. */
.tl-key {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 6px;
  margin: -3px 0 0 -1px;
  background: var(--key);
  opacity: 0.5;
  border-radius: 1px;
  /* So grabbing a dot grabs the bar under it. */
  pointer-events: none;
}

.tl-playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--playhead);
  pointer-events: none;
  z-index: 3;
}

/* The playhead's head, in the ruler rather than on the line, so it stays at the
   top with the ruler while the rows scroll. */
.tl-knob {
  position: absolute;
  pointer-events: none;
  margin-left: -4px;
  top: 1px;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--playhead);
}

.tl-empty { padding: 9px; color: var(--dim); }


/* --------------------------------------------------------------------------
   the video -> symbol dialog */

.convert-scrim {
  position: fixed;
  inset: 0;
  z-index: 20;
  display: grid;
  place-items: center;
  background: rgba(0, 0, 0, .35);
}

.convert {
  width: min(560px, calc(100vw - 32px));
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 3px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .3);
}

.convert-body { display: grid; gap: 8px; padding: 10px; }

.convert-video {
  width: 100%;
  max-height: 46vh;
  background: var(--stage);
  border-radius: 2px;
}

/* The range: the kept frames lit, a handle at each end. */
.convert-range {
  position: relative;
  height: 18px;
  margin: 0 7px;
  background: var(--sunk);
  border: 1px solid var(--hair);
  border-radius: 2px;
  touch-action: none;
}

.convert-kept {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--sel-bg);
  border-left: 1px solid var(--sel);
  border-right: 1px solid var(--sel);
}

.convert-handle {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 10px;
  margin-left: -5px;
  background: var(--sel);
  border-radius: 2px;
  cursor: ew-resize;
}

.convert-name { display: flex; align-items: center; gap: 8px; }
.convert-name input { flex: 1; }

.convert-actions {
  display: flex;
  justify-content: flex-end;
  gap: 6px;
  padding: 8px 10px;
  border-top: 1px solid var(--hair);
}

/* A transform or visibility channel: its key button, then a field per component. */
.facts dd.channel { display: flex; gap: 3px; align-items: center; overflow: visible; }
.facts dd.channel input[type=number] { width: 0; flex: 1; min-width: 0; }
.facts dd.channel .key { padding: 0 3px; color: var(--dim); }
.facts dd.channel .key.keyed { color: var(--fg); }
.facts dd.channel .key.on { color: var(--sel); }
.facts dd.channel .channel-swatches {
  display: flex;
  flex: 1;
  flex-wrap: wrap;
  gap: 3px;
  min-width: 0;
}
.facts dd.channel .channel-swatches .swatch { flex: 0 0 15px; }
.facts dd.channel.live {
  margin: -2px;
  padding: 2px;
  border-radius: 3px;
  background: var(--live-bg);
  box-shadow: 0 0 0 1px rgba(35, 148, 71, .45), 0 0 6px rgba(35, 148, 71, .28);
}
.facts dd.channel.live input { border-color: var(--live); }

/* The selected node's transform box, in stage pixels (the SVG's viewBox). */
.paint-overlay .handles .box { fill: none; stroke: #e6ca8b; stroke-width: 0.5; stroke-dasharray: 2 1; pointer-events: none; }
.paint-overlay .handles .knob-arm { stroke: #e6ca8b; stroke-width: 0.5; pointer-events: none; }
.paint-overlay .handles .knob { fill: #161820; stroke: #fff1be; stroke-width: 0.6; cursor: grab; }
.paint-overlay .handles .corner { fill: #fff1be; stroke: #161820; stroke-width: 0.5; cursor: nwse-resize; }
.paint-overlay .handles .pivot { stroke: #fff1be; stroke-width: 0.6; pointer-events: none; }
/* The cross is a handle on every node: a plain drag moves it, ctrl/cmd puts the
   pivot somewhere else. On a peg, which draws nothing, it is the only thing
   there is to grab. */
.paint-overlay .handles .pivot-grab { fill: transparent; stroke: none; cursor: move; }
/* While a ctrl/cmd-drag is placing it. */
.paint-overlay .handles .pivot.moving { stroke: #7ad7a0; stroke-width: 0.9; }
.paint-overlay .marquee { fill: rgba(230, 202, 139, 0.12); stroke: #e6ca8b; stroke-width: 0.6; stroke-dasharray: 2 1; pointer-events: none; }

/* The creation-target outline and its name tag. Solid, no handles, and a tag pinned just
   outside the bottom-right corner — see `ui/stage/creation-box` for why the box
   rotates with the node and the tag does not. Sizes are in user units because
   this SVG's viewBox is the stage's 320x200 scaled by `zoom`. */
.paint-overlay .creation-target { pointer-events: none; }
.paint-overlay .selected-paint-outline { fill: none; stroke: #fbbf24; stroke-width: 0.8; vector-effect: non-scaling-stroke; pointer-events: none; }
.paint-overlay .creation-target .creation-target-box { fill: none; stroke: var(--target-stage); stroke-width: 0.7; }
.paint-overlay .creation-target .creation-target-tag-bg { fill: var(--target-stage); }
.paint-overlay .creation-target .creation-target-tag {
  fill: #1b1030;
  font: 600 3.4px ui-sans-serif, system-ui, sans-serif;
  letter-spacing: 0.04px;
}
.paint-overlay:focus { outline: none; }

/* What a drag in flight would do, beside the pointer: a clip body moves in
   time, and with shift it goes inside the clip under the pointer. Said next to
   the cursor because that is where the eye already is mid-gesture. */
.tl-hint {
  position: fixed;
  z-index: 60;
  padding: 2px 6px;
  border-radius: 3px;
  background: var(--fg);
  color: var(--bg);
  font-size: 11px;
  white-space: nowrap;
  pointer-events: none;
}
.tl-hint.nesting { background: var(--sel); color: #fff; }

/* The clip a shift-drag would nest into. Inset, so it reads as "into this"
   rather than as a boundary between rows. */
.tl-cel.nest-target { outline: 2px solid var(--sel); outline-offset: -2px; }

/* A row inside a held clip: shown because the node is on screen for the whole
   hold, dimmed because its own frames have no place on this ruler. */
.tl-span.unmapped { opacity: 0.45; cursor: pointer; }
.tl-hint.refusing { background: var(--bad, #b00); color: #fff; }

/* --------------------------------------------------------------------------
   narrow windows

   A phone is not a small desk: there is room for ONE column, so the stage gets
   it and the two side panes become drawers over the top of it rather than
   columns beside it. They are mounted only while open (`ui/shell`), so a shut
   drawer is not holding thumbnails live, and the top bar's pane buttons are
   what opens them — which is why they are at the left end of a strip that
   scrolls.

   The timeline keeps its row. Shut, that row is its transport strip and
   nothing else, because a window with nowhere to press play is broken rather
   than small; its height is still draggable, by touch, on the grip above it. */

@media (max-width: 760px) {
  /* A uuid-wide label column is half the screen. */
  :root { --label: 136px; }

  .app {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: 30px minmax(0, 1fr) 21px var(--grip-time) var(--time-h);
    grid-template-areas:
      "top"
      "view"
      "loc"
      "gtime"
      "time";
  }

  /* Everything in the strip stays reachable, by scrolling it. */
  .top { overflow-x: auto; }
  .project-title { max-width: 32vw; }
  /* So that 34px below is the same distance on both pages: the editor's strip
     takes its 30px from the grid row, and the index page has no grid. */
  .index > .top { flex: 0 0 30px; }

  /* A SCROLL CONTAINER CLIPS BOTH AXES. `overflow-x: auto` above makes the
     strip one, so a panel anchored to its own button — open, snapshots, share,
     sign in — is cut off by a 30px-tall bar and drawn at whatever x the strip
     happens to be scrolled to, which reads as the button doing nothing at all.
     Fixed to the viewport instead, as `.menu-drop` already is for the same
     reason one pane down: these are all top-bar menus, the top bar is at the top
     of the window, and a near-full-width sheet is what there is room for. Both
     offsets on the axis, so `.menu-left`'s anchoring is overridden rather than
     left half-applied. `.menu-drop` is excluded because `ui/menu` places that
     one itself, inline, and would then be over-constrained. */
  .top .menu:not(.menu-drop),
  .top .menu.menu-left:not(.menu-drop) {
    position: fixed;
    top: 34px;
    left: 4px;
    right: 4px;
    width: auto;
    min-width: 0;
  }

  /* Out of the grid altogether: fixed to the viewport, so the column the grid
     has left is the stage's whether a drawer is open or not. */
  .pool, .params {
    position: fixed;
    z-index: 30;
    top: 31px;
    bottom: 0;
    width: min(78vw, 300px);
    box-shadow: 0 2px 16px rgba(0, 0, 0, .4);
  }
  .pool { left: 0; border-right: 1px solid var(--line); }
  .params { right: 0; border-left: 1px solid var(--line); }

  /* There is no column edge left to drag. */
  .grip.col { display: none; }

  /* The picture, not the desk around it. */
  .stage-area { padding: 5px; }

  /* Touch targets, in the strips that are all buttons. */
  .pane-head, .options-bar { gap: 7px; }
  .options-bar button, .pane-head button, .top button { min-height: 24px; }

  /* A STRIP OF CONTROLS SCROLLS AS A STRIP. Flexbox's instinct when a row does
     not fit is to shrink every item and then push the last ones off the end —
     and the last ones here are the polygon tool, the timing commands and both
     zoom readouts, which would be unreachable rather than merely off-screen.
     So nothing shrinks, nothing wraps, and each strip scrolls. The `.spacer`
     and `.status` rules above still win on specificity, which is what keeps the
     right-hand groups right-hand while there IS room. */
  .top > *, .pane-head > *, .options-bar > * { flex-shrink: 0; }
  .top button, .pane-head button { white-space: nowrap; }
  .pane-head, .options-bar { overflow-x: auto; }
  /* The hints go first: the tool's name and its controls are what is needed. */
  .options-bar .hint { display: none; }
}

/* --------------------------------------------------------------------------
   the tools: a strip down the left of the stage, an options bar above it.

   Photoshop's arrangement, which Illustrator and Flash share. The palette is a
   grid in the strip under the tools, as Deluxe Paint's and Animator Pro's were:
   a fixed table of slots, shown as one. */

.options-bar {
  gap: 8px;
  min-height: 29px;
  padding: 3px 8px;
}
.options-bar .tool-name { font-weight: 600; min-width: 46px; }
.options-bar .hint { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 0 1 auto; }
.options-bar .spacer { flex: 1; }
.option { display: flex; align-items: center; gap: 6px; color: var(--dim); white-space: nowrap; }
.option input[type="range"] { width: 110px; }
.option .value { color: var(--fg); min-width: 30px; font-variant-numeric: tabular-nums; }
.palette-assets { display: flex; align-items: center; gap: 3px; }

.workspace { flex: 1; min-height: 0; display: flex; }
.workspace > .stage-area { flex: 1; min-width: 0; position: relative; }

.toolbox {
  flex: 0 0 52px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  padding: 7px 0;
  background: var(--chrome);
  border-right: 1px solid var(--line);
  overflow-y: auto;
}

.tools { display: grid; gap: 2px; }
.tool {
  width: 34px; height: 30px;
  display: grid; place-items: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: none;
  color: var(--fg);
}
.tool:hover { background: var(--sunk); border-color: var(--hair); }
.tool.on { background: var(--sel-bg); border-color: var(--sel); color: var(--sel); }
.tool svg { fill: currentColor; stroke: none; }
.tool svg .cut { fill: none; stroke: var(--chrome); stroke-width: 1.2; }
.tool.on svg .cut { stroke: var(--sel-bg); }

.palette { display: grid; gap: 7px; justify-items: center; padding-top: 9px; border-top: 1px solid var(--hair); }

/* The colour a new shape gets: Photoshop's foreground chip. */
.palette .chip {
  width: 36px; height: 36px;
  display: grid; place-items: end start;
  border: 1px solid var(--fg);
  border-radius: 3px;
  box-shadow: inset 0 0 0 2px var(--pane);
}
.palette .chip span {
  margin: 0 0 2px 3px; padding: 0 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, .85);
  color: var(--fg); font-size: 9px; line-height: 12px;
}

.palette .cells { display: grid; grid-template-columns: repeat(2, 17px); gap: 2px; }
.palette .slot { display: contents; }
.palette .cell {
  width: 17px; height: 17px;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, .35);
  border-radius: 2px;
  cursor: pointer;
}
.palette .cell:hover { border-color: var(--fg); transform: scale(1.12); }
.palette .cell.on { border-color: var(--fg); box-shadow: 0 0 0 1px var(--pane), 0 0 0 2px var(--sel); position: relative; z-index: 1; }

/* Clear is no colour: a knockout. Checkered, as transparency is everywhere. */
.palette .clear,
.palette .chip.clear {
  background-color: #fff;
  background-image:
    linear-gradient(45deg, #c9c9c5 25%, transparent 25%, transparent 75%, #c9c9c5 75%),
    linear-gradient(45deg, #c9c9c5 25%, transparent 25%, transparent 75%, #c9c9c5 75%);
  background-size: 8px 8px;
  background-position: 0 0, 4px 4px;
}

/* --------------------------------------------------------------------------
   on the stage: the tools' marks, in stage pixels (the SVG's viewBox), and their
   cursors. Hairlines, so the pixels under them can be judged. */

.paint-overlay.tool-pen {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M2 2 L9 5 L14 11 L11 14 L5 9 Z' fill='%23111' stroke='%23fff' stroke-width='1.2' stroke-linejoin='round'/%3E%3Cpath d='M2 2 L7 7' stroke='%23fff' stroke-width='1'/%3E%3C/svg%3E") 2 2, crosshair;
}
.paint-overlay.tool-pen.closing {
  cursor: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='22' height='22'%3E%3Cpath d='M2 2 L9 5 L14 11 L11 14 L5 9 Z' fill='%23111' stroke='%23fff' stroke-width='1.2' stroke-linejoin='round'/%3E%3Ccircle cx='17' cy='17' r='3' fill='none' stroke='%23111' stroke-width='1.6'/%3E%3Ccircle cx='17' cy='17' r='3' fill='none' stroke='%23fff' stroke-width='.6'/%3E%3C/svg%3E") 2 2, crosshair;
}
.paint-overlay.tool-brush, .paint-overlay.tool-eraser { cursor: none; }

.paint-overlay .footprint { fill: none; stroke: #fff1be; stroke-width: 0.5; vector-effect: non-scaling-stroke; pointer-events: none; }
.paint-overlay .footprint.eraser { stroke-dasharray: 3 2; }

.paint-overlay .draft polyline { fill: none; stroke: #fff1be; stroke-width: 1; vector-effect: non-scaling-stroke; pointer-events: none; }
.paint-overlay .draft .first { fill: #161820; stroke: #fff1be; stroke-width: 0.5; pointer-events: none; }
.paint-overlay .draft .first.closing { fill: #fff1be; r: 2.6; }

.paint-overlay .points .outline { fill: none; stroke: #fff1be; stroke-width: 1; stroke-opacity: .55; vector-effect: non-scaling-stroke; pointer-events: none; }
.paint-overlay .points .vertex { fill: #fff1be; stroke: #161820; stroke-width: 0.5; cursor: move; }
.paint-overlay .points .vertex:hover { fill: #fff; r: 2.4; }
.paint-overlay .points .insert { fill: #161820; stroke: #fff1be; stroke-width: 0.5; pointer-events: none; }

/* Blender's Adjust Last Operation: the last stroke's settings, in the corner of
   the view it was made in, until something else is done. */
.adjust-last {
  position: absolute;
  left: 10px; bottom: 10px;
  display: flex; align-items: center; gap: 10px;
  padding: 5px 9px;
  background: var(--pane);
  border: 1px solid var(--line);
  border-radius: 4px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .25);
}

/* Remap: the map swatch, and the inspector's grid for a remap shape. A slot is
   shown as what is under the shape will be drawn in; the original is the chip
   in its corner. */
.palette .cell.map,
.palette .chip.map {
  background: linear-gradient(135deg, #3a3f52 0 50%, #e8d9a8 50% 100%);
  color: #fff; font-size: 11px; line-height: 1; text-shadow: 0 1px 1px rgba(0, 0, 0, .6);
}
.palette .cell { position: relative; }
.palette .cell .badge {
  position: absolute; right: -2px; bottom: -2px;
  width: 7px; height: 7px;
  border: 1px solid var(--pane); border-radius: 50%;
}

.remap-head { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.remap, .remap-pick { display: grid; grid-template-columns: repeat(8, 22px); gap: 3px; }
.remap-pick { margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--hair); }
.remap-pick > .dim { grid-column: 1 / -1; }
.remap .cell, .remap-pick .cell {
  position: relative;
  width: 22px; height: 22px;
  padding: 0;
  border: 1px solid rgba(0, 0, 0, .35);
  border-radius: 3px;
  cursor: pointer;
}
.remap .cell:hover, .remap-pick .cell:hover { border-color: var(--fg); }
.remap .cell.on, .remap-pick .cell.on { box-shadow: 0 0 0 1px var(--pane), 0 0 0 2px var(--sel); }
.remap .cell.mapped i {
  position: absolute; left: 2px; top: 2px;
  width: 8px; height: 8px;
  border: 1px solid rgba(255, 255, 255, .8);
  border-radius: 2px;
}

/* The palette's own settings, at the foot of the strip: drawn as a tool button,
   because it is one more thing in the strip you press. */
.palette-assets > button {
  width: 34px; height: 30px; padding: 0;
  display: grid; place-items: center;
  border: 1px solid transparent; border-radius: 4px; background: none;
}
.palette-assets > button:hover:not(:disabled) { background: var(--sunk); border-color: var(--hair); }
.palette-assets > button.on { background: var(--sel-bg); border-color: var(--sel); }
.palette-assets svg path { fill: var(--fg); }
.palette-assets svg circle { fill: var(--chrome); }
.palette-assets > button.on svg circle { fill: var(--sel-bg); }

/* A popover's panel: settings, not commands, so narrower than a menu and laid
   out as a form. Placed and dismissed by `ui/menu`. */
.menu.popout { width: 240px; min-width: 0; padding: 10px; line-height: 1.4; }
.menu.popout:has(.palette-popout) { width: 280px; }
.palette-popout { display: grid; gap: 8px; }
.palette-popout .row { margin: 0; }

/* Every palette as its strip and its name: what you would switch to, seen
   before you switch. The open one is the selected row. */
.palette-list { display: grid; gap: 1px; max-height: 50vh; overflow: auto; }
.palette-row { display: flex; align-items: center; gap: 2px; border-radius: 3px; }
.palette-row.on { background: var(--sel-bg); }
.palette-row .choose {
  flex: 1; min-width: 0;
  display: grid; grid-template-columns: 1fr auto; align-items: center; gap: 3px 8px;
  padding: 5px 6px; border: 1px solid transparent; border-radius: 3px; background: none; text-align: left;
}
.palette-row button.choose:hover:not(:disabled) { background: var(--sunk); border-color: var(--hair); }
.palette-row.on button.choose { border-color: var(--sel); }
.palette-row.on button.choose:hover { background: var(--sel-bg); }
.palette-row .strip { grid-column: 1 / -1; }
.palette-row .name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.palette-row.on .name { font-weight: 600; color: #14395f; }
.palette-row .palette-name { grid-column: 1 / -1; width: 100%; min-width: 0; }
.palette-row .rename { align-self: stretch; padding: 0 7px; border-color: transparent; background: none; }
.palette-row .rename:hover:not(:disabled) { border-color: var(--sel); background: #fff; }

.strip { display: flex; height: 14px; border: 1px solid rgba(0, 0, 0, .35); border-radius: 2px; overflow: hidden; }
.strip i { flex: 1; }

/* On the stage at the playhead. */
.on-stage { width: 7px; height: 7px; border-radius: 50%; background: var(--live); }

/* A palette being edited that the stage is not drawing in here. Information,
   not alarm: the accent of a selection, not of a warning. */
.palette-assets-wrap { position: relative; }
.palette-info {
  position: absolute; top: -3px; right: -5px;
  width: 13px; height: 13px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--sel); color: #fff;
  font: italic 600 9px/1 Georgia, serif;
  cursor: help;
  box-shadow: 0 0 0 1.5px var(--chrome);
}
.info-card {
  position: fixed; z-index: 60;
  width: 240px; padding: 8px 10px;
  display: grid; gap: 3px;
  background: var(--pane); color: var(--fg);
  font: 11px/1.45 "Lucida Grande", "Segoe UI", system-ui, sans-serif;
  border: 1px solid var(--sel); border-left-width: 3px; border-radius: 3px;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .25);
  cursor: default;
}
.info-card strong { color: #14395f; }
.view-popout label { display: block; margin-bottom: 8px; }
.view-popout label:last-child { margin-bottom: 0; }
.view-popout input, .view-popout select { width: 100%; }

/* An overlay switch and its settings: one object, two parts. */
.overlay > .menu-wrap > button { margin-left: -1px; padding: 1px 5px; border-radius: 0 2px 2px 0; }
.overlay > button { border-radius: 2px 0 0 2px; }
.stage-wrap { flex: 0 0 auto; }


.onion-skin { position: absolute; inset: 0; pointer-events: none; image-rendering: pixelated; }


/* Expanded automation lanes expose editable keyframe dots. */
button.tl-key { pointer-events: auto; padding: 0; border: 0; width: 11px; height: 11px; margin: -5.5px; cursor: ew-resize; z-index: 4; opacity: 1; border-radius: 50%; }
button.tl-key.selected { background: var(--pick); box-shadow: 0 0 0 2px var(--pick); }
