/* Millwright Labs — site styles.
   Layers on brand/web/tokens.css, which is imported from the brand pack and
   never edited. Tokens own type, buttons, cards, fields and the .bars motif;
   this file only adds the nav, the layouts, motion, and the a11y fixes below.
   Brand hard rules: no gradients, shadows, bevels, rotation, or stretching. */

/* ---------------------------------------------------------------- a11y fixes
   Three token defaults miss WCAG AA for normal-size text. Measured contrast:
     --paper text on --rust background ............ 4.14:1  (AA needs 4.5)
     --rust link text on --ink background ......... 3.64:1
     a .btn--primary inside .on-ink ............... 1.30:1  — `.on-ink a`
       out-specifies `.btn--primary`, so the hero CTA painted rust on rust.
   Corrected here rather than in tokens.css (one-way import, never edited).
   Every fix stays inside the locked palette — no new hex. --rust keeps its
   role everywhere it is not carrying small text. */
.btn--primary             { background: var(--rust-hover); }   /* 5.38:1 with paper text */
.on-ink .btn--primary     { color: var(--paper); }
.on-ink a:not(.btn)       { color: var(--bone); text-decoration-color: var(--rust); }  /* 12.2:1 */
.on-ink a:not(.btn):hover { color: var(--paper); }

/* GUIDE ERRATUM — flagged for brand pack v1.1. DO NOT resolve by reverting the
   --rust-hover fill above. §04: "Rust on Paper is 4.2:1 ... not for small body
   copy" (measured 4.14:1). §07 then specifies the primary button as "Rust fill,
   paper text, Archivo 600 at 15px" — 15px is small text under the 18.66px-bold
   / 24px threshold §04 itself cites, and §05's scale sets the same control at
   16px on mobile, also small text. Both sections cannot hold, and the button as
   specified fails AA. Shipped resolution: --rust-hover, 5.38:1, inside the
   locked palette. The brand owner picks the exit:
     (a) ratify #A0522F as the §07 primary fill and update the pack;
     (b) keep #B8623F and raise button text to 18.66px/700 or 24px, which
         changes the type scale rather than only the colour;
     (c) keep #B8623F and accept a documented AA failure on the main CTA.
   Consequence of (a) worth recording: #B8623F then appears nowhere as a fill,
   and §04 names "buttons" first in rust's role list.

   §07: "Hover darkens to --rust-hover." That step was spent on the resting
   fill, so hover takes the next darker rust the palette has. One rule for every
   ground: the guide darkens the button, it does not swap its ground. */
.btn--primary:hover { background: var(--rust-text); color: var(--paper); }   /* 5.74:1 */

/* ------------------------------------------------------------------- global */
/* tokens.css ships no box-sizing reset, so min-height/width land on the
   content box: .btn anchors measured 72px against the form <button>'s 44px,
   and .field's 100% width sat outside its padding. */
*, *::before, *::after { box-sizing: border-box; }

/* <button> does not inherit the body's 1.6 line-height but <a class="btn">
   does, so the two rendered 8px apart. Pin it and both settle on min-height. */
.btn { line-height: 1; }

html { scroll-behavior: smooth; scroll-padding-top: var(--s-7); }

.skip-link { position: absolute; left: -9999px; }
.skip-link:focus {
  left: var(--s-3); top: var(--s-2); z-index: 100;
  padding: var(--s-2) var(--s-3); border-radius: var(--r-input);
  background: var(--paper); color: var(--ink); text-decoration: none;
  outline: 2px solid var(--ink); outline-offset: 2px;
}

.on-bone { background: var(--bone); }

.detail-mono {
  font-family: var(--font-mono); font-size: 12px;
  letter-spacing: 0.16em; text-transform: uppercase; color: var(--graphite);
}
.on-ink .detail-mono { color: var(--bone); opacity: 0.75; }   /* 7.4:1 */

/* §05's Eyebrow row is "13 / 1.2" desktop, "12 / 1.2" mobile. tokens.css
   declares no line-height, so every eyebrow inherited body's 1.6 and rendered a
   20.8px box instead of 15.6px — 5.2px of that landed straight in the
   eyebrow-to-headline gap on every section head. */
.eyebrow { line-height: 1.2; }

/* tokens.css paints .on-ink .eyebrow #8093B5, a sixth hue that is in neither
   §04's five colours ("Two brand colors, three neutrals") nor §07's token
   block. Wrong at source, corrected here because tokens.css is import-only.
   §04 gives bone for text on charcoal; 0.75 keeps it secondary to the h2 and
   matches the .on-ink .detail-mono treatment above. */
.on-ink .eyebrow { color: var(--bone); opacity: 0.75; }   /* 7.4:1 */

.hidden { display: none; }

/* -------------------------------------------------------------- logo lockup
   Styles from brand/web/logo-lockup.html; markup used verbatim. --lw is the
   wordmark size and every other measure derives from the pack's proportion
   table, so the lockup scales to any breakpoint without drifting. */
.ml-logo {
  /* §07: "horizontal lockup at 32px mark height". The pack's proportion table
     fixes mark height = wordmark size x 1.26, so 32 / 1.26 = 25.4px. The pack's
     own CSS ships 38px, but logo-lockup.html frames those numbers as a rebuild
     reference ("Proportions, if you rebuild at another size") and hands over
     the ratio, while §07 states the header size outright. */
  --lw: 25.4px;
  display: inline-flex; align-items: center;
  gap: calc(var(--lw) * 0.47); text-decoration: none;
}
.ml-logo__mark {
  flex: none; display: block;
  width: calc(var(--lw) * 1.26); height: calc(var(--lw) * 1.26);
}
.ml-logo__type {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: calc(var(--lw) * 0.19);
}
.ml-logo__word {
  font-family: var(--font-display); font-weight: 800;
  font-size: var(--lw); line-height: 0.82; letter-spacing: 0.01em;
  color: var(--bone); white-space: nowrap;
}
.ml-logo__word b { font-weight: 800; color: var(--rust); }
.ml-logo__sub { display: flex; align-items: center; gap: calc(var(--lw) * 0.24); }
.ml-logo__rule { width: calc(var(--lw) * 1.19); height: 2px; background: var(--rust); }
.ml-logo__labs {
  font-family: var(--font-mono); font-size: calc(var(--lw) * 0.25); font-weight: 600;
  letter-spacing: 0.46em; padding-left: 0.46em; color: var(--bone);
}
/* No light-ground lockup variant: the band is charcoal at every scroll
   position, so the ink wordmark, the #3F3D39 LABS colour and the ink mark fill
   are all unreachable. Deleting them also closes the file's only palette
   breach — #3F3D39 is in neither §04's five colours nor §07's token block. */

/* ---------------------------------------------------------------------- nav
   §07: "Charcoal band, 72px tall ... Sticky is fine; it stays a solid charcoal
   band with no blur or transparency." Solid ink at every scroll position also
   satisfies the page spec's "transparent -> solid": over the ink hero an ink
   band reads as transparent, and it never turns light. The paper band came from
   the implementation plan, which is not an authority over the locked guide.
   Padding moves to .nav-inner as min-height so the band measures exactly 72px —
   once the lockup drops to a 32px mark, the tallest child is the 44px CTA. */
.site-nav {
  position: fixed; inset: 0 0 auto; z-index: 50;
  background: var(--ink);
  padding: 0;
}
.nav-inner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); min-height: 72px;
}
.nav-links { display: flex; align-items: center; gap: var(--s-4); }
.nav-links a:not(.btn) {
  color: var(--bone); font-weight: 600; font-size: 15px; line-height: 1;
  text-decoration: none;
}
.nav-links a:not(.btn):hover { color: var(--paper); text-decoration: underline; text-underline-offset: 5px; }

/* tokens.css re-colours the §07 focus ring to bone on dark grounds but scopes
   that to .on-ink, and the header is a sibling of <main> — so the nav CTA kept
   an ink outline on a charcoal band: #182338 on #182338, 1:1, invisible, on the
   first control a keyboard user reaches. Same correction, applied to the one
   dark surface that sits outside .on-ink. */
.site-nav .btn:focus-visible { outline-color: var(--bone); }

/* -------------------------------------------------------------------- hero */
/* padding-top stays composed: the nav is fixed, so the hero has to clear it.
   176px is the 72px band plus 104px of air against §06's 96px — one grid step
   over, and the guide never addresses fixed-nav clearance. padding-bottom had
   no such excuse at 128px. */
.hero { padding-top: calc(var(--s-8) + var(--s-5)); padding-bottom: var(--s-7); }
.hero h1 { max-width: 16ch; }
/* §05's Lead row is 1.55 in both columns — the one row where leading is
   deliberately tighter than body, because at 20px the body ratio reads looser
   than it does at 17px. Sizes were already right; only leading was missing. */
.hero-sub {
  font-size: 20px; line-height: 1.55; max-width: 54ch;
  margin: var(--s-3) 0 var(--s-5);
}
.hero-foot { display: flex; align-items: center; gap: var(--s-3); margin-top: var(--s-7); }
.hero-foot .bars { width: 96px; }

/* ----------------------------------------------------------- section header */
.section-head { margin-bottom: var(--s-5); }
.section-head .bars { width: 96px; margin-bottom: var(--s-3); }
.section-head .eyebrow { margin: 0 0 var(--s-2); }
/* tokens.css zeroes heading margins, so a lead paragraph inside .section-head
   sits flush against its heading. Applies to #contact and to privacy.html. */
.section-head h1 + p,
.section-head h2 + p { margin-top: var(--s-2); }

/* ------------------------------------------------------------------- proof */
.shots {
  display: grid; grid-template-columns: 1fr;
  gap: var(--s-3); margin-top: var(--s-5);
}
/* All three captures are exported at one size, so the row bottoms line up
   without object-fit cropping anything out of a screenshot. The width and
   height attributes in the markup reserve the box, so there is no layout
   shift while they load. */
.shots img {
  display: block; width: 100%; height: auto;
  border: 1px solid var(--hairline); border-radius: var(--r-card);
}

/* §05 Caption row: Archivo 400, Graphite, 14/1.5 desktop and 13/1.5 mobile. */
.caption {
  font-size: 14px; line-height: 1.5; color: var(--graphite);
  margin-top: var(--s-2);
}

/* ---------------------------------------------------------------- services */
.cards { display: grid; grid-template-columns: 1fr; gap: var(--s-3); }
.cards .card p:last-child { margin-bottom: 0; }

/* The Built grid runs quiet on purpose. Services is two offers competing for a
   click and earns the raised card; Built is a list of things that exist, and
   five lit boxes in a row shout over the section that follows them. Same grid,
   stripped to a hairline rule and smaller heads. Hairline rather than rust:
   the pack caps how often the rust motif repeats, and five is over any
   reasonable reading of that cap. */
.cards--quiet .card {
  background: none; box-shadow: none; border: 0; border-radius: 0;
  border-top: 1px solid var(--hairline);
  padding: var(--s-3) 0 0;
}
.cards--quiet h3 { font-size: 18px; }

/* Both grids change at the one breakpoint actually chosen — the same 720px seam
   the nav, body size and section padding already use. The auto-fit minmax()
   values they replace generated three more breakpoints nobody picked (632 and
   936 for .shots, 672 for .cards), and in the 632-935 band the three-up proof
   row went two-up and orphaned its third box beside an empty cell. */
@media (min-width: 721px) {
  .shots { grid-template-columns: repeat(3, 1fr); }
  .cards { grid-template-columns: 1fr 1fr; }
}

/* ------------------------------------------------------------------- bench */
.bench-list {
  list-style: none; margin: 0; padding: 0;
  display: grid; max-width: var(--max-text);
}
/* 28px was the only hand-set track in the file and is off §06's step list.
   24px is the nearest step and makes marker + gap = 48px, so the text column
   starts on a step instead of at 52px. */
.bench-list li {
  display: grid; grid-template-columns: var(--s-3) 1fr; gap: var(--s-3);
  padding: var(--s-3) 0; border-top: 1px solid var(--hairline);
  text-wrap: pretty;
}
.bench-list li::before { content: ""; height: 5px; margin-top: 0.55em; background: var(--rust); }

/* ----------------------------------------------------------------- contact */
.contact-form {
  display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3);
  max-width: 760px;
}
.field-group--full { grid-column: 1 / -1; }

/* .label's 12px / 0.12em are not a defect — they follow §07 Forms, which is
   more specific than §05's 13px / 0.14em and rightly wins. But §07 is silent on
   leading, so §05's 1.2 is the only applicable value, and tokens.css left the
   labels inheriting body's 1.6. The 6px gap is also off §06's step list, and
   unlike the field's licensed 14px x 16px padding it has no exemption. */
.label { line-height: 1.2; margin-bottom: var(--s-1); }

/* §07's focus ring is a box-shadow and the border change is the only other
   signal. Forced-colors mode discards box-shadow and overrides border colours,
   and tokens.css has already removed the fallback with outline: none — so a
   Windows High Contrast user gets no focus state on any field. Restored in that
   mode only; no effect on the normal rendering §07 describes. */
@media (forced-colors: active) {
  .field:focus { outline: 2px solid Highlight; outline-offset: 1px; }
}

/* -------------------------------------------------------------- sub-pages
   privacy.html sits under the same fixed 72px band as the homepage, so it
   needs the same clearance. Its h1 takes the §05 Section-head size rather than
   the Hero size — a legal page title is not a hero, and 76px on a prose page
   reads as shouting. */
.prose-page { padding-top: calc(var(--s-7) + var(--s-4)); }
.prose-page h1 { font-size: clamp(32px, 4vw, 48px); line-height: 0.9; }
/* tokens.css zeroes heading margins, so a bare h3 between paragraphs sits
   flush. #how is the only homepage section with prose subheads, so it borrows
   the spacing already settled on for the sub-pages rather than inventing a
   second one. */
.prose-page h3,
#how h3 { margin-top: var(--s-5); margin-bottom: var(--s-1); }

/* thank-you.html: one short message on an ink ground, nothing else competing.
   Centred vertically so the confirmation is not stranded at the top of a tall
   empty screen. */
.endcap { min-height: 100vh; display: grid; align-content: center; }
.endcap .ml-logo { margin-bottom: var(--s-6); }
.endcap h1 { font-size: clamp(42px, 6vw, 76px); }
.endcap-lead { font-size: 20px; line-height: 1.55; margin: var(--s-3) 0 var(--s-5); }

/* ------------------------------------------------------------------ footer */
.site-footer { padding: var(--s-6) 0; }
.footer-inner { display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3) var(--s-4); }

/* ------------------------------------------------------------------ motion
   Scoped to .js so a script failure never leaves the page blank. */
.js .reveal {
  opacity: 0; transform: translateY(12px);
  transition: opacity 0.4s ease-out, transform 0.4s ease-out;
}
.js .reveal.in-view { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .reveal, .js .reveal.in-view { opacity: 1; transform: none; transition: none; }
}

/* ------------------------------------------------------------------ mobile
   Anchor links drop out below 720px — the page is one scroll and the CTA is
   what has to survive. */
@media (max-width: 720px) {
  /* §06: "Vertical padding 96px desktop / 56px mobile." tokens.css ships 48px
     and is import-only, so the step lands here. Must stay ABOVE .hero below,
     which re-declares its own top and bottom padding. */
  .section { padding: 56px 0; }

  /* §05's mobile column. The build had implemented the mobile step for body
     text only, so these rows were rendering desktop values at 375px. */
  h1 { line-height: 0.9; }                    /* Hero          42 / 0.9  */
  h2 { line-height: 0.95; }                   /* Section head  32 / 0.95 */
  h3 { font-size: 20px; line-height: 1.3; }   /* Card title    20 / 1.3  */
  .eyebrow { font-size: 12px; }               /* Eyebrow       12 / 1.2  */
  .btn { font-size: 16px; }                   /* Button / nav  16 / 1    */

  .nav-links { gap: var(--s-2); }
  .nav-links a:not(.btn) { display: none; }
  /* 8 x 16 is on §06's step list; 10 x 18 was not. font-size is dropped so the
     CTA takes the 16px above. §07's 14 x 28 cannot be restored here — it would
     overflow the 375px row. min-height: 44px still governs the hit target. */
  .nav-links .btn { padding: var(--s-1) var(--s-2); }

  /* 72px of fixed-band clearance plus §06's 56px mobile pad. */
  .hero { padding-top: calc(var(--s-7) + var(--s-4)); padding-bottom: 56px; }
  .hero-sub { font-size: 18px; }
  .hero-foot { margin-top: var(--s-5); }
  .contact-form { grid-template-columns: 1fr; }
}

/* §02: "Horizontal lockup: 150px wide digital ... Below 150px, drop the
   wordmark and use the mark alone." BRAND-OWNER OVERRIDE, Daniel 2026-08-01:
   the wordmark stays at phone widths — a mark-alone header read as a missing
   logo on his first phone review. The row is paid for with air, not lockup
   size: the band's side padding and its gap each drop one step, and the CTA
   gives up 4px of inline padding per side. At 375 the row keeps 8px of slack
   and it still clears 360. --lw is untouched, so §02's proportion table still
   governs the lockup itself; the 6.35px LABS line resolves at phone pixel
   densities (2-3 device px per CSS px). */
@media (max-width: 480px) {
  .site-nav .wrap { padding: 0 var(--s-2); }
  .nav-inner { gap: var(--s-1); }
  .nav-links .btn { padding-inline: 12px; }
}
/* Below ~340px the full lockup and the CTA cannot share the row at any honest
   padding, so §02's mark-alone rule returns — as the floor, not the default. */
@media (max-width: 340px) {
  .ml-logo__type { display: none; }
}
