/* The terrace planner's screen sheet.

   The look is soft and plain: a light grey or a dark grey ground with a dot
   grid on it, the drawing full screen, and two frosted columns floating over
   its left and right edges — the drawing tools and settings on the left, the
   materials, cost and prices on the right. One warm timber accent, used for
   what is selected or active and nothing else. */

:root {
  color-scheme: light;

  --bg: #f2f2f4;
  --surface: #ffffff;
  --ink: #1d1d1f;
  --ink-muted: #6e6e73;
  --line: rgba(0, 0, 0, 0.08);
  --accent: #95571f;

  /* The frosted columns and toolbar: a translucent surface, the dots of the
     grid behind the drawing, the soft fill of a button or a field at rest, and
     the one shadow every floating thing casts. */
  --panel: rgba(255, 255, 255, 0.8);
  --dot: rgba(0, 0, 0, 0.09);
  --fill: rgba(118, 118, 128, 0.12);
  --fill-strong: rgba(118, 118, 128, 0.22);
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.05);
  --shadow-sheet: 0 -8px 30px rgba(0, 0, 0, 0.08);
  --shadow-control: 0 1px 3px rgba(0, 0, 0, 0.12);

  /* The one colour that means "this is wrong": the border of a field the form
     could not read, the message under it, and the stripe down an error row in
     the warnings list. Named for what it says rather than for the red it is,
     so the three of them cannot drift apart. */
  --danger: #a3341f;

  /* The spacing scale. Six steps and no more: every padding, margin and gap in
     this sheet is one of them, so two things a screen apart are either the
     same distance or visibly a step different. --gap is the largest step under
     the name the shell already knew it by — the air between the columns and
     the edge of the screen. */
  --space-2xs: 0.15rem;
  --space-xs: 0.25rem;
  --space-sm: 0.35rem;
  --space-md: 0.5rem;
  --space-lg: 0.75rem;
  --space-xl: 1rem;
  --gap: var(--space-xl);

  /* The type scale: six steps, and every font-size outside this block is one
     of them. --text-md is the body of the columns; --text-xl is a heading
     figure and --text-2xl the one total the page leads with. */
  --text-xs: 0.75rem;
  --text-sm: 0.8125rem;
  --text-md: 0.875rem;
  --text-lg: 1rem;
  --text-xl: 1.5rem;
  --text-2xl: 2rem;

  /* One corner for controls, a rounder one for the floating surfaces, one
     height under anything a pointer has to hit, and one focus ring. */
  --radius: 8px;
  --radius-panel: 18px;
  --control-height: 2rem;
  --touch: 2.75rem;
  --focus-ring: 2px solid var(--accent);
  --focus-offset: 2px;

  /* How wide the two columns are, and what is left of one once collapsed. */
  --column-left: 20rem;
  --column-right: 22rem;
  --rail: 2.5rem;
  --left: var(--column-left);
  --right: var(--column-right);

  /* The bottom sheet on a phone: the height of its head, which is always
     there, and of the section under it, which folds away to nothing. */
  --sheet-head: 8.5rem;
  --sheet-body: 45vh;

  /* One colour per layer of the drawing, and the darker line drawn round it.
     Named for the layer rather than for the colour, so the rules further down
     say which layer they are painting and a repaint of the palette is a change
     here and nowhere else. */
  --layer-supports: #8e8e93;
  --layer-supports-line: #48484a;
  --layer-beams: #8a5a33;
  --layer-beams-line: #5a3818;
  --layer-boards: #e6c9a3;
  --layer-boards-line: #b48a5a;
  --layer-screws: #3a3a3c;

  /* How opaque each layer is painted, 0 to 1. These are the defaults; the
     slider in the left column overrides them on the drawing itself — see
     js/view/display.js. */
  --opacity-supports: 1;
  --opacity-beams: 1;
  --opacity-boards: 1;
  --opacity-screws: 1;

  /* The round knob of a switch, white on either theme. */
  --knob: #ffffff;
}

/* The dark theme: the same names, darker values. Once for a system that asks
   for dark and a page that has not been told light, and once for a page told
   dark — see js/view/theme.js, which sets data-theme on <html>. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg: #1c1c1e;
    --surface: #2c2c2e;
    --ink: #f5f5f7;
    --ink-muted: #a1a1a6;
    --line: rgba(255, 255, 255, 0.1);
    --accent: #d9a06a;
    --panel: rgba(44, 44, 46, 0.78);
    --dot: rgba(255, 255, 255, 0.07);
    --fill: rgba(118, 118, 128, 0.24);
    --fill-strong: rgba(118, 118, 128, 0.4);
    --shadow: 0 12px 40px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
    --shadow-sheet: 0 -8px 30px rgba(0, 0, 0, 0.4);
    --danger: #ff6961;
    --layer-supports: #8e8e93;
    --layer-supports-line: #c7c7cc;
    --layer-beams: #7a5433;
    --layer-beams-line: #b38153;
    --layer-boards: #a47d56;
    --layer-boards-line: #d0a77a;
    --layer-screws: #e5e5ea;
  }
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --bg: #1c1c1e;
  --surface: #2c2c2e;
  --ink: #f5f5f7;
  --ink-muted: #a1a1a6;
  --line: rgba(255, 255, 255, 0.1);
  --accent: #d9a06a;
  --panel: rgba(44, 44, 46, 0.78);
  --dot: rgba(255, 255, 255, 0.07);
  --fill: rgba(118, 118, 128, 0.24);
  --fill-strong: rgba(118, 118, 128, 0.4);
  --shadow: 0 12px 40px rgba(0, 0, 0, 0.45), 0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-sheet: 0 -8px 30px rgba(0, 0, 0, 0.4);
  --danger: #ff6961;
  --layer-supports: #8e8e93;
  --layer-supports-line: #c7c7cc;
  --layer-beams: #7a5433;
  --layer-beams-line: #b38153;
  --layer-boards: #a47d56;
  --layer-boards-line: #d0a77a;
  --layer-screws: #e5e5ea;
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background-color: var(--bg);
  background-image: radial-gradient(circle, var(--dot) 1px, transparent 1.4px);
  background-size: 24px 24px;
  color: var(--ink);
  font-family:
    -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue",
    system-ui, sans-serif;
  font-size: var(--text-md);
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

button,
input,
select {
  accent-color: var(--accent);
}

.app-header,
.app-footer {
  padding: var(--gap);
}

.app-footer {
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.app-header h1 {
  margin: 0;
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.tagline {
  margin: var(--space-xs) 0 0;
  color: var(--ink-muted);
}

/* The stamp on the printed sheet — see css/print.css and js/view/print.js.
   Absent from the screen, where the app itself is the live document and a
   date on it would only ever say "today". */
.print-meta {
  display: none;
}

/* The shell.

   On a phone there is no room to float anything over the drawing, so the
   base rule is one track and the regions stack — the drawing first, then the
   tools and settings, then the materials.

   From the breakpoint up the page is a stage the size of the window. It is
   one grid row of three tracks: the left column, the drawing, the right
   column. The canvas region spans all three, so the dot grid and the drawing
   run edge to edge under the two columns — but its padding is the width of
   those columns, so the drawing itself is fitted to the part of the screen
   they leave clear, and nothing is ever drawn underneath one. The columns sit
   in their tracks on the same row, over it: grid placement, not
   position: fixed, so collapsing one is still a change of track width and of
   nothing else. test/shell.test.js holds that. */
.app-main {
  display: grid;
  gap: var(--gap);
  padding: var(--gap);
  grid-template-columns: 1fr;
}

#canvas-region {
  order: -1;
}

/* Each region is its own stack. min-width: 0 because a grid item is
   otherwise as wide as its widest child, and one long word in the results
   table would push the column off the screen instead of scrolling. */
.app-region {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  min-width: 0;
}

#left-panel,
#right-panel {
  padding: var(--space-md);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  background: var(--panel);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: var(--shadow);
}

.panel-toggle {
  align-self: flex-end;
  padding: 0 var(--space-lg);
  min-height: var(--control-height);
  border: 0;
  border-radius: var(--radius);
  background: var(--fill);
  color: var(--ink-muted);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}

#left-panel > .panel-toggle {
  align-self: flex-start;
}

.panel-toggle:hover {
  background: var(--fill-strong);
  color: var(--ink);
}

@media (min-width: 60rem) {
  /* Collapsing a column, which only a wide screen does. The state lives on .app-main as data-left /
     data-right — put there by js/view/panels.js, remembered per browser. The
     panels inside the collapsed region stop being laid out; the toggle does
     not, so what is left is a rail with the way back in it. */
  .app-main[data-left="collapsed"] #left-panel > .panel,
  .app-main[data-right="collapsed"] #right-panel > .panel {
    display: none;
  }

  .app-main[data-left="collapsed"] #left-panel,
  .app-main[data-right="collapsed"] #right-panel {
    padding: 0;
    border-color: transparent;
    background: transparent;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
  }

  /* In a rail the name has to turn on its side. It is still the name — a
     chevron would leave the button saying nothing to anyone who cannot see
     which way it points. The rail is a small frosted tab of its own. */
  .app-main[data-left="collapsed"] .panel-toggle[data-panel-toggle="left"],
  .app-main[data-right="collapsed"] .panel-toggle[data-panel-toggle="right"] {
    writing-mode: vertical-rl;
    align-self: center;
    padding: var(--space-lg) 0;
    width: var(--rail);
    border: 1px solid var(--line);
    border-radius: var(--radius-panel);
    background: var(--panel);
    -webkit-backdrop-filter: blur(24px);
    backdrop-filter: blur(24px);
    box-shadow: var(--shadow);
  }

  .app-header,
  .app-footer {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .app-main {
    height: 100vh;
    height: 100dvh;
    grid-template-columns: var(--left) minmax(0, 1fr) var(--right);
    grid-template-rows: minmax(0, 1fr);
  }

  .app-main[data-left="collapsed"] {
    --left: var(--rail);
  }

  .app-main[data-right="collapsed"] {
    --right: var(--rail);
  }

  #canvas-region {
    order: 0;
    grid-row: 1;
    grid-column: 1 / -1;
    margin: calc(-1 * var(--gap));
    padding: var(--gap) calc(var(--right) + 2 * var(--gap)) var(--gap)
      calc(var(--left) + 2 * var(--gap));
  }

  #left-panel,
  #right-panel {
    grid-row: 1;
    z-index: 1;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  #left-panel {
    grid-column: 1;
  }

  #right-panel {
    grid-column: 3;
  }

  .app-main[data-left="collapsed"] #left-panel,
  .app-main[data-right="collapsed"] #right-panel {
    overflow: visible;
  }
}

/* A section inside a column. The column is the card; a section is a run of
   it under a small grey heading, split from the next by a hairline. */
.panel {
  padding: var(--space-md);
  min-width: 0;
}

.panel + .panel {
  border-top: 1px solid var(--line);
  padding-top: var(--space-lg);
}

.panel h2 {
  margin: 0 0 var(--space-md);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.panel-body {
  min-height: 0;
}

/* The canvas region is what the selection card is placed against. */
#canvas-region {
  position: relative;
}

/* The drawing's own section has no chrome at all: it is the stage. */
#plan-panel {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
  padding: 0;
}

#plan-panel h2 {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

#plan-body {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

/* The floating toolbar over the top of the drawing. */
.plan-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xs) var(--space-md);
  align-items: center;
}

#plan-toolbar {
  align-self: center;
  justify-content: center;
  padding: var(--space-xs);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  background: var(--panel);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: var(--shadow);
}

#plan-toolbar .plan-toolbar-group + .plan-toolbar-group,
.toolbar-layer + .toolbar-pill {
  padding-left: var(--space-md);
  border-left: 1px solid var(--line);
}

/* The buttons of the toolbar, in one run: a row here, a column on a phone. */
.toolbar-pill {
  display: flex;
  gap: var(--space-xs) var(--space-md);
  align-items: center;
}

#plan-toolbar label {
  padding-left: var(--space-sm);
}

/* The toolbar's icon buttons, the theme button among them: square-ish, the
   icon centred, the name in the aria-label. The selection tool, when on, is
   the one button tinted with the accent. */
.theme-toggle,
.tool-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
}

#plan-toolbar .tool-button[aria-pressed="true"] {
  background: var(--fill);
  color: var(--accent);
}

.theme-icon,
.tool-icon {
  width: 1.1em;
  height: 1.1em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.theme-toggle[aria-pressed="true"] .theme-icon-moon,
.theme-toggle[aria-pressed="false"] .theme-icon-sun {
  display: none;
}

/* The drawing surface. The outline is edited directly on it — see
   js/view/canvas.js — so pointer events must reach the SVG rather than being
   eaten by the browser's own panning and text selection. */

.plan-canvas[data-tool="select"] {
  cursor: default;
}

/* overflow: visible so a dimension standing off an edge of the fitted view
   is still drawn, over the padding the region keeps round the drawing. */
.plan-canvas {
  display: block;
  overflow: visible;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 3;
  touch-action: none;
  cursor: crosshair;
  user-select: none;
}

@media (min-width: 60rem) {
  .plan-canvas {
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
    aspect-ratio: auto;
  }
}

.plan-canvas [data-canvas="grid"] line {
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* The outline itself. It is painted last — see `renderCanvas` in
   js/view/canvas.js — so that the corner handles sit above the boards, and
   that puts its tinted fill over the whole terrace as well. A fill is a hit
   target, so without the rule below every click inside the outline lands on
   this one path: `js/view/selection.js` reads no element id off it, and a
   click on a board selects nothing. `pointer-events: none` takes the shape
   out of hit-testing without changing a pixel of it, so a click falls through
   to the board, beam, support or screw drawn underneath — or, where there is
   nothing under it, to the `<svg>` itself, which is where both the outline
   editor and the selection listener are bound. The handles keep their own
   pointer events: this rule names the edges path alone. */

.plan-canvas [data-canvas="outline"] [data-canvas-part="edges"] {
  fill: color-mix(in srgb, var(--accent) 8%, transparent);
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

/* The wall the boards start from, drawn over its edge, and the wall under
   the pointer. Neither takes a hit: the boards under them must still be
   clickable, and a wall is found by distance — see js/view/selection-card.js. */
.plan-canvas [data-canvas-part="start-wall"],
.plan-canvas [data-canvas-part="wall-hover"] {
  fill: none;
  stroke: var(--accent);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  pointer-events: none;
}

.plan-canvas [data-canvas-part="start-wall"] {
  stroke-width: 6;
}

.plan-canvas [data-canvas-part="wall-hover"] {
  stroke-width: 8;
  stroke-opacity: 0.35;
}

.plan-canvas[data-hover-wall] {
  cursor: pointer;
}

.plan-canvas [data-vertex-index] {
  fill: var(--surface);
  stroke: var(--accent);
  stroke-width: 2;
  vector-effect: non-scaling-stroke;
  cursor: grab;
}

.plan-canvas [data-vertex-index]:hover {
  fill: var(--accent);
}

/* A corner reached with the keyboard. Every handle is a tab stop — see
   js/view/canvas.js — so the ring is the only thing telling the user which
   corner the arrow keys are about to move, and it is drawn twice on purpose:
   an outline, which is what the rest of the page uses, and a filled, thicker
   handle, because outline on an SVG shape is a recent thing and a focus ring
   that only sometimes appears is worse than one that always looks the same.
   :focus as well as :focus-visible, since a handle is only ever focused
   deliberately. */

.plan-canvas [data-vertex-index]:focus,
.plan-canvas [data-vertex-index]:focus-visible {
  fill: var(--accent);
  stroke-width: 4;
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The four layers, painted so they can be told apart.
 *
 * `renderLayers` — see js/view/layers-svg.js — puts one node per element
 * inside a `<g data-layer>`, and says nothing about how any of it looks: a
 * support and a screw are both circles, a beam and a board are both paths, so
 * without these rules the whole structure paints in the browser's default
 * black and the drawing is one silhouette.
 *
 * Colour carries the meaning, so it is picked by what the thing is rather than
 * from a spread of hues: stone-grey pads on the ground, brown timber for the
 * beams under warm timber for the boards, steel for the fixings. The two
 * timber layers are the pair most easily confused — one runs across the other
 * — so they differ in lightness, not just in tint, and stay distinct to
 * someone who cannot separate the two browns.
 *
 * They are drawn bottom to top in that order, which means the boards cover
 * everything under them. So the boards are the one translucent layer: opaque
 * boards would be a correct drawing of a finished terrace and a useless
 * drawing of how it is built, with the joists and pads it rests on hidden
 * under it. Everything else paints solid.
 *
 * Strokes are `non-scaling-stroke` throughout: the viewBox is in millimetres,
 * where a stroke-width of 1 is a micrometre-thin line nobody would see.
 *
 * These rules come before the `[data-selected]` one below on purpose. They
 * have the same specificity, so the later rule is what wins the stroke of the
 * picked element, and the fill set here is what keeps it recognisable as a
 * board rather than turning it into a highlight-coloured blob.
 */

.plan-canvas [data-layer="supports"] > * {
  fill: var(--layer-supports);
  stroke: var(--layer-supports-line);
  stroke-width: 1.5;
  vector-effect: non-scaling-stroke;
}

.plan-canvas [data-layer="beams"] > * {
  fill: var(--layer-beams);
  stroke: var(--layer-beams-line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Boards butt up against each other, so the edge between two of them is the
   only thing saying there are two: the stroke here is what stops a deck from
   reading as one big rectangle. */
.plan-canvas [data-layer="boards"] > * {
  fill: var(--layer-boards);
  fill-opacity: 0.55;
  stroke: var(--layer-boards-line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* A screw is drawn at 12mm on a terrace metres across — a few pixels wide.
   The pale ring is what lifts that dot off the board it is driven into. */
.plan-canvas [data-layer="screws"] > * {
  fill: var(--layer-screws);
  stroke: var(--surface);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

/* Each layer as opaque as the slider says. On the group, not on its shapes,
   so the boards' own translucency is kept and multiplied, not replaced. */
.plan-canvas [data-layer="supports"] {
  opacity: var(--opacity-supports);
}

.plan-canvas [data-layer="beams"] {
  opacity: var(--opacity-beams);
}

.plan-canvas [data-layer="boards"] {
  opacity: var(--opacity-boards);
}

.plan-canvas [data-layer="screws"] {
  opacity: var(--opacity-screws);
}

/* The length of each edge of the outline, in a small pill beside it — see
   `paintDimensions` in js/view/canvas.js. Never a hit target: a label next
   to a corner must not stop the corner being grabbed. */
.plan-canvas [data-canvas="dimensions"],
.plan-canvas [data-canvas="dimensions"] [data-dimension-part] {
  pointer-events: none;
}

.plan-canvas [data-canvas="dimensions"] [data-dimension-part="pill"] {
  fill: var(--surface);
  stroke: var(--line);
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.plan-canvas [data-canvas="dimensions"] [data-dimension-part="label"] {
  fill: var(--ink);
  font-weight: 500;
  text-anchor: middle;
  dominant-baseline: central;
  font-variant-numeric: tabular-nums;
}

.plan-canvas[data-dimensions="off"] [data-canvas="dimensions"] {
  display: none;
}

/* A layer switched off is hidden with the `hidden` attribute — see
   js/view/layer-toggles.js. The browser's own stylesheet only hides HTML
   elements with it, and a <g> is not one, so this rule is what makes the
   attribute mean anything inside the drawing. */

.plan-canvas [hidden] {
  display: none;
}

/* The one element the user has picked, marked by renderLayers. Drawn as an
   outline around the shape rather than as a change of fill, so a selected
   beam is still recognisably a beam and a selected screw does not vanish
   under its own highlight. */

.plan-canvas [data-selected] {
  stroke: var(--accent);
  stroke-width: 3;
  vector-effect: non-scaling-stroke;
  paint-order: stroke;
}

/* The element under the pointer: outlined and haloed in the accent, and
   painted solid, so the user sees what a click or a right click will act on
   before making it — the outline alone is lost against the boards' own brown
   edges. Not while a wall is under the pointer, which is then the thing a
   click will pick. Not behind a (hover: hover) query: on a touch screen the element
   tapped becomes the selected one, which this rule leaves to the selection's
   own outline. */
.plan-canvas:not([data-hover-wall])
  [data-layer]
  > [data-element-id]:not([data-selected]):hover {
  stroke: var(--accent);
  stroke-width: 2.5;
  fill-opacity: 1;
  filter: drop-shadow(0 0 3px var(--accent));
  cursor: pointer;
}

.plan-canvas [data-vertex-index]:hover {
  stroke-width: 3;
}

.layer-toggles,
.display-options {
  margin: var(--space-lg) 0 0;
  padding: 0;
  border: 0;
}

.layer-toggles legend,
.display-options legend {
  padding: 0;
  margin-bottom: var(--space-xs);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.layer-toggle-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* One row per layer: its colour, its name, and the box that shows or hides
   it, at the right-hand end where the eye goes down the column. */
.layer-toggle {
  display: flex;
  flex-direction: row-reverse;
  gap: var(--space-md);
  align-items: center;
  min-height: var(--control-height);
  padding: 0 var(--space-md);
  border-radius: var(--radius);
}

.layer-toggle:hover {
  background: var(--fill);
}

.layer-toggle label {
  display: flex;
  flex: 1 1 auto;
  gap: var(--space-md);
  align-items: center;
  cursor: pointer;
}

.layer-toggle label::before {
  content: "";
  width: 0.9rem;
  height: 0.9rem;
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px var(--line);
}

.layer-toggle label[for="layer-toggle-supports"]::before {
  background: var(--layer-supports);
}

.layer-toggle label[for="layer-toggle-beams"]::before {
  background: var(--layer-beams);
}

.layer-toggle label[for="layer-toggle-boards"]::before {
  background: var(--layer-boards);
}

.layer-toggle label[for="layer-toggle-screws"]::before {
  background: var(--layer-screws);
}

/* The display options: the active layer's opacity and the dimensions switch,
   one row each, laid out like the layer rows above them. */
.display-row {
  display: flex;
  gap: var(--space-md);
  align-items: center;
  min-height: var(--control-height);
  padding: 0 var(--space-md);
}

.display-label {
  flex: 1 1 auto;
  min-width: 0;
}

.display-row input[type="range"] {
  width: 6rem;
  margin: 0;
}

.display-value {
  min-width: 2.75rem;
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* A switch: a track and a knob that slides across it. On is the accent, the
   colour this sheet keeps for whatever is active. */
.switch {
  position: relative;
  flex: none;
  width: 2.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--fill-strong);
  cursor: pointer;
  transition: background-color 0.15s ease;
}

.switch[aria-checked="true"] {
  background: var(--accent);
}

.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--knob);
  box-shadow: var(--shadow-control);
  transition: transform 0.15s ease;
}

.switch[aria-checked="true"] .switch-knob {
  transform: translateX(1rem);
}

.switch:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.canvas-hint {
  align-self: center;
  max-width: 40rem;
  margin: var(--space-md) 0 0;
  color: var(--ink-muted);
  font-size: var(--text-xs);
  text-align: center;
}

/* The editing toolbar's contents, and the export/import row in the left
   column, which is the same shape. */

.plan-toolbar-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  align-items: center;
}

.plan-toolbar label {
  color: var(--ink-muted);
  font-size: var(--text-md);
}

.plan-toolbar select,
.plan-toolbar button {
  padding: var(--space-xs) var(--space-md);
  min-height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-md);
}

.plan-toolbar button {
  font-weight: 500;
  cursor: pointer;
}

.plan-toolbar button:hover:not(:disabled) {
  background: var(--fill-strong);
}

/* In the floating toolbar the buttons are quiet until pointed at, the way a
   window's toolbar is. */
#plan-toolbar button,
#plan-toolbar select {
  background: transparent;
}

#plan-toolbar .toolbar-pill button {
  padding: 0;
}

#plan-toolbar .toolbar-pill .plan-toolbar-group {
  gap: var(--space-2xs);
}

#plan-toolbar button:hover:not(:disabled),
#plan-toolbar select:hover {
  background: var(--fill);
}

.plan-toolbar button:disabled {
  color: var(--ink-muted);
  cursor: default;
  opacity: 0.55;
}

/* Export and import: the same toolbar shape, with a line under it for a file
   that turned out not to be a plan. The line is `hidden` when there is nothing
   to say, so it takes no room at all rather than leaving a gap. */

.plan-toolbar input[type="file"] {
  font: inherit;
  font-size: var(--text-md);
  max-width: 100%;
}

.plan-file-error {
  flex-basis: 100%;
  margin: 0;
  color: var(--danger);
  font-size: var(--text-md);
}

/* The drawing takes keyboard focus, because Delete corrects the selected
   element and a keystroke has to land somewhere. */

.plan-canvas:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* A click focuses the drawing too, and Chrome then rings it in its own dark
   outline. Focus from a pointer is not the keyboard's, so that ring goes; the
   one above, for the keyboard, stays. */
.plan-canvas:focus:not(:focus-visible) {
  outline-style: none;
}

/* Where a dragged element would land. A dashed ghost outside all four layers:
   nothing generated moves until the pointer is released and the override is
   recorded — see js/view/editing.js. */

.plan-canvas [data-editing="preview"] {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-dasharray: 8 6;
  vector-effect: non-scaling-stroke;
}

/* The settings panel: one fieldset per layer, then the plan-wide numbers. The
   fields are a two-column grid on anything wide enough for one, so a label and
   the box it names stay on the same line — see js/view/form.js. */

.settings-form {
  display: grid;
  gap: var(--gap);
}

.settings-group {
  margin: 0;
  padding: 0;
  border: 0;
}

.settings-group legend {
  padding: 0;
  margin-bottom: var(--space-xs);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.setting {
  display: grid;
  gap: var(--space-2xs) var(--space-lg);
  align-items: baseline;
  margin-top: var(--space-sm);
  grid-template-columns: 1fr;
}

@media (min-width: 30rem) {
  .setting {
    grid-template-columns: 1fr 7rem;
  }

  .setting .setting-error,
  .setting .info-text {
    grid-column: 1 / -1;
  }
}

.setting label {
  font-size: var(--text-md);
}

.setting input {
  text-align: right;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-md);
}

/* A field the form could not read. Shown under the input it belongs to and
   pointed at by its aria-describedby, so the message reaches a screen reader
   as part of the field rather than as loose text near it. */

.setting input[aria-invalid="true"] {
  border-color: var(--danger);
}

.setting-error {
  margin: 0;
  color: var(--danger);
  font-size: var(--text-sm);
}

/* The circled i beside a label, and the sentence it opens.
   See js/view/info.js for why this is a button and a paragraph rather than a
   title attribute. The circle is drawn rather than an image so it inherits the
   text colour and scales with the font; the focus ring is spelled out because
   a round control with no visible focus is a control a keyboard user loses.
   The label and its button share a line, which is what .setting-label is for —
   the grid column belongs to the two of them together. */

.setting-label {
  display: flex;
  gap: var(--space-sm);
  align-items: baseline;
}

.info-tip {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface);
  color: var(--ink-muted);
  font: inherit;
  font-size: var(--text-xs);
  line-height: 1;
  cursor: pointer;
}

.info-tip:hover {
  border-color: var(--accent);
  color: var(--ink);
}

.info-tip:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

.info-text {
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

/* The items and prices panel: one row per thing you buy — see
   js/view/items-panel.js, which builds the rows. The table is wider than a
   phone, so it scrolls inside the panel rather than pushing the page sideways. */

.items-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-md);
}

/* The two tables are wider than a phone, so they scroll inside their panel
   rather than pushing the page sideways. A region that scrolls has to be
   reachable by keyboard or its right-hand columns are unreadable without a
   pointer (WCAG 2.1.1), which is what the `tabindex="0"` in index.html is for;
   the focus ring here is what makes that stop visible. */

/* `overflow-x: auto` clips the table and scrolls it, and that part works: at a
   390px viewport `#items-body` is 324px wide over a table 833px wide, and it
   scrolls inside itself. What it does not do is stop the page scrolling with
   it. A `display: table` child's scrollable overflow propagates up past the
   ancestor that clips it, all the way to the viewport, so the page itself
   scrolled 404px sideways on a phone and 26px at 768px — the drawing and the
   panels sliding off the left edge for a table nobody asked to move.
   `overflow: hidden` here does not fix that (it changes what is clipped, not
   what is propagated); `contain: paint` does, by making the region a
   containing block whose overflow ends at its own edge. Page scroll is 0 at
   both widths with it, both tables still scroll inside themselves, and the
   canvas column is the width it was. */

.scroll-region {
  contain: paint;
}

.scroll-region:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

#items-body {
  overflow-x: auto;
}

.items-caption {
  margin-bottom: var(--space-sm);
  color: var(--ink-muted);
  font-size: var(--text-sm);
  text-align: left;
}

.items-table th {
  padding: var(--space-xs) var(--space-sm);
  border-bottom: 1px solid var(--line);
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
}

.items-table td {
  padding: var(--space-xs) var(--space-sm);
  vertical-align: middle;
}

.items-table input,
.items-table select {
  width: 100%;
  min-width: 4.5rem;
  padding: var(--space-xs) var(--space-sm);
  min-height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-md);
}

/* A number reads as a number: right-aligned, so prices line up on the point. */
.items-table input.items-number {
  text-align: right;
}

.items-table input[aria-invalid="true"] {
  border-color: var(--danger);
}

.items-table button {
  padding: var(--space-xs) var(--space-md);
  min-height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

.items-error {
  margin: var(--space-sm) 0 0;
  color: var(--danger);
  font-size: var(--text-sm);
}

/* Present to a screen reader, absent from the screen: the header of the column
   the Remove buttons are in, which needs a name but not a word of chrome. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* The results panel: the sheet you take to the merchant — see
   js/view/results.js, which builds the whole table, caption and header row
   included, because every part of it changes with the plan. */

.results-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-md);
}

#results-body {
  overflow-x: auto;
}

.results-caption {
  margin-bottom: var(--space-sm);
  color: var(--ink-muted);
  font-size: var(--text-sm);
  text-align: left;
}

.results-table th {
  padding: var(--space-xs) var(--space-sm);
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
}

.results-table thead th {
  border-bottom: 1px solid var(--line);
}

.results-table tbody th {
  color: inherit;
  font-size: var(--text-md);
  font-weight: 400;
  white-space: normal;
}

.results-table td {
  padding: var(--space-xs) var(--space-sm);
  vertical-align: baseline;
}

/* A figure reads as a figure: right-aligned, so the euros line up on the
   comma and a column of them can be added up by eye. */
.results-number {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.results-table tfoot th,
.results-table tfoot td {
  padding-top: var(--space-sm);
}

.results-table tfoot tr:first-child th,
.results-table tfoot tr:first-child td {
  border-top: 1px solid var(--line);
}

/* The total is the line the eye goes to first. */
.results-table tfoot tr[data-results-total="total"] th,
.results-table tfoot tr[data-results-total="total"] td {
  padding-top: var(--space-md);
  font-size: var(--text-lg);
  font-weight: 600;
}

.results-empty {
  padding: var(--space-md) var(--space-sm);
  color: var(--ink-muted);
}

/* The warnings panel: what the model has to say about the plan, and every
   correction the pipeline could not apply — see js/view/warnings-panel.js,
   which builds the whole list. Severity is an attribute rather than a class
   because it is what the row *is*, and the stripe down its left edge is the
   only colour: a screen of red boxes is a screen nobody reads. */

.warnings-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-sm);
}

.warnings-row {
  padding: var(--space-md) var(--space-lg);
  border: 0 solid transparent;
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--fill);
  font-size: var(--text-md);
}

.warnings-row[data-severity="error"] {
  border-left-color: var(--danger);
}

.warnings-row[data-severity="warn"] {
  border-left-color: var(--accent);
}

.warnings-row[data-severity="info"] {
  border-left-color: var(--ink-muted);
}

.warnings-message {
  margin: 0;
}

.warnings-select {
  margin-top: var(--space-sm);
  padding: var(--space-xs) var(--space-md);
  min-height: var(--control-height);
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  cursor: pointer;
}

/* The head of the phone's bottom sheet — see js/view/sheet.js. It exists in
   the page at every width and is drawn only in the phone block below: on a
   wide screen both columns are on screen and there is nothing to switch. */

.sheet-head {
  display: none;
}

/* The grabber is the whole width of the sheet and a control's height, so it
   is easy to hit; the bar drawn in the middle of it is only the sign. */
.sheet-grabber {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: var(--control-height);
  padding: 0;
  border: 0;
  background: none;
  cursor: grab;
  touch-action: none;
}

.sheet-grabber-bar {
  width: 2.25rem;
  height: 5px;
  border-radius: 3px;
  background: var(--fill-strong);
}

.sheet-total {
  display: flex;
  gap: var(--space-md);
  align-items: baseline;
  margin: 0;
}

.sheet-total-value {
  flex: 1 1 auto;
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.sheet-total-meta {
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

/* A segmented control: one track, the pressed segment raised out of it. */
.segmented {
  display: flex;
  padding: var(--space-2xs);
  border-radius: var(--radius);
  background: var(--fill);
}

.segmented button {
  flex: 1 1 0;
  min-height: var(--control-height);
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}

.segmented button[aria-pressed="true"] {
  background: var(--surface);
  box-shadow: var(--shadow-control);
}

.sheet-grabber:focus-visible,
.segmented button:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The head of one layer's group in the prices table — see
   js/view/items-panel.js. The toggle is the layer's name and its count, with
   a chevron that points down while the group is open; the + beside it adds
   an item to that layer. */
.items-table .items-group-head th {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  padding: 0;
}

.items-table .items-group-toggle {
  display: flex;
  flex: 1 1 auto;
  gap: var(--space-md);
  align-items: center;
  min-width: 0;
  padding: 0 var(--space-xs);
  background: transparent;
  color: var(--ink);
  font-size: var(--text-md);
  font-weight: 600;
  text-align: left;
}

.items-group-toggle::before {
  content: "";
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-right: 2px solid var(--ink-muted);
  border-bottom: 2px solid var(--ink-muted);
  transform: rotate(45deg);
  transition: transform 0.15s ease;
}

.items-group-toggle[aria-expanded="false"]::before {
  transform: rotate(-45deg);
}

.items-group-count {
  min-width: 1.25rem;
  padding: 0 var(--space-sm);
  border-radius: 999px;
  background: var(--fill);
  color: var(--ink-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.items-table .items-group-add {
  flex: none;
  width: var(--control-height);
  padding: 0;
  border-radius: 50%;
  color: var(--accent);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: 1;
}

.items-table .items-group-toggle:hover,
.items-table .items-group-add:hover {
  background: var(--fill-strong);
}

.items-group-toggle:focus-visible,
.items-group-add:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The total card at the head of the materials column: the figure the column
   leads with, in the largest type on the page, the price per m² under it and
   the two amounts it adds up from. See js/view/summary.js. */
.total-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  margin: 0 0 var(--space-lg);
  padding: var(--space-lg) var(--space-xl);
  border-radius: var(--radius-panel);
  background: var(--fill);
  font-variant-numeric: tabular-nums;
}

.total-card p {
  margin: 0;
}

.total-card-label,
.total-card-parts {
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

.total-card-value {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.1;
}

.total-card-per-m2 {
  font-size: var(--text-lg);
  font-weight: 500;
}

/* The two tables in a column. A column is about twenty rem wide and the
   tables were written for a page: seven and eight columns that scrolled
   sideways inside it. On screen each row becomes a small block of its own
   instead — the header row is still there for a screen reader, but out of
   sight, and every cell carries its column's name (data-label, set by the
   module that builds it) to write over its value. Paper keeps the tables as
   tables: css/print.css, and none of this is screen-wide. */
@media screen {
  .results-table,
  .results-table tbody,
  .results-table tfoot,
  .items-table,
  .items-table tbody {
    display: block;
  }

  .results-caption,
  .items-caption {
    display: block;
  }

  .results-table thead,
  .items-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* A materials line: its name and what it comes to on one line, the rest of
     its figures, each named, in a small run under them. */
  .results-table tbody tr {
    display: flex;
    flex-wrap: wrap;
    gap: 0 var(--space-md);
    align-items: baseline;
    padding: var(--space-sm) 0;
    border-bottom: 1px solid var(--line);
  }

  .results-table tbody th {
    flex: 1 1 0;
    min-width: 0;
    padding: 0;
    font-weight: 500;
  }

  .results-table tbody td {
    padding: 0;
  }

  .results-table td[data-column="lineTotal"] {
    order: -1;
    flex: none;
    margin-left: auto;
    font-weight: 500;
  }

  .results-table tbody th {
    order: -2;
  }

  /* A zero-width item that is the whole width: it breaks the line, so the
     figures start under the name rather than beside it. */
  .results-table tbody tr::after {
    content: "";
    order: -1;
    flex-basis: 100%;
  }

  .results-table tbody td[data-label]:not([data-column="lineTotal"]) {
    color: var(--ink-muted);
    font-size: var(--text-sm);
    text-align: left;
  }

  .results-table tbody td[data-label]:not([data-column="lineTotal"])::before {
    content: attr(data-label) " ";
  }

  .results-table tfoot tr {
    display: flex;
    gap: var(--space-md);
    justify-content: space-between;
    align-items: baseline;
  }

  .results-table tfoot th,
  .results-table tfoot td {
    padding-left: 0;
    padding-right: 0;
  }

  /* An item: its name across the top, then the fields two by two, each with
     its name over it — how it is sold beside its price, the section's two
     sides together — and the button that removes it last. */
  .items-table tr {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-areas:
      "label label"
      "unit price"
      "width height"
      "stock actions";
    gap: var(--space-sm);
    align-items: end;
    padding: var(--space-md) 0;
    border-bottom: 1px solid var(--line);
  }

  .items-table td {
    display: flex;
    flex-direction: column;
    gap: var(--space-2xs);
    min-width: 0;
    padding: 0;
  }

  .items-table td[data-label]::before {
    content: attr(data-label);
    color: var(--ink-muted);
    font-size: var(--text-xs);
  }

  .items-table td[data-column="label"] {
    grid-area: label;
  }

  .items-table td[data-column="unit"] {
    grid-area: unit;
  }

  .items-table td[data-column="unitPrice"] {
    grid-area: price;
  }

  .items-table td[data-column="sectionWidth"] {
    grid-area: width;
  }

  .items-table td[data-column="sectionHeight"] {
    grid-area: height;
  }

  .items-table td[data-column="stockLength"] {
    grid-area: stock;
  }

  .items-table td[data-column="actions"] {
    grid-area: actions;
    align-items: flex-end;
  }

  /* A folded group's rows. The rule above lays every row out as a grid, which
     would otherwise beat the browser's own rule for [hidden]. */
  .items-table tr[hidden] {
    display: none;
  }

  .items-table tr.items-group-head {
    display: block;
    padding: var(--space-md) 0 var(--space-xs);
  }

  .items-table input,
  .items-table select {
    min-width: 0;
  }
}

/* The card beside the selected element — see js/view/selection-card.js,
   which fills it in and sets its left and top against the canvas region. A
   frosted surface like the columns, small enough to sit next to a beam
   without covering the ones either side of it. */
.selection-card {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 15rem;
  padding: var(--space-lg);
  border: 1px solid var(--line);
  border-radius: var(--radius-panel);
  background: var(--panel);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: var(--shadow);
}

.selection-card[hidden],
.selection-card-badge[hidden] {
  display: none;
}

.selection-card-head {
  display: flex;
  gap: var(--space-md);
  align-items: flex-start;
}

.selection-card-text {
  flex: 1 1 auto;
  min-width: 0;
}

.selection-card-title {
  margin: 0;
  font-weight: 600;
}

.selection-card-detail {
  margin: var(--space-2xs) 0 0;
  color: var(--ink-muted);
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
}

.selection-card-close {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--fill);
  color: var(--ink-muted);
  cursor: pointer;
}

/* "Moved by hand": the accent, since a correction is what the user did. */
.selection-card-badge {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--accent);
  font-size: var(--text-sm);
  font-weight: 500;
}

/* The card's actions: buttons two to a row, a switch on a row of its own. */
.selection-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
}

.selection-card-switch {
  display: flex;
  flex: 1 1 100%;
  gap: var(--space-md);
  align-items: center;
  justify-content: space-between;
  font-size: var(--text-sm);
}

.selection-card-actions button:not(.switch) {
  flex: 1 1 40%;
  min-height: var(--control-height);
  padding: 0 var(--space-md);
  border: 0;
  border-radius: var(--radius);
  background: var(--fill);
  color: inherit;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 500;
  cursor: pointer;
}

.selection-card-actions button:not(.switch):hover:not(:disabled),
.selection-card-close:hover {
  background: var(--fill-strong);
}

.selection-card-actions button:disabled {
  color: var(--ink-muted);
  cursor: default;
  opacity: 0.55;
}

.selection-card-actions .selection-card-delete {
  color: var(--danger);
}

.selection-card-actions button:focus-visible,
.selection-card-close:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
}

/* The phone.

   Below the breakpoint the drawing takes the whole screen and one sheet comes
   up over the bottom of it, in place of the two columns. It is still the same
   grid, one track wide: three rows, the drawing spanning all of them, the
   sheet's head in the second and whichever column the sheet is showing in the
   third. The two columns share that one cell and only one of them is laid out
   at a time — the segmented control says which, on .app-main as data-sheet —
   so this is grid placement like the wide layout, not position: fixed.

   The toolbar turns into a column of buttons down the right edge, with the
   layer picker on its own at the top left; the drawing is padded clear of
   both and of the sheet, so nothing is drawn under any of them.

   Screen only: the base sheet applies on paper too, and a sheet of A4 is
   narrower than the breakpoint. */
@media screen and (max-width: 59.9375rem) {
  .app-header,
  .app-footer {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .app-main {
    height: 100vh;
    height: 100dvh;
    padding: 0;
    gap: 0;
    grid-template-rows: minmax(0, 1fr) auto auto;
    overflow: hidden;
  }

  .app-main[data-sheet-open="false"] {
    --sheet-body: 0px;
  }

  #canvas-region {
    position: relative;
    order: 0;
    grid-row: 1 / -1;
    grid-column: 1;
    padding: calc(var(--touch) + 2 * var(--space-xl))
      calc(var(--touch) + 2 * var(--space-xl))
      calc(var(--sheet-head) + var(--sheet-body)) var(--space-xl);
  }

  .plan-canvas {
    flex: 1 1 auto;
    height: 100%;
    min-height: 0;
    aspect-ratio: auto;
  }

  .canvas-hint,
  .panel-toggle {
    display: none;
  }

  #plan-toolbar {
    position: absolute;
    top: var(--space-xl);
    right: var(--space-xl);
    left: var(--space-xl);
    z-index: 1;
    flex-wrap: nowrap;
    align-items: flex-start;
    justify-content: space-between;
    padding: 0;
    border: 0;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    box-shadow: none;
    pointer-events: none;
  }

  #plan-toolbar .toolbar-layer,
  #plan-toolbar .toolbar-pill {
    padding: var(--space-xs);
    border: 1px solid var(--line);
    border-radius: var(--radius-panel);
    background: var(--panel);
    -webkit-backdrop-filter: blur(24px) saturate(1.6);
    backdrop-filter: blur(24px) saturate(1.6);
    box-shadow: var(--shadow);
    pointer-events: auto;
  }

  #plan-toolbar .toolbar-layer {
    flex-wrap: nowrap;
  }

  #plan-toolbar .toolbar-pill,
  #plan-toolbar .toolbar-pill .plan-toolbar-group {
    flex-direction: column;
    flex-wrap: nowrap;
    gap: var(--space-2xs);
  }

  #plan-toolbar .toolbar-pill .plan-toolbar-group + .plan-toolbar-group {
    padding: var(--space-2xs) 0 0;
    border-left: 0;
    border-top: 1px solid var(--line);
  }

  #plan-toolbar .toolbar-pill button,
  #plan-toolbar select {
    min-width: var(--touch);
    min-height: var(--touch);
  }

  .sheet-head {
    display: flex;
    flex-direction: column;
    gap: var(--space-md);
    grid-row: 2;
    grid-column: 1;
    z-index: 1;
    padding: 0 var(--space-xl) var(--space-lg);
    border-top: 1px solid var(--line);
    border-radius: var(--radius-panel) var(--radius-panel) 0 0;
    background: var(--panel);
    -webkit-backdrop-filter: blur(24px) saturate(1.6);
    backdrop-filter: blur(24px) saturate(1.6);
    box-shadow: var(--shadow-sheet);
  }

  #left-panel,
  #right-panel {
    grid-row: 3;
    grid-column: 1;
    z-index: 1;
    height: var(--sheet-body);
    padding: 0 var(--space-md) var(--space-xl);
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }

  #left-panel > .panel {
    border-top: 0;
  }

  /* The sheet's head already leads with the total. */
  #total-card {
    display: none;
  }

  /* One section at a time. */
  .app-main[data-sheet="layers"] #settings-panel,
  .app-main[data-sheet="settings"] #tools-panel,
  .app-main[data-sheet="materials"] #left-panel,
  .app-main:not([data-sheet="materials"]) #right-panel,
  .app-main[data-sheet-open="false"] #left-panel,
  .app-main[data-sheet-open="false"] #right-panel {
    display: none;
  }
}
