/* Torspan — "Shop Drawing" design system
   Direction A from build/04-design-benchmarks.md. Warm paper, hard ink,
   one orange that only ever means "act here", numbers in mono. */

/* ---------- fonts ---------- */
@font-face{font-family:Archivo;src:url(/fonts/archivo.woff2)format("woff2");font-weight:100 900;font-stretch:62% 125%;font-display:swap}
@font-face{font-family:InterVar;src:url(/fonts/inter.woff2)format("woff2");font-weight:100 900;font-display:swap}
@font-face{font-family:MonoVar;src:url(/fonts/mono.woff2)format("woff2");font-weight:100 800;font-display:swap}

/* ---------- tokens ---------- */
:root{
  --paper:#FBFAF7; --paper-2:#F2F0EA; --paper-3:#E7E4DC;
  --rule:#DFDBD2; --rule-hard:#B9B4A8;
  --ink:#14130F; --ink-2:#45423B; --ink-3:#6B675E;
  --signal:#B4430F; --signal-ink:#8A330B; --linework:#2E7F96;
  /* --signal is 3.27:1 on --chrome and 2.94:1 on --chrome-2: LIGHT SURFACES ONLY.
     This is the same "act here" at the same CIELAB hue 49, lifted to L* 71 --
     8.21:1 on --chrome, 7.38:1 on --chrome-2, 8.79:1 on the instrument surface.
     The retired #E8A33D sat 25.5 deg off the estate's own warm hue and measured
     dE00 2.64 from DeWalt yellow #EDA220. Do not restore it. */
  --signal-on-dark:#FA9366;
  --chrome:#16150F; --chrome-2:#221F18; --on-chrome:#FBFAF7; --on-chrome-2:#CFC9BC;
  --rule-a2:#14130F14; --rule-a3:#14130F26; --rule-on-dark:#ffffff26;

  --w-regular:400; --w-medium:520; --w-semibold:600; --w-bold:700;

  /* The preferred term, not the min, is what stair-stepped the home H1 into
     four ragged lines at 390 ("The paperwork's" alone is 349px at 41px type).
     Measured: 36px at 390 breaks it into three near-full lines and the clamp
     still reaches 72px, so nothing above 875px moves. */
  --display:clamp(2.25rem,.44rem + 7.42vi,4.5rem);
  --display-tracking:clamp(-1.6px,-0.6px - 0.12vw,-0.5px);
  --t-1:clamp(2rem,1.3rem + 2.6vi,3.5rem);
  --t-2:clamp(1.75rem,1.2rem + 1.9vi,2.75rem);
  --t-3:clamp(1.5rem,1.15rem + 1.1vi,2rem);
  /* --t-4 and --t-5 were fixed while every step above them was clamped, so on a
     phone the scale collapsed: h2:h3 measured 1.167 at 390 against 1.81 at 1280,
     i.e. a section heading and a card heading ranked the same on the device that
     carries the traffic. Both clamps top out at their old value, so desktop is
     untouched. */
  --t-4:clamp(1.3125rem,1.19rem + .49vi,1.5rem);
  --t-5:clamp(1.125rem,1.05rem + .3vi,1.25rem);
  --t-6:1.0625rem;
  --body:1.0625rem; --body-lg:1.1875rem; --label:.8125rem;

  --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;

  --page:1120px; --gutter:20px;
  --dur-instant:100ms; --dur-quick:180ms; --dur-calm:320ms;
  --ease:cubic-bezier(.2,0,0,1);
}
@media(min-width:744px){:root{--gutter:32px}}
@media(min-width:1100px){:root{--gutter:48px}}

/* ---------- base ---------- */
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth}
body{
  margin:0;background:var(--paper);color:var(--ink);
  font-family:InterVar,-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
  font-size:var(--body);line-height:1.55;font-weight:var(--w-regular);
  font-feature-settings:"cv01","ss03";font-optical-sizing:auto;
  -webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility;
}
h1,h2,h3,h4{font-family:Archivo,InterVar,system-ui,sans-serif;font-variation-settings:"wdth" 112;margin:0;text-wrap:balance}
h1{font-size:var(--display);line-height:1.02;letter-spacing:var(--display-tracking);font-weight:var(--w-bold)}
h2{font-size:var(--t-2);line-height:1.06;letter-spacing:-.022em;font-weight:var(--w-bold)}
h3{font-size:var(--t-4);line-height:1.25;letter-spacing:-.012em;font-weight:var(--w-semibold)}
h4{font-size:var(--t-5);line-height:1.3;letter-spacing:-.012em;font-weight:var(--w-semibold)}
p{margin:0 0 var(--s-5);max-width:66ch}
a{color:var(--signal-ink);text-decoration:none;border-bottom:1px solid var(--rule-hard);transition:border-color var(--dur-instant) var(--ease),color var(--dur-instant) var(--ease)}
a:hover{border-bottom-color:var(--signal)}
strong{font-weight:var(--w-semibold)}
hr{border:0;border-top:1px solid var(--rule);margin:var(--s-8) 0}
img{max-width:100%;height:auto;display:block}
::selection{background:#B4430F26}
:focus-visible{outline:2px solid var(--signal);outline-offset:3px;border-radius:2px}

.wrap{width:100%;max-width:var(--page);margin-inline:auto;padding-inline:var(--gutter)}
.narrow{max-width:42rem}
.mono{font-family:MonoVar,ui-monospace,SFMono-Regular,Menlo,monospace;font-variant-numeric:tabular-nums}
.label{font-family:MonoVar,ui-monospace,monospace;font-size:var(--label);letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2);font-weight:var(--w-medium)}
.lead{font-size:var(--body-lg);color:var(--ink-2);max-width:60ch}
.muted{color:var(--ink-2)}
.center{text-align:center;margin-inline:auto}

/* ---------- header ---------- */
.site-head{position:sticky;top:0;z-index:60;background:var(--chrome);color:var(--on-chrome)}
.site-head > .wrap{display:flex;align-items:center;gap:var(--s-5);min-height:60px}
.mobile-nav .wrap{display:block}
.brand{display:flex;align-items:center;min-height:44px;gap:10px;color:var(--on-chrome);border:0;font-family:Archivo,sans-serif;font-weight:var(--w-bold);font-size:1.15rem;letter-spacing:-.01em;font-variation-settings:"wdth" 112}
.brand svg,.brand img{display:block;flex:none}
.brand img{width:auto;height:30px;object-fit:contain}
.foot-brand .brand img{width:auto;height:30px}
.nav{display:none;margin-left:auto;align-items:center;gap:var(--s-2);align-self:stretch}
@media(min-width:1000px){.nav{display:flex}}
/* The trigger has to fill the bar's height or the panel below it can only start
   inside the dark band: measured .nav-item bottom 49px against a 60px header,
   so the paper panel painted over the last 5px of chrome and left a 6px dead
   strip between button and panel that dropped :hover on the way down. */
.nav-item{position:relative;display:flex;align-items:center;align-self:stretch}
.nav-link{display:inline-flex;align-items:center;gap:6px;padding:9px 12px;color:var(--on-chrome-2);border:0;font-size:.95rem;font-weight:var(--w-medium);border-radius:3px;background:none;cursor:pointer;font-family:inherit}
.nav-link:hover,.nav-item:hover .nav-link{color:var(--on-chrome);background:var(--chrome-2)}
/* The burger is display:none from 1000px up, so in that band these ARE the
   navigation — and the band starts at an iPad in landscape, which has no
   pointer. Measured on torspan.com at 1000, 1024, 1280 and 1440: five links,
   every one 42px tall against a 44px floor. The bar is 60px, so 44 fits inside
   it and nothing above the fold moves. In the shared base because the header is
   the same component on every property. */
.nav-link{min-height:44px}
.nav-link[aria-current]{color:var(--signal-on-dark)}  /* --signal is only 3.27:1 on --chrome */
.nav-link .chev{width:9px;height:9px;opacity:.65}
.menu{position:absolute;top:100%;left:0;min-width:250px;max-height:calc(100dvh - 72px);overflow-y:auto;background:var(--paper);border:1px solid var(--rule-hard);border-radius:4px;padding:var(--s-2);box-shadow:0 14px 40px #14130f1f;opacity:0;visibility:hidden;transform:translateY(-4px);transition:opacity var(--dur-quick) var(--ease),transform var(--dur-quick) var(--ease),visibility var(--dur-quick) step-end}
/* aria-expanded on those buttons was a standing lie: the panels opened on hover
   and focus only, so at 1000-1279px — an iPad in landscape, where .burger is
   display:none — the primary navigation needed a pointer that a touch screen
   does not have. The attribute the markup already carried now drives the panel. */
.nav-item:hover .menu,
.nav-link[aria-expanded="true"] + .menu{opacity:1;visibility:visible;transform:none}
.menu a{display:block;padding:9px 12px;color:var(--ink);border:0;border-radius:3px;font-size:.95rem}
.menu a:hover{background:var(--paper-2);color:var(--signal-ink)}
.menu a[aria-current]{color:var(--signal-ink)}  /* current page; was 18 inline styles */
.menu a span{display:block;color:var(--ink-2);font-size:.8125rem;line-height:1.35;margin-top:2px}
.head-cta{margin-left:auto;display:flex;align-items:center;gap:var(--s-3)}
@media(min-width:1000px){.head-cta{margin-left:var(--s-4)}}
/* white-space: a label that breaks mid-phrase ("See it / work") is never the
   right answer, and in the header it grew the sticky bar to 79px at 320px wide,
   which the drawer's own max-height:calc(100dvh - 60px) then measured wrong. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:11px 18px;min-height:44px;border-radius:3px;font-weight:var(--w-semibold);font-size:.95rem;border:1px solid transparent;cursor:pointer;transition:color var(--dur-instant) var(--ease),background-color var(--dur-instant) var(--ease),border-color var(--dur-instant) var(--ease),transform var(--dur-instant) var(--ease-out),box-shadow var(--dur-instant) var(--ease);font-family:inherit;line-height:1.2;white-space:nowrap}
.btn-primary{background:var(--signal);color:#fff;border-color:var(--signal)}
.btn-primary:hover{background:var(--signal-ink);border-color:var(--signal-ink)}
.btn-ghost{color:var(--ink);border-color:var(--rule-hard);background:none}
.btn-ghost:hover{border-color:var(--ink);background:var(--paper-2)}
.btn-on-dark{color:var(--on-chrome);border-color:#ffffff33;background:none}
.btn-on-dark:hover{border-color:var(--on-chrome);background:#ffffff14}
/* .head-cta already carries margin-left:auto. A second one here split the
   leftover space between the two of them, so the button landed wherever the
   arithmetic put it — 36px/36px at 390, x=386-505 in an 820px bar. One auto,
   one pair, flush right. */
.burger{display:inline-flex;align-items:center;justify-content:center;background:none;border:1px solid #ffffff2e;border-radius:3px;padding:9px 11px;min-width:44px;min-height:44px;color:var(--on-chrome);cursor:pointer}
@media(min-width:1000px){.burger{display:none}}
/* Under 375px the bar cannot hold wordmark + button + burger without the button
   breaking in half. The burger is the one that has to survive; the CTA is a
   duplicate of the hero's primary and of a drawer row. */
@media(max-width:374px){.head-cta{display:none}.burger{margin-left:auto}}
.burger svg{display:block}
.mobile-nav{display:none;background:var(--chrome);border-top:1px solid #ffffff1a;padding:var(--s-4) 0 var(--s-6);
  max-height:calc(100vh - 60px);max-height:calc(100dvh - 60px);overflow-y:auto;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
.mobile-nav.open{display:block}
@media(min-width:1000px){.mobile-nav.open{display:none}}
.mobile-nav .group{padding:var(--s-3) 0;border-bottom:1px solid #ffffff14}
.mobile-nav .group:last-child{border-bottom:0}
/* one declaration; this was two rules two lines apart, which is the residue the
   overlapping-columns bug came out of */
.mobile-nav .label{color:#8c8779;margin-bottom:var(--s-2);display:block;
  white-space:normal;overflow-wrap:anywhere;max-width:100%}
.mobile-nav a{display:block;padding:11px 0;color:var(--on-chrome);border:0;font-size:1.05rem}
/* On the iPad the drawer was one 756px column, 1315px tall inside 1119px of
   screen — a menu you have to scroll while 60% of the width is empty. Measured
   with columns:2 at 820: 723px, scrollHeight === clientHeight, whole menu on
   one screen. Groups stay intact, so no cluster splits across the gutter. */
@media(min-width:744px) and (max-width:999px){
  .mobile-nav .wrap{columns:2;column-gap:var(--s-8)}
  .mobile-nav .group{break-inside:avoid}
}

/* ---------- sections ---------- */
/* 96px top AND bottom meant every boundary spent 192px of a 844px phone, on
   pages already running 20-39 screens. The step now scales with the screen it
   is pacing; 1000px up is exactly what it was. */
.section{padding-block:var(--s-8)}
@media(min-width:744px){.section{padding-block:var(--s-9)}}
@media(min-width:1000px){.section{padding-block:var(--s-11)}}
.section-tight{padding-block:var(--s-8)}
.section-paper2{background:var(--paper-2)}
.section-chrome{background:var(--chrome);color:var(--on-chrome)}
.section-chrome h2,.section-chrome h3{color:var(--on-chrome)}
.section-chrome p{color:var(--on-chrome-2)}
.section-chrome .label{color:#8c8779}
.section-chrome a{color:var(--signal-on-dark);border-bottom-color:#ffffff2e}
.rule-top{border-top:1px solid var(--rule)}
.section-head{max-width:52rem;margin-bottom:var(--s-7)}
.section-head .label{display:block;margin-bottom:var(--s-3)}
.section-head p{margin-top:var(--s-4);color:var(--ink-2);font-size:var(--body-lg)}

/* ---------- hero ---------- */
.hero{padding-block:var(--s-8) var(--s-9);border-bottom:1px solid var(--rule)}
@media(min-width:1000px){.hero{padding-block:var(--s-9) var(--s-10)}}
/* 999px was the only pill in a sheet whose every other corner is 2, 3 or 4px —
   a SaaS badge in a shop-drawing system, used 18 times. */
.hero .kicker{display:inline-flex;align-items:center;gap:9px;border:1px solid var(--rule-hard);border-radius:3px;padding:6px 13px;margin-bottom:var(--s-6)}
.hero h1{max-width:17ch}
.hero .lead{margin-top:var(--s-5);font-size:clamp(1.125rem,1rem + .6vi,1.375rem);line-height:1.45}
.hero-actions{display:flex;flex-wrap:wrap;gap:var(--s-3);margin-top:var(--s-7)}
.hero-note{margin-top:var(--s-5);color:var(--ink-3);font-size:.9375rem}

/* ---------- grids & cards ---------- */
.grid{display:grid;gap:var(--s-5)}
/* A grid item's automatic minimum is min-content, so one wide child (a table
   carrying min-width:520px) blows the whole track past the viewport and the
   PAGE scrolls sideways instead of the table scrolling inside its wrap. */
.grid>*{min-width:0}
.g2{grid-template-columns:1fr}
.g3{grid-template-columns:1fr}
@media(min-width:744px){.g2{grid-template-columns:repeat(2,1fr)}.g3{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1000px){.g3{grid-template-columns:repeat(3,1fr)}}
/* .g2 split at 744, so an 820 iPad — the width this site is designed for first —
   gave a table 330-366px against the 348px the same table gets on a 390 phone.
   Measured on /hours at 820: the crew week showed Mon-Wed of a five-day week
   beside a paragraph about Wednesday, and the payroll CSV showed 4 of 7 columns
   beside a paragraph about columns 5, 6 and 7. Eight clipped blocks became zero
   with this one rule. Browsers without :has() get today's behaviour. */
@media(max-width:999px){
  .g2:has(.table-wrap),.g2:has(.scroll-x){grid-template-columns:1fr}
}
.card{background:var(--paper);border:1px solid var(--rule);border-radius:4px;padding:var(--s-5)}
.card h3{margin-bottom:var(--s-3)}
.card p:last-child{margin-bottom:0}
/* .card-flat is a .card variant and /company's numbered blocks are built from
   it; without it here the page hand-rolled the same recipe inline ten times and
   drifted 4px of margin off the component. */
.card .num,.card-flat .num{font-family:MonoVar,monospace;font-size:var(--label);color:var(--signal-ink);letter-spacing:.06em;display:block;margin-bottom:var(--s-3)}
.card-flat{background:none;border:0;border-top:1px solid var(--rule-hard);border-radius:0;padding:var(--s-5) 0 0}

/* ---------- doctrine / quote ---------- */
.doctrine{border-left:3px solid var(--linework);padding:var(--s-2) 0 var(--s-2) var(--s-5);max-width:38rem}
.doctrine p{font-family:Archivo,sans-serif;font-variation-settings:"wdth" 112;font-size:var(--t-4);line-height:1.28;letter-spacing:-.012em;color:var(--ink);font-weight:var(--w-medium);margin:0}
.wall-line{font-family:Archivo,sans-serif;font-variation-settings:"wdth" 112;font-size:var(--t-3);line-height:1.15;letter-spacing:-.022em;font-weight:var(--w-bold);max-width:22ch;margin:0}

/* ---------- artifact: DOM-built product UI ---------- */
.artifact{background:var(--paper-2);border:1px solid var(--rule-hard);border-radius:4px;overflow:hidden}
/* flex-wrap:wrap places an item by its HYPOTHETICAL width, so min-width:0 on the
   title does not save it: the moment the title exceeded the bar minus the dot,
   the whole title jumped to line two and left an 8px dot alone on a 20px line.
   11 of 46 bars at 390. nowrap + flex:1 1 auto lets the title wrap inside the
   bar, beside its dot, where it belongs. */
.artifact-bar{display:flex;flex-wrap:nowrap;align-items:center;gap:8px;padding:10px 14px;background:var(--paper-3);border-bottom:1px solid var(--rule-hard)}
.artifact-bar .dot{width:8px;height:8px;border-radius:50%;background:var(--rule-hard);flex:none}
.artifact-bar .t{font-family:MonoVar,monospace;font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-2);
  flex:1 1 auto;min-width:0;overflow-wrap:anywhere}
/* .record-row is also used directly in an artifact body, without a .record
   wrapper, so the container below has to exist at both levels or the same row
   stacks on one page and sets ragged-left on another. Nearest container wins,
   so a .record inside an .artifact-body still measures itself. */
.artifact-body{padding:var(--s-5);container-type:inline-size}
/* 80% is a messaging-app convention for bubbles that ALTERNATE sides. Every
   incoming bubble here is left-aligned, so the 20% was permanently dead: 240px
   of bubble inside a 348px phone, and the long reply on the home page ran 13
   lines. .msg.out stays inset because that offset is the only thing saying "a
   person said this"; it is also dark-on-paper, so the grammar survives. */
.msg{max-width:min(100%,46rem);background:var(--paper);border:1px solid var(--rule);border-radius:4px;padding:12px 14px;margin-bottom:var(--s-3);font-size:1rem}
.msg.out{margin-left:auto;max-width:min(88%,42rem);background:var(--chrome);color:var(--on-chrome);border-color:var(--chrome)}
.msg .who{display:block;font-family:MonoVar,monospace;font-size:.6875rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3);margin-bottom:5px}
.msg.out .who{color:#9a9483}
.record{background:var(--paper);border:1px solid var(--rule-hard);border-radius:4px;padding:var(--s-4);margin-top:var(--s-4);container-type:inline-size}
.record-row{display:flex;justify-content:space-between;gap:var(--s-4);padding:9px 0;border-bottom:1px solid var(--rule);font-size:.9375rem}
.record-row:last-child{border-bottom:0}
.record-row .k{font-family:MonoVar,monospace;font-size:.75rem;letter-spacing:.05em;text-transform:uppercase;color:var(--ink-3);white-space:nowrap}
.record-row .v{text-align:right;font-weight:var(--w-medium)}
.record-row .v.blank{color:var(--signal-ink);font-family:MonoVar,monospace;font-size:.875rem}
/* The two-column spec-sheet read is right when the row is wide. When it is not,
   the value wraps and sets as ragged-LEFT prose with no fixed edge to scan
   down — 62 of 142 rows at 390, 50 at 820, and "AM" alone on a right-aligned
   second line. A viewport query is the wrong instrument: rows measure 266-332px
   at 820 against 316px at 390, because the row's width comes from its card, not
   from the screen. Fires at 390 and 820, does not fire at 1280. */
@container (max-width:380px){
  .record-row{display:block}
  .record-row .k{display:block;white-space:normal;margin-bottom:2px}
  .record-row .v{display:block;text-align:left}
}
/* .cite is named for citations and carries the editorial asides: 163 blocks and
   ~4,100 words of running prose set in mono site-wide. The system says numbers
   in mono — and it is making prose sans that lets the CSV, the email headers and
   the record values read as machine output instead of as more commentary.
   Strings that genuinely are machine output carry .mono inside the block. */
.cite{font-family:InterVar,-apple-system,BlinkMacSystemFont,"Segoe UI",system-ui,sans-serif;
  font-size:.9375rem;color:var(--ink-2);border-top:1px dashed var(--rule-hard);margin-top:var(--s-4);padding-top:var(--s-3)}

/* ---------- blocks that scroll sideways ----------
   Ten <pre> blocks on eight pages carried the same nine inline declarations,
   which is how the two that differ (the CSV sample, the day-rate maths) drifted
   a size and a leading away from the other eight. One class; the variants say
   what they are with one property each.

   Measured 2026-08-22 at 390px and at 820px: nine of the ten clip their longest
   line — the CSV sample by 279px, the export tree by 262px — at BOTH widths,
   because both put them in a half-width grid column. Eleven .table-wraps clip
   the same way. A row of CSV is wider than a phone at any type size, so the
   content has to scroll; what was missing was any sign that it could. iOS and
   iPadOS draw an overlay scrollbar that appears only once you have already
   scrolled, so on the surface this site is built for first, the sixth column of
   the payroll file — the column the paragraph beside it is entirely about —
   just looked like it wasn't there.

   Two gradients per edge. The pair painted in the block's own --cb-bg are
   attached `local`, so they ride the content: while there is nothing to scroll
   to they sit over the shadow pair and hide it, and they slide away to uncover
   a shadow the moment there is. No JS, nothing to keep in sync, and it costs
   nothing when the block already fits. */
.scroll-x,.table-wrap{
  --cb-bg:var(--paper);
  overflow-x:auto;overscroll-behavior-x:contain;-webkit-overflow-scrolling:touch;
  background-image:
    linear-gradient(to right,var(--cb-bg),rgba(251,250,247,0)),
    linear-gradient(to left,var(--cb-bg),rgba(251,250,247,0)),
    linear-gradient(to right,rgba(20,19,15,.17),rgba(20,19,15,0)),
    linear-gradient(to left,rgba(20,19,15,.17),rgba(20,19,15,0));
  background-position:left center,right center,left center,right center;
  background-repeat:no-repeat;
  background-size:26px 100%,26px 100%,11px 100%,11px 100%;
  background-attachment:local,local,scroll,scroll;
}
/* The cover has to be the colour that is actually showing at the block's edge
   or it reads as a smear. A .table-wrap paints nothing of its own, so that
   colour is whatever it is sitting on. Measured: every one of them resolves to
   --paper or --paper-2 and nothing else. .record/.card come last on purpose —
   a record inside a paper-2 section is still paper, and at equal specificity
   source order is what settles it. */
.section-paper2 :is(.scroll-x,.table-wrap),.artifact :is(.scroll-x,.table-wrap){--cb-bg:var(--paper-2)}
.record :is(.scroll-x,.table-wrap),.card :is(.scroll-x,.table-wrap){--cb-bg:var(--paper)}

/* A code block is the opposite case, and reading it as "match the surroundings"
   is a real bug I shipped for one pass: these declare a background ON PURPOSE,
   a shade off the panel they sit in, so that the sample reads as a thing quoted
   rather than as more panel. Both blocks on the home page contradict their
   container — the paper one sits in a paper-2 artifact, the paper-2 one sits in
   a paper record — so taking the colour from the surroundings swapped the two
   of them. A code block's cover comes from its own declaration. Specificity is
   the pair, so it has to be a two-class selector and it has to come after. */
.codeblock.scroll-x{--cb-bg:var(--paper);background-color:var(--cb-bg);
  margin:0;font-size:.8125rem;line-height:1.7;padding:var(--s-3);
  border:1px solid var(--rule);border-radius:3px}

/* ---------- tables ---------- */
.table-wrap{max-width:100%;border:1px solid var(--rule-hard);border-radius:4px}
/* min-width:520px was a blanket floor aimed at the wide comparison tables and
   applied to all 46. Twenty-one of them have two columns and fit a phone: the
   reference table on /drawings rendered "drawing · cut sheet · submitta",
   "As printed, plus the issue dat" — cut mid-word — under a paragraph that says
   "Both are readable on a phone." A table cannot shrink below its own
   min-content, so the genuinely wide ones (the CSV export, the spreadsheet mock)
   still scroll, from their content instead of from a hand-set number.
   Measured at 390 with the floor gone: 19 of 21 tables fit whole. */
table{border-collapse:collapse;width:100%;font-size:.9375rem}
th,td{text-align:left;padding:13px 16px;border-bottom:1px solid var(--rule);vertical-align:top}
@media(max-width:539px){th,td{padding:11px 10px}}
th{font-family:MonoVar,monospace;font-size:.75rem;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2);background:var(--paper-2);font-weight:var(--w-medium)}
tbody tr:last-child td{border-bottom:0}
/* Tabular figures buy nothing left-aligned — the glyphs are equal width, the
   decimals still land in different places. On /hours the ACTUAL column ended
   96.0 then 1,026.0 and the total did not line up with the column it totals, on
   a page whose whole argument is that the columns add up. */
td.num,th.num{font-family:MonoVar,monospace;font-variant-numeric:tabular-nums;text-align:right}

/* ---------- lists ---------- */
.list{list-style:none;padding:0;margin:0 0 var(--s-5);max-width:60ch}
.list li{padding:11px 0 11px 26px;border-bottom:1px solid var(--rule);position:relative}
.list li:last-child{border-bottom:0}
/* a bullet is not an action: 207 of these site-wide, 26 on /pricing alone,
   all wearing the one colour that is supposed to mean "act here" */
.list li::before{content:"";position:absolute;left:4px;top:19px;width:7px;height:1px;background:var(--ink-3)}
.list-check li::before{content:"✓";font-family:MonoVar,monospace;background:none;color:var(--signal-ink);width:auto;height:auto;top:11px;left:0;font-size:.875rem}
.list-no li::before{content:"×";font-family:MonoVar,monospace;background:none;color:var(--ink-3);width:auto;height:auto;top:11px;left:0;font-size:1rem}

/* ---------- on-page nav ----------
   The <ul> carries an inline font-size:.8125rem on every page. Styling the
   anchor (which has no inline style) lifts both the type and the tap target
   without needing !important or 16 hand edits.
   Measured: 7px of padding landed these at 43.1px and 38.3px — a pass that
   missed by under a pixel is still a miss, so the padding is set from the
   target, not by eye. */
nav[aria-label="On this page"] a{display:inline-block;padding-block:11px;font-size:.9375rem}
/* The <ul> variant on 17 pages inherited the global a{border-bottom} and then
   had it pushed 11px clear of the text by that padding, so each row drew a
   detached grey stub of a different length: four broken rules, not a list of
   links. This is the same failure the .eyebrow-line comment below diagnosed —
   a border draws across the whole box — fixed in one place and left standing in
   seventeen. The rule moves to the <li>, where a full-width row is what it is.
   /for-foremen/ and /for-owners/ build the same nav out of .toc-row anchors and
   carried the type and padding inline, so neither the size nor the target above
   could reach them. One component, one appearance, and one colour: a jump link
   is "act here", so both variants say so. */
nav[aria-label="On this page"] li{border-bottom:1px solid var(--rule)}
nav[aria-label="On this page"] li:last-child{border-bottom:0}
nav[aria-label="On this page"] li a{display:block;border-bottom:0;padding-block:12px;font-size:.9375rem}
nav[aria-label="On this page"] .toc-row{display:block;font-size:.9375rem;padding-block:12px;
  border-bottom:1px solid var(--rule)}
nav[aria-label="On this page"] .toc-row:last-child{border-bottom:0}
nav[aria-label="On this page"] .toc-row,
nav[aria-label="On this page"] li a{color:var(--signal-ink)}
/* A citation sitting alone in its own flex column is a target, not a word in a
   sentence; 21px of statute link is not something you hit from a lift. */
.cite-row a{display:inline-block;padding-block:12px;margin-block:-12px;border-bottom:0;
  text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
.cite-row a:hover{text-decoration-color:var(--signal)}

/* ---------- faq ---------- */
.qa{border-top:1px solid var(--rule-hard)}
.qa details{border-bottom:1px solid var(--rule);padding:var(--s-4) 0}
.qa summary{cursor:pointer;font-weight:var(--w-semibold);font-size:var(--t-5);list-style:none;display:flex;justify-content:space-between;gap:var(--s-4);align-items:flex-start;min-height:44px}
.qa summary::-webkit-details-marker{display:none}
.qa summary::after{content:"+";font-family:MonoVar,monospace;color:var(--signal-ink);font-size:1.25rem;line-height:1;flex:none}
.qa details[open] summary::after{content:"–"}
.qa details p{margin-top:var(--s-4);margin-bottom:0;color:var(--ink-2)}

/* ---------- cta band ---------- */
.cta-band{background:var(--chrome);color:var(--on-chrome);padding-block:var(--s-9)}
.cta-band h2{color:var(--on-chrome);max-width:20ch}
.cta-band p{color:var(--on-chrome-2);max-width:52ch}
.cta-band .btn-primary:hover{background:#C94D12;border-color:#C94D12}

/* ---------- footer ---------- */
.site-foot{background:var(--chrome);color:var(--on-chrome-2);padding-block:var(--s-8) var(--s-6);border-top:1px solid #ffffff1a}
/* Four tracks at 744 gave an 820 iPad 210/150/150/150: "Answers off the prints"
   and "Reports and the office" wrapped to 55px rows against 44px neighbours and
   column 4 ended 400px above column 2. Same two breakpoints .g3 already uses. */
.foot-grid{display:grid;gap:var(--s-6);grid-template-columns:1fr}
@media(min-width:744px){.foot-grid{grid-template-columns:repeat(2,1fr)}}
@media(min-width:1000px){.foot-grid{grid-template-columns:1.4fr 1fr 1fr 1fr}}
.site-foot h2{font-family:MonoVar,monospace;font-size:var(--label);letter-spacing:.06em;text-transform:uppercase;color:#8c8779;font-weight:var(--w-medium);margin-bottom:var(--s-3)}
.site-foot a{display:flex;align-items:center;min-height:44px;color:var(--on-chrome-2);border:0;padding:4px 0;font-size:.9375rem}
.site-foot a:hover{color:var(--signal)}
.foot-brand p{color:#8c8779;font-size:.9375rem;max-width:34ch;margin-top:var(--s-4)}
.foot-legal{display:flex;flex-wrap:wrap;gap:var(--s-3) var(--s-6);justify-content:space-between;align-items:center;margin-top:var(--s-7);padding-top:var(--s-5);border-top:1px solid #ffffff14;color:#85806f;font-size:.875rem}  /* #7d7869 was 4.15:1 */
/* inline-block + padding takes the legal row from a 27px target to 45px without
   moving the baseline; these are standalone links, not links inside a sentence,
   so the WCAG 2.5.8 inline exception does not cover them. min-width carries the
   short labels ("Terms" measured 43.5px wide) over the same 44px floor. */
.foot-legal a{display:inline-block;min-height:0;min-width:44px;text-align:center;padding-block:12px;color:var(--on-chrome-2);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px;text-decoration-color:#85806f}
/* The email is the tail of a sentence, not a standalone label, so the WCAG 2.5.8
   inline exception covers it and the rule above does not: at 15px inside a 13px
   mono address it read as a different size, and its 12px of block padding
   inflated the line box until the two wrapped address lines sat 33px apart
   instead of 20px — two paragraphs where there is one. */
.foot-legal .addr a{min-width:0;text-align:left;font-size:inherit;min-height:0;
  padding-block:12px;margin-block:-12px}
.foot-legal .addr{font-family:MonoVar,monospace;font-size:.8125rem;letter-spacing:.02em}

/* ---------- eyebrow ----------
   The two-line company line above the H1 on / and the Company cluster. It was
   hand-styled inline on each of the four pages that carry it and drifted into
   three different treatments — 12px vs 13px, ink vs signal-ink, with and
   without the padding. One component, so it can only look like one thing.
   The underline is text-decoration, not border-bottom: a border draws across
   the whole box, so on a 390px screen the wrapped two-line link rendered a
   full-width rule that read as a section divider and cut the eyebrow's second
   line away from its first. */
.eyebrow{margin:0 0 var(--s-6)}
/* padding takes the single-line case from 34px to 44px; the negative margin
   gives back exactly what the padding added, so the eyebrow sits where it sat */
.eyebrow-line{display:inline-block;max-width:100%;padding-block:13px;margin-block:-3px -7px;
  font-size:.75rem;line-height:1.5;color:var(--ink);border-bottom:0}
a.eyebrow-line{text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
a.eyebrow-line:hover{text-decoration-color:var(--signal);color:var(--signal-ink)}
.eyebrow-sub{display:block;margin-top:3px;font-size:.75rem;line-height:1.5;color:var(--ink-3)}
.eyebrow-sub.sentence{letter-spacing:.02em;text-transform:none}

/* ---------- dark bands ----------
   .section-chrome and .cta-band are the same near-black surface, but only the
   first one ever got text rules, so every closing CTA on all 22 pages drew its
   links in --signal-ink on --chrome: 2.23:1. Separately, .section-head p is
   declared later in this sheet at equal specificity, which quietly repainted
   chrome-band leads --ink-2 on black: 1.82:1. Both are one bug — a light-surface
   rule reaching into a dark band — so both are settled here, at the end, where
   source order decides. Anything added below this point has to think about it. */
.cta-band{color:var(--on-chrome)}
.cta-band h3,.cta-band h4{color:var(--on-chrome)}
.cta-band p,.cta-band li{color:var(--on-chrome-2)}
.cta-band .label,.cta-band .muted{color:#8c8779}
.section-chrome .section-head p,.cta-band .section-head p{color:var(--on-chrome-2)}
/* .doctrine p is --ink: right on paper, 1.05:1 on the chrome. One block on the
   estate sits in a dark band and had been hand-patched inline, which is the same
   light-rule-reaching-into-a-dark-band bug this section exists to settle. The
   component settles it here so the next one cannot ship invisible; the bar itself
   is --linework, which clears 3:1 on every surface the estate has. */
.section-chrome .doctrine p,.cta-band .doctrine p{color:var(--on-chrome)}
/* :not(.btn) matters — a button on a dark band paints its own background and
   already carries its own text colour; letting the band's link colour reach it
   put amber type on the orange primary button at 2.6:1. */
.section-chrome a:not(.btn),.cta-band a:not(.btn){color:var(--signal-on-dark);border-bottom-color:#ffffff2e}
.section-chrome a:not(.btn):hover,.cta-band a:not(.btn):hover{border-bottom-color:var(--signal-on-dark)}
/* --signal is 3.27:1 on --chrome. It is the right colour on paper and the wrong
   one on the chrome, and it was reaching the chrome three ways: the current-page
   link in the drawer, the current-page link in the footer, and every footer
   hover. Same amber as the desktop nav uses. */
.mobile-nav a[aria-current],.site-foot a[aria-current]{color:var(--signal-on-dark)}
.mobile-nav a:hover,.site-foot a:hover{color:var(--signal-on-dark)}

/* A component that paints its own light background has to declare its own ink.
   Inheriting it is correct right up until the component is dropped into a dark
   band — which is what put the weekly-hours table's rows at 1.09:1, white type
   on paper, unreadable on /how-it-works/ and /safety/. */
.artifact,.record,.card,.menu{color:var(--ink)}
.section-chrome :is(.artifact,.record,.card) :is(h2,h3,h4,p),
.cta-band :is(.artifact,.record,.card) :is(h2,h3,h4,p){color:var(--ink)}
.section-chrome :is(.artifact,.record,.card) :is(.label,.muted,.cite),
.cta-band :is(.artifact,.record,.card) :is(.label,.muted,.cite){color:var(--ink-2)}
.section-chrome :is(.artifact,.record,.card) a,
.cta-band :is(.artifact,.record,.card) a{color:var(--signal-ink);border-bottom-color:var(--rule-hard)}
.section-chrome .msg.out,.cta-band .msg.out{color:var(--on-chrome)}
.section-chrome .msg.out .who,.cta-band .msg.out .who{color:#9a9483}

/* Standalone links sitting alone in a record row or a citation line are not
   links inside a sentence, so they get a real target. Padding grows the hit
   box, the equal negative margin keeps the line box exactly where it was, and
   the underline moves to text-decoration so it stays glued to the glyphs
   instead of dropping to the bottom of the new padding. */
/* .hero-note carries the audience router — "Foremen · Crews · Owners", the most
   important routing decision a first-time visitor makes — at 20px tall, and the
   card CTAs ("How hours get made →") at 21px. Same pattern, same two lines, the
   two places it was missed. Measured: 20 -> 47px and 21 -> 50px, baselines
   unmoved. */
.record-row a,.cite a,.hero-note a,.card p:last-child a{display:inline-block;padding-block:12px;margin-block:-12px;
  border-bottom:0;text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
.record-row a:hover,.cite a:hover,.hero-note a:hover,.card p:last-child a:hover{text-decoration-color:var(--signal)}

/* The fifth shape, and the commonest one on the estate: a paragraph, list item
   or table cell whose whole content is one link — a bare <p> holding nothing but
   "The whole list of what we don't take on →", a list item that is one route
   name, a table cell that is one citation. The four selectors above cover a
   record row, a citation, a hero note and the last line of a card; they never
   covered this. Measured at 390 with device metrics: 22 on torspan.com across
   /, /contact/, /what-we-build/, /privacy/ and /404.html, every one 21px tall
   against a 44px floor. Lives in the shared base rather than in one property's
   layer, because all three domains build links this way.
   The padding goes on an INLINE box on purpose. Vertical padding on a
   non-replaced inline element enlarges the hit area without touching the line
   box, so nothing on any page moves; inline-block would take the link's ability
   to break across lines with it, and that is how a 314px link becomes
   horizontal scroll on a phone.
   :only-child keeps it off links that sit inside a sentence, which WCAG 2.5.8
   exempts and where +/-12px would overlap the line above.
   14 and not 12, because the floor has to clear 44 at the SMALLEST type the
   link can be set in, not at body size: a 13px .label line box is 18px, so 12px
   a side lands on 42 and fails by two. 18+28=46 and 21+28=49.
   A list item and a table cell are the two places the anchor sits alone on its
   own line with no sibling text to shove sideways, so those two — and only
   those two — also get a width floor. max-width keeps the inline-block able to
   wrap its own text, which is what stops a long cell link from becoming
   horizontal scroll on a phone. */
/* The underline has to move with the padding. The sheet draws a link with
   border-bottom, and a border sits on the padding edge — so 14px of padding
   drops the rule 14px below the glyphs, where it reads as a horizontal divider
   rather than as an underline. Measured on /contact/: the line under the URL
   in the reply mock landed on top of the signature. Same swap the four
   selectors above already make, for the same reason. */
main :is(p,dd) > a:only-child,
main :is(p,li,td,dd) > :is(strong,em) > a:only-child,
main :is(li,td) > a:only-child{padding-block:14px;
  border-bottom:0;text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
main :is(p,dd) > a:only-child:hover,
main :is(p,li,td,dd) > :is(strong,em) > a:only-child:hover,
main :is(li,td) > a:only-child:hover{text-decoration-color:var(--signal)}
/* A list row and a table cell are the two shapes that already carry their own
   vertical padding, and a list row also has its marker pinned to the row's top
   edge — so padding on the anchor pushes the words 14px off their own bullet.
   Measured on the home page's product list: first-line centre at 35px against a
   marker at 19px. In those two shapes the ROW is the target, not the word, so
   the anchor takes the row's padding over and fills it edge to edge: the height
   and the marker stay where the system put them, and the whole row is
   clickable rather than just the phrase. */
main :is(.list,.list-check,.list-no) li:has(> a:only-child),
main td:has(> a:only-child){padding-block:0}
main :is(li,td) > a:only-child{display:block;padding-block:12px;max-width:100%}

/* ---------- motion ---------- */
/* content is visible by default; only a JS-capable page arms the reveal */
.rise{transition:opacity var(--dur-calm) var(--ease),transform var(--dur-calm) var(--ease)}
.js .rise{opacity:0;transform:translateY(8px)}
.rise.in{opacity:1;transform:none}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
  .rise,.js .rise{opacity:1;transform:none}
}

/* ================================================================
   MAKER LAYER — apluxor.com
   Everything above this line is the estate's shared foundation. This
   role layer gives the maker shelf its own composition and identity.

   THE MASTHEAD, and nothing else. .site-foot is one of the twelve
   invariants and is untouched. The header is not on that list. A
   maker shelf opens with its imprint on paper and a rule under it, not
   with a company's dark chrome band, and it does not follow you down
   the page: the catalog is compact enough to keep navigation static.

   THE ACCENT IS NOT OVERRIDDEN HERE, AND MUST NOT BE AGAIN. This layer
   used to repaint --signal, --signal-ink and --signal-on-dark blue so
   that the person's property carried its own accent colour. One hex
   cannot mean "act here" on one property and "this is the drawing" on
   the other two — a visitor crossing between them would be taught two
   opposite things by one colour. The estate holds two hues, a warm at
   CIELAB 49 and a cool at 229.5, and on every property the warm is the
   one thing you are meant to touch and the cool belongs to the drawing.
   The distinction of this property is its maker composition, not a second
   palette. The swap needed five patches to stand up — an override
   block, a selection colour, a button hover, and two doctrine rules;
   all five are gone.
   ================================================================ */

/* ---------- the masthead ----------
   apluxor is an imprint, so its masthead is a nameplate: a device, a name, and a
   descriptor line. Not a byline (there is no person's name in it) and not a
   corporate bar (nothing is sticky, there is no button, no burger, no drawer,
   and the nav is two words).

   THE MARK IS THE SAME SIZE AT EVERY WIDTH, 320 to 1440, AND IT NEVER SHRINKS ON
   A PHONE. The one feature that distinguishes this A from every other heavy
   geometric A is the rounded arch counter, which is 15% of the mark's width and
   7.2% of its height. Rendered alone at dpr 1 and dpr 3 and looked at:

     dpr 3 (a phone)   every height from 25px up shows a clean round crown.
                       Resolution is NEVER the constraint on a phone.
     dpr 1 (a monitor) at 25.3px the crown is FLAT -- a square slot, and the mark
                       is a triangle with a nick in it. First rounding at 30,
                       reads at 32, unambiguous at 34.

   So the rasteriser binds on the DESKTOP, not the phone, and a phone is held at
   half a laptop's distance besides. Shrinking the mark on a phone is backwards
   twice over, and it buys nothing: the band's height is set by the 44px tap
   target, so 25.3 -> 34 costs zero vertical pixels and 10px of a 350px measure.
   The sprawl was two nav words pushed to opposite edges; the fix for that is the
   pair below, not a smaller mark.

   THE MARK IS SIZED BY HEIGHT, NEVER BY A SQUARE BOX. mark.png is 427x360
   (1.1861:1) with ink to all four edges, so the old
   30px-square box with object-fit:contain letterboxed it and painted 30x26
   while the stylesheet said 30x30 -- measured off the pixels, not computed. The
   number in the CSS has to be the mark.

   --lock is the wordmark's font-size and the ONLY variable in the lockup;
   everything else is an em of it, so the mark and the word cannot drift apart.
   1.48em is 2.05x Archivo's ascender (0.723em, measured in-browser): the mark is
   a capital A and the wordmark is lowercase, so anywhere near the height of the
   "l" the eye reads "Aapluxor". At twice the ascender it stops being a letter of
   the word and becomes a device beside a name.
   0.43em is the gap: below ~7px the A's right foot crowds the "a", above ~11px
   they read as two objects. */
.site-head.head-personal{position:static;background:var(--paper);color:var(--ink);
  border-bottom:1px solid var(--rule-hard)}

/* ONE VALUE, EVERY WIDTH. Do not add a breakpoint that changes it. If the mark
   ever has to move, it moves at every width together with the wordmark, and the
   direction is never "smaller on the phone". */
.head-personal{--lock:23px}

.head-personal > .wrap{display:flex;flex-wrap:wrap;align-items:center;
  gap:0 var(--s-4);padding-block:var(--s-3);min-height:0}

/* The lockup is the link; the tagline below is a caption, not a nav label. It
   used to sit inside this anchor to make the block clear 44px -- the lockup does
   that on its own (46.03px measured at every width). flex:none on both this and
   .pnav so that if the tagline ever outgrows the line it is the tagline that
   gives way, not the nameplate: without it flexbox shrinks the wordmark and drags
   the hairline across the "or" of apluxor. Verified with a lengthened string. */
.head-personal .brand.imprint{display:flex;align-items:baseline;flex-wrap:nowrap;flex:none;
  gap:calc(var(--lock)*.43);min-height:44px;padding-block:4px;margin:0;border:0;
  color:var(--ink);text-transform:none;font-size:var(--lock)}
.head-personal .brand.imprint:hover{color:var(--signal-ink)}
/* align-self:baseline puts the mark's FOOT on the wordmark's baseline and lets
   the apex overshoot. Optically centred instead, the mark's feet sink below the
   word and the lockup loses its ground line. */
.head-personal .brand.imprint .amark{height:calc(var(--lock)*1.48);width:auto;
  object-fit:fill;display:block;flex:none;align-self:baseline}
.head-personal .brand.imprint .wordmark{font-family:Archivo,InterVar,system-ui,sans-serif;
  font-variation-settings:"wdth" 112;font-weight:var(--w-semibold);
  font-size:var(--lock);line-height:1;letter-spacing:-.015em;
  text-transform:none;color:inherit;white-space:nowrap}

/* Flush left on the page gutter, full measure, under BOTH the lockup and the nav.
   NOT indented to the wordmark: the shipped 42px hanging indent gives the phone
   masthead three different left edges in three lines and opens a white notch
   under the mark -- which is the "gap between the mark and the tagline" that was
   reported. Running the full width makes it a beam that ties the two ends of the
   row together. --label (13px) at every width; no phone-only shrink, and never a
   new type step below it. */
.imprint-sub{order:1;flex-basis:100%;margin:2px 0 0;padding:0;max-width:none;
  font-family:MonoVar,ui-monospace,monospace;font-size:var(--label);
  letter-spacing:.02em;line-height:1.35;color:var(--ink-3);font-weight:var(--w-regular)}

/* Two words, kept as an adjacent pair 24px apart and never spread across the
   measure. 44px is this project's own floor and the deleted @media(max-width:520px)
   block broke it at 38px. */
.pnav{display:flex;align-items:center;flex-wrap:nowrap;flex:none;gap:0 var(--s-5);
  margin:0 0 0 auto;width:auto;justify-content:flex-end}
.pnav a{display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;color:var(--ink-2);border:0;
  border-bottom:2px solid transparent;font-size:.9375rem;font-weight:var(--w-medium)}
.pnav a:hover{color:var(--ink);border-bottom-color:var(--rule-hard)}
.pnav a[aria-current]{color:var(--ink);border-bottom-color:var(--signal)}

/* 744 -- the estate's own tablet gutter step, not a new breakpoint. From here the
   measure carries nameplate + rule + strapline + nav on one line. This was 900,
   which failed the iPad-first rule: at 820 the tagline was still stacked, leaving
   the lockup and the nav as two islands with nothing between them.
   The hairline is calc(--lock*1.48) -- exactly the mark's height -- so the rule
   reads as measured against the mark and brackets the wordmark, instead of
   floating inside the tagline's line box.
   min-width:0 + ellipsis so a longer strapline clips instead of pushing the nav
   off the screen. Measured slack at 744: 63.96px. */
@media(min-width:744px){
  .head-personal > .wrap{flex-wrap:nowrap;gap:0 var(--s-5);padding-block:14px}
  .imprint-sub{order:0;flex-basis:auto;margin:0;padding-left:var(--s-5);
    display:flex;align-items:center;height:calc(var(--lock)*1.48);
    border-left:1px solid var(--rule);white-space:nowrap;
    min-width:0;overflow:hidden;text-overflow:ellipsis}
}

/* ---------- skip link ----------
   Present consistently across the ecosystem's static properties. */
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--chrome);color:var(--on-chrome);
  padding:12px 18px;border:0;border-radius:0 0 3px 0;font-size:.9375rem}
.skip:focus{left:0}

/* ---------- the standing line ----------
   One sentence, set as the page's own rule, used three times across
   four pages. It is .doctrine's weight without .doctrine's frame,
   because a person quoting himself in a box is a different tone. */
.standing{font-family:Archivo,InterVar,system-ui,sans-serif;font-variation-settings:"wdth" 112;
  font-size:var(--t-4);line-height:1.28;letter-spacing:-.012em;font-weight:var(--w-medium);
  color:var(--ink);max-width:36rem}
.section-chrome .standing,.cta-band .standing{color:var(--on-chrome)}

/* A link standing alone as its own paragraph is a target, not a word in a
   sentence, so the WCAG inline exception does not cover it. The shared sheet
   gives this treatment to .record-row a, .cite a, .hero-note a and
   .card p:last-child a; the one on 404 is none of those and measured 21px. */
.p-link a{display:inline-block;padding-block:12px;margin-block:-12px;border-bottom:0;
  text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
.p-link a:hover{text-decoration-color:var(--signal)}

/* ---------- the shelf's type entry point ----------
   Nothing is forked and no step is invented: this property enters the shared
   scale one step lower than the company does. The ecosystem cover gives the
   maker promise the strongest line; product names follow one clear tier below
   inside full-width shelf records. */
h1{font-size:var(--t-2)}
h2{font-size:var(--t-3);line-height:1.1}

/* The second .imprint block that used to sit here is gone, not amended. It set
   the mark in a 30px SQUARE with object-fit:contain -- which painted 30x26 --
   indented the strapline 42px under the wordmark, and moved it behind a hairline
   only from 900px, so at 820 (the width this estate designs for first) the band
   was still a stack. Every property it set is set once, above, at ".imprint --
   the masthead". Two blocks styling one component is how the phone overrides
   became dead code: equal specificity, later wins, and nobody could see it. */

/* ---------- .shelf — the product card ----------
   .card lets a two-sentence product and a four-sentence product end their price
   strip and action link at different heights, and a shelf whose prices do not
   line up is not a shelf. Two lines fix it.

   This is a defect in the shared .card, not a need specific to this property:
   .card sits in a .grid g2/.g3 on the other two properties and rags the same
   way there. The correct fix is these two lines in the shared sheet — but the
   shared sheet is not edited from a personal property, so it is logged for
   torspan-site/BUILD-LOG.md and implemented here meanwhile. */
.shelf{display:flex;flex-direction:column}
.shelf > .shelf-cols{flex:1}

/* ---------- .shelf-cols — description beside the price column ----------
   A single full-width row per item, description left and the record right, so
   every price lands on ONE shared axis you can scan down in five seconds. That
   aligned column is this property's distinguishing component and no other
   property on the estate has one.

   It only splits at 1000px. Below that the record sits under the prose and the
   shared sheet's own 380px container query drops each row to key-over-value —
   which is why the shelf needs no phone-specific markup. A card grid was
   rejected instead: .g3 gives two columns between 744 and 999, an iPad in
   portrait, and an odd number of products then leaves a hole at exactly the
   width this estate designs for first. */
.shelf-cols{display:grid;gap:0;align-items:start}
@media(min-width:1000px){
  .shelf-cols{grid-template-columns:minmax(0,1fr) minmax(0,24rem);gap:var(--s-7)}
  .shelf-cols > .record{margin-top:0}
}

/* One row per item means the rows need an edge between them: .card carries no
   margin of its own, so consecutive shelf rows rendered as one continuous block
   with a shared hairline and read as a single card with two headings in it. */
.shelf + .shelf{margin-top:var(--s-5)}

/* The action link is the card's door and it was landing hard against whatever
   ended above it — against the record on one card and against the last line of
   prose on another, because .shelf-cols is a grid whose height is set by the
   taller of its two columns. Spacing it from the card body rather than from the
   element above it makes every row's door sit at the same distance, whichever
   column happened to be taller. */
.shelf > .p-link{margin-top:var(--s-5);margin-bottom:0}

/* A product name is the card's primary link and it was the smallest target on
   the page: measured 136x23 at 390 on the one-line card, because a heading is
   only as tall as its own line. The two-line names passed by accident, which is
   the worst kind of pass — the check succeeded on the length of the words.
   Padding plus equal negative margin lifts it to 45px and moves nothing.

   The underline moves to text-decoration for the same reason it does on
   .p-link: the global a{border-bottom} draws one rule across the whole box, so
   a name that wraps to two lines would get a detached stub under the second.
   text-decoration stays glued to the glyphs on every line.

   NOTE for torspan-site/BUILD-LOG.md: the shared .card has the same defect —
   .card h3 a gets no target treatment anywhere in the sheet, and the other two
   properties use .card in .g2/.g3 grids with short headings. Not fixed here;
   the shared sheet is not edited from a personal property. */
.shelf > :is(h2,h3) a{display:inline-block;padding-block:11px;margin-block:-11px;border-bottom:0;
  text-decoration:underline;text-decoration-color:var(--rule-hard);
  text-decoration-thickness:1px;text-underline-offset:3px}
.shelf > :is(h2,h3) a:hover{text-decoration-color:var(--signal)}

/* ================================================================
   ECOSYSTEM FOUNDATION — 2026-08
   Shared interaction, typography, spacing, focus, and motion grammar.
   Each property composes these ingredients differently below.
   ================================================================ */
:root{
  --page:1184px;
  --gutter:clamp(20px,4.2vw,56px);
  --surface-raised:#FFFFFF;
  --shadow-lift:0 18px 48px rgba(20,19,15,.08);
  --shadow-menu:0 18px 52px rgba(20,19,15,.14);
  --dur-instant:120ms;
  --dur-quick:180ms;
  --dur-calm:360ms;
  --ease:cubic-bezier(.2,.75,.2,1);
  --ease-out:cubic-bezier(.16,1,.3,1);
}
html{scroll-padding-top:84px}
body{
  font-synthesis:none;
  font-kerning:normal;
  overflow-wrap:break-word;
}
h1,h2,h3,h4{font-optical-sizing:auto}
p,li,dd{ text-wrap:pretty }
.wrap{
  isolation:isolate;
  padding-inline-start:max(var(--gutter),env(safe-area-inset-left));
  padding-inline-end:max(var(--gutter),env(safe-area-inset-right));
}
.site-head{border-bottom:1px solid var(--rule-on-dark);padding-top:env(safe-area-inset-top)}
.site-foot{padding-bottom:calc(var(--s-6) + env(safe-area-inset-bottom))}
.skip:focus{left:env(safe-area-inset-left);top:env(safe-area-inset-top)}
.site-head > .wrap{min-height:68px}
.nav-link,.menu a,.btn,.burger,.pnav a,.qa summary{touch-action:manipulation}
.menu{
  box-shadow:var(--shadow-menu);
  transition:
    opacity var(--dur-quick) var(--ease-out),
    transform var(--dur-quick) var(--ease-out),
    visibility var(--dur-quick) step-end;
}
.nav-item:hover .menu,
.nav-link[aria-expanded="true"] + .menu{
  transition:
    opacity var(--dur-quick) var(--ease-out),
    transform var(--dur-quick) var(--ease-out),
    visibility 0ms step-start;
}
.nav-item[data-escape-closed] .menu{opacity:0;visibility:hidden;transform:translateY(-4px)}
.btn{
  transition:
    color var(--dur-instant) var(--ease),
    background-color var(--dur-instant) var(--ease),
    border-color var(--dur-instant) var(--ease),
    transform var(--dur-instant) var(--ease-out),
    box-shadow var(--dur-instant) var(--ease);
}
.btn-primary{box-shadow:0 1px 0 rgba(20,19,15,.14)}
.artifact,.record,.card{overflow-wrap:anywhere}
.foot-legal{font-size:.9375rem}
.rise{
  transition:
    opacity var(--dur-calm) var(--ease-out),
    transform var(--dur-calm) var(--ease-out);
}
/* Reveal section transitions, not every card, row and example. The old broad
   .rise selector animated more than a thousand repeated elements estate-wide. */
.js .rise{opacity:1;transform:none}
.js .section-head.rise,.js .shelf.rise,
.js .cta-band.rise,.js .cta-band .rise{opacity:0;transform:translateY(8px)}
.js .section-head.rise.in,.js .shelf.rise.in,
.js .cta-band.rise.in,.js .cta-band .rise.in{opacity:1;transform:none}
:focus-visible{outline-width:3px;outline-offset:3px}
.site-head :focus-visible,.section-chrome :focus-visible,
.cta-band :focus-visible,.site-foot :focus-visible{outline-color:var(--signal-on-dark)}
@media(hover:hover) and (pointer:fine){
  .btn:hover{transform:translateY(-1px)}
  .btn:active{transform:translateY(0) scale(.985)}
  .pnav a:active,.nav-link:active,.menu a:active{transform:scale(.985)}
}
@media(max-width:743px){
  .hero-actions{gap:var(--s-2)}
  .hero-actions .btn{flex:1 1 12rem}
}
@media(forced-colors:active){
  :focus-visible{outline:3px solid Highlight}
  .btn,.card,.artifact,.record,.menu{border:1px solid CanvasText}
}
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    scroll-behavior:auto!important;
  }
  .rise,.js .rise,
  .js .section-head.rise,.js .shelf.rise,
  .js .cta-band.rise,.js .cta-band .rise{opacity:1;transform:none}
}

/* ================================================================
   MAKER EXPRESSION — a tactile workshop catalog, not a company site.
   ================================================================ */
.site-head.head-personal{
  border-bottom-color:var(--rule-hard);
  background:var(--paper);
}
.site-head.head-personal :focus-visible{outline-color:var(--signal)}
.head-personal > .wrap{min-height:76px}
.head-personal .brand.imprint{transition:color var(--dur-instant) var(--ease)}
.hero{
  position:relative;
  padding-block:clamp(80px,10vw,144px);
  border-bottom:0;
  overflow:hidden;
}
.hero::after{
  content:"";
  position:absolute;
  left:max(var(--gutter),env(safe-area-inset-left));
  right:max(var(--gutter),env(safe-area-inset-right));bottom:0;
  max-width:var(--page);height:1px;
  margin-inline:auto;
  background:var(--rule-hard);
}
.hero h1{
  max-width:13ch;
  font-size:clamp(2rem,1.5rem + 2vi,3rem);
  line-height:1.04;
  letter-spacing:-.032em;
}
.hero .lead{margin-top:var(--s-5);max-width:52ch}
.section{padding-block:clamp(72px,9vw,136px)}
.section-paper2{
  background-color:var(--paper-2);
  background-image:
    linear-gradient(to right,rgba(20,19,15,.035) 1px,transparent 1px),
    linear-gradient(to bottom,rgba(20,19,15,.025) 1px,transparent 1px);
  background-size:64px 64px;
  background-position:center top;
}
.shelf{
  position:relative;
  padding:clamp(24px,4vw,48px);
  border:1px solid var(--rule-hard);
  border-top:3px solid var(--ink);
  border-radius:4px;
  background:var(--surface-raised);
  box-shadow:0 1px 0 rgba(20,19,15,.04);
}
.shelf + .shelf{margin-top:clamp(24px,4vw,48px)}
.shelf > :is(h2,h3){
  max-width:30ch;
  margin-bottom:clamp(24px,3vw,40px);
  font-size:clamp(1.625rem,1.3rem + 1.3vi,2.25rem);
  line-height:1.08;
  letter-spacing:-.026em;
}
.shelf-cols{gap:var(--s-6)}
.shelf-cols > div:first-child{max-width:62ch}
.shelf .record{
  margin-top:0;
  border:0;
  border-top:1px solid var(--rule-hard);
  border-radius:0;
  background:var(--paper-2);
  padding:var(--s-4) var(--s-5);
}
.shelf .record-row{gap:var(--s-5)}
.shelf .record-row .v{max-width:62%;font-size:.875rem}
.shelf > .p-link{
  padding-top:var(--s-5);
  border-top:1px solid var(--rule);
}
.shelf > .p-link a{font-weight:var(--w-semibold)}
.standing{
  max-width:44rem;
  font-size:clamp(1.375rem,1.12rem + 1vi,1.875rem);
  line-height:1.24;
  letter-spacing:-.018em;
}
.site-foot{padding-top:clamp(56px,7vw,88px)}
@media(min-width:1000px){
  .shelf-cols{grid-template-columns:minmax(0,1.3fr) minmax(20rem,.7fr);gap:clamp(48px,6vw,80px)}
  .shelf .record{
    border-top:0;
    border-left:1px solid var(--rule-hard);
    padding:0 0 0 clamp(24px,3vw,40px);
    background:transparent;
  }
}
@media(max-width:743px){
  .head-personal > .wrap{min-height:68px}
  .hero{padding-block:72px 80px}
  .section-paper2{background-size:48px 48px}
  .shelf{padding:var(--s-5)}
  .shelf .record{padding-inline:var(--s-4)}
  .shelf .record-row .v{max-width:none}
}
