@charset "utf-8";
/* ===========================================================================
   TERPSTRA BENCH CO. — Grand Rapids
   Built to DESIGN_SYSTEM.md v1.0.  Context A4 · B1 · C5 · D5 · E2.

   THE COLD PAGE, AND WHY. A furniture maker on a warm cream ground with a
   serif and a terracotta accent is the house style with sawdust on it — this
   category walks into §12 more directly than any other we have built. So the
   page is cold on purpose: a blue-gray ground, graphite rules, Delft blue.
   The wood is then the only warm thing anywhere on the site, which is what
   makes it read as the subject rather than as decoration. Grand Rapids is the
   most Dutch city in America and Delftware blue is the color it earned; it is
   also the exact opposite temperature to every species in the rack, so it can
   sit beside walnut without arguing with it.

   THE EXPRESSIVE CHANNEL IS OPTICAL SIZE. Bodoni Moda ships real optical cuts
   and this site uses them deliberately: hairlines that survive at 86px would
   vanish at 15px, so the face changes its CUT and not merely its scale. A maker
   changes the tool for the scale of the work — you do not chamfer with a jack
   plane — and this is the typographic version of that. Previous channels have
   been width, weight extremes, the numeral, alignment, case and a closed size
   set; this is the seventh and it repeats none of them.

   THE INTERACTION SIGNATURE IS THE CHAMFER. At rest every control is square.
   Touch it and a corner cuts away. Breaking the arris is the last operation
   before finishing and it is what makes a piece feel resolved in the hand, so
   it is what the interface does when you put a hand on it. It resolves on
   :active, so touch gets it too.

   THE TEXTURAL SIGNATURE IS THE CABINETMAKER'S TRIANGLE — the mark drawn
   across a stack of boards so they can be reassembled in the right order and
   the right way up. A mark that already means "these belong together, in this
   order." It is the wordmark, the list bullet, the divider and the current-page
   indicator. No decorative grid anywhere (§12.0a); the drawings are the only
   ruled surfaces and they are genuinely drawings.
   =========================================================================== */

:root {
  /* ---- color. Measured, not eyeballed.
     ink 13.40 on ground · graphite 7.39 · delft 7.42 · ink-2 6.25
     stage-on 13.27 on stage · stage-2 7.46 · delft-lt 7.31
     ground against stage 12.28, which is the one ground change on the page. */
  --ground:   #DEE3E6;
  --panel:    #EDF0F1;
  --sunk:     #D2D8DC;
  --stage:    #1C2329;
  --stage-2:  #232B32;
  --ink:      #161B20;
  --ink-2:    #47515C;
  --graphite: #3E4650;
  --line:     #B9C2C8;
  --delft:    #17457E;
  --delft-hi: #205AA3;
  --stage-on: #E8EBED;
  --stage-ink2: #A9B3BC;
  --delft-lt: #7FB4EE;

  /* ---- type. Bodoni Moda for display, carrying the optical-size channel;
     Libre Franklin for text, which is the American gothic that stood beside
     Didones in the Grand Rapids catalogs of the 1910s. Both OFL. */
  --f-d: 'Bodoni Moda', 'Bodoni 72', Didot, 'Times New Roman', serif;
  --f-t: 'Libre Franklin', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;

  /* scale: perfect fifth, 1.5, six steps. Display is 5.06x body on desktop and
     3.2x on mobile, both clear of the §2.3 floor. */
  --t--1: 0.8125rem;
  --t-0:  1.0625rem;
  --t-1:  1.5rem;
  --t-2:  2.375rem;
  --t-3:  clamp(2.5rem, 1.4rem + 4.4vw, 3.5rem);
  --t-4:  clamp(3.4rem, 1.2rem + 8.2vw, 5.375rem);

  /* ---- space. Base four, with real range at the top so sections can breathe. */
  --s-1: 4px;  --s-2: 8px;  --s-3: 12px; --s-4: 16px; --s-5: 24px;
  --s-6: 32px; --s-7: 48px; --s-8: 64px; --s-9: 96px; --s-10: 128px; --s-11: 160px;

  /* ---- motion. One primary curve and one exit curve, drawn once, used
     everywhere. B=1 and C=5 put this project on the full motion budget — the
     first in the series to get there — so there is a scrubbed sequence, and it
     is the only one. */
  --ease: cubic-bezier(.22, .68, .24, 1);
  --exit: cubic-bezier(.4, 0, .7, .3);
  --fast: 140ms; --mid: 260ms; --slow: 520ms;

  --measure: 62ch;
  --edge: 1180px;
}

*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--ground); color: var(--ink);
  font-family: var(--f-t); font-size: var(--t-0); line-height: 1.62;
  font-weight: 400; font-variant-numeric: tabular-nums lining-nums;
  -webkit-font-smoothing: antialiased;
}
/* height:auto is not optional here. A width/height ATTRIBUTE maps to the
   presentational `height` property, and an explicit height beats `aspect-ratio`
   — so an <img width="1200" height="800"> under `width:100%; aspect-ratio:3/2`
   renders 100% wide by EIGHT HUNDRED PIXELS tall and object-fit quietly crops
   the difference. It looks like a design decision rather than a bug, which is
   why it survived a whole build the last time it appeared. */
img { max-width: 100%; height: auto; }
svg { max-width: 100%; }
::selection { background: var(--delft); color: #F2F5F7; }
:root { caret-color: var(--delft); accent-color: var(--delft); }

/* Underlines are a real control, not a browser default. */
a { color: var(--delft); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--delft-hi); text-decoration-thickness: 2px; }

:where(a, button, input, select, summary, [tabindex]):focus-visible {
  outline: 3px solid var(--delft); outline-offset: 3px;
}

/* ============================================================ DISPLAY TYPE
   OPTICAL SIZE IS SET FOR THE MEDIUM, NOT THE POINT SIZE. This is the project's
   expressive channel and the first pass used it backwards.

   A Didone's opsz-96 cut is drawn for ink on paper at ninety-six points, where
   a hairline survives because it is a physical ridge of ink. A screen at 1x
   cannot hold that stroke and simply drops it. Set at 86px with opsz 96, the
   thin diagonal of the A disappeared outright and the serifs broke up — the
   headline read as fragile rather than elegant.

   So the mapping inverts. On screen a large setting wants a SMALLER opsz — a
   sturdier cut, drawn for a size at which the thins still had to work — plus a
   weight step to carry them. Tested down the axis at one rendered size, 28 to
   40 at 600 to 700 is where the strokes hold and the face still reads as a
   Didone rather than a slab.

   Which is the same argument the rest of this site makes: a maker changes the
   tool for the scale of the work, and the right tool depends on the material
   being cut. The material here is a screen. */
.d {
  font-family: var(--f-d); font-weight: 650; line-height: 1.04;
  letter-spacing: -0.008em; margin: 0;
  font-variation-settings: 'opsz' 30;
  text-wrap: balance;
}
.d--1 { font-size: var(--t-4); font-variation-settings: 'opsz' 30; }
.d--2 { font-size: var(--t-3); font-variation-settings: 'opsz' 26; }
.d--3 { font-size: var(--t-2); font-variation-settings: 'opsz' 22; letter-spacing: -0.006em; }
.d--4 { font-size: var(--t-1); font-variation-settings: 'opsz' 18; font-weight: 600;
        letter-spacing: -0.002em; line-height: 1.18; }
.d--sm { font-family: var(--f-d); font-size: var(--t-0); font-variation-settings: 'opsz' 14;
         font-weight: 600; letter-spacing: 0; line-height: 1.3; margin: 0; }

.lead { font-size: var(--t-1); line-height: 1.42; max-width: 30ch; font-weight: 400;
        text-wrap: pretty; }
.prose { max-width: var(--measure); }
.prose p { margin: 0 0 1.05em; text-wrap: pretty; }
.prose p:last-child { margin-bottom: 0; }
.note { font-size: var(--t--1); line-height: 1.5; color: var(--ink-2); }

/* THE SMALL CAPS WERE FAKE, so they are gone. Neither Libre Franklin nor Bodoni
   Moda ships a real `smcp` cut, which means the browser was synthesising them:
   capitals scaled down at unchanged stroke weight, so they came out lighter
   than the text they labeled and read as cheap. §2.4 is right that small caps
   are the honest replacement for the mono eyebrow — but only where the face
   actually has them, and neither of these does.

   What replaces it is split by JOB rather than by look:
     .sc   a UI control label — Length, Wood, Total. Real capitals, small,
           tracked, at a weight that holds. A form label in caps is a control
           affordance; §12.0a bans the section-marking DEVICE, not the case.
     .sh   a heading standing over a list — sentence case, because that IS the
           marker position and caps there would be the banned device. */
.sc {
  text-transform: uppercase; letter-spacing: .085em;
  font-size: .71875rem; font-weight: 700; line-height: 1.4; color: var(--ink-2);
}
.sh {
  font-size: var(--t--1); font-weight: 700; letter-spacing: .005em; color: var(--ink-2);
}

/* ============================================================== THE MARK
   The cabinetmaker's triangle. Drawn across a stack of boards so they go back
   together the right way round — a mark that already means "these belong
   together, in this order". */
.tri { width: .68em; height: .58em; flex: none; fill: none;
       stroke: currentColor; stroke-width: 1.7; stroke-linejoin: round; }
.tri--solid { fill: currentColor; stroke: none; }

/* Divider idiom: a graphite layout line with the witness mark at its head.
   This is the only rule weight system on the site — .5px does not exist here,
   because a pencil line does not. */
.rule { display: flex; align-items: center; gap: var(--s-3); margin: 0; border: 0; }
.rule::after { content: ''; flex: 1; height: 1.5px; background: var(--graphite); opacity: .5; }
.rule .tri { color: var(--graphite); }

/* ================================================================= LAYOUT */
.shell { width: min(100% - var(--s-6) * 2, var(--edge)); margin-inline: auto; }
.band { padding-block: var(--s-10); }
.band--tight { padding-block: var(--s-8); }
.band--loose { padding-block: var(--s-11); }
.skip {
  position: absolute; left: -9999px; top: 0; background: var(--ink); color: var(--panel);
  padding: var(--s-3) var(--s-5); z-index: 60;
}
.skip:focus { left: var(--s-4); top: var(--s-4); }

/* ============================================================ NAVIGATION
   A horizontal bar, static rather than sticky. Nine destinations at most, and
   nothing here is urgent enough to warrant the bar following you down the page
   — at B=1 a persistent band would be taking space from the drawings for no
   reason. Current page is marked by the triangle AND by weight, never by
   color alone. */
.mast { border-bottom: 1.5px solid var(--graphite); }
.mast__in { display: flex; align-items: center; justify-content: space-between;
            gap: var(--s-5); padding-block: var(--s-4); flex-wrap: wrap; }
.wordmark { display: inline-flex; align-items: baseline; gap: var(--s-3);
            min-height: 44px; text-decoration: none; color: var(--ink); }
.wordmark__n { font-family: var(--f-d); font-variation-settings: 'opsz' 16;
               font-weight: 650; font-size: 1.4rem; letter-spacing: .005em; line-height: 1; }
.wordmark .tri { align-self: center; width: 1rem; height: .86rem; color: var(--delft); }
.wordmark__sub { font-size: var(--t--1); color: var(--ink-2); }
.nav { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5); }
.nav a { display: inline-flex; align-items: center; gap: var(--s-2); min-height: 44px;
         color: var(--ink); text-decoration: none; font-size: var(--t--1);
         font-weight: 500; letter-spacing: .01em; }
.nav a:hover { text-decoration: underline; text-underline-offset: 5px; }
.nav a[aria-current="page"] { font-weight: 700; }
.nav a[aria-current="page"] .tri { color: var(--delft); }
.nav .tri { width: .6rem; height: .52rem; opacity: 0; }
.nav a[aria-current="page"] .tri { opacity: 1; }

/* ========================================================== THE CHAMFER
   The one signature interaction. Square at rest; a corner cuts on hover, cuts
   deeper on press. Breaking the arris is the last thing that happens to a piece
   before it is finished, and it is the difference between a board and a part.

   clip-path would eat an outline, so focus is carried by an inset ring instead
   — which is louder than the outline it replaces, as §7.3 requires. */
.ch {
  --cut: 0px;
  clip-path: polygon(0 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% 100%, 0 100%);
  transition: clip-path var(--fast) var(--ease), background var(--fast) var(--ease),
              color var(--fast) var(--ease);
}
.ch:hover { --cut: 11px; }
.ch:focus-visible { --cut: 11px; outline: none;
  box-shadow: inset 0 0 0 3px var(--delft), inset 0 0 0 6px var(--panel); }
.ch:active { --cut: 18px; transition-duration: 90ms; }
@media (prefers-reduced-motion: reduce) {
  .ch { transition: none; }        /* the cut still happens — it is feedback, not travel */
}

/* ================================================================ CONTROLS */
/* A link sitting alone in its own paragraph is a target, not prose. WCAG 2.5.8
   exempts links INSIDE a sentence, where line-height constrains them; it does
   not exempt a standalone one, and the exemption is about what is possible
   rather than what is comfortable. */
.tlink { display: inline-flex; align-items: center; min-height: 44px; }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-3);
  min-height: 52px; padding: var(--s-3) var(--s-6); border: 1.5px solid var(--ink);
  background: var(--ink); color: var(--panel); font: inherit; font-weight: 600;
  font-size: var(--t-0); text-decoration: none; cursor: pointer; border-radius: 0;
}
.btn:hover { background: var(--delft); border-color: var(--delft); color: #fff; }
.btn--ghost { background: transparent; color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: var(--panel); }
.btn--onstage { background: var(--stage-on); color: var(--stage); border-color: var(--stage-on); }
.btn--onstage:hover { background: var(--delft-lt); border-color: var(--delft-lt); color: var(--stage); }

/* ============================================================== THE PAGES */

/* ========================================================= THE JOINT
   THE FIRST VIEWPORT USED TO DO NO JOB. Five sections on this index were named
   in the record with a job apiece and a compositional system apiece; the hero
   was in neither list, because it was not doing anything — a headline and two
   lines of subhead on a flat field, with the entire right half of the screen
   empty and the site's one real idea starting below the fold. §3 is blunt about
   that case: a section that cannot name its job is the section to cut. So it is
   cut, and what replaces it names its job — ASSERT — and then proves the claim
   in the same viewport it makes it in.

   The claim is "six parts and one joint". The joint was drawn nowhere. It is
   drawn here, at 8:1, cropped by the right edge because the post is thirty
   inches long and this is two and a half inches of it.

FOUR DIVISION CHANNELS ARRIVE BEFORE ANYONE SCROLLS, where there had been
   one: ground change (the light masthead against this), bleed (the drawing off
   the right edge, the post out of frame top and bottom), depth (three planes)
   and temperature (a cold ground against the only warm thing on the site). The
   fifth arrives on the first scroll and is the biggest — the scale jump into
   the assembly below, about a hundred to one, 8:1 down to 1:12. None of them
   repeats the one before it.

   THE PLANE STACK IS THE SUBJECT'S OWN, which §8 requires and is the whole
   reason no scenery was borrowed. Back: the cabinetmaker's triangle across a
   stack of boards, which is this project's textural signature and had until now
   only ever appeared as a 10px glyph nobody could read. Mid: the joint. Fore:
   the annotation, because a drafting sheet has an ink layer over the pencil.
   Rates 0.06 / 0.17 / 0.28 — a differential of 0.22, inside the 0.3 ceiling. */
.joint {
  position: relative; overflow: hidden;
  background: var(--stage); color: var(--stage-on);
  border-bottom: 1.5px solid var(--stage-2);
  /* NO BOTTOM PADDING, deliberately. The post is cropped by the section's own
     bottom edge, so the ground change from this dark section into the light one
     below cuts through thirty inches of oak — which is what makes the crop read
     as a crop and not as a drawing that happens to end. A 64px band of ground
     under it undid the whole bleed. */
  padding-block: var(--s-9) 0;
}
/* atmospheric value, not a gradient for its own sake: the back of the scene is
   lifted and the front dropped, which is the cheapest depth channel there is
   and the only one that costs no markup. Every text color on this page was
   then measured against the RENDERED pixels of this gradient plus the grain
   plus whatever the drawing puts behind a glyph, rather than against the flat
   token — see docs/DESIGN.md §6. */
.joint::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(178deg, #212932 0%, #1C2329 46%, #161C21 100%);
}
.joint > * { position: relative; }

.joint__back { position: absolute; inset: -8% -4% -8% auto;
                width: min(74%, 1180px); pointer-events: none;
                -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 26%, #000 100%);
                mask-image: linear-gradient(90deg, transparent 0, #000 26%, #000 100%); }
.joint__back svg { width: 100%; height: 100%; display: block; }
.jb-boards rect { fill: #212930; }
.jb-arris path { fill: none; stroke: #303C46; stroke-width: 1.5; }
.jb-tri { fill: none; stroke: var(--stage-ink2); stroke-width: 4; stroke-linejoin: round;
          opacity: .17; }
/* §11: grain under the 6% the section allows — and NO BLEND MODE. `overlay` at
   5% is not a 5% texture: on a base as dark as these boards it doubles the
   value, so the brightest noise pixels came out at rgb(63,72,79) against a
   #212930 board and took a label on top of them down to 4.38:1. Painted
   straight at 5.5% the worst lift is a couple of values and the ground under
   every glyph is a number you can predict from the token. */
.jb-grain { opacity: .055; }

.joint__in {
  display: grid; align-items: center; gap: var(--s-9);
  grid-template-columns: minmax(0, 34rem) minmax(0, 1fr);
  padding-left: max(var(--s-6), calc(50vw - var(--edge) / 2));
  padding-right: 0;
}
.joint__claim { max-width: 13ch; }
.joint__sub { margin-top: var(--s-5); max-width: 42ch; font-size: var(--t-1);
              line-height: 1.38; color: var(--stage-on); text-wrap: pretty; }
.joint__spec { margin-top: var(--s-6); max-width: 44ch; font-size: var(--t--1);
               line-height: 1.5; color: var(--stage-ink2); }
.joint__act { margin: var(--s-1) 0 0; }
.joint__act a { color: var(--delft-lt); font-size: var(--t-0); font-weight: 600; }
.joint__act a:hover { color: #A6CCF6; }
/* the drawing runs past the right edge of the viewport. The section clips it,
   which is what keeps a bleed from becoming horizontal overflow. */
.joint__draw { margin-right: max(-11vw, calc(var(--edge) / 2 - 50vw - 3rem)); }

/* the detail's own line system. Heavier than the piece drawings because it is
   eight times the scale — a 1.1px edge at 8:1 would be a hairline pretending to
   be a board. */
/* .dw.jt AND NOT .jt, EVERYWHERE BELOW. The joint carries both classes, and the
   site-wide `.dw .edge` / `.dw .dimtext` / `.dw .ghost` rules sit LATER in this
   file at the same (0,2,0) specificity — so for three rounds of work every one
   of these declarations was being silently discarded and the drawing was still
   running the light system meant for a pale ground: graphite edges and, worse,
   GRAPHITE ANNOTATION TEXT at 12.5px on a #1C2329 ground, which is about 1.2:1
   and effectively invisible.

   It survived because the check that should have caught it was asking the wrong
   question: it was handed the hex the text was SUPPOSED to be and measured
   that against the real ground, rather than reading the fill the browser had
   actually computed. A contrast check that trusts your intention is a contrast
   check that confirms your intention. It reads getComputedStyle now. */
.dw.jt .edge { stroke: var(--stage-ink2); stroke-width: 1.8; }
.dw.jt .ghost { fill: none; stroke: var(--stage-ink2); stroke-width: 1.6;
             stroke-dasharray: 14 10; opacity: .6; }
.dw.jt .cl-line { fill: none; stroke: var(--stage-ink2); stroke-width: 1.2; opacity: .45;
               stroke-dasharray: 34 8 5 8; }
.dw.jt .ext, .dw.jt .dimline { fill: none; stroke: var(--stage-ink2); stroke-width: 1.3; }
.dw.jt .ext { opacity: .55; }
.dw.jt .lead circle, .dw.jt marker path { fill: var(--stage-ink2); stroke: none; }
/* The annotation TEXT is a step lighter than the annotation LINES, which is not
   an inconsistency: a line has to clear 3:1 and a word has to clear 4.5:1, and
   the two sit on the same ground. At the drawing's own #A9B3BC a label landing
   on the triangle behind it measured 4.18:1 at 390px — the back plane sits
   differently there, so which pixels a label gets is a layout accident, and a
   contrast that depends on the viewport is not a contrast. Lighting the text
   alone fixes it everywhere at once and leaves the line system as drawn. */
.dw.jt .dimtext { fill: #BCC5CC; font-family: var(--f-t); font-size: 22px;
               font-weight: 600; letter-spacing: .01em; }

/* THE WEDGE IS THE MOTION, and it is the same idea as the assembly below at a
   hundredth of the scale: a part arriving where it belongs, driven by the
   reader rather than by a timer. Scrubbed, so scrolling back lifts it out.
   The static page ships it SEATED — the proud state is opted into by script, so
   a blocked script leaves a finished joint rather than a wedge hanging in the
   air. Same architecture as the assembly, for the same reason. */
.jt-wedge { transition: transform var(--slow) var(--ease); }

@media (max-width: 1200px) {
  .joint { padding-block: var(--s-7) 0; }
  .joint__sub { margin-top: var(--s-4); }
  .joint__spec { margin-top: var(--s-5); }
  .joint__back { inset: auto -4% -2% auto; width: 100%; height: 62%; }
  .joint__in { grid-template-columns: minmax(0, 1fr); gap: var(--s-6);
               padding-right: max(var(--s-6), calc(50vw - var(--edge) / 2)); }
  /* NO NEGATIVE MARGIN BELOW THE BREAKPOINT. It used to pull the drawing 13vw
     left, which was right when the readable half of the drawing was its right
     half. Since the joint was handed round — tenon and dimension on the left,
     stretcher off to the right — pulling left crops the exact labels the
     handing-round existed to save. One column gets the drawing square on. */
  .joint__draw { margin: 0; }
  .joint__claim { max-width: 16ch; }
  .joint__sub { font-size: var(--t-0); max-width: 46ch; }
  .joint__spec { max-width: 52ch; }
}

/* THE ANNOTATION HAS TO STAY LEGIBLE AT EVERY DRAWING SCALE, and the drawing's
   scale is not the viewport's. The text column is a fixed 34rem, so in the two
   column regime the DRAWING column absorbs every pixel the window loses — at
   1100px it was down to 0.5 of the drawing's own units and setting 22px SVG
   text at seven real pixels. A label you can see and cannot read is not a
   label. So the one-column fold moved from 1020 to 1200, where the squeeze
   starts rather than where the layout finally gives up, and the two bands that
   still run the drawing small are set a step larger.

   26 rather than 30 is not caution: at 30 the wedge's label grows past x=360
   and onto the post, and a label on oak is the 2.39:1 failure again. */
@media (min-width: 1201px) and (max-width: 1439px) {
  .dw.jt .dimtext { font-size: 26px; }
}
@media (max-width: 640px) {
  .dw.jt .dimtext { font-size: 26px; }
}

/* Under 700px the drawing is small enough that two callouts on it is clutter,
   and the shoulder's label is the one furthest right — the first thing a narrow
   screen would cut in half. Cut it cleanly instead. The 2½″ dimension stays,
   because it is the evidence rather than the commentary. */
@media (max-width: 700px) {
  .dw.jt .lead--r { display: none; }
}

/* A DETAIL ON A PHONE IS CROPPED HARDER, NOT SHRUNK. Fitted to the column at
   390px the drawing renders its 22px annotation at nine, which is a drawing you
   can see and cannot read. Scaled by HEIGHT it crops off the right — the
   stretcher, which was always the thing to crop — and the annotation comes back
   to a legible size. The breakpoint is where fitting stops clearing 12px. */
@media (max-width: 640px) {
  /* the floor is 360 and not 300 because 50vh on a 667px-tall phone is 333,
     which scales the 26px annotation to eleven real pixels. 360 holds it at
     twelve, measured as font-size x the SVG's own rendered scale rather than
     guessed from a bounding box. */
  .joint__draw { height: clamp(360px, 50vh, 430px); }
  .joint__draw svg { height: 100%; width: auto; max-width: none; }
}

/* The masthead wrapped to three rows at 390 and took 190px off the top of every
   page before anything had been said. Nothing here is a fix for the joint; it is
   the same complaint about the same part of the screen. */
@media (max-width: 560px) {
  .mast__in { padding-block: var(--s-3); }
  .wordmark { min-height: 36px; }
  .wordmark__n { font-size: 1.22rem; }
  /* Five destinations onto ONE row. The first attempt at this only made three
     rows shorter, which is not the same thing, and it bought the space by
     dropping the tap target to 40px — under the 44px floor §12 sets and the
     one number on this site that is not negotiable. The height stays at 44;
     the row is bought back out of tracking and the gap instead. */
  .nav { gap: 0 var(--s-3); }
  .nav a { min-height: 44px; font-size: .75rem; letter-spacing: 0; gap: 4px; }
  .nav .tri { width: .52rem; height: .45rem; }
}
@media (prefers-reduced-motion: reduce) {
  /* every scroll transform resolves to its end state, and nothing is left
     depending on a scroll that will not be animated */
  .joint__back, .joint .jt-mid, .joint .jt-fore { transform: none !important; }
  .jt-wedge { transform: none !important; transition: none; }
}

.hero { padding-block: var(--s-9) var(--s-8); }
.hero__claim { max-width: 17ch; }
.hero__sub { margin-top: var(--s-5); max-width: 46ch; font-size: var(--t-1);
             line-height: 1.38; font-weight: 400; color: var(--ink); text-wrap: pretty; }

/* The assembly. §6.2 pin-and-advance, used once on the site and nowhere else.
   The subject genuinely changes — six parts become a table — and the end state
   stands alone as a still, which are two of the four conditions the pattern
   requires before it may be used at all. */
.assembly { position: relative; margin-top: var(--s-7); }
/* The pin exists only when the script is running it. A page whose script failed
   gets a finished table and no empty scroll — the travel is opted INTO, exactly
   as §6.3 requires of anything scroll-linked. */
/* min-height, NOT padding-bottom. A sticky element's travel is bounded by its
   parent's CONTENT box, and padding is not in the content box — pad the parent
   and the page gets the scroll length while the sticky child gets nowhere to
   go, which is silent and looks exactly like sticky being unsupported. */
#assembly.is-live .assembly { min-height: 212vh; }
#assembly.is-live .assembly__stick { position: sticky; top: var(--s-6); }
.assembly__stage { background: var(--panel); border: 1.5px solid var(--graphite); }
.assembly__stage svg { display: block; width: 100%; height: auto; }
.assembly__cap { display: flex; justify-content: space-between; gap: var(--s-5);
                 margin-top: var(--s-3); align-items: baseline; }
.pt { transition: transform var(--slow) var(--ease); }
.pt--hidden .ghost { fill: none; stroke: var(--graphite); stroke-width: 1; stroke-dasharray: 6 5; opacity: .5; }

/* the drawing's own line system, shared by every drawing on the site */
.dw { display: block; width: 100%; height: auto; }
.dw .edge { fill: none; stroke: var(--graphite); stroke-width: 1.1; }
.dw .ext, .dw .dimline, .dw .floor { fill: none; stroke: var(--graphite); stroke-width: .9; }
.dw .ext { opacity: .5; }
.dw .floor { opacity: .45; }
.dw .dimtext { font-family: var(--f-t); font-size: 12.5px; font-weight: 600;
               fill: var(--graphite); letter-spacing: .01em; }
.dw .ghost { fill: none; stroke: var(--graphite); stroke-width: .9;
             stroke-dasharray: 6 5; opacity: .55; }

/* ---- the editorial rail. One sustained claim; the rail carries the dates. */
.rail { display: grid; grid-template-columns: 12rem 1fr; gap: var(--s-8); align-items: start; }
.rail__side { position: sticky; top: var(--s-6); }
.rail__side dl { margin: 0; display: grid; gap: var(--s-4); }
.rail__side dt { font-size: .71875rem; text-transform: uppercase; letter-spacing: .085em;
                 color: var(--ink-2); font-weight: 700; }
.rail__side dd { margin: 0; font-family: var(--f-d); font-variation-settings: 'opsz' 16;
                 font-weight: 600; font-size: var(--t-1); line-height: 1.1; }

/* ---- the collage. Pieces at genuinely unequal scale, composed on the grid
   and allowed to overlap — §4.1's cheapest and strongest depth cue, and the
   only place on the site where anything overlaps anything. */
.collage { display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--s-5);
           align-items: end; }
.cl { position: relative; }
.cl--a { grid-column: 1 / span 7; }
.cl--b { grid-column: 8 / span 5; margin-bottom: var(--s-9); }
.cl--c { grid-column: 3 / span 6; margin-top: calc(var(--s-8) * -1); z-index: 2; }
.cl--d { grid-column: 9 / span 4; }
.cl__fig { background: var(--panel); border: 1.5px solid var(--graphite); display: block; }
.cl a { text-decoration: none; color: inherit; display: block; }
.cl a:hover .cl__name { text-decoration: underline; text-underline-offset: 4px; }
.cl__meta { display: flex; justify-content: space-between; gap: var(--s-4);
            margin-top: var(--s-3); align-items: baseline; }
.cl__name { font-family: var(--f-d); font-variation-settings: 'opsz' 16; font-weight: 600;
            font-size: var(--t-1); line-height: 1.1; }

/* ================================================================ THE STAGE
   The one ground change on the page (§3.3), and it coincides with a change of
   job — Explore becomes Configure. Furniture is shot against a dark ground in
   every auction catalog ever printed, for the reason it works here: wood is
   the only warm thing in the frame and the ground gets out of its way. */
.stage { background: var(--stage); color: var(--stage-on); }
.stage a:not(.btn) { color: var(--delft-lt); }
.stage .sc, .stage .note { color: var(--stage-ink2); }
.stage .rule::after { background: var(--stage-ink2); opacity: .45; }
.stage .rule .tri { color: var(--stage-ink2); }
.stage .dw .edge, .stage .dw .ext, .stage .dw .dimline, .stage .dw .floor { stroke: var(--stage-ink2); }
.stage .dw .dimtext { fill: var(--stage-ink2); }
.stage .dw .ghost { stroke: var(--stage-ink2); }
.stage .assembly__stage, .stage .cl__fig { background: var(--stage-2); border-color: #38424B; }

/* ============================================================ CONFIGURATOR
   Split, 60/40 (§1.3 — a 50/50 is the least interesting version). Controls on
   one side and the consequence on the other, so a change shows its result in
   the same glance. */
.cfg { display: grid; grid-template-columns: minmax(0, 1.35fr) minmax(320px, .85fr);
       gap: var(--s-8); align-items: start; }
.cfg__view { position: sticky; top: var(--s-5); }
.cfg__frame { background: var(--stage-2); border: 1.5px solid #38424B; padding: var(--s-4); }
.cfg__views { display: flex; gap: var(--s-2); margin-bottom: var(--s-4); flex-wrap: wrap; }
.vbtn { min-height: 44px; padding: var(--s-2) var(--s-5); border: 1.5px solid #47535D;
        background: transparent; color: var(--stage-ink2); font: inherit; font-size: var(--t--1);
        font-weight: 600; cursor: pointer; }
.vbtn[aria-pressed="true"] { background: var(--stage-on); color: var(--stage);
                             border-color: var(--stage-on); }

.fields { display: grid; gap: var(--s-6); margin: 0; padding: 0; border: 0; }
.field { display: grid; gap: var(--s-3); }
.field__head { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s-4); }
.field__val { font-family: var(--f-d); font-variation-settings: 'opsz' 16; font-weight: 600;
              font-size: var(--t-1); line-height: 1; }

/* Swatches carry the species. Color is never the only cue — every swatch
   names its wood, and the checked one takes a rule and the triangle as well. */
.swatches { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s-3); }
/* The full-width picker is a rack of six to be compared at a glance, not three
   posters. The sidebar picker stays at three because it has a sidebar's width. */
.swatches--rack { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.sw { position: relative; }
.sw input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.sw__face { display: block; border: 1.5px solid #47535D; cursor: pointer; }
.sw__img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.sw__name { display: flex; align-items: center; gap: var(--s-2); padding: var(--s-2) var(--s-3);
            font-size: var(--t--1); font-weight: 600; background: var(--stage-2);
            color: var(--stage-on); }
.sw__name .tri { opacity: 0; color: var(--delft-lt); }
.sw input:checked + .sw__face { border-color: var(--stage-on); border-width: 3px; }
.sw input:checked + .sw__face .sw__name .tri { opacity: 1; }
.sw input:focus-visible + .sw__face { outline: 3px solid var(--delft-lt); outline-offset: 3px; }

.opts { display: grid; gap: var(--s-2); }
.opt { display: grid; grid-template-columns: auto 1fr; gap: var(--s-3);
       padding: var(--s-3) var(--s-4); border: 1.5px solid #47535D; cursor: pointer; }
.opt:hover { border-color: var(--stage-ink2); }
.opt input { margin: 0; width: 18px; height: 18px; align-self: center; }
.opt:has(input:checked) { border-color: var(--stage-on); background: rgba(255,255,255,.045); }
.opt__n { font-weight: 600; }
.opt__d { display: block; font-size: var(--t--1); color: var(--stage-ink2); line-height: 1.45; }

input[type="range"] { width: 100%; height: 44px; }

/* the quote. Broken down, always — a shop that will not tell you what the
   timber cost is hiding one of the two numbers. */
.quote { border-top: 1.5px solid #38424B; margin-top: var(--s-6); padding-top: var(--s-5); }
.qline { display: grid; grid-template-columns: 1fr auto; gap: var(--s-3) var(--s-5);
         padding: var(--s-3) 0; border-bottom: 1px solid #2C353D; }
.qline__k { font-weight: 600; }
.qline__d { grid-column: 1; font-size: var(--t--1); color: var(--stage-ink2); }
.qline__v { grid-row: 1; grid-column: 2; font-variant-numeric: tabular-nums; font-weight: 600; }
.qtotal { display: flex; justify-content: space-between; align-items: baseline;
          gap: var(--s-5); margin-top: var(--s-5); }
.qtotal__v { font-family: var(--f-d); font-variation-settings: 'opsz' 22; font-weight: 650;
             font-size: var(--t-2); line-height: 1; }

/* ============================================================== THE INDEX
   Reference, not reading. Someone is looking up a species. */
.tbl { width: 100%; border-collapse: collapse; font-variant-numeric: tabular-nums lining-nums; }
.tbl caption { text-align: left; padding-bottom: var(--s-4); }
.tbl th, .tbl td { text-align: left; padding: var(--s-4) var(--s-4) var(--s-4) 0;
                   border-bottom: 1px solid var(--line); vertical-align: baseline; }
.tbl thead th { border-bottom: 1.5px solid var(--graphite); font-size: .71875rem;
                text-transform: uppercase; letter-spacing: .085em; font-weight: 700;
                color: var(--ink-2); }
.tbl td.num, .tbl th.num { text-align: right; padding-right: var(--s-5); }
.scroller { overflow-x: auto; }

.species { display: grid; grid-template-columns: 20rem 1fr; gap: var(--s-7);
           align-items: start; padding-block: var(--s-8);
           border-top: 1.5px solid var(--graphite); }
.species:first-of-type { border-top: 0; }
.species__img { display: block; width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
                border: 1.5px solid var(--graphite); }
.spec { display: flex; flex-wrap: wrap; gap: var(--s-5) var(--s-7); margin: var(--s-5) 0 0; }
.spec div { display: grid; gap: 2px; }
.spec dt { font-size: .71875rem; text-transform: uppercase; letter-spacing: .085em;
           color: var(--ink-2); font-weight: 700; }
.spec dd { margin: 0; font-family: var(--f-d); font-variation-settings: 'opsz' 16;
           font-weight: 600; font-size: var(--t-1); line-height: 1; }

/* the cut list — an actual working document, which is why it is numbered:
   the order IS the content, and that is the only thing §12.0a permits a number
   to mark. Parts are made in this order because a mortise is cut before its
   tenon and a wedge is fitted last. */
.cut td:first-child { color: var(--ink-2); width: 3ch; }


/* ================================================================ FIGURES
   FULL COLOUR, BY DIRECTION. The first pass graded every photograph to a
   duotone and argued itself into it; the client's standing instruction is that
   duotone is not a default, here or anywhere. They were right on the merits
   too: the warmth of real timber inside the photographs makes the cold-page /
   warm-wood argument better than draining them ever did.

   What unifies the set is what §5.2 actually asks for — one crop (3:2), one
   light grade (gentle S, lifted floor, a whisper of cool in the shadows so the
   images sit on the blue-gray ground), one edge rule (the graphite hairline).
   The archive images stay monochrome because 1924 was monochrome.

   The bleed is a real width break, done by dropping the shell rather than by
   100vw, because 100vw includes the scrollbar and quietly pushes a horizontal
   scroll. */
.fig { margin: 0; }
.fig img { display: block; width: 100%; height: auto; border: 1.5px solid var(--graphite); }
.fig figcaption { margin-top: var(--s-3); font-size: var(--t--1); line-height: 1.5;
                  color: var(--ink-2); max-width: 64ch; }
/* A bleed caption has to align with the page column, not float in the middle of
   it. .shell centers itself with auto margins, so leaving the 64ch measure on
   the same element made the caption 500px wide and centered it in the viewport.
   The shell does the alignment; an inner block keeps the measure. */
.fig--bleed figcaption { max-width: none; }
.capw { display: block; max-width: 64ch; }
.fig--bleed img { border-left: 0; border-right: 0;
                  /* a band, not a screen. At 3:2 across a wide viewport this
                     runs almost a full screen tall, which stops reading as a
                     division and starts reading as a destination. */
                  max-height: 56vh; object-fit: cover; object-position: 50% 44%; }
.credit { display: block; margin-top: var(--s-2); opacity: .75; }
.two-up { display: grid; grid-template-columns: 1.35fr 1fr; gap: var(--s-6); align-items: start; }
.stage .fig img { border-color: #38424B; }
.stage .fig figcaption { color: var(--stage-ink2); }
@media (max-width: 760px) {
  .two-up { grid-template-columns: 1fr; gap: var(--s-5); }
}

/* ================================================================== AXIAL
   Genuine symmetry with one thing at the center worth centering: the single
   action this whole site exists to reach. */
.axial { text-align: center; display: grid; justify-items: center; gap: var(--s-5); }
.axial .prose { text-align: left; }

.foot { border-top: 1.5px solid var(--graphite); padding-block: var(--s-8); }
.foot__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
              gap: var(--s-7); }
.foot h2 { font-size: var(--t--1); font-weight: 700; letter-spacing: .005em;
           color: var(--ink-2); margin: 0 0 var(--s-3); }
.foot ul { list-style: none; margin: 0; padding: 0; display: grid; }
.foot li { display: flex; align-items: center; min-height: 44px; }
.foot a { display: inline-flex; align-items: center; min-height: 44px; }
.disclaim { margin-top: var(--s-7); padding-top: var(--s-5);
            border-top: 1px solid var(--line); }

/* =============================================================== RESPONSIVE
   E=2, so this is designed at desk width and folded down — but the fold is a
   real design pass, not a stack. The configurator puts the drawing FIRST on a
   phone, because the drawing is the answer and the controls are the question. */
@media (max-width: 900px) {
  .rail { grid-template-columns: 1fr; gap: var(--s-6); }
  .rail__side { position: static; }
  .rail__side dl { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
  .cfg { grid-template-columns: 1fr; }
  .cfg__view { position: static; }
  .species { grid-template-columns: 1fr; gap: var(--s-5); }
  /* ONE COLUMN, NOT TWELVE COLUMNS EACH SPANNED IN FULL. Every cell already
     spans 1/-1 here, so the twelve tracks do nothing — but their ELEVEN GAPS
     survive, and 11 x 32px is 352px of grid that cannot shrink inside a 272px
     shell. That is the whole of the 56px overflow this page had at 320: not
     the masthead, not the drawing, just eleven gaps between columns nobody
     could see. A stack does not need a grid to stack on. */
  .collage { grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
  .cl--a, .cl--b, .cl--c, .cl--d { grid-column: 1 / -1; margin: 0; }
  .band { padding-block: var(--s-8); }
  .band--loose { padding-block: var(--s-9); }
  .shell { width: min(100% - var(--s-5) * 2, var(--edge)); }
}
@media (max-width: 560px) {
  .swatches { grid-template-columns: repeat(2, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  .pt { transition: none; }
  html { scroll-behavior: auto; }
  /* resolve the scrubbed sequence to its end state and give the scroll length
     back — holding somebody in a pin they cannot see the point of is worse
     than not having built it */
  #assembly.is-live .assembly { min-height: 0; }
  #assembly.is-live .assembly__stick { position: static; }
}

/* ====================================================== HIGH CONTRAST MODE
   Learned on the last build: repair anything whose meaning lived in a
   background, and do not opt out of the user's color choice. The chamfer is
   geometry rather than color, so it survives untouched. */
@media (forced-colors: active) {
  .sw input:checked + .sw__face { border-width: 3px; }
  .vbtn[aria-pressed="true"] { border-width: 3px; }
  .opt:has(input:checked) { border-width: 3px; }
  .ch:focus-visible { box-shadow: none; outline: 3px solid CanvasText; outline-offset: -6px; }
  .dw .edge, .dw .ext, .dw .dimline, .dw .floor, .dw .ghost { stroke: CanvasText; }
}

/* ---------- Built by Iron Digital MI ----------
   currentColor throughout so the line inherits whatever the footer already is.
   The mark itself is never recolored. */
.id-by { margin: 1.6em 0 0; }
.id-by a { display: inline-flex; align-items: center; gap: .6em;
  color: inherit; text-decoration: none; font-size: .8125rem; line-height: 1.3; }
.id-by a:hover span, .id-by a:focus-visible span { text-decoration: underline; }
.id-by img { width: 26px; height: 26px; display: block; flex: 0 0 auto; }
.id-by b { font-weight: 700; }

/* September UI refinement */
@media(max-width:560px){.mast__in{gap:8px}.nav{flex-wrap:nowrap;overflow-x:auto;width:100%;gap:12px}.nav a{white-space:nowrap;font-size:14px}.nav .tri{display:none}.joint__in{padding-block:24px;gap:16px}.joint__claim{font-size:clamp(38px,10vw,52px)}.joint__sub{font-size:16px}.joint__spec{font-size:14px;margin-top:12px}.joint__draw{height:320px}}
