/**
 * studio.css — the Drawing Studio shell.
 *
 * Loaded after artist.css. It styles only what the studio adds: the workspace
 * grid, the collapsible panels, the layer rows and the brush grid. Everything
 * it can borrow from the design system (paper, hairline, madder accent, the
 * 2px radius, the short warm shadow) it borrows — this file is about layout
 * and the few studio-specific affordances, not a second visual language.
 *
 * Deliberately unlayered, for the same reason artist.css is: the component
 * rules it refines are unlayered, and a layered override would lose to them.
 */

/* ── 0. The studio takes the screen ───────────────────────────────────── */

/*
  The site's sidebar AND top bar are hidden while the studio is open.

  The studio carries its own tool rail and its own panel dock; the site nav on
  top of those is two sets of chrome fighting for the same edge, and it costs
  ~270px of the canvas. `shell.js` sets the attribute; the rule lives here so the
  studio owns everything about how the studio looks.

  The top bar goes too, and that is what makes the dock FULL-height rather than
  merely workspace-height. The studio's own header already carries a way back
  (`Back to your studio`), undo/redo and save, so a sticky site search bar above
  it is redundant chrome — and it was stealing ~70px top and ~32px bottom from a
  dock that is supposed to reach both edges of the screen.
*/
[data-studio-focus="true"] #sidebar {
  display: none;
}

[data-studio-focus="true"] header.glass-float {
  display: none;
}

/*
  Give the studio the whole remaining column.

  Three things happen here, and all three are needed:

    • the shell's view padding is dropped — it is right for a document-shaped
      page and wrong for a workspace that measures itself against the screen;
    • the root becomes a flex column, so the studio can flex into the leftover
      height instead of guessing at it;
    • overflow is clipped, so nothing the studio draws can scroll the page.

  A `calc(100dvh - <constant>)` was the first attempt. The constant was wrong by
  the height of the view padding, which is invisible until something overflows —
  which it did.
*/
/*
  The shell's main column is `min-h-screen`, which is a MINIMUM: it grows to fit
  its content because nothing bounds its height. A dock sized in percentages
  therefore has nothing to resolve against and the studio grows without limit.

  The fix is to make the column a bounded box — viewport height, not min-height
  — so the flex chain below it has a real height to divide up. The selector
  reaches the column through the studio, which is the only view that needs this;
  every other page keeps scrolling the way it always did.
*/
[data-studio-focus="true"] {
  height: 100dvh;
}

[data-studio-focus="true"] #view-root {
  display: flex;
  flex-direction: column;
  /* Both of these are required: flex children default to min-height:auto, which
     refuses to shrink below their content and re-creates the same overflow. */
  min-height: 0;
  overflow: hidden;
  /* The shell's view padding is for document-shaped pages; with the site header
     gone the studio owns every edge, so all of it goes — not just the bottom. */
  padding: 0;
}

/*
  The mount fills the root, and the studio fills the mount. Two links in the
  chain because the view layer owns the first and the studio owns the second;
  both have to be explicit or the height stops resolving halfway down.
*/
[data-studio-focus="true"] .studio-mount {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* ── 1. The workspace ─────────────────────────────────────────────────── */

.studio {
  --dock-w: 18rem;
  --rail-w: 3.25rem;

  /*
    The studio fills the mount exactly. `height: 100%` resolves against the
    sized mount, so the internal columns divide real space rather than guessing.
    The page itself never scrolls while you are drawing.

    A flex column by default: header on top, workspace below. Desktop re-lays this
    out as a grid so the dock can span the FULL height (see the >=900px block) —
    with the dock in the same flow as the header it could only ever be as tall as
    the workspace, which is the difference between "full-height" and "most of it".
  */
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

/*
  The header is a sibling of the workspace, so it is measured into the height by
  the layout rather than by a magic number.
*/
.studio-header {
  flex: 0 0 auto;
  padding: 0.25rem 0.25rem 0;
}

.studio-exit {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.5rem;
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-decoration: none;
}

.studio-exit:hover { color: var(--accent); }

.studio-workspace {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: 1fr;
  /* The row fills the studio box, so the dock is genuinely full-height. */
  flex: 1 1 auto;
  min-height: 0;
}

@media (min-width: 900px) {
  /*
    Desktop: the dock is taken OUT of the workspace's flow and pinned to the
    studio box itself, so it spans the FULL height — top edge to bottom edge —
    rather than only the strip left over beneath the header.

    Why absolute rather than a grid track: `.studio-dock-wrap` is a child of
    `.studio-workspace`, not of `.studio`. Any track it occupies is therefore a
    track of the workspace, which is already shortened by the header. Pinning it
    to `.studio`'s padding box is the only way, without moving markup, for it to
    reach the top of the screen while the header stays a sibling of the
    workspace.

    `.studio-workspace` reserves the dock's width on the right, so the canvas
    never slides under the dock; the collapsed state removes that reservation.
  */
  .studio { position: relative; }

  .studio-workspace {
    /* Rail, canvas, dock gutter. The rail is icons only; the dock holds panels. */
    grid-template-columns: var(--rail-w) minmax(0, 1fr) var(--dock-w);
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
    transition: grid-template-columns var(--t) var(--ease-out);
  }

  /*
    The header shares the studio's width with the (now out-of-flow) dock, so it
    must reserve the same right gutter as the workspace — otherwise its Undo /
    Redo / Save rail runs underneath the dock and Save is half-covered by the
    brush panel. The workspace gets this reservation from its grid tracks; the
    header is a plain block and needs it as padding.
  */
  .studio-header {
    padding-right: calc(var(--dock-w) + 0.75rem);
    transition: padding-right var(--t) var(--ease-out);
  }

  .studio[data-dock="closed"] .studio-header { padding-right: 0.25rem; }

  /*
    Two selectors of specificity, not one: the base `.studio-dock-wrap` rule is
    declared FURTHER DOWN this file and also sets `position`, and at equal
    specificity the later rule wins. Without the extra `.studio` the base
    `position: relative` quietly beat this one, so the dock never left the
    workspace's flow — which is exactly what kept it 112px short of full height.
  */
  .studio .studio-dock-wrap {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: var(--dock-w);
  }

  /*
    Dock collapsed (Tab). The gutter goes to zero and the canvas takes the room.
    The dock is hidden rather than slid off-screen, so a focusable panel can
    never sit invisibly over the artwork.
  */
  .studio[data-dock="closed"] .studio-workspace {
    grid-template-columns: var(--rail-w) minmax(0, 1fr) 0;
  }
  .studio[data-dock="closed"] .studio-dock-wrap {
    visibility: hidden;
    overflow: hidden;
  }
}

/*
  Distraction-free keeps the dock state but hides the rail, so a keyboard user
  can still reach the panels with Tab and nothing at all is drawn over the art.
*/
.studio[data-zen="true"] .studio-rail { display: none; }
@media (min-width: 900px) {
  /* The rail column goes away; the canvas and the dock gutter remain. */
  .studio[data-zen="true"] .studio-workspace {
    grid-template-columns: minmax(0, 1fr) var(--dock-w);
  }
  .studio[data-zen="true"][data-dock="closed"] .studio-workspace {
    grid-template-columns: minmax(0, 1fr) 0;
  }
}

/* ── 2. The canvas stage ──────────────────────────────────────────────── */

/*
  The column is a flex column so the stage can flex into the height left over
  after the status strip. Both children are sized, not guessed at.
*/
.studio-canvas-column {
  display: flex;
  flex-direction: column;
  min-height: 0;
  min-width: 0;
}

/*
  The stage is a recessed well the paper sits in — a sheet on a table, not a
  bright rectangle floating in space. Its own scrolling keeps a large document
  pannable without moving the panels.
*/
.studio-stage {
  position: relative;
  display: grid;
  place-items: center;
  /*
    `overflow: auto` gives the stage a scroll container, and a scroll container
    rounds its content box up. The sheet was then one sub-pixel wider than the
    area it was centred in, which produced a permanent horizontal scrollbar on a
    document that already fitted.

    Both the centring and the sizing are handled inside the stage now, so the
    scrollbars only appear when the VIEW is genuinely panned beyond the edge —
    which is the only case where scrolling is meaningful.
  */
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  /*
    The stage fills whatever height the canvas column has. It is the flexible
    child of a flex column, so the studio box's height decides it — no dvh guess
    and no cap, which is what produced the dead space this replaces.
  */
  flex: 1 1 auto;
  min-height: 14rem;
  padding: 1rem;
  /* Clip rather than scroll: the viewport's own pan is the scrolling, and a
     second scroll container on top of it fought the drag handlers. */
  overflow: hidden;
  /*
    The mat behind the paper: a faint two-tone weave, not a checkerboard.

    Deliberately small and low-contrast. At 24px with 40% line strength it read
    as a chessboard competing with the artwork; at 12px and 22% it is texture you
    stop noticing once there is something on the paper, which is the point of a
    mat.
  */
  background:
    repeating-conic-gradient(
      from 0deg at 50% 50%,
      color-mix(in oklab, var(--line) 22%, transparent) 0deg 90deg,
      transparent 90deg 180deg
    );
  background-size: 0.75rem 0.75rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  touch-action: none;          /* the pointer handlers own every gesture */
}

/* A hand when panning, a crosshair when drawing. */
.studio-stage[data-tool="move"] { cursor: grab; }
.studio-stage[data-tool="move"]:active { cursor: grabbing; }
.studio-stage[data-tool="brush"],
.studio-stage[data-tool="eraser"] { cursor: crosshair; }

/*
  The sheet is the paper. It must be OPAQUE: the canvas above it is transparent
  wherever nothing has been painted, so anything showing through — the stage's
  transparency pattern, most visibly — reads as part of the drawing.
*/
.studio-sheet {
  position: relative;
  box-shadow: var(--shadow-frame);
  /* Set from the document's own aspect ratio, so a resize is honest. */
  background: #faf6ee;
  background-image: none;
  isolation: isolate;
  /*
    Bounded by the stage's CONTENT box, not its border box.

    The inline width is `min(100%, <doc>px)`, and 100% there is measured against
    the grid area — the border box — so the sheet came out one padding-width
    wider than the space inside it and the stage scrolled horizontally on a
    document that already fitted. `max-*` with `100%` of the padding box, plus
    `box-sizing`, is what makes the paper sit inside the well.
  */
  /*
    The sheet's width and height are set by the viewport (`fitSheet`), which is
    the only thing that knows both the document's ratio and the stage's real box.
    CSS deliberately does not size it: a declared width would win over the fit
    and a TALL document would overflow the bottom of the well.

    The fallback size matters for the one frame before the first paint, and the
    min keeps the canvas from being zero-sized, which would make the first
    backing-store allocation throw.
  */
  width: 6rem;
  height: 4.5rem;
  min-width: 1px;
  min-height: 1px;
  overflow: hidden;
}

.studio-sheet > canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* The backing store is sized by the viewport from this box times DPR. */
  background: transparent;
  /* No transition on size: a live stroke must not be animated. */
}

/* A hairline mat, so the sheet edge reads as paper rather than as a viewport. */
.studio-sheet::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  border: 1px solid color-mix(in oklab, var(--text) 18%, transparent);
}

/* Status strip under the stage: zoom, cursor position, document size. */
.studio-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-top: 0.5rem;
}

.studio-status .meta { letter-spacing: 0.1em; }

/* ── 3. The dock and its panels ───────────────────────────────────────── */

/*
  The dock is a full-height column on desktop and a bottom sheet on a phone.

  Full height, not a dvh cap: capping it left the canvas column shorter than the
  page and produced a wall of dead space below the stage. The column stretches
  to the studio's own box, and the panel list scrolls inside it.
*/
.studio-dock-wrap {
  position: relative;
  display: flex;
  min-height: 0;
  min-width: 0;
}

.studio-dock {
  display: grid;
  gap: 0.5rem;
  align-content: start;
  /*
    Each ROW is sized to its panel, not to an equal share of the dock.

    A grid's default `grid-auto-rows: auto` combined with a definite container
    height and more rows than fit makes every row a fraction of the height: four
    open panels in a 700px dock came out at exactly 174px each, whatever their
    content. A 524px brush panel was showing 174px of itself and hiding the rest
    behind `overflow: hidden`, and an already-open panel could not be scrolled to
    either — the content simply was not reachable.

    `min-content` sizes each row to what the panel actually needs, so the dock's
    own `overflow-y: auto` does the scrolling, which is what a panel stack is
    supposed to do.
  */
  grid-auto-rows: min-content;
  min-height: 0;
  min-width: 0;
  width: 100%;
  overflow-y: auto;
  /* Room for the scrollbar so the panel edges do not sit under it. */
  padding-right: 0.25rem;
  scrollbar-gutter: stable;
}

@media (min-width: 900px) {
  /*
    A short fade over the last little bit of the dock, so a cut-off panel reads
    as "there is more below" rather than as one that failed to render.

    Drawn as an overlay rather than as a background on the scroller: a scroller's
    own background scrolls with its content, which would carry the fade up the
    panel stack instead of holding it at the bottom edge.
  */
  .studio-dock-wrap::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 1.75rem;
    pointer-events: none;
    background: linear-gradient(to bottom,
      transparent,
      color-mix(in oklab, var(--bg-core) 92%, transparent));
  }
}

.studio-panel {
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  overflow: hidden;
}

.studio-panel > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  cursor: pointer;
  list-style: none;
  /* A chapter heading in a catalogue. */
  font-family: var(--font-body);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.studio-panel > summary::-webkit-details-marker { display: none; }

.studio-panel > summary:hover { color: var(--accent); }

.studio-panel > summary .studio-chevron {
  margin-left: auto;
  transition: transform var(--t) var(--ease-out);
}

.studio-panel[open] > summary .studio-chevron { transform: rotate(90deg); }

.studio-panel-body {
  padding: 0 0.75rem 0.75rem;
  border-top: 1px solid var(--line);
  padding-top: 0.75rem;
}

/* Focus ring in ink, not a neon halo. */
.studio :is(button, input, select, textarea, canvas, [tabindex]):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── 4. The tool rail ─────────────────────────────────────────────────── */

.studio-rail {
  display: flex;
  flex-direction: row;
  gap: 0.25rem;
  padding: 0.375rem;
  background: var(--bg-surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

@media (min-width: 900px) {
  .studio-rail {
    flex-direction: column;
    position: sticky;
    top: 1rem;
    /*
      The rail is a toolbar, not a column: it takes the height of the tools it
      holds. Left to stretch it floated at the top of an empty 18rem box, which
      read as a layout mistake rather than as a control.
    */
    align-self: start;
  }
}

.studio-tool {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
  transition: color var(--t) var(--ease-out),
              background-color var(--t) var(--ease-out),
              border-color var(--t) var(--ease-out);
}

.studio-tool:hover { color: var(--text); border-color: var(--line); }

.studio-tool[aria-pressed="true"] {
  color: var(--bg-core);
  background: var(--text);
  border-color: var(--text);
}

/* ── 5. Layers ────────────────────────────────────────────────────────── */

.studio-layers {
  display: grid;
  gap: 0.25rem;
  max-height: 18rem;
  overflow-y: auto;
}

.studio-layer {
  display: grid;
  /* visible · thumbnail · name · clip · lock */
  grid-template-columns: auto auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.3rem;
  padding: 0.35rem 0.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
  /*
    The clip indent is a margin rather than a different padding, so it shifts the
    whole row — including its hover border — rather than just its contents, which
    would leave the border sitting proud of the indent.
  */
  margin-left: 0;
  transition: margin-left var(--t) var(--ease-out);
}

/*
  A clipped layer, drawn the way an outliner draws a child: stepped in, with a
  rule joining it to the row it clips to.

  A clip is a RELATIONSHIP between two rows, and a flat list gives no clue which
  layer is doing the masking. The indent is the whole affordance.
*/
.studio-layer[data-clipped="true"] {
  margin-left: 1rem;
  border-left: 2px solid color-mix(in oklab, var(--accent) 55%, transparent);
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

/* The clip button is only meaningful on a layer that has something beneath it. */
.studio-layer [data-act="clip"]:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}

.studio-layer [data-act="clip"][aria-pressed="true"] {
  color: var(--accent);
}

.studio-layer:hover { border-color: var(--line); }

.studio-layer[aria-selected="true"] {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 7%, transparent);
}

.studio-layer[data-dragging="true"] { opacity: 0.5; }

/* Drop indicator while dragging a layer to a new position. */
.studio-layer[data-drop="above"] { box-shadow: inset 0 2px 0 var(--accent); }
.studio-layer[data-drop="below"] { box-shadow: inset 0 -2px 0 var(--accent); }

.studio-layer-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
  color: var(--text);
}

.studio-layer[data-visible="false"] .studio-layer-name {
  color: var(--text-faint);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}

/*
  Layer thumbnails: a tiny print, scaled to the document.

  Backed with the paper colour rather than a transparency pattern. A layer is
  mostly empty most of the time, so a checkerboard would be the common case and
  would read as texture the artwork does not have; white is the honest backdrop
  for ink.
*/
.studio-thumb {
  width: 2rem;
  height: 1.5rem;
  object-fit: contain;
  background: #faf6ee;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
}

.studio-icon-btn {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--radius-xs);
  background: none;
  color: var(--text-muted);
  cursor: pointer;
}

.studio-icon-btn:hover { color: var(--accent); }
.studio-icon-btn[aria-pressed="true"] { color: var(--text); }

/* ── 6. Brushes ───────────────────────────────────────────────────────── */

/*
  A fixed three-column grid rather than auto-fill: the preset names wrap to two
  lines on the narrow dock, and auto-fill was producing ragged rows where one
  cell was taller than its neighbours. `align-items: start` keeps each cell at
  its own height instead of stretching every row to the tallest one.
*/
.studio-brushes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: 0.3rem;
}

.studio-brush {
  display: grid;
  grid-template-rows: auto 1fr;
  gap: 0.25rem;
  justify-items: center;
  /* A floor on the height so a one-line and a two-line name still line up. */
  min-height: 3.6rem;
  padding: 0.4rem 0.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--bg-raised);
  cursor: pointer;
  transition: border-color var(--t) var(--ease-out);
}

.studio-brush:hover { border-color: var(--line-strong); }

.studio-brush[aria-pressed="true"] {
  border-color: var(--accent);
  background: color-mix(in oklab, var(--accent) 6%, transparent);
}

/* The nib preview is drawn as a scaled dot, so it reads at any size. */
.studio-brush-nib {
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: radial-gradient(circle at 35% 32%, var(--text), color-mix(in oklab, var(--text) 55%, transparent));
}

.studio-brush-name {
  align-self: start;
  font-size: 0.58rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-muted);
  text-align: center;
  line-height: 1.15;
  /* Names are the widest part of a cell; let them break rather than clip. */
  overflow-wrap: anywhere;
  hyphens: auto;
}

/* ── 7. Sliders in a row ──────────────────────────────────────────────── */

.studio-field {
  display: grid;
  gap: 0.25rem;
  margin-bottom: 0.6rem;
}

.studio-field-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.studio-field output {
  font-variant-numeric: tabular-nums;
  font-size: 0.72rem;
  color: var(--text-muted);
}

.studio-range {
  width: 100%;
  height: 1px;
  appearance: none;
  background: var(--line-strong);
  cursor: pointer;
}

.studio-range::-webkit-slider-thumb {
  appearance: none;
  width: 0.85rem;
  height: 0.85rem;
  border-radius: 50%;
  background: var(--text);
  border: 0;
}

.studio-range::-moz-range-thumb {
  width: 0.85rem;
  height: 0.85rem;
  border: 0;
  border-radius: 50%;
  background: var(--text);
}

/* ── 7b. Selection ────────────────────────────────────────────────────── */

/*
  The four boolean operations, as a segmented row.

  One row of four, not a dropdown: which boolean is active is the single most
  important fact about the next selection gesture, and a dropdown hides it.
*/
.studio-ops {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 0.2rem;
  margin-bottom: 0.6rem;
}

.studio-op {
  padding: 0.35rem 0.15rem;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  background: var(--bg-raised);
  color: var(--text-muted);
  /*
    Small and tight so the four fit on one line. At 0.6rem with the dock's own
    padding, "SUBTRACT" and "INTERSECT" wrapped onto two lines and made the row
    twice as tall as it needed to be — which pushed the rest of the panel out of
    view for no gain. The letter-spacing is tightened with it, since tracking is
    the other half of the width.
  */
  font-size: 0.55rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  min-height: 1.75rem;
  transition: color var(--t) var(--ease-out), border-color var(--t) var(--ease-out);
}

.studio-op:hover { color: var(--text); border-color: var(--line-strong); }

.studio-op[aria-pressed="true"] {
  color: var(--bg-core);
  background: var(--text);
  border-color: var(--text);
}

/* A selection tool gets a crosshair, but a dashed one — it is not the brush. */
.studio-stage[data-tool="marquee"],
.studio-stage[data-tool="ellipse-select"],
.studio-stage[data-tool="lasso"],
.studio-stage[data-tool="wand"] { cursor: crosshair; }

/* ── 7c. Transform ────────────────────────────────────────────────────── */

/*
  The numeric fields, two per row.

  A five-field stack would push Apply below the fold in the dock, so X/Y share a
  row and W/H/Angle share the next. `min-width: 0` on the label is required or
  the input refuses to shrink below its intrinsic size and the grid overflows.
*/
.studio-nums {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.4rem;
}

.studio-num {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
}

.studio-num .meta { letter-spacing: 0.06em; }

.studio-num input {
  min-width: 0;
  /* A 44px-tall field: the dock is a touch surface on a tablet. */
  min-height: 2.75rem;
  padding: 0.25rem 0.4rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.studio-num input:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* A checkbox row, with the label as the tap target rather than just the box. */
.studio-check {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  min-height: 2.75rem;
  cursor: pointer;
}

.studio-check input { width: 1.15rem; height: 1.15rem; accent-color: var(--accent); }

/*
  The transform stage shows a move cursor rather than a crosshair: the gesture
  here is "grab the box", and a crosshair would read as "draw a point".
*/
.studio-stage[data-tool="transform"] { cursor: move; }

/* ── 8. Colour ────────────────────────────────────────────────────────── */

.studio-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(1.75rem, 1fr));
  gap: 0.3rem;
}

.studio-swatch {
  aspect-ratio: 1;
  border: 1px solid var(--line);
  border-radius: var(--radius-xs);
  cursor: pointer;
}

.studio-swatch[aria-pressed="true"] {
  border-color: var(--text);
  box-shadow: 0 0 0 1px var(--text);
}

/* ── 9. The bottom sheet on a phone ───────────────────────────────────── */

/*
  Below 900px the dock is a sheet pinned to the bottom of the screen, with
  three snap points held in `--sheet-pos` (0 = closed, 0.42 = half, 1 = full).

  The canvas is never covered: the workspace reserves the sheet's CURRENT height
  at every snap, so the stage shrinks as the sheet grows. The artwork is the
  point; the panels are the tool tray.
*/
@media (max-width: 899px) {
  .studio {
    /*
      The sheet's FULL height. It is deliberately less than the screen: the
      canvas keeps a usable floor even with the panels dragged all the way up.
      At 68dvh the reservation (sheet + handle) plus the stage's own minimum
      exceeded the viewport, so "full" pushed the status strip back under the
      sheet. 50dvh keeps the header, an 8rem stage and the status strip all above
      the sheet at every snap, on a short phone and a tall one alike.
    */
    --sheet-h: 50dvh;
    --sheet-closed: 3.25rem;
    --sheet-handle: 2.25rem;
  }

  /*
    A grab bar. Always visible, because it is the only affordance that says the
    sheet can be moved — and it is the tap target for cycling the snap points
    when a drag is not possible.
  */
  .studio-sheet-grip {
    display: flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    height: var(--sheet-handle, 2.25rem);
    border: 1px solid var(--line);
    border-bottom: 0;
    border-radius: var(--radius) var(--radius) 0 0;
    background: var(--bg-surface);
    box-shadow: var(--shadow-lift);
    color: var(--text-muted);
    cursor: grab;
    touch-action: none;
  }

  .studio-sheet-grip:active { cursor: grabbing; }

  .studio-sheet-grip-bar {
    width: 2.5rem;
    height: 0.25rem;
    border-radius: 9999px;
    background: var(--line-strong);
  }

  /* The dock becomes a fixed sheet. */
  .studio-dock-wrap {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 45;
    display: flex;
    flex-direction: column;
    /* Height is driven by the snap point, not by the content. */
    /*
      Driven by `--sheet-pos` (0 closed .. 1 full), which `dock.js` writes to
      THIS element. The fallback is 0, not a mid value: if the variable is
      missing the sheet must sit at a snap point the grip can return to, and an
      unreachable height is the kind of state that looks like a broken drag.
    */
    height: calc(var(--sheet-closed) + (var(--sheet-h) - var(--sheet-closed)) * var(--sheet-pos, 0));
    max-height: calc(100dvh - var(--sheet-handle));
    transition: height var(--t) var(--ease-out);
  }

  .studio-dock {
    /* One column on a sheet: two would make every panel a sliver. */
    grid-template-columns: 1fr;
    padding: 0 0.75rem 0.75rem;
    border-radius: 0;
    box-shadow: none;
  }

  /*
    Keep the canvas clear of the sheet at EVERY snap point, not just at rest.

    The sheet's height is `closed + (h - closed) * pos`; the workspace reserves
    the grip plus exactly that, so the canvas ends where the sheet begins
    whatever the snap. Reserving only the CLOSED height was the bug: the sheet
    opened to 276-585px over a strip that reserved 88px, so it covered the
    bottom of the stage and the whole status strip at half and full.

    The same `--sheet-pos` drives both, so they can never drift apart. It is
    written to the STUDIO ROOT by `dock.js` precisely so this ancestor can read
    it — a value set on the sheet itself would not inherit upwards to here.
  */
  .studio-workspace {
    padding-bottom: calc(
      var(--sheet-handle) + var(--sheet-closed)
      + (var(--sheet-h) - var(--sheet-closed)) * var(--sheet-pos, 0)
    );
    transition: padding-bottom var(--t) var(--ease-out);
  }

  .studio-stage {
    /*
      Only 8rem of floor on a phone. The stage has to be able to GIVE height
      back when the sheet is dragged up — a 12rem minimum forced the workspace
      taller than the space left for it and pushed the status strip back under
      the sheet.
    */
    min-height: 8rem;
    padding: 0.5rem;
  }
}

/* Above the breakpoint the grip is noise — the dock is always visible there. */
@media (min-width: 900px) {
  .studio-sheet-grip { display: none; }
}

/* Respect a reduced-motion preference: no chevron spin, no sheet slide. */
@media (prefers-reduced-motion: reduce) {
  .studio-panel > summary .studio-chevron { transition: none; }
  .studio-tool,
  .studio-brush,
  .studio-layer { transition: none; }
  .studio-dock-wrap,
  .studio-workspace { transition: none; }
}
