/* ===========================================================================
   Create your desk shelf
   Four steps, full screen, two sections.

   LEFT   a dark column carrying the four steps and, under them, the order as
          it is being built. It is the same column on every step, so what you
          have made and what it costs never leaves the screen.
   RIGHT  the step itself, with the step title and the two navigation buttons
          along the top, and the work under them.

   Everything is built on the tokens in styles.css so this reads as part of the
   same site: greige paper, ink, a warm accent, Fraunces and Inter.
   =========================================================================== */

:root {
  --st-panel:  #d5d1c6;   /* light panels inside the work area */
  --st-panel2: #c3bfb2;
  --st-well:   #98948a;   /* the drawing well */
  --st-sheet:  #f7f5ef;   /* the paper a drawing sits on */
  --st-line:   #918c7d;
  --st-line2:  #6f6a5d;
  --st-ink:    #100e0c;
  --st-soft:   #2a2620;
  --st-faint:  #453f36;
  --st-accent: #4c443a;
  /* The only cool colour in the studio, and it has one job: the frame round the
     sector being edited on the plan. Nothing we make comes in it, so it can
     never be mistaken for a part. */
  --st-focus:  #17646f;
  --st-err:    #7a2314;
  --st-warn:   #5d4310;
  --st-ok:     #2c4a2b;
  --st-shadow: 0 18px 44px -20px rgba(25, 22, 19, .65);
  --st-r: 12px;

  /* the dark side column */
  --sd-bg:     #1f1c19;
  --sd-fg:     #d8d6ce;
  --sd-soft:   #9a948a;
  --sd-line:   #3a352f;
  --sd-accent: #b0a89b;

  /* Wood. Dowels are bought, not made, so they never take the finish. */
  --wood:      #cfa871;
  --wood-dark: #a37f4c;
  --wood-line: rgba(72, 48, 20, .68);
  --wood-hi:   rgba(255, 246, 228, .6);
}

[hidden] { display: none !important; }
html, body.flow-page { height: 100%; }
body.flow-page {
  margin: 0;
  overflow: hidden;
  background: var(--bg);
  color: var(--st-ink);
  font-family: 'Inter', sans-serif;
  -webkit-font-smoothing: antialiased;
}

.flow {
  height: 100%;
  display: grid;
  grid-template-columns: 340px minmax(0, 1fr);
}

/* ---- the dark side ------------------------------------------------------ */

.side {
  background: var(--sd-bg);
  color: var(--sd-fg);
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 26px 24px 22px;
}
.side-brand { display: flex; align-items: center; gap: 11px; text-decoration: none; color: inherit; }
.side-brand .bar { width: 4px; height: 30px; background: var(--sd-accent); flex: 0 0 auto; }
.side-brand .txt { font-family: 'Fraunces', serif; font-weight: 600; font-size: 23px; letter-spacing: -.025em; }
.side-brand .txt .d { color: var(--sd-accent); }
.side-task {
  font-size: 10.5px; letter-spacing: .19em; text-transform: uppercase;
  color: var(--sd-accent); font-weight: 700; margin: 10px 0 24px;
}

.fsteps { list-style: none; margin: 0 0 22px; padding: 0; position: relative; }
.fsteps::before { content: ""; position: absolute; left: 15px; top: 18px; bottom: 20px; width: 2px; background: var(--sd-line); }
.fstep { position: relative; padding: 0 0 18px 44px; cursor: pointer; }
.fstep:last-child { padding-bottom: 0; }
.fstep .dot {
  position: absolute; left: 0; top: 0; width: 32px; height: 32px; border-radius: 50%;
  background: var(--sd-bg); border: 2px solid var(--sd-line); z-index: 1;
  display: flex; align-items: center; justify-content: center;
  font-family: 'Fraunces', serif; font-size: 14px; color: var(--sd-soft);
  transition: background .18s, border-color .18s, color .18s;
}
.fstep .tx { display: block; padding-top: 4px; }
.fstep .tx b { display: block; font-size: 14px; font-weight: 600; color: var(--sd-soft); transition: color .18s; }
.fstep .tx em { display: block; font-style: normal; font-size: 12px; color: var(--sd-soft); opacity: .72; }
.fstep:hover .tx b { color: var(--sd-fg); }
.fstep.done .dot { background: var(--sd-accent); border-color: var(--sd-accent); color: var(--sd-bg); }
.fstep.done .tx b { color: var(--sd-fg); }
.fstep.on .dot { background: var(--sd-fg); border-color: var(--sd-fg); color: var(--sd-bg); }
.fstep.on .tx b { color: #fff; font-weight: 700; }
.fstep.on .tx em { opacity: 1; color: var(--sd-accent); }
.fstep.on::after { content: ""; position: absolute; left: -24px; top: 2px; bottom: 18px; width: 3px; background: var(--sd-accent); }

/* You are building. It has to hold a long order without pushing the total off
   the screen, so it is the only thing in the column that grows, and it scrolls
   inside itself. */
/* The order can run long, so this is the only thing in the column that grows.
   A dark panel with a thin scrollbar gives no clue that there is more below it,
   so it says so out loud: a count in the heading and a fading edge at the foot
   that appears only when there is something under it. */
.building { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; border-top: 1px solid var(--sd-line); padding-top: 16px; position: relative; }
.building-k { display: flex; align-items: baseline; gap: 8px; }
.building-k .count { margin-left: auto; font-size: 10px; letter-spacing: .1em; color: var(--sd-soft); font-weight: 600; }
.building.more::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 46px; pointer-events: none;
  background: linear-gradient(rgba(31, 28, 25, 0), var(--sd-bg) 78%);
}
.building.more .scroll-cue { display: flex; }
.scroll-cue {
  display: none; position: absolute; left: 50%; transform: translateX(-50%); bottom: 4px; z-index: 2;
  align-items: center; gap: 6px; padding: 4px 12px; border-radius: 100px;
  background: rgba(216, 214, 206, .14); border: 1px solid var(--sd-line);
  font: 600 10px 'Inter', sans-serif; letter-spacing: .12em; text-transform: uppercase; color: var(--sd-accent);
  cursor: pointer;
}
.scroll-cue:hover { background: rgba(216, 214, 206, .24); color: var(--sd-fg); }
.scroll-cue svg { width: 11px; height: 11px; fill: none; stroke: currentColor; stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.building-k { font-size: 10.5px; letter-spacing: .18em; text-transform: uppercase; color: var(--sd-accent); font-weight: 700; margin-bottom: 10px; flex: 0 0 auto; }
.building-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding-right: 4px; margin-right: -4px; }
.building-scroll::-webkit-scrollbar { width: 8px; }
.building-scroll::-webkit-scrollbar-track { background: rgba(216, 214, 206, .07); border-radius: 8px; }
.building-scroll::-webkit-scrollbar-thumb { background: var(--sd-accent); border-radius: 8px; }

.bgrp {
  font-size: 9.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--sd-bg);
  background: var(--sd-accent); font-weight: 700;
  margin: 14px 0 0; padding: 4px 8px; border-radius: 4px;
}
.bgrp:first-child { margin-top: 0; }
.bline { display: grid; grid-template-columns: auto minmax(0, 1fr) auto auto; gap: 8px; align-items: baseline; padding: 6px 0; border-bottom: 1px solid var(--sd-line); font-size: 12.5px; color: var(--sd-soft); }
.bline > .n:last-child { grid-column: 3 / span 2; }
.bline .q { font-variant-numeric: tabular-nums; font-weight: 700; color: var(--sd-accent); }
.bline .d { min-width: 0; color: var(--sd-fg); }
.bline .d em { display: block; font-style: normal; font-size: 11px; color: var(--sd-soft); line-height: 1.45; }
/* What the customer picked, under the name of the kit: the model and the leg
   height. It is their own choice read back to them, not the packing list under
   it, so it is set brighter than a note and closer to the name it belongs to. */
.bline .d em.spec { color: var(--sd-fg); font-size: 11.5px; margin-top: 1px; }
.bline .n { font-variant-numeric: tabular-nums; color: var(--sd-fg); font-weight: 500; white-space: nowrap; }
.bline.free .n { color: var(--sd-accent); }
.bline .rm { background: none; border: none; color: var(--sd-soft); cursor: pointer; font-size: 15px; line-height: 1; padding: 0 0 0 4px; }
.bline .rm:hover { color: #e59a86; }
.bempty { font-size: 12.5px; color: var(--sd-soft); line-height: 1.6; }

/* the shipping block, above the price, on every step */
.side-sum { flex: 0 0 auto; margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--sd-line); }
.side-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 12.5px; color: var(--sd-soft); }
.side-row + .side-row { margin-top: 5px; }
.side-row b { font-weight: 600; color: var(--sd-fg); }
.side-row b.is-free { color: #a8c8a0; letter-spacing: .06em; font-size: 11.5px; }
.side-ship-note { margin: 7px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--sd-accent); }

.side-total { flex: 0 0 auto; display: flex; align-items: baseline; justify-content: space-between; margin-top: 12px; padding-top: 12px; border-top: 2px solid var(--sd-line); }
.side-total span { font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--sd-soft); font-weight: 700; }
.side-total b { font-family: 'Fraunces', serif; font-size: 30px; font-weight: 500; }
.side-flag { font-size: 12px; margin: 6px 0 0; display: flex; align-items: center; gap: 7px; }
.side-flag:empty { display: none; }
.side-flag::before { content: ""; width: 8px; height: 8px; border-radius: 50%; background: currentColor; flex: 0 0 auto; }
.side-flag.e { color: #e59a86; }
.side-flag.w { color: #e0c184; }
.side-flag.k { color: #a8c8a0; }
.side-foot { font-size: 10.5px; color: var(--sd-soft); line-height: 1.6; margin: 14px 0 0; }

/* ---- the work side ------------------------------------------------------ */

.work { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: var(--bg); }
.work-head {
  flex: 0 0 auto; display: flex; align-items: flex-end; gap: 20px;
  padding: 24px 34px 18px; border-bottom: 1px solid var(--st-line);
}
.work-title { min-width: 0; }
.work-head .k { display: block; font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--st-accent); font-weight: 700; margin-bottom: 6px; }
.work-head h1 { font-family: 'Fraunces', serif; font-weight: 400; font-size: 32px; letter-spacing: -.02em; line-height: 1.1; margin: 0; }
.work-nav { margin-left: auto; display: flex; gap: 10px; flex: 0 0 auto; }

.btn {
  background: var(--st-ink); color: var(--bg); padding: 12px 26px; border-radius: 100px;
  font: 600 14px 'Inter', sans-serif; border: 1.5px solid var(--st-ink); cursor: pointer; white-space: nowrap;
  transition: background .16s, color .16s, opacity .16s;
}
.btn:hover:not(:disabled) { background: #000; }
.btn.ghost { background: transparent; color: var(--st-ink); }
.btn.ghost:hover:not(:disabled) { background: var(--st-ink); color: var(--bg); }
.btn.small { padding: 8px 16px; font-size: 12.5px; }
.btn:disabled { opacity: .3; cursor: not-allowed; }

.work-body { flex: 1 1 auto; min-height: 0; overflow: hidden; display: flex; }
.pane { display: none; flex: 1 1 auto; min-width: 0; min-height: 0; overflow-y: auto; padding: 26px 34px 40px; }
.pane.on { display: block; }
.pane.pane-bench.on { display: flex; overflow: hidden; padding: 0; }
.pane-lead { font-size: 14.5px; color: var(--st-soft); max-width: 74ch; margin: 0 0 22px; line-height: 1.6; }

/* ---- cards, step 1 and step 4 ------------------------------------------- */

/* `align-items: start` let each column take its own height, so the desk column
   grew past the shelf column as soon as the L fields opened. They are a pair of
   panels side by side and they read as one band, so they stretch to match. */
.cols2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; align-items: stretch; }
.cart-cols { grid-template-columns: 1fr 1.05fr; }
.blk { background: var(--st-panel); border: 1.5px solid var(--st-line); border-radius: var(--st-r); padding: 22px 24px; }
.blk + .blk, .blk-wide { margin-top: 18px; }
.blk h3 { font-family: 'Fraunces', serif; font-weight: 400; font-size: 21px; letter-spacing: -.02em; margin: 0 0 3px; }
.blk-note { font-size: 13px; color: var(--st-soft); margin: 0 0 18px; line-height: 1.55; }
.fld-h { font: 700 10.5px 'Inter', sans-serif; letter-spacing: .16em; text-transform: uppercase; color: var(--st-accent); margin: 20px 0 10px; }

/* The base kit reads as one line: six counted things across the card, with the
   heading beside them rather than above. It used to wrap to three rows of
   sentences, which made a fixed and unchangeable kit look like a decision. */
.kit { display: flex; align-items: center; gap: 26px; flex-wrap: wrap; }
.kit-head { flex: 0 0 auto; max-width: 260px; }
.kit-head h3 { margin-bottom: 2px; }
.kit-head .blk-note { margin: 0; }
.kitlist {
  list-style: none; margin: 0; padding: 0;
  flex: 1 1 380px; min-width: 0;
  display: flex; gap: 0; align-items: stretch;
}
.kitlist li {
  flex: 1 1 0; min-width: 0; padding: 2px 12px;
  border-left: 1px solid var(--st-line); text-align: center;
}
.kitlist li:first-child { border-left: none; padding-left: 0; }
.kitlist li:last-child { padding-right: 0; }
.kitlist b { display: block; font-family: 'Fraunces', serif; font-size: 24px; font-weight: 400; line-height: 1.1; }
.kitlist span { display: block; font-size: 11.5px; color: var(--st-soft); line-height: 1.35; margin-top: 3px; }

/* reopening a saved design, on step 1 where it belongs */
/* the two columns of step 1 must start level. `.blk + .blk` adds a top margin
   to any block following another, which inside the grid pushed the right column
   down by 18px on its own. Inside `.cols2` they are columns, not a stack. */
.cols2 > .blk + .blk { margin-top: 0; }
.blk-reopen { margin-top: 0; margin-bottom: 26px; }
#load-msg:empty, #run-len-note:empty { display: none; }
.devnote { font-size: 11.5px; color: var(--st-soft); line-height: 1.6; margin: 12px 0 0; }

.ship-service { margin: 2px 0 0; font-size: 13.5px; color: var(--st-ink); font-weight: 500; }
.save-row { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--st-line); }
.save-row .fld-note { display: block; margin-top: 8px; }

.reopen { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.reopen .num { flex: 0 0 200px; }
.reopen .num input { font-family: 'Fraunces', serif; font-size: 19px; letter-spacing: .1em; text-transform: uppercase; }
#load-msg.bad { color: var(--st-err); }

/* ---- step 2, the bench --------------------------------------------------
   The two docks are gone. The catalog is a strip across the top, the drawing
   takes the whole width under it, and the piece you picked up opens over the
   right hand edge of the drawing rather than in a column of its own, so the
   sheet keeps the room when nothing is selected. */

.bench { flex: 1 1 auto; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
.bench-bar { flex: 0 0 auto; background: var(--st-panel); border-bottom: 1px solid var(--st-line); }
.parts-strip { display: flex; gap: 9px; overflow-x: auto; padding: 12px 22px; }
.parts-strip::-webkit-scrollbar { height: 7px; }
.parts-strip::-webkit-scrollbar-thumb { background: var(--st-line); border-radius: 7px; }

.bench-tools {
  flex: 0 0 auto; display: flex; align-items: center; gap: 12px;
  padding: 9px 22px; background: var(--st-panel); border-bottom: 1px solid var(--st-line2);
}
.stage-tools { margin-left: auto; display: flex; gap: 4px; }
.tool {
  width: 33px; height: 33px; border-radius: 8px; border: 1.5px solid transparent; background: none; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--st-soft); transition: background .16s, color .16s, border-color .16s;
}
.tool svg { width: 17px; height: 17px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.tool:hover:not(:disabled) { background: #fbfaf5; border-color: var(--st-line); color: var(--st-ink); }
.tool:disabled { opacity: .32; cursor: not-allowed; }
.tool-warn:hover:not(:disabled) { color: var(--st-err); }
.zoomer { display: flex; align-items: center; gap: 2px; }
.zoom-pct {
  min-width: 48px; text-align: center; background: none; border: 1.5px solid transparent; cursor: pointer;
  font: 600 11.5px 'Inter', sans-serif; font-variant-numeric: tabular-nums;
  color: var(--st-soft); padding: 8px 4px; border-radius: 7px;
}
.zoom-pct:hover:not(:disabled) { border-color: var(--st-line); background: #fbfaf5; color: var(--st-ink); }
.zoom-pct:disabled { opacity: .5; cursor: default; }

.run-tabs { display: flex; gap: 4px; }
.run-tab { font: 500 12px 'Inter', sans-serif; padding: 7px 13px; border-radius: 7px; cursor: pointer;
  color: var(--st-soft); background: #fbfaf5; border: 1.5px solid var(--st-line); }
.run-tab:hover { border-color: var(--st-ink); color: var(--st-ink); }
.run-tab.on { background: var(--st-ink); border-color: var(--st-ink); color: var(--bg); font-weight: 600; }
.run-tab-wrap { display: inline-flex; align-items: stretch; }
.run-tab-wrap .run-tab { border-right-width: 0; border-radius: 7px 0 0 7px; }
.run-tab-x { font: 400 15px 'Inter', sans-serif; padding: 0 9px; cursor: pointer;
  border: 1.5px solid var(--st-line); border-radius: 0 7px 7px 0; background: #fbfaf5; color: var(--st-faint); }
.run-tab-x:hover { color: var(--st-err); border-color: var(--st-err); background: #fff; }
.run-tab-wrap.on .run-tab-x { background: var(--st-ink); border-color: var(--st-ink); color: rgba(226, 223, 214, .75); }

.stage { flex: 1 1 auto; min-height: 0; min-width: 0; position: relative; background: var(--st-well); display: flex; flex-direction: column; }

/* The dock is always there, and the paper always leaves room for it.

   It used to appear on selection and push the drawing over, which read well in
   a screenshot and badly under a pointer: pressing on a leg resized the sheet
   the leg was on, so the piece moved out from under the finger that had just
   grabbed it, and a drag that followed measured against a scale that had
   changed mid gesture. Reserving the space costs about 310px of drawing width
   and nothing ever moves. The drawing still has far more room than it had when
   there were two docks. */
.piece-dock {
  position: absolute; top: 0; right: 0; bottom: 0; width: 372px; z-index: 6;
  background: var(--st-panel); border-left: 2px solid var(--st-line2);
  display: flex; flex-direction: column;
}
.piece-dock-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 8px; padding: 14px 22px;
  border-bottom: 1px solid var(--st-line);
  font: 700 10.5px 'Inter', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--st-accent);
}
.piece-x { margin-left: auto; background: none; border: none; cursor: pointer; font-size: 21px; line-height: 1; color: var(--st-faint); }
.piece-x[hidden] { display: none; }
.piece-x:hover { color: var(--st-ink); }
.piece-dock-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
/* The pane had no padding of its own, so every control in it started against
   the border it shares with the drawings. The dock is 20px wider to pay for the
   gutters rather than taking them out of the controls, and the paper gives up
   that width. */
.pane-piece { padding: 18px 22px 26px; }
.stage .stage-paper { padding-right: 384px; }

/* ---- step 3, the add-ons ------------------------------------------------ */

.addon-layout { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 22px; height: calc(100% - 4px); align-items: start; }
.addon-side { display: flex; flex-direction: column; min-height: 0; max-height: 100%; }
.cat-pills { display: flex; flex-wrap: wrap; gap: 7px; flex: 0 0 auto; margin-bottom: 14px; }
.cat-pill { padding: 8px 15px; border-radius: 100px; border: 1.5px solid var(--st-line); background: var(--st-panel);
  font: 500 13px 'Inter', sans-serif; cursor: pointer; color: var(--st-soft); }
.cat-pill:hover { border-color: var(--st-ink); color: var(--st-ink); }
.cat-pill.on { background: var(--st-ink); border-color: var(--st-ink); color: var(--bg); font-weight: 600; }
.cat-pill i { font-style: normal; opacity: .6; margin-left: 5px; font-size: 11.5px; }

.addon-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding-right: 6px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); gap: 10px; align-content: start; }
.addon-scroll::-webkit-scrollbar { width: 9px; }
.addon-scroll::-webkit-scrollbar-thumb { background: var(--st-line); border-radius: 9px; }

.addon {
  display: flex; gap: 11px; align-items: center; text-align: left; cursor: pointer;
  background: var(--st-panel); border: 1.5px solid var(--st-line); border-radius: 11px; padding: 11px;
  min-height: 72px;
  transition: border-color .16s, background .16s;
}
.addon:hover { border-color: var(--st-line2); }
.addon.picked { border-color: var(--st-ink); background: #e4e1d8; }
.addon.sel { border-color: var(--st-ink); box-shadow: 0 0 0 2px rgba(31, 28, 25, .18); }
.addon-art { width: 52px; height: 40px; flex: 0 0 auto; display: block; }
.addon-art svg { width: 100%; height: 100%; display: block; }
.addon-mid { min-width: 0; flex: 1 1 auto; }
.addon-name { display: block; font-size: 13px; font-weight: 600; line-height: 1.28; }
.addon-price { display: block; font-size: 12.5px; color: var(--st-soft); }
.addon-qty { display: flex; align-items: center; gap: 3px; flex: 0 0 auto; }
.addon-add { border: 1.5px solid var(--st-ink); border-radius: 100px; padding: 6px 13px; background: none; cursor: pointer;
  font: 600 12.5px 'Inter', sans-serif; color: var(--st-ink); }
.addon-add:hover { background: var(--st-ink); color: var(--bg); }
.addon-n { min-width: 17px; text-align: center; font-variant-numeric: tabular-nums; font-size: 13px; font-weight: 600; }

.addon-detail { background: var(--st-panel); border: 1.5px solid var(--st-line); border-radius: var(--st-r); padding: 20px; position: sticky; top: 0; }
/* The add-on picture. A square, always, whatever the file happens to be.

   Photographs come off a phone at whatever shape the phone was held, and the
   drawings are wider than they are tall. Letting each one set its own height
   made the pane resize every time somebody clicked a different add-on. The
   square is `aspect-ratio`, so it holds at any pane width.

   A photograph covers it: filled edge to edge, cropped if it has to be, never
   stretched. A drawing is contained inside it, because a drawing cropped is a
   part with its end cut off. tools/prep-addon-photo.py outputs 1200 by 1200, so
   a photograph prepared that way is never cropped here at all. */
.addon-photo {
  background: var(--st-sheet); border: 1px solid var(--st-line);
  border-radius: 10px; margin-bottom: 16px; position: relative;
  aspect-ratio: 1 / 1; width: 100%; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
}
.addon-photo img { display: block; width: 100%; height: 100%; object-fit: cover; }
.addon-photo svg { display: block; width: 100%; height: 100%; object-fit: contain; padding: 18px; }
/* The reference note, under the photograph of a family that shares one picture.

   The frame below it already carries `margin-bottom: 16px`, so this takes that
   space back with a negative top margin and keeps its own gap to the name. It
   reads as a caption on the picture rather than as a second heading: small, in
   the faint grey, and well away from the not-included line, which sits at the
   very bottom under the Add button. */
.addon-detail .addon-photo-note {
  font-size: 11.5px; line-height: 1.5; color: var(--st-faint);
  margin: -8px 0 14px;
}

.addon-detail h3 { font-family: 'Fraunces', serif; font-weight: 400; font-size: 23px; letter-spacing: -.02em; margin: 0 0 4px; }
.addon-detail .pr { font-family: 'Fraunces', serif; font-size: 26px; font-weight: 500; display: block; margin-bottom: 10px; }
.addon-detail p { font-size: 13.5px; color: var(--st-soft); line-height: 1.6; margin: 0 0 14px; }
.addon-detail dl { display: grid; grid-template-columns: auto 1fr; gap: 5px 14px; font-size: 12.5px; margin: 0 0 18px; }
.addon-detail dt { color: var(--st-faint); }
.addon-detail dd { margin: 0; color: var(--st-ink); font-weight: 500; }
.addon-detail .none { text-align: center; padding: 40px 10px; color: var(--st-faint); font-size: 13.5px; line-height: 1.65; }
/* What is in the photographs but not in the box. One line under the card,
   quieter than the copy that sells the part above it. */
.addon-detail .addon-gear-note { font-size: 12px; color: var(--st-faint); line-height: 1.55; margin: 14px 0 0; text-align: center; }

/* ---- step 4, the cart --------------------------------------------------- */

.cart-lines { border-top: 1px solid var(--st-line); }
.cline { display: grid; grid-template-columns: auto 1fr auto; gap: 10px; align-items: baseline; padding: 11px 0; border-bottom: 1px solid var(--st-line); font-size: 13.5px; }
.cline .q { color: var(--st-faint); font-weight: 700; font-variant-numeric: tabular-nums; }
.cline .d em { display: block; font-style: normal; font-size: 12px; color: var(--st-faint); line-height: 1.5; margin-top: 2px; }
.cline .d em.spec { color: var(--st-soft); font-size: 12.5px; margin-top: 1px; }
.cline .n { font-variant-numeric: tabular-nums; font-weight: 600; white-space: nowrap; }
/* The group headings sit on the same rhythm as the rows under them, and they
   read as headings rather than as another line of the order. */
.cgrp {
  display: flex; align-items: center; gap: 10px;
  font: 700 10.5px 'Inter', sans-serif; letter-spacing: .16em; text-transform: uppercase;
  color: var(--st-ink); background: var(--st-panel2);
  margin: 0; padding: 8px 10px;
  border-top: 1px solid var(--st-line); border-bottom: 1px solid var(--st-line);
}
.cgrp::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--st-line); }
.cgrp + .cline { border-top: none; }
.cline { padding: 11px 10px; }
.cart-sum { margin: 18px 0 0; padding-top: 14px; border-top: 2px solid var(--st-line2); }
.cart-sum > div { display: flex; justify-content: space-between; align-items: baseline; padding: 5px 0; font-size: 14px; color: var(--st-soft); }
.cart-sum dt, .cart-sum dd { margin: 0; }
.cart-sum dd { font-variant-numeric: tabular-nums; color: var(--st-ink); font-weight: 500; }
.cart-sum dd.soft { color: var(--st-faint); font-weight: 400; font-size: 13px; }
.cart-sum .grand { margin-top: 8px; padding-top: 11px; border-top: 1px solid var(--st-line); }
.cart-sum .grand dt { font-weight: 600; color: var(--st-ink); }
.cart-sum .grand dd { font-family: 'Fraunces', serif; font-size: 27px; font-weight: 500; }

.ofld { display: block; margin-bottom: 13px; font: 700 10.5px 'Inter', sans-serif; letter-spacing: .13em; text-transform: uppercase; color: var(--st-faint); }
.ofld input, .ofld textarea {
  display: block; width: 100%; margin-top: 6px; background: var(--st-sheet);
  border: 1.5px solid var(--st-line); border-radius: 9px; padding: 11px 13px;
  font: 400 14.5px 'Inter', sans-serif; color: var(--st-ink); letter-spacing: 0; text-transform: none;
}
.ofld input:focus, .ofld textarea:focus { outline: none; border-color: var(--st-ink); }
.ofld input.bad { border-color: var(--st-err); background: #fbf0ec; }
.fld-err { display: block; margin-top: 5px; font: 500 11.5px 'Inter', sans-serif; letter-spacing: 0; text-transform: none; color: var(--st-err); font-style: normal; }
/* The section label above these sits at the same size and weight as the field
   labels inside them, so without this the block opens on two lines of small
   caps with nothing between them and reads as one list rather than a heading
   over a form. */
.billing-fields { display: block; margin-top: 9px; }
.billing-fields .fld-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.stripe-block { margin-bottom: 15px; }
.stripe-k { display: block; font: 700 10.5px 'Inter', sans-serif; letter-spacing: .13em; text-transform: uppercase; color: var(--st-faint); margin-bottom: 6px; }
.stripe-mount { min-height: 54px; }
.stub { background: var(--st-sheet); border: 1.5px dashed var(--st-line); border-radius: 10px; padding: 14px 16px; }
.stub b { display: block; font-size: 13px; font-weight: 700; margin-bottom: 4px; }
.stub p { font-size: 12.5px; color: var(--st-soft); line-height: 1.6; margin: 0; }
.stub code { background: rgba(25, 22, 19, .08); padding: 1px 5px; border-radius: 4px; font-size: 11.5px; }
.ck { display: flex; align-items: center; gap: 9px; font-size: 13.5px; color: var(--st-soft); margin: 0 0 15px; cursor: pointer; }
.ck input { width: 17px; height: 17px; accent-color: var(--st-ink); }
.btn-pay { width: 100%; margin-top: 6px; padding: 15px; font-size: 15px; }
.pay-note { font-size: 12px; color: var(--st-faint); line-height: 1.6; margin: 10px 0 0; }
.devtools { margin-top: 20px; border-top: 1px solid var(--st-line); padding-top: 14px; }
.devtools summary { cursor: pointer; font: 600 12.5px 'Inter', sans-serif; color: var(--st-soft); }
.devtools p { font-size: 12.5px; color: var(--st-soft); line-height: 1.6; margin: 10px 0; }
.devrow { display: flex; flex-wrap: wrap; gap: 8px; }
.load-error { color: var(--st-err); font-size: 12.5px; margin-top: 8px; }

.dowel-note { margin: 18px 0 0; padding: 13px 14px 14px; background: var(--st-panel2); border: 1.5px solid var(--st-line); border-radius: var(--st-r); }
.dowel-note:empty { display: none; }
.dowel-note h4 {
  display: flex; align-items: center; gap: 9px;
  font: 700 11.5px 'Inter', sans-serif; letter-spacing: .06em; color: var(--st-ink); margin: 0 0 12px;
}
.dw-tag {
  font: 700 9.5px 'Inter', sans-serif; letter-spacing: .14em; text-transform: uppercase;
  background: var(--st-ink); color: var(--bg); padding: 3px 8px; border-radius: 100px;
}
.dw-buy { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 14px; }
.dw-buy > div { border-left: 2px solid var(--st-line); padding-left: 11px; }
.dw-buy b { display: block; font-family: 'Fraunces', serif; font-size: 17px; font-weight: 500; line-height: 1.2; }
.dw-buy span { display: block; font-size: 11.5px; color: var(--st-soft); line-height: 1.4; margin-top: 2px; }
.dw-k { font: 700 9.5px 'Inter', sans-serif; letter-spacing: .15em; text-transform: uppercase; color: var(--st-accent); margin: 0 0 7px; }
.cutlist {
  list-style: none; margin: 0 0 12px; padding: 0;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 6px;
}
.cutlist li {
  display: flex; align-items: baseline; gap: 6px;
  background: var(--st-sheet); border: 1px solid var(--st-line); border-radius: 7px; padding: 7px 10px;
  font-variant-numeric: tabular-nums;
}
.cutlist b { font-size: 12.5px; font-weight: 700; color: var(--st-accent); }
.cutlist span { font-size: 13px; font-weight: 600; color: var(--st-ink); }
.dowel-note p { font-size: 12px; line-height: 1.55; color: var(--st-soft); margin: 0; }
.cut-total { font-size: 11.5px; color: var(--st-faint); }

/* ---- fields and chips --------------------------------------------------- */

.fld { display: block; margin-bottom: 16px; }
.fld-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.fld-label { display: block; font: 700 10.5px 'Inter', sans-serif; letter-spacing: .13em; text-transform: uppercase; color: var(--st-faint); margin-bottom: 7px; }
.fld-note { display: block; font-size: 12.5px; color: var(--st-soft); line-height: 1.55; margin-top: 7px; }
.num { display: flex; align-items: center; gap: 8px; background: var(--st-sheet); border: 1.5px solid var(--st-line); border-radius: 9px; padding: 10px 13px; }
.num input { flex: 1 1 auto; min-width: 0; border: none; background: none; font: 500 16px 'Inter', sans-serif; color: var(--st-ink); }
.num input:focus { outline: none; }
.num input::-webkit-outer-spin-button, .num input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.num input[type=number] { -moz-appearance: textfield; }
.unit-tag { font-style: normal; font-size: 12px; color: var(--st-faint); flex: 0 0 auto; }

/* Every photograph on this page is served WebP first with the JPEG behind it, which
   needs a picture around the img. A picture is only a wrapper for picking the file,
   so it is taken out of the layout and the img is the thing that lays out. The add-on
   frames build theirs in js/studio.js.

   The source has to be hidden alongside it. display:contents hands ALL of the
   picture's children to the parent, not just the img, and a bare <source> computes to
   inline in the browser rather than to none, so it comes back as a second, zero-height
   item in whatever grid or flex row the picture sat in. That is what broke the two
   column story rows on the home page. Hiding it is what makes display:contents mean
   "the img is the item". It does not affect which file is chosen: the picture element
   picks its source from the DOM, not from the box tree. */
picture { display: contents; }
source { display: none; }

.chips { display: flex; flex-wrap: wrap; gap: 7px; }
/* Two fields in step one are empty in the HTML and filled by render() a moment later:
   the leg heights depend on the shelf size, and the dowel row count is computed. Empty
   they measure zero, so when the script filled them everything under them jumped down.
   Holding one row of chips and one line of note keeps the step still. 36px is the
   measured height of a .chip row: 13.5px text, 9px of padding and 1.5px of border top
   and bottom. The note is one line of its own 1.55 line-height. Both are floors, so a
   row that wraps or a note that runs to two lines still grows past them. */
#chips-leg { min-height: 36px; }
#size-note { min-height: 1.55em; }
.chip { display: inline-flex; align-items: center; gap: 7px; padding: 9px 15px; border-radius: 100px; cursor: pointer;
  border: 1.5px solid var(--st-line); background: var(--st-sheet); font: 500 13.5px 'Inter', sans-serif; color: var(--st-ink); }
.chip:hover:not(:disabled) { border-color: var(--st-ink); }
.chip.on { background: var(--st-ink); border-color: var(--st-ink); color: var(--bg); font-weight: 600; }
.chip:disabled { opacity: .38; cursor: not-allowed; }
.chip .unit-tag { padding: 0; font-size: 10.5px; font-weight: 400; color: var(--st-faint); }
.chip.on .unit-tag { color: rgba(226, 223, 214, .8); }
.chips-view .chip { padding: 7px 14px; font-size: 12px; }
.sw { width: 13px; height: 13px; border-radius: 50%; border: 1px solid rgba(25, 22, 19, .4); }
.sw-white { background: #f4f2ec; }
.sw-black { background: #1b1917; }

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

.side-open { display: none; }
@media (max-width: 1180px) {
  .flow { grid-template-columns: 288px minmax(0, 1fr); }
  .addon-layout { grid-template-columns: minmax(0, 1fr) 300px; }
  .cols2 { grid-template-columns: 1fr; }
}
@media (max-width: 900px) {
  .flow { grid-template-columns: minmax(0, 1fr); }
  .side { position: fixed; inset: 0 auto 0 0; width: 300px; z-index: 40; transform: translateX(-100%); transition: transform .24s; }
  .side.open { transform: none; }
  .side-open { display: inline-flex; align-items: center; justify-content: center; width: 38px; height: 38px; border-radius: 9px;
    border: 1.5px solid var(--st-line); background: var(--st-sheet); cursor: pointer; margin-right: 4px; }
  .side-open .bars, .side-open .bars::before, .side-open .bars::after { display: block; width: 16px; height: 2px; background: var(--st-ink); position: relative; }
  .side-open .bars::before { content: ""; position: absolute; top: -5px; }
  .side-open .bars::after { content: ""; position: absolute; top: 5px; }
  .work-head { flex-wrap: wrap; }
  /* Stacked, the add-on list stops being its own scroller and the pane scrolls
     instead. Left as it is on a desktop, the list is `flex: 1 1 auto` inside a
     column measured against `height: calc(100% - 4px)`, and once the grid goes
     to one column that height resolves to nothing: the list collapsed to zero
     and the detail card, taller and later in the DOM, painted straight over the
     buttons. They were visible and every tap landed on the card behind them,
     which made this step unusable on a phone. */
  .addon-layout { grid-template-columns: minmax(0, 1fr); height: auto; }
  .addon-side { max-height: none; }
  .addon-scroll { flex: 0 0 auto; overflow-y: visible; padding-right: 0; }
  .addon-detail { position: static; }
  .piece-dock { width: 320px; }
  .stage .stage-paper { padding-right: 332px; }
  .pane-piece { padding: 16px 16px 22px; }
}
/* ---- part cards --------------------------------------------------------- */

.pcard {
  flex: 0 0 auto; width: 118px;
  display: flex; flex-direction: column; align-items: stretch; gap: 6px;
  padding: 9px 9px 10px;
  background: var(--st-sheet);
  border: 1px solid var(--st-line);
  border-radius: var(--st-r);
  text-align: left;
  cursor: grab;
  transition: border-color .16s, box-shadow .16s, transform .16s, background .16s;
  touch-action: none;
}
.pcard:hover:not(:disabled) {
  border-color: var(--st-accent);
  box-shadow: 0 6px 16px -10px rgba(31, 28, 25, .5);
  transform: translateY(-1px);
}
.pcard:active:not(:disabled) { cursor: grabbing; transform: translateY(0); }
.pcard:disabled { opacity: .42; cursor: not-allowed; }
.pcard-art {
  height: 48px; display: flex; align-items: center; justify-content: center;
  background: linear-gradient(180deg, #f6f4ee, #e8e5db);
  border-radius: 6px; border: 1px solid rgba(25, 22, 19, .14);
}
.pcard-art svg { width: 100%; height: 100%; display: block; }
.pcard-text { display: block; min-width: 0; }
.pcard-name { display: block; font-size: 12.5px; font-weight: 600; line-height: 1.25; color: var(--st-ink); }
.pcard-sub { display: block; font-size: 11px; line-height: 1.4; color: var(--st-faint); margin-top: 2px; }

/* The ghost that follows the pointer while a card is carried onto the stage.
   Named carry-ghost, not ghost: the site's own .btn.ghost modifier owns that
   word already, and the two collided. */
.carry-ghost {
  position: fixed; z-index: 900; pointer-events: none;
  width: 108px; padding: 7px; border-radius: var(--st-r);
  background: var(--st-sheet); border: 1px solid var(--st-accent);
  box-shadow: 0 14px 30px -14px rgba(31, 28, 25, .7);
  transform: translate(-50%, -50%) rotate(-2deg);
}
.carry-ghost svg { width: 100%; height: 40px; display: block; }
.carry-ghost .carry-name { font-size: 10.5px; font-weight: 600; text-align: center; margin-top: 4px; }
body.is-carrying, body.is-carrying * { cursor: grabbing !important; }

/* ---- paper -------------------------------------------------------------- */

.stage-paper {
  flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column; gap: 10px;
  padding: 10px 12px;
  overflow: auto;
}
.sheet {
  flex: 1 1 0; min-height: 120px;
  display: flex; flex-direction: column;
  background: var(--st-sheet);
  border: 1.5px solid var(--st-line2);
  border-radius: 12px;
  box-shadow: var(--st-shadow);
  overflow: hidden;
  position: relative;
}
.sheet figcaption {
  flex: 0 0 auto;
  display: flex; align-items: baseline; gap: 9px;
  padding: 8px 14px 7px;
  border-bottom: 1px solid rgba(25, 22, 19, .16);
  background: rgba(25, 22, 19, .05);
}
.sheet figcaption b {
  font: 700 11px/1 'Inter', sans-serif; letter-spacing: .14em; text-transform: uppercase; color: var(--st-ink);
}
.sheet-canvas { flex: 1 1 auto; min-height: 0; position: relative; }
.sheet-canvas { overscroll-behavior: contain; }
.sheet-canvas svg {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; touch-action: none; user-select: none; -webkit-user-select: none;
}
.sheet.is-hidden { display: none; }
/* One sheet is on the stage at a time and it fills it. */
#sheet-front { min-height: 148px; }
.chip .unit-tag { padding: 0; font-size: 10.5px; font-weight: 400; color: var(--st-faint); }
.chip.on .unit-tag { color: rgba(226, 223, 214, .8); }
.sheet.is-sliding { cursor: grabbing; }

/* Once a drawing is magnified there is somewhere to move it to, so the paper
   offers a hand. Pieces keep their own cursor: they are still draggable. */
/* the view is not dragged any more: zoom is the two buttons and nothing else */
/* ---- the drawing -------------------------------------------------------- */

/* paper grid */
.grid-fine  { stroke: rgba(25, 22, 19, .07);  stroke-width: .6; fill: none; }
.grid-bold  { stroke: rgba(25, 22, 19, .12);  stroke-width: 1; fill: none; }

/* the desk itself */
.desk-slab { fill: #a9a496; stroke: rgba(25, 22, 19, .55); stroke-width: 1.3; }
.desk-edge { fill: rgba(25, 22, 19, .22); }
.desk-plan { fill: #c0bbac; stroke: rgba(25, 22, 19, .55); stroke-width: 1.4; cursor: grab; }
.desk-plan:active { cursor: grabbing; }

/* The sector being edited, on the plan.

   A dashed frame drawn round it and nothing else. Nothing is washed over, faded
   or recoloured: every sector stays exactly as dark, as wooden and as legible as
   it was, and one of them is ringed. That is the whole idea, and it is why the
   frame is the only thing on this drawing in a colour the shelf itself never
   uses. Everything made is black or white, the dowels are wood and the paper is
   warm; a cool ink reads instantly as a mark about the drawing rather than a
   part of it, at any zoom and on either finish.

   It rides outside the sector's own footprint, so it never sits on a piece, and
   inside the sector's rotated frame, so it lands square on a sector at any
   angle. The name rides on the frame in the same ink, which is what ties the
   ring to the tab that was clicked. */
.run-focus {
  fill: none;
  stroke: var(--st-focus);
  stroke-width: 2.1;
  stroke-dasharray: 10 6;
  stroke-linecap: round;
  stroke-linejoin: round;
  pointer-events: none;
}
.run-focus-name { fill: var(--st-focus); font-weight: 700; letter-spacing: .07em; }
/* parts. Fills come from the finish, set on the svg as .fin-white or .fin-black */
/* Two line colours, not one. --pl draws the silhouette, which always sits
   against the paper and is always dark. --pi draws everything inside a part,
   which sits against the part's own fill and has to flip with the finish. */
.part-fill  { fill: var(--pf); stroke: var(--pl); stroke-width: 1.4; stroke-linejoin: round; }
/* Dowels. Always wood, in both finishes, because they are wood. */
.dowel-body  { fill: var(--wood); stroke: var(--wood-line); stroke-width: 1.4; }
.dowel-hi    { stroke: var(--wood-hi); stroke-width: 1.4; fill: none; stroke-linecap: round; }
.dowel-grain { stroke: var(--wood-dark); stroke-width: .9; fill: none; stroke-linecap: round; opacity: .55; }
.dowel-line  { stroke: var(--wood-line); stroke-width: 1; fill: none; stroke-linecap: round; }
.pc.dowel:hover .dowel-body { stroke: var(--st-ink); stroke-width: 1.9; }
.pc.dowel.is-error .dowel-body   { stroke: var(--st-err);  stroke-width: 1.9; }
.pc.dowel.is-caution .dowel-body { stroke: var(--st-warn); stroke-width: 1.7; }
.part-shade { fill: var(--psh); stroke: none; }
.part-cut   { fill: var(--pcut); stroke: var(--pi); stroke-width: .7; stroke-linejoin: round; }
.part-line  { stroke: var(--pi); stroke-width: 1; fill: none; stroke-linecap: round; }

/* Interior detail that has to survive a black finish.

   A recessed face used to be a fill on a fill, which on black is one black
   rectangle: a drawer read as a solid block with no drawer in it. A face now
   carries its own outline in the interior line colour, which flips with the
   finish, and the fill is a shade off the body rather than the same value. The
   pull and the divider are drawn in the same colour so a black unit shows its
   fronts, its divider and its handles at a glance. */
.part-face {
  fill: var(--pfd);
  stroke: var(--pi);
  stroke-width: 1.15;
  stroke-linejoin: round;
}
.part-pull    { fill: var(--pi); stroke: none; }
.part-divider { stroke: var(--pi); stroke-width: 1.5; fill: none; stroke-linecap: round; }
.part-thin  { stroke: var(--pi); stroke-width: .85; fill: none; stroke-linecap: round; }
/* a cable is not part of the part, so it is drawn thinner and softer */
.part-cable { stroke: var(--pi); stroke-width: 1.6; fill: none; stroke-linecap: round; opacity: .55; }
.part-hi    { stroke: var(--ph); stroke-width: 1.4; fill: none; stroke-linecap: round; }
.part-soft  { fill: var(--ps); stroke: var(--pi); stroke-width: 1.1; stroke-linejoin: round; }

/* The post a corner stands on. It is a leg, drawn as one, but it belongs to the
   corner, so it takes the corner's hover cue rather than a leg's drag cursor. */
.pc.cornerpost { cursor: pointer; }
.pc.cornerpost .part-fill { stroke-dasharray: none; }

svg {
  --pf: #fdfdfa; --ps: #e5e1d6;
  --pl: rgba(16, 14, 12, .88);
  --pi: rgba(16, 14, 12, .62);
  --pfd: #dfdbd0;                    /* a recessed face, one step off the body */
  --pcut: rgba(25, 22, 19, .3);
  --psh: rgba(25, 22, 19, .13);
  --ph: rgba(255, 255, 255, .95);
}
svg.fin-black {
  --pf: #26221e; --ps: #3f3a31;
  --pl: rgba(8, 7, 6, 1);
  --pi: rgba(245, 242, 234, .72);
  --pfd: #35312c;                    /* a recessed face, lifted off the black body */
  --pcut: rgba(240, 236, 226, .42);
  --psh: rgba(0, 0, 0, .45);
  --ph: rgba(255, 255, 255, .16);
}

.dim-line { stroke: rgba(25, 22, 19, .45); stroke-width: .9; stroke-dasharray: 4 3; }
.dim-tick { stroke: rgba(25, 22, 19, .45); stroke-width: .9; }
/* Lettering on a drawing holds its size on screen however far the drawing is
   zoomed, because a label is there to be read, not to be measured. `--zk` is the
   zoom of the sheet, set on the svg when it is drawn, and it is 1 everywhere
   else, including on the catalog thumbnails. */
.lbl {
  font-family: 'Inter', sans-serif; font-size: calc(15px / var(--zk, 1)); fill: var(--st-faint);
  letter-spacing: .03em; paint-order: stroke; stroke: rgba(242, 240, 233, .85);
  stroke-width: calc(4px / var(--zk, 1));
}
.lbl-strong { fill: var(--st-ink); font-weight: 600; }
.lbl-tiny { font-size: calc(12.5px / var(--zk, 1)); fill: rgba(16, 14, 12, .74); }
.lbl-on-part { font-size: calc(13px / var(--zk, 1)); fill: rgba(16, 14, 12, .8); font-weight: 500; stroke: none; }
svg.fin-black .lbl-on-part { fill: rgba(248, 246, 240, .92); }

/* Names written on the top view. They are drawn in their own layer, last, so a
   dowel can never sit on top of one, and they carry a halo the colour of the
   paper so they read over a dowel, a cover or the desk without changing colour
   with the finish. */
.plan-labels { pointer-events: none; }
/* The letter on a leg. All three legs are the same part, so this is the only
   thing on the drawing that says which one somebody is looking at: L and R at
   the ends, M in the middle, C for a post that belongs to a corner. It holds its
   size when the drawing is zoomed, like every other piece of lettering. */
.leg-mark {
  font: 700 10px/1 'Inter', sans-serif;
  font-size: calc(10px / var(--zk, 1));
  letter-spacing: .04em;
  fill: var(--pi);
  paint-order: stroke fill;
  stroke: var(--pf);
  stroke-width: calc(2.6px / var(--zk, 1));
  stroke-linejoin: round;
  pointer-events: none;
}

.lbl-plan {
  font: 600 12.5px/1 'Inter', sans-serif;
  font-size: calc(12.5px / var(--zk, 1));
  fill: var(--st-ink);
  paint-order: stroke fill;
  stroke: var(--st-sheet);
  stroke-width: calc(3.6px / var(--zk, 1));
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* selectable piece groups */
.pc { cursor: grab; }
.pc[data-drag=""] { cursor: pointer; }
.pc .hit { fill: transparent; stroke: none; }
.pc:hover .part-fill, .pc:hover .part-soft { stroke: var(--st-ink); stroke-width: 1.9; }
.pc.is-dragging { cursor: grabbing; }
.pc.is-dragging .part-fill { stroke: var(--st-ink); }

.sel-tag-bg { fill: var(--st-ink); }
.sel-tag-tx { font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 600; fill: var(--bg); letter-spacing: .01em; }

/* validation stamps. There were two halo rules here, one of them dashed. Nothing
   has drawn them since validation moved onto the pieces' own outlines, and a
   dashed frame is exactly what the drawing is not supposed to have, so they are
   gone rather than sitting here waiting to come back. */
/* Selection is drawn on the piece, following its own silhouette. There is no
   dashed rectangle anywhere on the drawing: a marquee around a corner or an L
   sector boxes in a shape that is not a box. Error and caution come after this
   on purpose, so a selected piece that is also wrong still reads as wrong. */
.pc.is-selected .part-fill, .pc.is-selected .part-soft { stroke: var(--st-ink); stroke-width: 2.4; }
.pc.is-selected .dowel-body { stroke: var(--st-ink); stroke-width: 2.2; }

.pc.is-error .part-fill, .pc.is-error .part-soft   { stroke: var(--st-err); stroke-width: 1.8; }
.pc.is-caution .part-fill, .pc.is-caution .part-soft { stroke: var(--st-warn); stroke-width: 1.6; }
.stamp-err  { fill: var(--st-err); }
.stamp-warn { fill: var(--st-sheet); stroke: var(--st-warn); stroke-width: 1.5; }
.stamp-tx   { font-family: 'Inter', sans-serif; font-size: 13px; font-weight: 700; text-anchor: middle; }
.stamp-err + .stamp-tx, .stamp-tx.on-err { fill: #f4f2ec; }
.stamp-tx.on-warn { fill: var(--st-warn); }
/* ---- the ledger --------------------------------------------------------- */

.ledger {
  flex: 0 0 auto;
  border-top: 2px solid var(--st-line2);
  background: var(--st-panel);
  /* A share of the bench meant that the more the studio had to say, the less of
     the drawing was left to say it about, and the plan is the drawing that
     cannot afford to lose height. It is a short fixed strip now and the list
     inside it scrolls, so three messages cost exactly what one costs. The
     counter above it opens all of them in full, and clicking the header folds
     the strip down to nothing. */
  max-height: 112px;
  display: flex; flex-direction: column;
}
@media (max-height: 780px) { .ledger { max-height: 96px; } }
.ledger-head {
  flex: 0 0 auto; display: flex; align-items: center; gap: 10px;
  padding: 9px 16px; width: 100%; text-align: left;
}
.ledger-state { display: flex; align-items: center; gap: 14px; font-size: 13px; color: var(--st-soft); }
.ledger-state i { font-style: normal; display: inline-flex; align-items: center; gap: 6px; font-weight: 600; }
.ledger-state i::before { content: ""; width: 8px; height: 8px; border-radius: 50%; }
.ledger-state i.e { color: var(--st-err); }  .ledger-state i.e::before { background: var(--st-err); }
.ledger-state i.w { color: var(--st-warn); } .ledger-state i.w::before { background: var(--st-warn); }
.ledger-state i.k { color: var(--st-ok); }   .ledger-state i.k::before { background: var(--st-ok); }
.ledger-caret {
  margin-left: auto; width: 8px; height: 8px; border-right: 1.6px solid var(--st-faint);
  border-bottom: 1.6px solid var(--st-faint); transform: rotate(45deg) translate(-2px, -2px);
  transition: transform .2s;
}
.ledger.closed .ledger-caret { transform: rotate(-135deg) translate(-3px, -3px); }
.ledger.closed .ledger-list, .ledger.is-clean .ledger-list { display: none; }
.ledger.is-clean .ledger-caret { display: none; }
.ledger.is-clean .ledger-head { cursor: default; }
.ledger-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 0 16px 12px; }
.note {
  display: grid; grid-template-columns: 21px 1fr; gap: 10px; align-items: start;
  width: 100%; text-align: left; padding: 8px 0; border-top: 1px solid var(--st-line);
  font-size: 13px; line-height: 1.5; color: var(--st-soft);
}
.note:first-child { border-top: 0; }
.note-n {
  width: 19px; height: 19px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-size: 10px; font-weight: 700; margin-top: 1px;
}
.note-e .note-n { background: var(--st-err); color: #f4f2ec; }
.note-w .note-n { background: transparent; color: var(--st-warn); border: 1.5px solid var(--st-warn); }
.note.clickable { cursor: pointer; }
.note.clickable:hover { color: var(--st-ink); }
.note-ok { padding: 8px 0 10px; font-size: 13px; color: var(--st-ok); }
.note-ok b { font-weight: 600; }
/* While the module graph is still arriving. Step 1 is already drawn by the
   HTML, so this is a status line and not a curtain: nothing is hidden, and the
   moment boot() finishes the class comes off and the strip goes with it. */
.booting-note {
  display: none; margin: 0 auto 0 0; padding: 6px 14px; border-radius: 999px;
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; font-weight: 700;
  color: var(--st-accent); background: var(--st-panel); border: 1.5px solid var(--st-line);
}
body.is-booting .booting-note { display: inline-block; }
/* the controls that only mean something once the studio is running */
body.is-booting .work-nav .btn { opacity: .45; pointer-events: none; }

/* ---- the counters, and the messages in full ----------------------------- */

/* The counter is a control now, not a label, so it looks like one and it is
   loud enough to be seen from the drawing. */
.flagbtn {
  font: inherit; font-weight: 700; font-size: 12px; letter-spacing: .02em;
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 12px 5px 10px; border-radius: 999px; cursor: pointer;
  border: 1.5px solid currentColor; background: transparent;
}
.flagbtn::before { content: ""; width: 9px; height: 9px; border-radius: 50%; background: currentColor; }
.flagbtn.e { color: var(--st-err); background: rgba(122, 35, 20, .10); }
.flagbtn.w { color: var(--st-warn); background: rgba(93, 67, 16, .12); }
.flagbtn:hover { filter: brightness(.88); }
.flagbtn:focus-visible { outline: 2px solid var(--st-ink); outline-offset: 2px; }

.side-flag.is-btn { cursor: pointer; font-weight: 700; }
.side-flag.is-btn:hover { text-decoration: underline; }
.side-flag.is-btn:focus-visible { outline: 2px solid currentColor; outline-offset: 3px; border-radius: 4px; }

.msgback {
  position: fixed; inset: 0; z-index: 1300; display: flex;
  align-items: center; justify-content: center; padding: 24px;
  background: rgba(16, 14, 12, .5);
}
.msgbox {
  width: min(560px, 100%); max-height: min(560px, 86vh); display: flex; flex-direction: column;
  background: var(--st-sheet); color: var(--st-ink);
  border: 2px solid var(--st-line2); border-radius: 14px;
  box-shadow: 0 24px 60px rgba(16, 14, 12, .35);
}
.msgbox-head {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 16px 18px 12px; border-bottom: 1.5px solid var(--st-line);
}
.msgbox-head b { font-family: 'Fraunces', serif; font-size: 19px; font-weight: 500; letter-spacing: -.01em; }
.msgbox-x {
  width: 30px; height: 30px; flex: 0 0 auto; border-radius: 50%; cursor: pointer;
  border: 1.5px solid var(--st-line); background: transparent; color: var(--st-soft);
  font-size: 17px; line-height: 1;
}
.msgbox-x:hover { color: var(--st-ink); border-color: var(--st-ink); }
.msgbox-list { overflow-y: auto; padding: 4px 18px; }
.msgrow {
  display: grid; grid-template-columns: 22px 1fr; gap: 11px; align-items: start;
  width: 100%; text-align: left; font: inherit; font-size: 13.5px; line-height: 1.55;
  color: var(--st-soft); background: transparent; border: 0;
  border-top: 1px solid var(--st-line); padding: 13px 0;
}
.msgrow:first-child { border-top: 0; }
.msgrow-n {
  width: 20px; height: 20px; border-radius: 50%; display: inline-flex;
  align-items: center; justify-content: center; font-size: 10.5px; font-weight: 700; margin-top: 2px;
}
.msgrow-e .msgrow-n { background: var(--st-err); color: #f4f2ec; }
.msgrow-w .msgrow-n { background: transparent; color: var(--st-warn); border: 1.5px solid var(--st-warn); }
.msgrow-k {
  display: block; font-style: normal; font-size: 10px; font-weight: 700;
  letter-spacing: .14em; text-transform: uppercase; margin-bottom: 3px;
}
.msgrow-e .msgrow-k { color: var(--st-err); }
.msgrow-w .msgrow-k { color: var(--st-warn); }
.msgrow-go { display: block; font-style: normal; font-size: 12px; font-weight: 600; margin-top: 5px; color: var(--st-ink); }
.msgrow.clickable { cursor: pointer; }
.msgrow.clickable:hover { color: var(--st-ink); }
.msgrow.clickable:hover .msgrow-go { text-decoration: underline; }
/* A note is prose rather than a list, so it gets a wider box: at 560px the
   second sentence broke onto a third line for one word. */
.msgbox-note { width: min(680px, 100%); }
.msgbox-note .msgnote { max-width: none; }
.msgnote { margin: 0 0 12px; font-size: 13.5px; line-height: 1.6; color: var(--st-soft); }
.msgnote:last-child { margin-bottom: 4px; }
.msgbox-note .msgbox-list { padding-top: 14px; padding-bottom: 8px; }

.msgbox-foot {
  display: flex; align-items: center; gap: 14px;
  margin: 0; padding: 12px 18px 16px; border-top: 1.5px solid var(--st-line);
  font-size: 12.5px; line-height: 1.55; color: var(--st-soft);
}
.msgbox-foot-t { margin: 0; flex: 1 1 auto; }
.msgbox-mail { color: var(--st-ink); font-weight: 600; border-bottom: 1px solid currentColor; }
.msgbox-mail:hover { color: var(--st-err); }
.msgbox-foot .btn { flex: 0 0 auto; white-space: nowrap; }

/* ---- the piece pane ----------------------------------------------------- */

.piece-head { display: flex; gap: 11px; align-items: center; margin-bottom: 16px; }
.piece-art {
  width: 54px; height: 46px; flex: 0 0 auto; border-radius: 8px;
  background: linear-gradient(180deg, #f4f2ec, #e7e4dc); border: 1px solid rgba(31, 28, 25, .08);
  display: flex; align-items: center; justify-content: center; padding: 4px;
}
.piece-art svg { width: 100%; height: 100%; }
.piece-title { display: block; font-family: 'Fraunces', serif; font-size: 20px; font-weight: 500; letter-spacing: -.01em; line-height: 1.15; }
.piece-where { display: block; font-size: 11.5px; color: var(--st-faint); margin-top: 3px; }

.piece-empty { padding: 26px 4px; text-align: center; color: var(--st-faint); }
.piece-empty svg { width: 54px; height: 54px; margin: 0 auto 14px; display: block; opacity: .5; stroke: currentColor; fill: none; stroke-width: 1.2; }
.piece-empty p { font-size: 13px; line-height: 1.65; color: var(--st-soft); }
.piece-empty b { display: block; color: var(--st-ink); font-size: 14px; font-weight: 600; margin-bottom: 6px; }
/* how to get a closer look, said where somebody with nothing selected will read it */
.piece-empty-tip {
  margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--st-line);
  font-size: 12.5px !important; color: var(--st-faint) !important;
}
.piece-empty-tip b { font-size: 12.5px; letter-spacing: .01em; }

.hint { font-size: 12.5px; line-height: 1.6; color: var(--st-soft); margin-top: 10px; }
.hint b { color: var(--st-ink); font-weight: 600; }

/* position slider */
.slider { display: flex; align-items: center; gap: 8px; }
.slider input[type="range"] { flex: 1; min-width: 0; accent-color: var(--st-accent); height: 20px; }
.step {
  width: 30px; height: 30px; flex: 0 0 auto; border-radius: 7px; border: 1.5px solid var(--st-line);
  background: #fbfaf5; color: var(--st-soft); font-size: 15px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.step:hover:not(:disabled) { border-color: var(--st-ink); color: var(--st-ink); }
.step:disabled { opacity: .35; cursor: not-allowed; }
.readout { font-size: 12.5px; font-weight: 600; color: var(--st-ink); font-variant-numeric: tabular-nums; min-width: 58px; text-align: right; }

.piece-acts { display: flex; gap: 7px; margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--st-line); }
.pact {
  flex: 1; padding: 10px; border-radius: 8px; font: 500 12.5px/1 'Inter', sans-serif;
  border: 1.5px solid var(--st-line); background: #fbfaf5; color: var(--st-soft);
  transition: border-color .16s, color .16s, background .16s;
}
.pact:hover { border-color: var(--st-ink); color: var(--st-ink); }
.pact-del:hover { border-color: var(--st-err); color: var(--st-err); background: rgba(157, 59, 42, .06); }
/* ---- the crash banner --------------------------------------------------- */

.crash {
  position: fixed; left: 50%; bottom: 92px; transform: translateX(-50%); z-index: 1100;
  display: flex; align-items: center; gap: 12px; padding: 12px 18px; border-radius: 10px;
  background: var(--st-ink); color: var(--bg); font-size: 13px; box-shadow: 0 20px 40px -18px rgba(0, 0, 0, .7);
}
.crash button { color: var(--darkaccent); text-decoration: underline; font-size: 13px; }
.crash button:hover { color: var(--bg); }

/* ---- the toast ---------------------------------------------------------- */

.toast {
  position: fixed; left: 50%; bottom: 92px; transform: translate(-50%, 8px); z-index: 1200;
  padding: 11px 20px; border-radius: 100px; background: var(--st-ink); color: var(--bg);
  font-size: 13px; opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
}
.toast.on { opacity: 1; transform: translate(-50%, 0); }

/* ===========================================================================
   NARROWER SCREENS
   The invariant: when a piece is selected, its options are on screen.
   Below 1080 the right dock becomes a sheet that rises from the bottom.
   Below 760 the left dock does the same, opened from the stage header.
   =========================================================================== */

@media (max-width: 1180px) {
  .bench { grid-template-columns: 258px minmax(0, 1fr) 296px; }
  .cards { grid-template-columns: 1fr; }
  .card { flex-direction: row; align-items: center; gap: 10px; }
  .card-art { width: 52px; height: 40px; flex: 0 0 auto; }
  .card-text { min-width: 0; }
}

@media (max-width: 1080px) {
  .bench { grid-template-columns: 250px minmax(0, 1fr); }
  .dock-r {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 700;
    max-height: min(64vh, 460px); border-top: 1px solid var(--st-line2);
    border-radius: 14px 14px 0 0; box-shadow: 0 -20px 44px -22px rgba(31, 28, 25, .6);
    transform: translateY(102%); transition: transform .26s cubic-bezier(.2, .8, .2, 1);
  }
  .dock-r.open { transform: none; }
  .dock-r .dock-close { display: block; }
  .dock-r .dock-scroll { padding-bottom: 78px; }
  .rail { position: relative; z-index: 800; }
  .rail-price { cursor: pointer; }
}

@media (max-width: 760px) {
  .bench { grid-template-columns: minmax(0, 1fr); }
  .dock-l {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 710;
    max-height: min(72vh, 520px); border-radius: 14px 14px 0 0;
    box-shadow: 0 -20px 44px -22px rgba(31, 28, 25, .6);
    transform: translateY(102%); transition: transform .26s cubic-bezier(.2, .8, .2, 1);
  }
  .dock-l.open { transform: none; }
  .dock-l .dock-close { display: block; }
  .dock-l .dock-scroll { padding-bottom: 78px; }
  .dock-foot { display: none; }
  .stage-open {
    display: inline-flex; align-items: center; gap: 8px; padding: 7px 13px 7px 11px;
    border-radius: 100px; background: var(--st-ink); color: var(--bg); font-size: 12px; font-weight: 600;
  }
  .stage-open .bars { width: 13px; height: 9px; border-top: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor; position: relative; }
  .stage-open .bars::after { content: ""; position: absolute; left: 0; right: 0; top: 3px; border-top: 1.6px solid currentColor; }
  .bench-nav .links { display: none; }
  .bench-title { display: none; }
  .stage-head { flex-wrap: wrap; gap: 8px; padding: 8px 12px; }
  .stage-tools { margin-left: auto; }
  /* One row, never two: the send button must not be pushed off the bottom. */
  .rail { display: flex; align-items: center; gap: 12px; padding: 9px 12px; }
  .rail-sum { display: none; }
  .rail-legal { display: none; }
  .rail-flag { font-size: 11.5px; white-space: nowrap; }
  .rail-price { margin-left: auto; }
  .rail-price-k { display: none; }
  .rail-btn { flex: 0 0 auto; }
  .ov-card { padding: 26px 20px 24px; }
  .ov-grid { grid-template-columns: 1fr; gap: 24px; }
  .stage-paper { padding: 10px; }
}

@media (max-width: 520px) {
  .chips-view .chip { padding: 7px 11px; }
  .rail-price b { font-size: 21px; }
  .rail-btn { padding: 11px 18px; font-size: 13px; }
}

/* Short screens: let the whole thing scroll rather than crushing the drawing. */
@media (max-height: 560px) {
  body.bench-page { overflow: auto; height: auto; }
  .bench { min-height: 540px; }
}

.ledger-sub { color: var(--st-faint); font-size: 12.5px; }
@media (max-width: 900px) { .ledger-sub { display: none; } }
/* ---- keyboard focus ------------------------------------------------------
   Everything on the bench is reachable by tab, so everything needs a ring the
   mouse never shows. */
.bench :focus { outline: none; }
.bench :focus-visible,
.ov :focus-visible,
.rail :focus-visible {
  outline: 2px solid var(--st-accent);
  outline-offset: 2px;
  border-radius: 6px;
}
.card:focus-visible { outline-offset: 1px; }
.chip:focus-visible { border-radius: 100px; }
.pc:focus-visible { outline: none; }

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