/* ===========================================================
   KILN BASE — the studio's portable layer
   ===========================================================
   Load this FIRST, then the project's own stylesheet:

     <link rel="stylesheet" href="assets/kiln-base.css">
     <link rel="stylesheet" href="assets/kiln.css">

   WHAT LIVES HERE vs. WHAT LIVES IN THE PROJECT
   ---------------------------------------------------------
   Here:     token NAMES, the scales, the reset, and the
             components every client site needs regardless of
             how it looks (forms, buttons, focus, motion rules).
   Project:  token VALUES, the visual world, the signature work.

   The whole point is that the names never move. `--accent` means
   "the one accent" on every site the studio ships. Only the hex
   changes. That is what makes the 4th client site take two days
   instead of a week — and it is why the poetic names (--ember,
   --amber) are aliases at the foot of this file rather than the
   real tokens. Poetry is per-project; roles are forever.
   =========================================================== */

/* ===========================================================
   1. SCALES — values, not decisions
   These are brand-neutral and should not be overridden per
   project. If a project needs a different rhythm, override the
   VALUES on :root in the project stylesheet, never invent a
   parallel set of names.
   =========================================================== */
:root{

  /* ---- spacing: 4px base, roughly 1.5× steps ----
     20 distinct padding values in kiln.css and 39 in trex told us
     these were being eyeballed. Eleven steps is more than enough
     for a marketing site; if a value isn't on the scale, the
     answer is almost always the nearest step, not a new one. */
  --sp-1:4px;   --sp-2:8px;   --sp-3:12px;  --sp-4:16px;
  --sp-5:24px;  --sp-6:32px;  --sp-7:48px;  --sp-8:64px;
  --sp-9:96px;  --sp-10:120px; --sp-11:160px;

  /* section rhythm — the two that actually get typed most */
  --sp-section:var(--sp-10);
  --sp-gutter:40px;
  --sp-gutter-sm:20px;

  /* ---- type ----
     Collapsed from 15 sizes. 10px/10.5px/11px were three sizes
     inside one point and nobody could tell them apart; 12/12.5
     and 14/14.5 the same. Two mono label sizes is the real need:
     one for cramped instrument labels, one for everything else. */
  --fs-micro:10.5px;   /* mono: gauge ticks, footer column heads */
  --fs-label:12px;     /* mono: eyebrows, section labels, meta   */
  --fs-sm:13px;        /* nav links, dense UI                    */
  --fs-body:15px;      /* running text floor                     */
  --fs-md:17px;
  --fs-lg:19px;
  --fs-xl:23px;

  /* fluid display steps — clamp so they breathe without a media query */
  --fs-d1:clamp(38px,6vw,74px);
  --fs-d2:clamp(30px,5vw,58px);
  --fs-d3:clamp(26px,3.4vw,40px);
  --fs-d4:clamp(22px,2.8vw,32px);
  --fs-hero:clamp(44px,8vw,104px);

  --lh-tight:1.02;
  --lh-snug:1.2;
  --lh-body:1.65;
  --lh-loose:1.72;

  --ls-mono:.16em;     /* the uppercase mono tracking, one value */
  --ls-mono-wide:.22em;
  --ls-display:-.02em;

  --measure:64ch;      /* prose max-width */
  --measure-tight:44ch;

  /* ---- radius ----
     The pill was 100px in KILN and 999px in trex. One value. */
  --radius-sm:2px;
  --radius-md:6px;
  --radius-lg:10px;
  --radius-pill:999px;

  /* ---- motion ----
     11 durations in KILN, 12 in trex, none named. Four covers it.
     --dur-1 is the "did that even animate?" tier for hovers on
     small controls; anything slower on a 13px nav link feels laggy. */
  --dur-1:.16s;        /* micro: colour on hover, small controls */
  --dur-2:.25s;        /* standard control transitions           */
  --dur-3:.4s;         /* panels, nav chrome, backdrops          */
  --dur-4:.8s;         /* entrances, veils                       */
  --dur-draw:1.1s;     /* reveals, masked type, rules drawing in  */

  --ease:cubic-bezier(.22,.8,.28,1);      /* project may override */
  --ease-out:cubic-bezier(.16,1,.3,1);
  --ease-in-out:cubic-bezier(.65,0,.35,1);

  /* ---- layout ---- */
  --wrap:1180px;

  /* ---- z-index: one ladder, so nothing gets invented mid-build ---- */
  --z-base:1; --z-raised:2; --z-nav:90; --z-hud:92; --z-fx:94;
  --z-overlay:120; --z-cursor:200; --z-transition:250;
}

/* ===========================================================
   2. ROLE TOKENS — names fixed, values per project
   ===========================================================
   The values below are a neutral warm-dark default so this file
   is never broken on its own. Every project overrides them.

   CHANNEL TOKENS (--*-rgb) are not optional. Any translucent
   colour must be built from them, so that re-theming a page
   (KILN's data-theme, a client's dark mode) carries every
   surface with it. A literal rgba() is a colour that will be
   left behind — this rule already held perfectly across
   kiln.css and it is the single best thing in the system.
   =========================================================== */
:root{
  --ground:        #0E1013;   /* page ground                     */
  --ground-raised: #161A1F;   /* cards, panels — LIGHTER         */
  --ground-deep:   #080A0C;   /* wells, footer  — DARKER         */

  --ink:      #E8ECF1;        /* primary text                    */
  --ink-dim:  #A5AFBC;        /* secondary text                  */
  --ink-mute: #6C7885;        /* labels, metadata                */

  --accent:      #E08A2C;     /* the one accent                  */
  --accent-hi:   #F5A94A;     /* its hottest / brightest point   */
  --accent-deep: #7A4610;     /* its shadow / cooled edge        */

  /* channels — keep in step with the hexes above */
  --ground-rgb:14,16,19;
  --ground-deep-rgb:8,10,12;
  --ink-rgb:232,236,241;
  --accent-rgb:224,138,44;
  --accent-hi-rgb:245,169,74;

  /* derived — never hardcode these downstream.
     ⚠ These are ALSO restated in the [data-brand] block at the
     foot of this file, and they have to be. See the note there:
     a derived token declared only on :root freezes against
     :root's channels and silently ignores a theme set further
     down the tree. kiln.css currently has this bug. */
  --line:      rgba(var(--ink-rgb),.12);
  --line-soft: rgba(var(--ink-rgb),.06);
  --veil:      rgba(var(--ground-rgb),.82);

  /* Semantic states. Kept deliberately few: a marketing site
     needs "this went wrong" and "this worked", not a palette.
     Desaturated and warm-biased on purpose — the studio's stance
     is no pure/neon values, and a screaming #FF0000 next to a
     considered palette reads as a stock component dropped in.

     ⚠ RE-TUNE THESE ON ANY BRAND WHOSE ACCENT IS RED OR ORANGE.
     KILN's ember is #E2632A; a bright red error sits ~25° of hue
     away from it and the two start to read as the same signal.
     The fix used below is to pull danger cooler and darker so it
     separates by value, not just hue. Note also that .err carries
     a "→" glyph and aria-invalid: colour is never the only cue,
     which is what keeps this WCAG 1.4.1 clean regardless. */
  --danger:  #C43D45;
  --danger-rgb:196,61,69;
  --success: #4E9E6A;
  --success-rgb:78,158,106;

  /* type families — project sets these */
  --disp:"Libre Caslon Display",Georgia,serif;
  --sans:"Hanken Grotesk",system-ui,-apple-system,sans-serif;
  --mono:"JetBrains Mono",ui-monospace,Consolas,monospace;
}

/* ===========================================================
   3. RESET + GROUND
   =========================================================== */
*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
/* NOTE: body deliberately does NOT set font-size or line-height.
   Both were here in an earlier draft and both silently changed
   the existing sites when this file was layered under them —
   kiln.css never declares either, so a base-level `font-size:15px`
   won the cascade by default and shrank every unstyled run of
   text on the page, while `line-height:1.65` grew about.html by
   112px. A base layer that quietly restyles the pages it is
   added to is not a base layer.

   --fs-body and --lh-body remain as tokens; apply them
   explicitly where you want them. New projects should set both
   on body in their own stylesheet. */
body{
  background:var(--ground);
  color:var(--ink);
  font-family:var(--sans);
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  overflow-x:hidden;
}
a{color:inherit;text-decoration:none}
img,svg,video,canvas{max-width:100%;display:block}
::selection{background:var(--accent);color:var(--ground)}

/* One rule covers keyboard accessibility for every interactive
   element on the site, including ones not written yet. Do not
   replace this with per-component focus styles. */
:focus-visible{outline:2px solid var(--accent);outline-offset:3px;border-radius:var(--radius-sm)}
:focus:not(:focus-visible){outline:none}

.wrap{max-width:var(--wrap);margin:0 auto;padding:0 var(--sp-gutter)}
@media(max-width:640px){.wrap{padding:0 var(--sp-gutter-sm)}}

/* The masthead is NOT the body column, and this component exists so that
   the lazy path produces the right answer.

   .wrap was the only container in this file for seventeen mockup builds.
   Every one of them reached for it twice — once for the header, once for
   the hero body — so the wordmark could only ever land flush with the
   headline, and at 2560 the header reads as though it were obeying a
   margin nothing else on the page obeys. The studio's own site does the
   opposite: kiln.css .bar is fixed, full-bleed, 40px off the viewport
   edge. Nobody chose the difference; it was just what was available.

   Same gutter token as .wrap and the same 640px step, so header and body
   agree at every width without being the same box. Full-bleed is the
   DEFAULT here, not the rule — hero-build.md asks for the masthead to be
   a choice named in DESIGN.md, because swapping one uniform for another
   is the fault this was written to fix.

   Naming note: .mast, .bar, .top, .nav, .hero and .band are each already
   defined inline by several built mockups, all of which link this file.
   .masthead was chosen because it collides with none of them. */
.masthead{padding:var(--sp-5) var(--sp-gutter)}
@media(max-width:640px){.masthead{padding:var(--sp-5) var(--sp-gutter-sm)}}

/* Visible only to screen readers — needed by every form label
   you decide not to draw, and by the skip link below. */
.sr-only{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0;
}
.skip{
  position:absolute;left:var(--sp-4);top:-100px;z-index:var(--z-transition);
  background:var(--accent);color:var(--ground);padding:var(--sp-3) var(--sp-4);
  border-radius:var(--radius-sm);font-weight:600;transition:top var(--dur-2) var(--ease);
}
.skip:focus{top:var(--sp-4)}

/* ===========================================================
   4. TYPE PRIMITIVES
   =========================================================== */
.disp{font-family:var(--disp);font-weight:400;line-height:var(--lh-tight);letter-spacing:var(--ls-display)}
.mono{font-family:var(--mono)}
.lbl{
  font-family:var(--mono);font-size:var(--fs-label);
  letter-spacing:var(--ls-mono);text-transform:uppercase;color:var(--ink-mute);
}
.lbl-micro{font-size:var(--fs-micro);letter-spacing:var(--ls-mono)}
.em{color:var(--accent)}

.prose{max-width:var(--measure)}
.prose p{font-size:var(--fs-body);line-height:var(--lh-loose);color:var(--ink-dim);margin-bottom:1.3em}
.prose p:last-child{margin-bottom:0}
.prose strong{color:var(--ink);font-weight:600}
/* :where() so this carries zero specificity — it is a sensible
   default, not a decision, and any project rule beats it. */
:where(h1,h2,h3,h4){text-wrap:balance}

/* ===========================================================
   5. BUTTONS — the full state set
   ===========================================================
   The old .btn had solid/ghost and hover. That is fine right up
   until a client site has a form, at which point you need
   :disabled and a submitting state at 11pm the night before
   launch. They are here now.
   =========================================================== */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:var(--sp-2);
  position:relative;
  font-family:var(--sans);font-weight:600;font-size:14px;letter-spacing:.01em;
  line-height:1;
  padding:var(--sp-3) var(--sp-5);
  min-height:44px;                    /* WCAG 2.5.8 target size */
  border-radius:var(--radius-sm);
  border:1px solid transparent;
  cursor:pointer;
  transition:background var(--dur-2) var(--ease),
             border-color var(--dur-2) var(--ease),
             color var(--dur-2) var(--ease),
             opacity var(--dur-2) var(--ease),
             transform var(--dur-1) var(--ease);
  -webkit-tap-highlight-color:transparent;
}

/* --- variants --- */
.btn.solid{background:var(--accent);color:var(--ground)}
.btn.solid:hover:not(:disabled){background:var(--accent-hi)}

.btn.ghost{border-color:rgba(var(--ink-rgb),.26);color:var(--ink);background:transparent}
.btn.ghost:hover:not(:disabled){border-color:var(--ink-mute);background:rgba(var(--ink-rgb),.04)}

.btn.quiet{color:var(--ink-dim);background:transparent;padding-left:var(--sp-3);padding-right:var(--sp-3)}
.btn.quiet:hover:not(:disabled){color:var(--ink)}

.btn.danger{background:var(--danger);color:#fff}
.btn.danger:hover:not(:disabled){filter:brightness(1.1)}

/* --- sizes --- */
.btn.sm{font-size:var(--fs-sm);padding:var(--sp-2) var(--sp-4);min-height:36px}
.btn.lg{font-size:var(--fs-md);padding:var(--sp-4) var(--sp-7);min-height:52px}

/* --- states --- */
.btn:active:not(:disabled){transform:translateY(1px)}

/* :disabled for <button>, [aria-disabled] for <a> — a disabled
   link has no native disabled state, and removing href to fake
   it destroys the accessible name. */
.btn:disabled,
.btn[aria-disabled="true"]{
  opacity:.42;cursor:not-allowed;pointer-events:none;
  filter:saturate(.4);
}

/* Loading. The label stays in the DOM so the button does not
   change width mid-submit (which reads as a glitch); it is just
   made invisible while the spinner sits on top. */
.btn[data-loading="true"]{cursor:progress;pointer-events:none;color:transparent}
.btn[data-loading="true"] > *{opacity:0}
.btn[data-loading="true"]::after{
  content:"";position:absolute;width:16px;height:16px;
  border:2px solid currentColor;border-radius:50%;
  border-top-color:transparent;
  color:var(--ground);
  animation:btnSpin .7s linear infinite;
}
.btn.ghost[data-loading="true"]::after,
.btn.quiet[data-loading="true"]::after{color:var(--ink)}
@keyframes btnSpin{to{transform:rotate(360deg)}}

.btn .arw{transition:transform var(--dur-2) var(--ease)}
.btn:hover:not(:disabled) .arw{transform:translateX(5px)}

/* ===========================================================
   6. FORMS — the set that did not exist
   ===========================================================
   Every site the studio has shipped so far ends in a mailto:.
   The first client who wants a quote request needs all of this,
   and needs it to already be right.
   =========================================================== */
.field{display:flex;flex-direction:column;gap:var(--sp-2);margin-bottom:var(--sp-5)}

/* :not(.check) matters. A checkbox's <label> is also a direct
   child of .field, and without the guard it picks up the mono
   uppercase treatment — which is right for "EMAIL" above an
   input and very wrong for a sentence the visitor has to read
   and agree to. */
.field > label:not(.check){
  font-family:var(--mono);font-size:var(--fs-micro);
  letter-spacing:var(--ls-mono);text-transform:uppercase;
  color:var(--ink-mute);
}
/* Required marks the LABEL, not the input, because that is what
   the visitor is reading when they decide whether to skip it. */
.field > label .req{color:var(--accent);margin-left:.35em}

.input,.textarea,.select{
  width:100%;
  font-family:var(--sans);
  /* 16px is not a style choice: any smaller and iOS Safari zooms
     the whole page on focus, which on a phone reads as the site
     breaking. Do not lower this. */
  font-size:16px;
  line-height:1.5;
  color:var(--ink);
  background:var(--ground-raised);
  border:1px solid var(--line);
  border-radius:var(--radius-sm);
  padding:var(--sp-3) var(--sp-4);
  min-height:48px;
  transition:border-color var(--dur-2) var(--ease),
             background var(--dur-2) var(--ease),
             box-shadow var(--dur-2) var(--ease);
  -webkit-appearance:none;appearance:none;
}
.input::placeholder,.textarea::placeholder{color:var(--ink-mute);opacity:1}
.input:hover:not(:disabled),.textarea:hover:not(:disabled),.select:hover:not(:disabled){
  border-color:rgba(var(--ink-rgb),.24)
}
.input:focus,.textarea:focus,.select:focus{
  outline:none;
  border-color:var(--accent);
  box-shadow:0 0 0 3px rgba(var(--accent-rgb),.16);
}
.input:disabled,.textarea:disabled,.select:disabled{
  opacity:.5;cursor:not-allowed;background:var(--ground-deep)
}
.textarea{min-height:132px;resize:vertical;line-height:var(--lh-body)}

/* Native select arrow is unstyleable, so it is drawn here.
   Uses currentColor via a mask so it follows the theme. */
.select{
  padding-right:var(--sp-8);
  background-image:linear-gradient(45deg,transparent 50%,var(--ink-mute) 50%),
                   linear-gradient(135deg,var(--ink-mute) 50%,transparent 50%);
  background-position:calc(100% - 22px) 50%, calc(100% - 16px) 50%;
  background-size:6px 6px,6px 6px;
  background-repeat:no-repeat;
  cursor:pointer;
}

/* --- validation ---
   Driven by aria-invalid, so the accessible state and the visual
   state can never drift apart. Never style an error with a class
   alone: a red border a screen reader cannot see is not an error
   message, it is a decoration. */
.input[aria-invalid="true"],
.textarea[aria-invalid="true"],
.select[aria-invalid="true"]{
  border-color:var(--danger);
  background:rgba(var(--danger-rgb),.05);
}
.input[aria-invalid="true"]:focus,
.textarea[aria-invalid="true"]:focus,
.select[aria-invalid="true"]:focus{box-shadow:0 0 0 3px rgba(var(--danger-rgb),.18)}

/* Hint and error share a slot. Wire the input with
   aria-describedby to whichever is showing. */
.field .hint{font-size:var(--fs-sm);color:var(--ink-mute);line-height:1.5}
.field .err{
  display:flex;align-items:flex-start;gap:var(--sp-2);
  font-size:var(--fs-sm);color:var(--danger);line-height:1.5;
}
.field .err::before{content:"→";flex:none;opacity:.8}

/* --- checkbox + radio ---
   Custom-drawn because the native control cannot take the accent
   reliably across browsers, but built on a real input so keyboard
   and screen-reader behaviour is untouched. */
.check{display:flex;align-items:flex-start;gap:var(--sp-3);cursor:pointer;
  font-size:var(--fs-body);color:var(--ink-dim);line-height:1.5;min-height:24px}
.check input{
  flex:none;width:20px;height:20px;margin-top:1px;
  -webkit-appearance:none;appearance:none;cursor:pointer;
  background:var(--ground-raised);
  border:1px solid rgba(var(--ink-rgb),.3);
  border-radius:var(--radius-sm);
  display:grid;place-content:center;
  transition:background var(--dur-1) var(--ease),border-color var(--dur-1) var(--ease);
}
.check input[type="radio"]{border-radius:50%}
.check input::before{
  content:"";width:10px;height:10px;transform:scale(0);
  transition:transform var(--dur-1) var(--ease);
  background:var(--ground);
  clip-path:polygon(14% 44%,0 60%,39% 100%,100% 18%,84% 3%,38% 68%);
}
.check input[type="radio"]::before{clip-path:none;border-radius:50%;width:8px;height:8px}
.check input:checked{background:var(--accent);border-color:var(--accent)}
.check input:checked::before{transform:scale(1)}
.check input:disabled{opacity:.5;cursor:not-allowed}
.check input:disabled ~ span{opacity:.5}

/* --- form-level feedback --- */
.form-note{
  display:flex;gap:var(--sp-3);align-items:flex-start;
  padding:var(--sp-4);border-radius:var(--radius-sm);
  font-size:var(--fs-body);line-height:1.55;
  border-left:2px solid;margin-bottom:var(--sp-5);
}
.form-note.is-error{border-color:var(--danger);background:rgba(var(--danger-rgb),.07);color:var(--ink)}
.form-note.is-ok{border-color:var(--success);background:rgba(var(--success-rgb),.07);color:var(--ink)}

/* ===========================================================
   7. MOTION SAFETY
   ===========================================================
   Anything the studio builds inherits this. A project's own
   reduced-motion block extends it; it should never have to
   restate the basics.
   =========================================================== */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
    scroll-behavior:auto !important;
  }
  html{scroll-behavior:auto}
}

/* ===========================================================
   8. PROJECT ALIASES
   ===========================================================
   Bridges the existing sites onto the role names without a
   rewrite. New work should use the role tokens directly; these
   exist so kiln.css and site.css keep working today.

   To migrate a project: delete its :root colour block, add the
   matching alias set below, then over time replace --ember with
   --accent in that project's CSS and drop the alias.
   =========================================================== */

/* KILN portfolio — charcoal + ember */
body[data-brand="kiln"]{
  --ground:#141009; --ground-raised:#1C160E; --ground-deep:#0E0B06;
  --ink:#EADFCE; --ink-dim:#B7AC96; --ink-mute:#8A7C63;
  --accent:#E2632A; --accent-hi:#F08A3C; --accent-deep:#7A3417;
  --ground-rgb:20,16,9; --ground-deep-rgb:14,11,6;
  --ink-rgb:234,223,206; --accent-rgb:226,99,42; --accent-hi-rgb:240,138,60;
  --wrap:1180px;
}
/* trex — graphite + amber */
body[data-brand="trex"]{
  --ground:#0E1013; --ground-raised:#161A1F; --ground-deep:#080A0C;
  --ink:#E8ECF1; --ink-dim:#A5AFBC; --ink-mute:#6C7885;
  --accent:#E08A2C; --accent-hi:#F5A94A; --accent-deep:#7A4610;
  --ground-rgb:14,16,19; --ground-deep-rgb:8,10,12;
  --ink-rgb:232,236,241; --accent-rgb:224,138,44; --accent-hi-rgb:245,169,74;
  --wrap:1200px;
  --ease:cubic-bezier(.2,.7,.3,1);
}

/* ---------------------------------------------------------
   DERIVED TOKENS + LEGACY NAME BRIDGE

   ⚠ THE TRAP THIS BLOCK EXISTS TO AVOID — read once, it is
   subtle and it is already live in kiln.css:

   A custom property is resolved on the element that DECLARES
   it. `--line: rgba(var(--ink-rgb),.12)` written on :root is
   computed against :ROOT's --ink-rgb and then inherits as a
   finished colour string. A theme further down the tree that
   re-points --ink-rgb on <body> arrives too late — --line was
   already baked.

   In kiln.css this means `--line` and `--line-2` keep the warm
   kiln ash tint (234,223,206) on EVERY themed case study. On
   Slow Glass and Keel, whose ash is neutral/cool, every hairline
   rule on the page is quietly warmer than the text it sits under.
   Small, but it is exactly the kind of thing the whole channel-
   token system was built to prevent.

   The fix is to declare derived tokens on the SAME element as
   the theme — which is what this block does, by selecting
   [data-brand] rather than :root.
   --------------------------------------------------------- */
[data-brand]{
  /* derived, re-resolved against this element's own channels */
  --line:      rgba(var(--ink-rgb),.12);
  --line-soft: rgba(var(--ink-rgb),.06);
  --veil:      rgba(var(--ground-rgb),.82);

  /* KILN vocabulary */
  --char:var(--ground); --char-2:var(--ground-raised); --char-3:var(--ground-deep);
  --ash:var(--ink); --ash-dim:var(--ink-dim); --ash-mute:var(--ink-mute);
  --ember:var(--accent); --ember-hi:var(--accent-hi); --ember-deep:var(--accent-deep);
  --char-rgb:var(--ground-rgb); --char3-rgb:var(--ground-deep-rgb);
  --ash-rgb:var(--ink-rgb); --ember-rgb:var(--accent-rgb); --ember-hi-rgb:var(--accent-hi-rgb);
  --line-2:var(--line-soft);

  /* trex vocabulary */
  --steel:var(--ground-raised); --graphite:#2A313A;
  --amber:var(--accent); --amber-hi:var(--accent-hi); --amber-deep:var(--accent-deep);
}

/* The aliases are scoped to [data-brand] on purpose, so this
   file can be layered under an existing site and adopted one
   page at a time.

   MEASURED, not assumed. about.html was rendered with kiln.css
   alone and again with kiln-base.css loaded beneath it, and the
   two were diffed:

     page height   5675px → 5673px
     nav, type, colour, layout, wrap width   identical
     .btn          46px tall → 44px, and gains an explicit
                   min-height:44px

   That single remaining delta is deliberate: 44px is the WCAG
   2.5.8 minimum target size, and the 2px is imperceptible. The
   earlier draft of this file also moved body font-size and
   line-height and shifted the page by 112px — that was a bug,
   found by running this exact comparison, and it is why the
   comparison is worth re-running whenever this file changes. */
