@font-face{font-family:Archivo;src:url('/assets/fonts/archivo-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:Archivo;src:url('/assets/fonts/archivo-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:Archivo;src:url('/assets/fonts/archivo-600.woff2') format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:Archivo;src:url('/assets/fonts/archivo-700.woff2') format('woff2');font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url('/assets/fonts/ibm-plex-mono-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:"IBM Plex Mono";src:url('/assets/fonts/ibm-plex-mono-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}

:root{
  --ink:#17191c;
  --ink-raised:#1e2126;
  --ink-deep:#111316;
  --paper:#f4f1eb;
  --paper-deep:#e8e4dc;
  --concrete:#b7b2aa;
  --grey:#96928b;
  --copper:#a65332;
  --copper-dark:#7e3e26;
  --rule-dark:rgba(244,241,235,.18);
  --rule-light:rgba(23,25,28,.2);
  --shell:1380px;
  --gutter:clamp(22px,4.15vw,72px);
  --font:Archivo,Arial,sans-serif;
  --mono:"IBM Plex Mono","Courier New",ui-monospace,monospace;
  /* WEB-17. Sol's "approximately 80% Paper on Ink" composited to a real
     value. 10.33:1 on Ink. PROVISIONAL for the preview only, authorised by
     Rich so Sol judges it rendered. NOT ratified into V5-DOC-02. */
  --body-mid:#C8C6C2;

  /* WEB-17T. SURFACE ALIASES, adopted as the mechanism per Sol's governance
     rule: no text-colour token is assigned globally across Ink and Paper. A
     text colour is chosen by NAMING ITS GROUND, so using the wrong one is a
     visible naming error rather than an invisible contrast failure. Every
     value below carries its permitted ground and measured ratio; the full
     table is docs/COLOUR_TOKENS.md. Thresholds: 4.5:1 normal text, 3:1
     large text and non-text UI. */
  --label-on-ink:#B7B2AA;      /* Concrete on Ink #17191C, 8.36:1. Small mono labels, eyebrows, footer headings on Ink */
  --label-on-paper:#5D5B57;    /* register neutral on Paper #F4F1EB, 6.01:1. Same roles on Paper grounds */
  --body-on-ink:#C8C6C2;       /* = --body-mid, 10.33:1 on Ink. Body copy on Ink only, never Paper (1.51:1) */
  --body-on-paper:#5D5B57;     /* 6.01:1 on Paper. Body copy on Paper */
  --accent-on-paper:#7E3E26;   /* copper-dark on Paper 7.15:1. The ONE place small copper text is permitted */
}

*{box-sizing:border-box;margin:0;padding:0}
html{scroll-behavior:smooth;-webkit-text-size-adjust:100%}
/* WEB-17R. overflow-x:hidden was REMOVED, not relaxed. It was the reason the
   81-cell scrollWidth matrix returned a confident zero while text was being
   visibly clipped at 390px: content was cut instead of widening the
   document, so the instrument never moved. Containment is now proven per
   element, not per document. */
body{background:var(--ink);color:var(--body-mid);font-family:var(--font);font-size:17px;font-weight:400;line-height:1.65}
img{display:block;width:100%;height:100%;object-fit:cover}
svg{display:block;fill:currentColor}
a{color:inherit;text-decoration:none}
button{font:inherit}
::selection{background:var(--copper);color:var(--paper)}
.svg-defs{position:absolute}
.shell{width:min(100%,var(--shell));margin-inline:auto;padding-inline:var(--gutter)}
.skip{position:fixed;left:12px;top:0;z-index:200;transform:translateY(-120%);background:var(--paper);color:var(--ink);padding:10px 16px;font:600 12px/1 var(--mono);text-transform:uppercase;letter-spacing:.12em;transition:transform .2s}
.skip:focus{top:12px;transform:none}
:focus-visible{outline:2px solid var(--copper);outline-offset:4px}

.site-header{height:76px;position:sticky;top:0;z-index:100;background:rgba(23,25,28,.97);border-bottom:1px solid var(--rule-dark);backdrop-filter:blur(14px)}
.nav-shell{height:76px;display:flex;align-items:center;gap:36px}
/* WEB-17R. The header carries the composed V7C lockup: master mark plus
   outlined wordmark, one accessible home link. Desktop 30x40 mark, 168px
   wordmark, 13px gap. Mobile 24x32, 142px, 10px gap. Both mark sizes are
   MASTER geometry: 40px and 32px high both sit on or above the pack's
   documented 32px master floor, so the micro is not substituted. */
.brand{display:flex;align-items:center;gap:13px;flex:0 0 auto;min-width:0}
.brand-mk{width:30px;height:40px;flex:0 0 auto;color:var(--paper)}
.brand svg{color:var(--paper);transform:translateY(-1px)}
.wordmark{font-size:16px;font-weight:600;line-height:1;text-transform:uppercase;letter-spacing:.15em;white-space:nowrap}
/* WEB-16 V7C. The outlined wordmark is the primary identity and replaces the
   mark-plus-live-text lockup. It is sized by WIDTH: the 7788x718 artwork is
   10.85:1, so 168px wide gives 15.5px of cap height inside the 76px shell.
   The old 600/0.15em live-text rule above no longer draws the visible logo and
   is left only so any unconverted caller degrades legibly rather than to zero.
   Never set this below the pack's 120px floor. */
.brand-wm{width:168px;height:auto;display:block;flex:0 0 auto}
@media(max-width:959.98px){
  /* WEB-17R. Was keyed to 380px, so at 390 the header still carried the
     168px desktop wordmark and overflowed, pushing the MENU control off
     screen where overflow-x:hidden then hid it. */
  .brand{gap:10px}
  .brand-mk{width:24px;height:32px}
  .brand-wm{width:142px}
}
/* WEB-16 V7C service signature: page identity only, one per page, never
   a repeating motif. 1120x160 artwork, 7:1, so 280px reads at 40px tall. */
.page-sig{width:280px;height:auto;display:block;margin-bottom:26px;color:var(--paper)}
/* Accessible name is carried twice on purpose: aria-label on the SVG for AT,
   and real text here so the company name stays searchable in the DOM. */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0}
.site-header nav{display:flex;align-items:center;gap:clamp(18px,2vw,34px);margin-left:auto}
.site-header nav a{font-size:13px;font-weight:500;color:#d3cfc7;transition:color .18s}
.site-header nav a:hover{color:var(--paper)}
.button{display:inline-flex;min-height:48px;align-items:center;justify-content:center;padding:0 22px;border:1px solid transparent;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.11em;transition:background .18s,color .18s,border-color .18s}
.button-copper{background:var(--copper);color:var(--paper)}
.button-copper:hover{background:#b85d39}
/* WEB-17. Copper is one event per viewport and the PRIMARY CTA takes
   priority. In the hero viewport the header CTA and the hero CTA were both
   filled copper, which is two events. The header yields to an outline so
   the hero CTA owns the accent. */
.header-cta{min-height:42px;padding-inline:18px;white-space:nowrap;
  background:transparent;color:var(--paper);border-color:var(--rule-dark)}
.header-cta:hover{background:transparent;border-color:var(--paper)}
.menu-button{display:none;border:0;background:none;color:var(--paper);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.12em;cursor:pointer}

.hero{min-height:calc(100svh - 76px);display:grid;grid-template-columns:minmax(0,53%) minmax(0,47%);background:var(--ink);border-bottom:1px solid var(--rule-dark)}
.hero-copy{min-height:720px;display:flex;flex-direction:column;padding:clamp(64px,7vw,112px) var(--gutter) 0;position:relative}
.hero-copy-inner{width:min(100%,730px);margin-left:auto;margin-right:0;display:flex;flex-direction:column;align-items:flex-start}
.kicker{display:flex;align-items:center;gap:14px;font:500 11px/1.2 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--label-on-ink)}
/* WEB-17. Copper is one event per viewport and the primary CTA owns it.
   Section markers move to Concrete. */
.kicker span{color:var(--label-on-ink)}
/* WEB-17S. max-width was 14ch, and at 68px CONSTRUCTION. is 13 characters plus
   tracking, so the full stop wrapped to a line of its own. Widened to 15ch;
   the four-line hierarchy STRUCTURED / CONSTRUCTION. / CONTROLLED / DELIVERY.
   holds at every width, verified by the rendered-word gate. */
.hero h1{margin-top:clamp(30px,4vw,54px);font-size:clamp(40px,4.4vw,68px);font-weight:600;line-height:.96;letter-spacing:-.02em;text-transform:uppercase;max-width:15.5ch}
.hero h1 em{color:var(--concrete);font-style:normal}
.hero-lede{margin-top:clamp(32px,4vw,52px);max-width:64ch;color:var(--body-mid);font-size:17px;line-height:1.65}
.hero-actions{display:flex;align-items:center;gap:28px;margin-top:36px}
.text-link{display:inline-flex;align-items:center;gap:18px;padding-block:8px;border-bottom:1px solid rgba(244,241,235,.48);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.1em}
.text-link span{color:var(--concrete);font-size:15px}
.hero-index{width:calc(100% + var(--gutter));margin-top:auto;margin-left:calc(var(--gutter) * -1);display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--rule-dark)}
.hero-index span{min-height:70px;display:flex;align-items:center;padding-inline:var(--gutter);border-right:1px solid var(--rule-dark);font:500 10px/1.25 var(--mono);text-transform:uppercase;letter-spacing:.12em;color:var(--label-on-ink)}
.hero-image{position:relative;min-width:0;overflow:hidden;background:#62605c}
/* WEB-17: superseded, the img now sits inside <picture>. See .hero-image img below. */
.hero-image::after{content:"";position:absolute;inset:0;background:linear-gradient(90deg,rgba(23,25,28,.15),transparent 28%),linear-gradient(0deg,rgba(17,19,22,.62),transparent 38%);pointer-events:none}
.hero-image::before{content:"";position:absolute;left:0;top:0;bottom:0;width:1px;background:var(--rule-dark);z-index:2}
.hero-mark{position:absolute;z-index:3;right:clamp(24px,4vw,64px);top:clamp(24px,4vw,60px);width:78px;height:132px;background:rgba(23,25,28,.92);display:grid;place-items:center;border-top:3px solid var(--copper)}
/* WEB-16 V7C: the mark aspect changed from 203:430 to 240:320, so the old
   34x73 pair no longer matches the artwork and would letterbox inside the
   badge. Re-cut to the V7C 3:4 ratio at the same optical weight. */
.hero-mark svg{width:48px;height:64px;color:var(--paper)}
.hero-image figcaption{position:absolute;z-index:3;left:0;right:0;bottom:0;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:24px;padding:23px clamp(24px,4vw,64px);border-top:1px solid rgba(244,241,235,.3);font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.12em;color:#d1cdc5}
.hero-image figcaption strong{text-align:center;color:var(--paper);font-weight:500}
.hero-image figcaption span:last-child{text-align:right}

.credential-rail{background:var(--paper);color:var(--ink);border-bottom:1px solid var(--rule-light)}
.credential-grid{display:grid;grid-template-columns:repeat(4,1fr)}
.credential-grid>div{min-height:106px;display:flex;flex-direction:column;justify-content:center;padding:24px clamp(18px,2.5vw,38px);border-right:1px solid var(--rule-light)}
.credential-grid>div:first-child{border-left:1px solid var(--rule-light)}
.credential-grid span{font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.12em;color:var(--label-on-paper)}
.credential-grid strong{margin-top:10px;font-size:17px;font-weight:400;letter-spacing:.01em;color:var(--paper)}

.dark-section{background:var(--ink);color:var(--paper)}
.positioning{padding:clamp(80px,10vw,156px) 0}
.section-intro{display:grid;grid-template-columns:1.4fr .8fr;column-gap:clamp(48px,8vw,130px);row-gap:32px;align-items:end}
.section-intro>.kicker{grid-column:1/-1}
/* WEB-17. H2 was 76px/600, against a 90px H1: a ratio of 1.18, which is why
   they fought. Now 30px/400 against 68px, a ratio of 2.27. */
h2{font-size:clamp(24px,2.2vw,30px);font-weight:400;line-height:1.2;letter-spacing:-.01em;text-transform:uppercase}
.intro-copy{padding-bottom:4px}
.intro-copy p{color:var(--body-mid);font-size:17px;line-height:1.65;max-width:66ch}
.intro-copy p+p{margin-top:20px}
.service-architecture{margin-top:clamp(68px,8vw,118px);display:grid;grid-template-columns:1.15fr .85fr;border:1px solid var(--rule-dark)}
.service-primary,.service-secondary{min-width:0;position:relative;display:grid;grid-template-columns:52px 1fr;gap:clamp(22px,3vw,46px);transition:background .2s,color .2s}
/* WEB-17X. The 52px track is the numbering datum. It held a number element
   until WEB-17 (11fe84b) dropped it, after which the content div auto-placed
   into the datum and rendered 52px wide inside a 718px card. Sol's ruling: the
   numbers are NOT restored and the track IS kept, so placement is explicit and
   no longer depends on a source-order occupant that can be deleted. The dead
   rule for the old number element is removed. */
.service-primary>div,.service-secondary>div{grid-column:2;min-width:0}
.service-primary{min-height:620px;padding:clamp(34px,5vw,72px);background:var(--paper);color:var(--ink);border-top:5px solid var(--copper)}
.service-primary:hover{background:#fffdf8}
.service-stack{min-width:0;display:grid;grid-template-rows:1fr 1fr}
/* WEB-17X. min-height is a RESERVE against .service-go, which is out of flow at
   the card foot and was overlapping the last line of copy. The max-width:1180
   and max-width:680 rules below govern their own ranges. */
.service-secondary{min-height:364px;padding:clamp(32px,4vw,54px);border-bottom:1px solid var(--rule-dark);background:var(--ink-raised)}
.service-secondary:last-child{border-bottom:0}
.service-secondary:hover{background:#252930}
.service-label{font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.13em;color:var(--label-on-paper)}
.service-primary h3{margin-top:clamp(28px,4vw,52px);font-size:17px;font-weight:500;line-height:1.35;letter-spacing:.01em;text-transform:uppercase}
.service-primary p:not(.service-label){max-width:49ch;margin-top:32px;color:#5c5954;line-height:1.7}
.service-secondary .service-label{color:var(--label-on-ink)}
.service-secondary h3{margin-top:22px;font-size:clamp(23px,2.4vw,34px);font-weight:600;line-height:1.05;letter-spacing:-.02em;text-transform:uppercase}
.service-secondary p:not(.service-label){margin-top:18px;color:#aaa69f;max-width:44ch}
.service-go{position:absolute;left:calc(52px + clamp(22px,3vw,46px) + clamp(34px,5vw,72px));right:clamp(34px,5vw,72px);bottom:clamp(34px,5vw,58px);display:flex;justify-content:space-between;padding-top:16px;border-top:1px solid currentColor;font:600 11px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.1em}
.service-secondary .service-go{left:calc(52px + clamp(22px,3vw,46px) + clamp(32px,4vw,54px));right:clamp(32px,4vw,54px);bottom:30px;color:#dedad2;border-color:var(--rule-dark)}
.service-go b{color:var(--copper);font-weight:500}

.paper-section{background:var(--paper);color:var(--ink)}
.project-evidence{padding:clamp(80px,10vw,156px) 0}
.paper-heading{display:grid;grid-template-columns:1.25fr .75fr;gap:clamp(50px,8vw,130px);align-items:end}
.paper-heading h2{margin-top:28px}
.paper-heading>p{font-size:17px;line-height:1.7;color:#5d5b57;max-width:48ch}
.featured-project{margin-top:clamp(60px,7vw,100px);display:grid;grid-template-columns:1.32fr .68fr;min-height:620px;border:1px solid var(--rule-light)}
.featured-project figure{min-width:0;overflow:hidden;background:#aaa}
.featured-project img{object-position:center 68%}
.project-detail{display:flex;flex-direction:column;padding:clamp(32px,4.5vw,64px);border-left:1px solid var(--rule-light)}
.project-ref,.project-card-copy span{font:500 10px/1.2 var(--mono);letter-spacing:.13em;text-transform:uppercase;color:var(--label-on-paper)}
.project-detail h3{margin-top:22px;font-size:17px;font-weight:500;line-height:1.35;letter-spacing:.01em;text-transform:uppercase}
.project-detail dl{margin-top:48px;border-top:1px solid var(--rule-light)}
.project-detail dl div{display:grid;grid-template-columns:110px 1fr;gap:16px;padding:15px 0;border-bottom:1px solid var(--rule-light)}
.project-detail dt{font:500 10px/1.3 var(--mono);text-transform:uppercase;letter-spacing:.1em;color:var(--label-on-paper)}
.project-detail dd{font-size:13px;font-weight:500}
.project-detail .text-link{margin-top:auto;align-self:flex-start}
.ink-link{color:var(--ink);border-color:rgba(23,25,28,.5)}
.project-pair{margin-top:32px;display:grid;grid-template-columns:1.25fr .75fr;gap:32px}
.project-card{border:1px solid var(--rule-light)}
.project-card figure{height:clamp(360px,40vw,560px);overflow:hidden;background:#aaa}
.project-card img{object-position:center}
.project-card-tall img{object-position:center 52%}
.project-card-copy{padding:28px 30px 32px}
.project-card-copy h3{margin-top:14px;font-size:17px;font-weight:500;line-height:1.35;letter-spacing:.01em;text-transform:uppercase}
.project-card-copy p{margin-top:10px;color:#625f59}

.control-system{padding:clamp(80px,9vw,144px) 0;border-top:1px solid var(--rule-dark)}
.control-heading{display:grid;grid-template-columns:1fr .7fr;gap:clamp(48px,8vw,130px);align-items:end}
.control-heading h2{margin-top:28px}
.control-heading>p{color:#aaa69f;font-size:17px;line-height:1.7;max-width:50ch}
.control-steps{list-style:none;margin-top:clamp(58px,7vw,96px);display:grid;grid-template-columns:repeat(5,1fr);border-top:1px solid var(--rule-dark);border-bottom:1px solid var(--rule-dark)}
.control-steps li{min-height:260px;padding:30px 26px;border-right:1px solid var(--rule-dark);display:flex;flex-direction:column}
.control-steps li:first-child{border-left:1px solid var(--rule-dark)}
/* WEB-17Y. 11fe84b replaced <span>01</span><strong>Define</strong> with
   <b>Define</b> and left both rules behind, so the headings rendered at browser
   default. Sol's ruling: restore the treatment on the live <b>, delete the two
   dead selectors, do not restore the numbering. */
.control-steps b{margin-top:48px;font-size:19px;font-weight:600;text-transform:uppercase;letter-spacing:.025em}
.control-steps p{margin-top:auto;color:#96928b;font-size:14px;line-height:1.55}

.joinery-feature{display:grid;grid-template-columns:1fr 1fr;min-height:780px;background:var(--paper);color:var(--ink)}
.joinery-image{position:relative;overflow:hidden;background:#b4b1aa}
.joinery-image img{object-position:center 50%;filter:saturate(.72) contrast(1.04)}
.image-index{position:absolute;left:0;bottom:0;background:var(--ink);color:var(--paper);padding:16px 24px;font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.12em}
.joinery-copy{display:flex;flex-direction:column;justify-content:center;padding:clamp(56px,7vw,112px)}
.joinery-copy h2{margin-top:34px}
.joinery-copy>p:not(.kicker){margin-top:34px;max-width:56ch;color:#5d5b57;font-size:17px;line-height:1.7}
.joinery-copy ul{list-style:none;margin:36px 0 42px;border-top:1px solid var(--rule-light)}
.joinery-copy li{padding:14px 0 14px 20px;border-bottom:1px solid var(--rule-light);position:relative;font-size:14px;font-weight:500}
.joinery-copy li::before{content:"";position:absolute;left:0;top:22px;width:7px;height:2px;background:var(--copper)}
.button-outline{align-self:flex-start;color:var(--ink);border-color:var(--ink);background:transparent}
.button-outline:hover{background:var(--ink);color:var(--paper)}

.final-cta{padding:clamp(82px,9vw,132px) 0;border-top:1px solid var(--rule-light)}
.final-cta-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:clamp(60px,9vw,150px);align-items:end}
.final-cta h2{margin-top:30px}
.final-cta-grid>div:last-child>p{color:#5d5b57;font-size:17px;line-height:1.7;max-width:43ch}
.button-ink{margin-top:30px;background:var(--ink);color:var(--paper)}
.button-ink:hover{background:#2a2e34}
.direct-contact{display:flex;flex-wrap:wrap;gap:12px 26px;margin-top:30px;font:500 11px/1.4 var(--mono);letter-spacing:.05em}
.direct-contact a{border-bottom:1px solid var(--rule-light)}

.site-footer{padding:80px 0 28px;background:var(--ink-deep);border-top:4px solid var(--copper)}
.footer-main{display:grid;grid-template-columns:1.2fr .8fr;gap:60px;padding-bottom:70px}
.footer-brand .brand{display:inline-flex}
.footer-brand .wordmark{font-size:18px}
.footer-brand p{margin-top:24px;max-width:48ch;color:#98948d;font-size:14px}
.footer-links{display:grid;grid-template-columns:1fr 1fr;gap:40px}
.footer-links h3{margin-bottom:20px;font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.13em;color:var(--label-on-ink)}
.footer-links a{display:block;padding:7px 0;color:#cbc7bf;font-size:14px}
.footer-links a:hover{color:var(--paper)}
.footer-legal{display:grid;grid-template-columns:1fr 1fr auto;gap:24px;padding-top:26px;border-top:1px solid var(--rule-dark);font:500 9px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.1em;color:var(--label-on-ink)}

@media(max-width:1180px){
  .site-header nav{gap:18px}
  .header-cta{display:none}
  .hero{grid-template-columns:56% 44%}
  .hero h1{font-size:clamp(46px,6.3vw,74px)}
  .hero-image figcaption{grid-template-columns:1fr;gap:6px}
  .hero-image figcaption strong,.hero-image figcaption span:last-child{text-align:left}
  .service-architecture{grid-template-columns:1fr}
  .service-primary{min-height:540px}
  .service-stack{grid-template-columns:1fr 1fr;grid-template-rows:auto}
  .service-secondary{min-height:370px;border-bottom:0;border-right:1px solid var(--rule-dark)}
  .service-secondary:last-child{border-right:0}
  .control-steps{grid-template-columns:repeat(3,1fr)}
  .control-steps li:nth-child(3){border-right:0}
  .control-steps li:nth-child(n+4){border-top:1px solid var(--rule-dark)}
  .joinery-copy{padding:clamp(48px,6vw,80px)}
}

@media(max-width:959.98px){
  .site-header nav{position:fixed;display:none;top:76px;left:0;right:0;background:var(--ink);padding:16px var(--gutter) 26px;border-bottom:1px solid var(--rule-dark);flex-direction:column;align-items:stretch;gap:0}
  .site-header nav.open{display:flex}
  .site-header nav a{padding:15px 0;border-bottom:1px solid var(--rule-dark);font-size:14px}
  .menu-button{display:block;margin-left:auto}
  .hero{min-height:auto;grid-template-columns:1fr}
  .hero-copy{min-height:620px;padding-top:72px}
  .hero-copy-inner{margin-left:0}
  .hero h1{font-size:clamp(48px,9vw,78px);max-width:11ch}
  .hero-image{height:700px}
  .credential-grid{grid-template-columns:1fr 1fr}
  .credential-grid>div:nth-child(3){border-left:1px solid var(--rule-light)}
  .credential-grid>div:nth-child(n+3){border-top:1px solid var(--rule-light)}
  .section-intro,.paper-heading,.control-heading,.final-cta-grid{grid-template-columns:1fr}
  .section-intro h2,.paper-heading>div,.control-heading>div{max-width:800px}
  .intro-copy,.paper-heading>p,.control-heading>p{max-width:640px}
  .featured-project{grid-template-columns:1fr;min-height:0}
  .featured-project figure{height:560px}
  .project-detail{min-height:520px;border-left:0;border-top:1px solid var(--rule-light)}
  .project-pair{grid-template-columns:1fr 1fr;gap:22px}
  .project-card figure{height:420px}
  .joinery-feature{grid-template-columns:1fr}
  .joinery-image{height:680px}
  .joinery-copy{padding:80px var(--gutter)}
  .footer-main{grid-template-columns:1fr;gap:50px}
}

/* WEB-17X. Sol's ruling. Between 681 and 959.98 the five control steps were
   setting full sentences in 154px columns. Two equal tracks, steps 1 to 4 in two
   complete rows, step 5 spanning as the close-out stage. Written as an explicit
   range, not a max-width cascade, so the one-column rules below are not relied
   on to undo it. The nth-child(3) border-right removal from the three-column
   range is restored here: in two columns that edge is internal, not the outer
   edge, and dropping it would leave a missing internal border. */
@media(min-width:681px) and (max-width:959.98px){
  .control-steps{grid-template-columns:minmax(0,1fr) minmax(0,1fr)}
  .control-steps li:nth-child(3){border-right:1px solid var(--rule-dark)}
  .control-steps li:nth-child(odd){border-left:1px solid var(--rule-dark)}
  .control-steps li:nth-child(n+3){border-top:1px solid var(--rule-dark)}
  .control-steps li:nth-child(5){grid-column:1/-1}
}

@media(max-width:680px){
  .wordmark{font-size:14px}
  .hero-copy{min-height:630px;padding-top:58px;padding-left:var(--gutter);padding-right:var(--gutter)}
  .hero h1{max-width:100%;font-size:40px;line-height:1.0}
  .hero-lede{font-size:16px}
  .hero-actions{align-items:flex-start;flex-direction:column;gap:18px}
  /* WEB-17R. The three service lanes now STACK at mobile rather than being
     squeezed into three narrow columns and cut. */
  .hero-index{width:calc(100% + (var(--gutter) * 2));margin-left:calc(var(--gutter) * -1);grid-template-columns:1fr}
  .hero-index span{border-bottom:1px solid var(--rule-dark)}
  .hero-index span{padding:16px 12px;min-height:64px;font-size:8px}
  .hero-image{height:620px}
  .hero-mark{width:62px;height:104px}
  .hero-mark svg{width:38px;height:51px} /* WEB-16 V7C 3:4 */
  .hero-image figcaption{padding:20px var(--gutter)}
  .credential-grid{grid-template-columns:1fr}
  .credential-grid>div{min-height:86px;border-left:1px solid var(--rule-light);border-top:1px solid var(--rule-light)}
  .credential-grid>div:first-child{border-top:0}
  h2{font-size:24px}
  .section-intro h2 br{display:none}
  .service-primary,.service-secondary{grid-template-columns:1fr;gap:24px}
  /* WEB-17X. One column here, so the datum track does not exist. Reset the
     explicit placement or the content would create an implicit second column. */
  .service-primary>div,.service-secondary>div{grid-column:1}
  .service-primary{min-height:600px;padding:34px 26px}
  .service-primary h3{font-size:16px}
  .service-stack{grid-template-columns:1fr}
  .service-secondary{min-height:410px;padding:32px 26px;border-right:0;border-bottom:1px solid var(--rule-dark)}
  .service-secondary h3{font-size:clamp(25px,8vw,31px)}
  .service-secondary:last-child{border-bottom:0}
  .service-go,.service-secondary .service-go{left:26px;right:26px;bottom:28px}
  .paper-heading{gap:28px}
  .featured-project figure{height:390px}
  .project-detail{min-height:560px;padding:34px 26px}
  .project-detail dl div{grid-template-columns:90px 1fr}
  .project-pair{grid-template-columns:1fr}
  .project-card figure{height:390px}
  .control-steps{grid-template-columns:1fr}
  .control-steps li{min-height:190px;border-left:1px solid var(--rule-dark);border-right:1px solid var(--rule-dark);border-top:1px solid var(--rule-dark)}
  .control-steps li:first-child{border-top:0}
  .control-steps li:nth-child(3){border-right:1px solid var(--rule-dark)}
  /* WEB-17Y. Carried from the deleted .control-steps strong. Deleting the dead
     selector without moving this would silently drop the mobile spacing. */
  .control-steps b{margin-top:28px}
  .joinery-image{height:570px}
  .joinery-copy{padding:70px var(--gutter)}
  .footer-links{grid-template-columns:1fr 1fr;gap:28px}
  .footer-legal{grid-template-columns:1fr;gap:8px}
}

@media(max-width:380px){
  .brand{gap:10px}
  .wordmark{font-size:12px;letter-spacing:.12em}
  .page-sig{width:214px;margin-bottom:20px}
  .hero h1{font-size:40px}
  .hero-index span{min-height:42px;border-bottom:1px solid var(--rule-dark)}
  .hero-copy{min-height:720px}
  .footer-links{grid-template-columns:1fr}
}

@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*{transition:none!important}}

/* WEB15 inner-page system --------------------------------------------------
   The homepage sets the identity. These rules bring the six public inner
   routes into the same material, type, spacing and evidence hierarchy. */
.wrap{width:min(100%,var(--shell));margin-inline:auto;padding-inline:var(--gutter)}
.mono{font-family:var(--mono)}
.eyebrow{display:block;font:500 11px/1.2 var(--mono);letter-spacing:.14em;text-transform:uppercase;color:var(--label-on-ink)}
.eyebrow::before{content:"";display:inline-block;width:18px;height:2px;margin:0 12px 3px 0;background:var(--copper)}
.eyebrow.dim{color:var(--label-on-ink)}

.nav{height:76px;position:sticky;top:0;z-index:100;background:rgba(23,25,28,.97);border-bottom:1px solid var(--rule-dark);backdrop-filter:blur(14px)}
.nav>.wrap{height:76px;display:flex;align-items:center;gap:36px}
.nav .wm{font-size:16px;font-weight:600;line-height:1;text-transform:uppercase;letter-spacing:.15em;white-space:nowrap}
.nav nav{display:flex;align-items:center;gap:clamp(18px,2vw,34px);margin-left:auto}
.nav nav a{font-size:13px;font-weight:500;color:#d3cfc7;transition:color .18s}
.nav nav a:hover,.nav nav a.on{color:var(--paper)}
.nav nav a.on{position:relative}
.nav nav a.on::after{content:"";position:absolute;left:0;right:0;bottom:-28px;height:3px;background:var(--copper)}
.btn{display:inline-flex;min-height:48px;align-items:center;justify-content:center;padding:0 22px;border:1px solid currentColor;font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.11em;transition:background .18s,color .18s,border-color .18s}
.btn.solid{background:var(--copper);border-color:var(--copper);color:var(--paper)}
.btn.solid:hover{background:#b85d39;border-color:#b85d39}
.btn.top{min-height:42px;padding-inline:18px;white-space:nowrap}
.burger{display:none;border:0;background:none;color:var(--paper);font-size:12px;font-weight:600;text-transform:uppercase;letter-spacing:.12em;cursor:pointer}

main>section:not(.phead):not(.paper):not(.band){padding:clamp(80px,9vw,144px) 0;border-top:1px solid var(--rule-dark)}
.phead{position:relative;overflow:hidden;background:var(--ink-deep);border-bottom:1px solid var(--rule-dark)}
.phead>.wrap{padding-top:clamp(76px,8vw,124px);padding-bottom:clamp(76px,8vw,124px)}
.phead>.wrap>.eyebrow{margin-bottom:32px}
.phead h1{margin-top:30px;max-width:15ch;font-size:clamp(40px,4.4vw,68px);font-weight:600;line-height:.96;letter-spacing:-.02em;text-transform:uppercase}
.phead .lede{margin-top:34px;max-width:65ch;color:var(--body-mid);font-size:17px;line-height:1.7}
.phead .hero-grid{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(300px,.85fr);gap:clamp(54px,8vw,130px);align-items:stretch}
.phead .hero-copy{min-height:560px;padding:0;display:flex;flex-direction:column;justify-content:center;align-items:flex-start}
.phead .hero-copy h1{max-width:13ch}
.phead .hero-mat{min-height:560px;position:relative;overflow:hidden;border:1px solid var(--rule-dark);border-top:5px solid var(--copper);background-color:var(--ink-raised)}
.phead .hero-mat::before,.phead .hero-mat::after{content:"";position:absolute;inset:0;pointer-events:none}
.phead .hero-mat::after{inset:10%;border:1px solid rgba(244,241,235,.13);box-shadow:18px 18px 0 rgba(0,0,0,.18)}
.phead .m-concrete{background:
  linear-gradient(90deg,transparent 0 49.85%,rgba(244,241,235,.13) 50%,transparent 50.15%),
  linear-gradient(0deg,transparent 0 63.85%,rgba(244,241,235,.11) 64%,transparent 64.15%),
  linear-gradient(138deg,#474b50 0 28%,#2c3035 28% 64%,#3b3f44 64% 100%)}
.phead .m-concrete::before{inset:9%;background:
  linear-gradient(90deg,rgba(244,241,235,.07) 0 24%,transparent 24% 48%,rgba(244,241,235,.045) 48% 72%,transparent 72%),
  linear-gradient(0deg,rgba(0,0,0,.2),transparent 42%);border-left:1px solid rgba(244,241,235,.18);border-top:1px solid rgba(244,241,235,.18);box-shadow:22px 22px 0 rgba(0,0,0,.2)}
.phead .m-veneer{background-image:linear-gradient(0deg,rgba(17,19,22,.32),rgba(17,19,22,.05));background-size:cover;background-position:center}
.phead .m-veneer::after{inset:auto 9% 9% 9%;height:31%;background:rgba(23,25,28,.88);border:0;border-top:3px solid var(--copper);box-shadow:none}
.phead .datum{position:absolute;z-index:2;left:0;right:0;top:61%;height:1px;background:var(--copper)}

.head{display:flex;justify-content:space-between;align-items:end;gap:42px;margin-bottom:clamp(48px,6vw,82px)}
.head>div{max-width:920px}
.head h2{margin-top:28px}
.head p{margin-top:26px;max-width:63ch;color:#aaa69f;font-size:17px;line-height:1.7}
.g2,.g3,.g4{display:grid;border-top:1px solid var(--rule-dark);border-left:1px solid var(--rule-dark)}
.g2{grid-template-columns:repeat(2,1fr)}
.g3{grid-template-columns:repeat(3,1fr)}
.g4{grid-template-columns:repeat(4,1fr)}
.g2>div,.g3>div,.g4>div{min-width:0;padding:clamp(30px,3.2vw,48px);border-right:1px solid var(--rule-dark);border-bottom:1px solid var(--rule-dark);background:var(--ink-raised)}
.g2 h3,.g3 h3,.g4 h3{font-size:clamp(19px,1.8vw,27px);font-weight:600;line-height:1.08;letter-spacing:-.02em;text-transform:uppercase}
.g2 p,.g3 p,.g4 p{margin-top:20px;color:#aaa69f;font-size:14.5px;line-height:1.7}
.panel{border-top:4px solid var(--copper)!important}
.panel.quiet{border-top:1px solid var(--rule-dark)!important}

.steps{border-top:1px solid var(--rule-dark)}
.step{display:grid;grid-template-columns:76px minmax(0,1fr);gap:32px;padding:34px 0;border-bottom:1px solid var(--rule-dark)}
/* WEB-17X. The same datum-track defect as the homepage cards, from a different
   commit: 6aabdf7 removed the number element and the content collapsed into the
   76px track, 76px wide and 1017px tall. NOTE the mobile rule below keeps .step
   at 46px 1fr, so unlike the homepage cards this child stays in column 2 at
   EVERY width. It must NOT be reset to column 1 at mobile, or the content goes
   back into a 46px track on phones, which is this defect at its worst. */
.step>div{grid-column:2;min-width:0}
.step h3{font-size:21px;font-weight:600;text-transform:uppercase;letter-spacing:-.01em}
.step p{margin-top:12px;max-width:76ch;color:#aaa69f;line-height:1.7}

.paper{padding:clamp(80px,8vw,126px) 0;background:var(--paper);color:var(--ink);border-top:1px solid var(--rule-light)}
.paper .head p{color:#5d5b57}
.paper .eyebrow{color:var(--label-on-paper)}
.paper .btn{color:var(--ink)}
.paper .btn.solid{color:var(--paper)}
.paper .g2,.paper .g3,.paper .g4{border-color:var(--rule-light)}
.paper .g2>div,.paper .g3>div,.paper .g4>div{background:transparent;border-color:var(--rule-light)}
.paper .g2 p,.paper .g3 p,.paper .g4 p{color:#5d5b57}

.filters{display:flex;flex-wrap:wrap;gap:0;margin-bottom:34px;border-bottom:1px solid var(--rule-light)}
.filters button{appearance:none;border:0;border-top:3px solid transparent;background:transparent;color:#5d5b57;padding:17px 20px 18px;font:600 10px/1 var(--mono);letter-spacing:.11em;text-transform:uppercase;cursor:pointer}
.filters button:hover,.filters button.on{border-top-color:var(--copper);background:rgba(23,25,28,.055);color:var(--ink)}
.sched{border-top:2px solid var(--ink)}
.sched .row{display:grid;grid-template-columns:120px minmax(230px,1.25fr) minmax(220px,1fr) 150px 110px;gap:22px;align-items:center;min-height:78px;padding:15px 12px;border-bottom:1px solid var(--rule-light);transition:background .18s}
.sched .row:not(.hdr):hover{background:rgba(23,25,28,.055)}
.sched .row.hdr{min-height:46px;color:var(--label-on-paper);font:500 9px/1.2 var(--mono);letter-spacing:.12em;text-transform:uppercase}
.sched .code{font:500 10px/1.3 var(--mono);color:#7a4940}
.sched .site{font-size:14px;font-weight:600}
.sched .site span{display:block;margin-top:4px;color:var(--label-on-paper);font-size:11px;font-weight:400}
.sched .cell{font-size:12px;color:#5d5b57}
.tag{display:inline-flex;padding:7px 9px;background:#dedad2;font:600 9px/1 var(--mono);text-transform:uppercase;letter-spacing:.08em;color:#595650}
.tag.live{background:var(--copper);color:var(--paper)}

.band{padding:clamp(82px,9vw,138px) 0;border-top:1px solid var(--rule-dark);background:var(--ink-deep)}
.band .inner{display:grid;grid-template-columns:1fr 1fr;gap:clamp(58px,9vw,150px);align-items:start}
.band h2{margin-top:28px}
.band p{margin-top:28px;max-width:58ch;color:#aaa69f;font-size:17px;line-height:1.7}
.specs{border-top:1px solid currentColor}
.specs .r{display:grid;grid-template-columns:minmax(110px,.7fr) minmax(0,1.3fr);gap:24px;padding:18px 0;border-bottom:1px solid currentColor}
/* WEB-26-FIX, 1/9/26. THE OPACITY STACK, REMOVED RATHER THAN RE-TUNED.

   These two rules carried opacity:.6 and .62 onto real text. WEB-18 named the
   pattern and repaired the GATE that was blind to it; these two rules were
   never themselves changed, so the defect outlived its own diagnosis. Measured
   on the locked palette across all seven routes: 34 opacity-dimmed text
   elements, and 15 of them composited BELOW AA.

       .specs dt   Ink @.6 on Paper #F4F1EB          4.43:1   9 on /capability
       .specs dt   #C8C6C2 @.6 on Ink-raised         4.31:1   6 on /contact

   L6 bans opacity on text outright, so the fix is a solid token per ground, not
   a heavier alpha. The surface-alias rule applies: a text colour is chosen by
   NAMING ITS GROUND, so using the wrong one is a visible naming error rather
   than an invisible contrast failure.

       on Ink grounds    --label-on-ink   #B7B2AA   8.36:1, 7.66 on ink-raised
       on Paper grounds  --label-on-paper #5D5B57   6.01:1

   Nothing else moves. The palette is untouched, the type sizes are untouched,
   and the copper button was measured and already passes at 4.77:1. */
.specs dt{font:500 10px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.1em;color:var(--label-on-ink)}
.paper .specs dt,.paper-section .specs dt,.docbody .specs dt{color:var(--label-on-paper)}
.specs dd{font-size:14px;font-weight:600}
.specs small{display:block;margin-top:5px;font-size:11px;font-weight:400;color:var(--label-on-ink)}
.paper .specs small,.paper-section .specs small,.docbody .specs small{color:var(--label-on-paper)}

.cta-row{display:flex;flex-wrap:wrap;gap:14px;align-items:center;margin:30px 0}

footer:not(.site-footer){padding:80px 0 28px;background:var(--ink-deep);border-top:4px solid var(--copper)}
footer:not(.site-footer) .wm{font-size:16px;font-weight:600;line-height:1;text-transform:uppercase;letter-spacing:.15em}
.fgrid{display:grid;grid-template-columns:1.4fr repeat(3,1fr);gap:clamp(34px,5vw,74px);padding-bottom:70px}
.fgrid p{color:#98948d}
/* WEB-17R. Footer group labels: h4 -> .fh div (they are nav group labels,
   not document headings, and h4-after-h2 failed heading order). Copper ->
   Concrete: 10px copper on Ink was 3.7:1, failing AA, and three extra
   copper events per viewport against the one-event rule. */
.fgrid .fh{margin-bottom:20px;font:500 10px/1.2 var(--mono);text-transform:uppercase;letter-spacing:.13em;color:var(--label-on-ink)}
.fgrid>div>a:not(.brand){display:block;padding:7px 0;color:#cbc7bf;font-size:14px}
.fgrid>div>a:hover{color:var(--paper)}
.legal{display:grid;grid-template-columns:1fr 1fr auto;gap:24px;padding-top:26px;border-top:1px solid var(--rule-dark);font:500 9px/1.4 var(--mono);text-transform:uppercase;letter-spacing:.1em;color:var(--label-on-ink)}

@media(max-width:1180px){
  .nav nav{gap:18px}.btn.top{display:none}
  .g4{grid-template-columns:repeat(2,1fr)}
  .sched .row{grid-template-columns:100px minmax(210px,1.25fr) minmax(180px,1fr) 125px 100px;gap:16px}
  .fgrid{grid-template-columns:1.25fr repeat(3,1fr)}
}
@media(max-width:959.98px){
  .nav nav{position:fixed;display:none;top:76px;left:0;right:0;background:var(--ink);padding:16px var(--gutter) 26px;border-bottom:1px solid var(--rule-dark);flex-direction:column;align-items:stretch;gap:0}
  .nav nav.open{display:flex}.nav nav a{padding:15px 0;border-bottom:1px solid var(--rule-dark);font-size:14px}.nav nav a.on::after{display:none}.burger{display:block;margin-left:auto}
  .phead .hero-grid{grid-template-columns:1fr}.phead .hero-copy{min-height:470px}.phead .hero-mat{min-height:560px}
  .g3{grid-template-columns:repeat(2,1fr)}
  .band .inner{grid-template-columns:1fr}
  .sched .row{grid-template-columns:90px minmax(190px,1fr) 100px}.sched .hide-m{display:none}
  .fgrid{grid-template-columns:1.25fr 1fr 1fr}.fgrid>div:last-child{grid-column:2/4}
}
@media(max-width:680px){
  .nav .wm{font-size:14px}
  .phead>.wrap{padding-top:64px;padding-bottom:64px}/* WEB-17R. This clamp resolved to 46.8px at 390 and overrode the approved
   40px endpoint, which is what clipped REGISTERED and DIFFICULT. */.phead h1{font-size:40px;max-width:100%}.phead .hero-copy{min-height:470px}.phead .hero-mat{min-height:460px}
  .head{align-items:start;flex-direction:column;margin-bottom:44px}.head .btn{align-self:flex-start}
  .g2,.g3,.g4{grid-template-columns:1fr}
  .step{grid-template-columns:46px 1fr;gap:18px}
  .filters{display:grid;grid-template-columns:1fr 1fr}.filters button{text-align:left;border-top:1px solid var(--rule-light)}
  .sched .row{grid-template-columns:minmax(0,1fr) 90px;padding:16px 8px;gap:14px}.sched .code{display:none}.sched .row.hdr .site{font-size:9px;font-weight:500}.sched .site{font-size:13px}
  .specs .r{grid-template-columns:1fr;gap:8px}
  .fgrid{grid-template-columns:1fr 1fr}.fgrid>div:first-child{grid-column:1/-1}.fgrid>div:last-child{grid-column:auto}.legal{grid-template-columns:1fr;gap:8px}
}
@media(max-width:380px){.nav .wm{font-size:12px;letter-spacing:.12em}.fgrid{grid-template-columns:1fr}.fgrid>div:first-child{grid-column:auto}}

/* ============================================ WEB-17 PHOTOGRAPHY COMPONENTS
   Evidence, not decoration. Hard rectilinear crops, no rounded cards, no
   shadows, no gradients beyond the existing identity scrim. Material texture
   never covers a photograph. */
.hero-image picture{position:absolute;inset:0;display:block}
.hero-image img{width:100%;height:100%;object-fit:cover;object-position:50% 46%;
  filter:saturate(.82) contrast(1.03) brightness(.94)}
.hero-mat-photo{position:relative;overflow:hidden;border:1px solid var(--rule-dark);
  border-top:5px solid var(--copper);background-color:var(--ink-raised);min-height:560px}
.hero-mat-photo picture{position:absolute;inset:0;display:block}
.hero-mat-photo img{width:100%;height:100%;object-fit:cover;
  filter:saturate(.82) contrast(1.03) brightness(.9)}
.hero-mat-photo::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(0deg,rgba(17,19,22,.55),transparent 46%)}
/* WEB-27. .phead .hero-mat::after draws an inset frame and a hard 18px drop
   shadow, which is decoration for the MATERIAL PANEL. It scores (0,2,1) and so
   silently outranked .hero-mat-photo::after at (0,1,1): with a photograph in
   the cell the frame read as a stray rectangle across the image and the
   gradient above never applied at all. Both pseudo-elements are neutralised
   by .hero-mat-shot, which is set in the markup ONLY on a cell that actually
   holds a photograph. /construction still holds a material panel and keeps its
   frame, so scoping this to .hero-mat-photo would have changed a page this
   order does not touch. */
.phead .hero-mat.hero-mat-shot::before{content:none}
.phead .hero-mat.hero-mat-shot::after{inset:0;border:0;box-shadow:none;
  background:linear-gradient(0deg,rgba(17,19,22,.55),transparent 46%)}
.evidence-plates{margin-top:clamp(48px,5vw,72px);display:grid;
  grid-template-columns:repeat(3,1fr);gap:1px;background:var(--rule-light)}
.plate{position:relative;background:var(--paper);display:flex;flex-direction:column}
.plate picture{display:block;position:relative;width:100%;aspect-ratio:3/2;overflow:hidden}
.plate img{width:100%;height:100%;object-fit:cover;filter:saturate(.86) contrast(1.02)}
.plate .pair{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule-light)}
.plate .pair picture{aspect-ratio:3/4}
.plate figcaption{padding:20px clamp(18px,2vw,26px) 24px;display:flex;
  flex-direction:column;gap:6px;border-top:1px solid var(--rule-light)}
.plate-label{font:500 11px/1.2 var(--mono);letter-spacing:.14em;text-transform:uppercase;
  color:var(--label-on-paper)}
.plate-note{font-size:15px;color:#5D5B57}
.joinery-evidence{padding:clamp(80px,9vw,144px) 0;border-top:1px solid var(--rule-dark)}
.je-lede{margin-top:22px;max-width:62ch;color:var(--body-mid)}
.je-grid{margin-top:clamp(40px,4vw,64px);display:grid;grid-template-columns:2fr 1fr 1fr;
  gap:1px;background:var(--rule-dark)}
@media(max-width:959.98px){
  .evidence-plates,.je-grid{grid-template-columns:1fr}
  .plate picture{aspect-ratio:16/9}
  .hero-mat-photo{min-height:420px}
}
.ce-strip,.ce-remed{padding:clamp(80px,9vw,144px) 0;border-top:1px solid var(--rule-dark)}
.ce-strip .evidence-plates,.remed-pair{margin-top:clamp(40px,4vw,64px)}
.remed-pair{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--rule-dark);max-width:820px}
.remed-pair .plate picture{aspect-ratio:3/4}
@media(max-width:680px){.remed-pair{grid-template-columns:1fr 1fr}}

/* ==================================================== WEB-17R CONTAINMENT
   Every grid and flex child that holds text can shrink. Without min-width:0 a
   grid child's automatic minimum size is its content, so long words force the
   track wider than the viewport and the page clips instead of wrapping. */
.nav-shell>*,.nav>.wrap>*{min-width:0}
.hero-copy,.hero-copy-inner,.phead .hero-copy,.wrap,.shell,.inner{min-width:0}
.hero h1,.phead h1,h2,p,li,dd,dt{overflow-wrap:break-word}
.hero-copy-inner,.phead .hero-copy{max-width:100%}
@media(max-width:959.98px){
  .hero h1,.phead h1{max-width:100%}
  .hero-lede,.phead .lede,.intro-copy p,.je-lede{max-width:100%}
  .g2,.g3,.g4,.service-architecture,.service-stack{grid-template-columns:1fr}
  .credential-grid{grid-template-columns:1fr 1fr}
  .hero-actions{flex-wrap:wrap;gap:18px}
  .button,.btn{max-width:100%}
}
@media(max-width:680px){
  .credential-grid{grid-template-columns:1fr}
  .remed-pair{grid-template-columns:1fr 1fr}
}

/* ============================================================ WEB-17S
   BRANDED MATERIAL PANELS AND NAMED MEDIA SLOTS.

   Interim by design: photography is being selected properly and each
   approved photograph replaces a panel as it clears. So every media
   position is a NAMED SLOT (data-slot) whose content is driven by ONE
   declaration in the SLOT MAP below. Dropping a photograph in is a
   one-line change at one known place, and the aspect ratio never moves.

   Panels are crisp vector: V7C master pillar geometry, byte-identical, as
   an oversized cropped fragment at 4 to 7% Paper, over restrained
   texture, with one copper datum. No blur, no glow, no gradient artwork,
   no fake photography. Decorative only, aria-hidden. */

.mslot{position:absolute;inset:0;overflow:hidden;background:var(--ink-raised)}
/* Inside a plate the slot is the figure's in-flow media box, exactly where
   <picture> sat, so the figcaption's grid track keeps the plate width. Left
   absolute, the figure collapsed and captions ran 453px wide at 390: the
   per-element gate caught it. */
.plate>.mslot,.plate .pair>.mslot{position:relative;inset:auto;width:100%;height:auto;min-width:0}
.plate .pair{min-width:0}
.plate figcaption{min-width:0}
/* STANDING RULE: no mark inside a Paper band. Homepage evidence plates sit
   on Paper, so their slots carry texture and datum only. */
.paper-section .mslot-mk,.paper .mslot-mk{display:none}
.mslot-mk{position:absolute;color:var(--paper);opacity:.055;pointer-events:none;
  /* oversized and cropped: taller than the panel, offset so a fragment reads */
  width:auto;height:190%;left:auto;right:-18%;top:-32%}
.mslot-datum{position:absolute;left:0;right:0;height:1px;background:var(--copper);opacity:.85}

/* A. CONSTRUCTION: formwork texture + technical grid + copper datum */
.mslot-construction{background:
  linear-gradient(90deg,transparent 0 49.9%,rgba(244,241,235,.10) 50%,transparent 50.1%),
  linear-gradient(0deg,transparent 0 63.9%,rgba(244,241,235,.08) 64%,transparent 64.1%),
  repeating-linear-gradient(90deg,transparent 0 47px,rgba(244,241,235,.045) 47px 48px),
  repeating-linear-gradient(0deg,transparent 0 47px,rgba(244,241,235,.045) 47px 48px),
  linear-gradient(138deg,#2f3338 0 30%,#24272c 30% 66%,#2b2f34 66% 100%)}
.mslot-construction .mslot-datum{top:61%}

/* B. PROJECT DELIVERY: drawing grid, register lines, document datum */
.mslot-delivery{background:
  repeating-linear-gradient(0deg,transparent 0 23px,rgba(244,241,235,.055) 23px 24px),
  repeating-linear-gradient(90deg,transparent 0 95px,rgba(244,241,235,.07) 95px 96px),
  linear-gradient(0deg,#1e2126,#232629)}
.mslot-delivery .mslot-mk{opacity:.045;height:170%;right:-24%;top:-20%}
.mslot-delivery .mslot-datum{top:38%;opacity:.55}
.mslot-delivery::after{content:"";position:absolute;left:8%;right:8%;top:38%;height:1px;
  background:rgba(244,241,235,.16)}

/* C. COMMERCIAL JOINERY: the site's existing dark veneer language, no luxury
   timber, no corrugation */
.mslot-joinery{background:
  repeating-linear-gradient(90deg,rgba(0,0,0,.14) 0 3px,transparent 3px 11px,rgba(244,241,235,.03) 11px 12px,transparent 12px 27px),
  linear-gradient(0deg,rgba(17,19,22,.35),rgba(17,19,22,.05)),
  linear-gradient(90deg,#26221f,#2c2723 48%,#241f1c)}
.mslot-joinery .mslot-mk{opacity:.06}
.mslot-joinery .mslot-datum{bottom:22%}

/* Hero cells: the panel fills the existing cell so the grid rhythm is
   identical to the photographic version. */
.hero-image .mslot{position:absolute;inset:0}
.hero-mat .mslot{position:absolute;inset:0}
.hero-mat.hero-mat-photo{background-color:var(--ink-raised)}

/* ---------------------------------------------------------- SLOT MAP
   One line per slot. To place an approved photograph: give the slot a
   background-image here (or swap the div for the <picture> markup in
   docs/MEDIA_SLOTS.md) and nothing else moves. Aspect ratios live on the
   wrappers (.hero-image, .hero-mat, .plate picture) and are unchanged. */
[data-slot="home-hero"]{}      /* construction, hero grid cell,      480/768/1200/1600 */
[data-slot="home-plate-1"]{}   /* construction, 3:2 (16:9 mobile),   480/768/1200 */
[data-slot="home-plate-2a"]{}  /* delivery,     3:4,                 480/768/1200 */
[data-slot="home-plate-2b"]{}  /* delivery,     3:4,                 480/768/1200 */
[data-slot="con-hero"]{}       /* construction, min-height 560,      480/768/1200/1600 */
/* THE NEXT SIX WERE DECLARED BY WEB-17S AND HAVE NO MARKUP ON ANY PAGE.
   Verified 1/9/26 by counting data-slot in every served document: con-strip-1,
   con-strip-2, con-strip-3, con-remed-a, con-remed-b and joi-plane all return
   zero. The pages were restructured after WEB-17S and the declarations were
   not. They are kept, and labelled, because a rule that matches nothing is
   harmless but a rule that silently implies a position is not. */
[data-slot="con-strip-1"]{}    /* construction, 3:2,                 480/768/1200 */
[data-slot="con-strip-2"]{}    /* delivery,     3:2,                 480/768/1200 */
[data-slot="con-strip-3"]{}    /* construction, 3:2,                 480/768/1200 */
[data-slot="con-remed-a"]{}    /* delivery,     3:4,                 480/768/1200 */
[data-slot="con-remed-b"]{}    /* construction, 3:4,                 480/768/1200 */
/* joi-hero FILLED by WEB-27, 1/9/26: the slot div was replaced by <picture>
   per method B, so there is no [data-slot="joi-hero"] left to address. */
[data-slot="joi-plane"]{}      /* joinery,      3:2,                 480/768/1200 */

/* WEB-27. The register plate grid is filled from app.js and holds only the
   entries with a cleared photograph, which is 2 of 10 today. Fixed at three
   columns it would show a dead third column, so it tracks its own count. The
   1px gap and the rule colour are inherited from .evidence-plates unchanged. */
#all-work,#con-work{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}

/* Plate wrappers that used to hold <picture> now hold the slot div; give
   them the same box so CLS is zero. */
.plate>.mslot{aspect-ratio:3/2;display:block}
.plate .pair>.mslot{aspect-ratio:3/4}
.remed-pair .plate>.mslot{aspect-ratio:3/4}
@media(max-width:959.98px){.plate>.mslot{aspect-ratio:16/9}}

/* ---------------------------------------------- WEB-17S NARROW-PHONE H1
   Below 390px the 40px H1 could not fit CONSTRUCTION. on one line and the
   browser broke the word internally: CONSTRUCTI / ON. Fluid scale from
   32px at 320 to the approved 40px at 390; every word stays intact and
   the four-line hierarchy holds. Nothing changes at 390 and above. */
@media(max-width:389.98px){
  .hero h1,.phead h1{font-size:clamp(32px,10.25vw,40px);overflow-wrap:normal;word-break:keep-all;hyphens:none}
}

/* WEB-17T. The kicker role crosses surfaces. On Ink it is --label-on-ink; on
   every Paper ground it is --label-on-paper. This scoped rule is why the
   Lighthouse contrast audit failed on Paper bands (grey 2.75:1, concrete
   1.87:1) and is the fix. */
.paper .kicker,.paper-section .kicker,.service-primary .kicker,.credential-rail .kicker,.joinery-feature .kicker,
.paper .kicker span,.paper-section .kicker span,.service-primary .kicker span,.credential-rail .kicker span,.joinery-feature .kicker span{color:var(--label-on-paper)}
.paper .text-link span,.paper-section .text-link span{color:var(--label-on-paper)}


/* ==========================================================================
   WEB-19  SURFACE TEXTURE, DRAFT ONLY, BRANCH PREVIEW
   --------------------------------------------------------------------------
   Rich's ruling 19 Aug 2026 from the Ink-to-Concrete-to-Paper moodboard.
   Material grain on the grounds: surface up close, same palette at reading
   distance.

   PROVENANCE. Generated in the browser from SVG feTurbulence written in this
   file. NO stock photography, no third-party asset, no binary added to the
   repo. Final texture assets remain a Sol and project-photography question.

   NO BRICK. Not as texture, image or tint. Rich: "not the bricks".

   HOW IT KEEPS THE PALETTE EXACT, which took three attempts to get right:
   v1 laid a repeating-linear-gradient for trowel and board marks. At a 9px
   period the eye locked onto the rhythm and read corduroy, not a worked
   surface. Removed.
   v2 laid mid-grey noise over the ground at low alpha. Measured on the render
   it LIFTED every dark ground by +7 to +9 per channel, because grey composited
   onto near-black can only lighten. That is a base-colour change and the order
   forbids it.
   v3 tried soft-light blending. Worse, +14.7, for the same reason: a near-black
   ground has 23 units of headroom down and 232 up, so any symmetric operation
   in blend space lifts the mean.
   v4, this one, remaps the turbulence with feComponentTransfer so its MEAN IS
   EXACTLY THE GROUND COLOUR and it varies +/- a fixed amplitude around it.
   One noise per exact palette value, so no shade is flattened into another.
   Mean shift is nil by construction, not by hope.

   NOT TOUCHED. Palette values unchanged. No type change. No copper anywhere in
   this block. Media slots (.mslot, .hero-mat, .m-veneer) excluded so
   photography swaps stay a separate pass.

   PRINT ISOLATION, TWO INDEPENDENT GUARDS, because the governed documents set
   print-color-adjust:exact and would render any leaked background:
     1. the block is inside @media screen, which paged media never matches;
     2. every selector is scoped body:not(.docbody), and both governed
        documents are <body class="docbody">.
   Verified: both documents rendered on this branch are PIXEL-IDENTICAL to
   production at 150dpi, 0.0000 mm drift.
   ========================================================================== */
@media screen{

  /* INK #17191c */
  body:not(.docbody),
  body:not(.docbody) .hero,
  body:not(.docbody) .dark-section{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='t' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.80' numOctaves='4' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.1100' intercept='0.0352'/%3E%3CfeFuncG type='linear' slope='0.1100' intercept='0.0430'/%3E%3CfeFuncB type='linear' slope='0.1100' intercept='0.0548'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23t)'/%3E%3C/svg%3E");
    background-repeat:repeat;background-size:200px 200px}

  /* INK DEEP #111316 */
  body:not(.docbody) .band,
  body:not(.docbody) .site-footer,
  body:not(.docbody) footer:not(.site-footer),
  body:not(.docbody) .phead{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='t' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.80' numOctaves='4' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.0900' intercept='0.0217'/%3E%3CfeFuncG type='linear' slope='0.0900' intercept='0.0295'/%3E%3CfeFuncB type='linear' slope='0.0900' intercept='0.0413'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23t)'/%3E%3C/svg%3E");
    background-repeat:repeat;background-size:200px 200px}

  /* MID, --ink-raised #1e2126. Off-form concrete: tighter aggregate speckle. */
  body:not(.docbody) .service-secondary,
  body:not(.docbody) .g2>div,
  body:not(.docbody) .g3>div,
  body:not(.docbody) .g4>div{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='170' height='170'%3E%3Cfilter id='t' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.20' numOctaves='3' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.1200' intercept='0.0576'/%3E%3CfeFuncG type='linear' slope='0.1200' intercept='0.0694'/%3E%3CfeFuncB type='linear' slope='0.1200' intercept='0.0890'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23t)'/%3E%3C/svg%3E");
    background-repeat:repeat;background-size:170px 170px}

  /* PAPER #f4f1eb. Barely there, paper tooth only. */
  body:not(.docbody) .credential-rail,
  body:not(.docbody) .service-primary,
  body:not(.docbody) .paper-section,
  body:not(.docbody) .paper,
  body:not(.docbody) .joinery-feature,
  body:not(.docbody) .plate{
    background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='t' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='3' seed='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.0440' intercept='0.9349'/%3E%3CfeFuncG type='linear' slope='0.0440' intercept='0.9231'/%3E%3CfeFuncB type='linear' slope='0.0440' intercept='0.8996'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23t)'/%3E%3C/svg%3E");
    background-repeat:repeat;background-size:180px 180px}

  /* Users who ask for more contrast get the flat grounds back. */
  @media (prefers-contrast:more){
    body:not(.docbody),
    body:not(.docbody) .hero,
    body:not(.docbody) .dark-section,
    body:not(.docbody) .band,
    body:not(.docbody) .site-footer,
    body:not(.docbody) footer:not(.site-footer),
    body:not(.docbody) .phead,
    body:not(.docbody) .service-secondary,
    body:not(.docbody) .g2>div,
    body:not(.docbody) .g3>div,
    body:not(.docbody) .g4>div,
    body:not(.docbody) .credential-rail,
    body:not(.docbody) .service-primary,
    body:not(.docbody) .paper-section,
    body:not(.docbody) .paper,
    body:not(.docbody) .joinery-feature,
    body:not(.docbody) .plate{background-image:none}
  }
}
/* ===================== end WEB-19 surface texture ====================== */


/* ==========================================================================
   WEB-20  BRAND DIRECTION PREVIEW, BRANCH ONLY
   --------------------------------------------------------------------------
   Sol's WEB-20 lock is the DESIGN INTENT authority, the WEB-20W wrapper is the
   EXECUTION authority and wins on conflict. Built on top of the WEB-19 v4
   texture layer, which is inherited from that branch and satisfies Sol's
   "quiet real material texture" line without rebuilding it.

   WHAT THE REFERENCE PNG GOVERNS: mood, material, mark treatment and
   placement, visual weight, spacing, photography placement, the dark header,
   and the expanded copper of amendment A1.

   WHAT THE LOCKED SYSTEM GOVERNS: all copy, all routes and nav structure, and
   the type system. None of those is touched here. The live hero copy stands.

   A1, COPPER EXPANSION, RULED 21 Aug 2026, SETTLED. Copper may take small
   text, micro-headings, separators and small interface cues ON PAPER GROUNDS
   where the contrast gate passes. It is NOT licensed for small text on Ink,
   where the proposed value measures 3.07:1 and fails AA. The pre-existing
   datum and non-text accent rules are not deleted by this. Everything copper
   added below stays inside this block so the scope remains reviewable in one
   place. Recorded in the V7C application-rulings addendum.

   A2, SECTOR TAXONOMY, RULED 21 Aug 2026, SETTLED. FOUR sector labels. The
   fifth is withdrawn and is named in the addendum, not here.

   NO photography ships here yet. Every slot is a DELIBERATE MATERIAL PANEL on
   the ink-raised ground, carrying texture and datum with no caption: no stock,
   no generated imagery, and no placeholder wording facing a client. Project
   photography lands only when its site provenance clears.
   ========================================================================== */
@media screen{

  /* A1. Copper micro-heading.
     MEASURED, NOT ASSUMED: --copper #A65332 on Ink is 3.07:1, which FAILS AA
     for small text. So the default here is the safe ground-named token and
     copper is used ONLY on Paper, where --accent-on-paper measures 7.03:1.
     A1 expands where copper appears; it does not licence copper at a failing
     ratio. If Sol wants copper micro-headings on Ink, the value has to change,
     and that is a palette decision, not a CSS one. */
  /* CUT BY WEB-24: the sector-head eyebrow rule, and its Paper-ground
     override. Its only markup was the branch-state label the closeout removed
     on 21 Aug, so it had styled nothing for two days. Deliberately not named
     here: a search of this repo for it now returns nothing, which is the
     point of the gate that found it. */

  /* A1. Copper hairline separator between sector cells. */
  body:not(.docbody) .w20-sectors{padding:clamp(64px,7vw,104px) 0}
  body:not(.docbody) .w20-sectors .w20-head{
    display:flex;align-items:baseline;justify-content:space-between;gap:24px;
    margin-bottom:clamp(22px,2.4vw,34px);flex-wrap:wrap}
  body:not(.docbody) .w20-sectors h2{margin:0}
  body:not(.docbody) .w20-grid{
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:2px}
  body:not(.docbody) .w20-cell{
    position:relative;min-height:210px;display:flex;align-items:flex-end;
    background:var(--ink-raised);border-top:2px solid var(--copper);
    overflow:hidden}
  body:not(.docbody) .w20-cell .w20-label{
    position:relative;z-index:2;width:100%;padding:12px 14px;
    font:600 10px/1.2 var(--mono);letter-spacing:.12em;text-transform:uppercase;
    color:var(--paper);background:linear-gradient(0deg,rgba(17,19,22,.86),rgba(17,19,22,0))}
  /* CUT BY WEB-24: the dashed empty-frame rule that used to sit inside each
     sector cell. The markup went on 21 Aug; leaving the rule alive left the
     dashed box one class attribute away from coming back, and this order
     exists because empty frames are the thing to be rid of. */

  @media (max-width:1080px){
    body:not(.docbody) .w20-grid{grid-template-columns:repeat(2,minmax(0,1fr))}
  }
  @media (max-width:560px){
    body:not(.docbody) .w20-grid{grid-template-columns:minmax(0,1fr)}
    body:not(.docbody) .w20-cell{min-height:168px}
  }

  /* CUT BY WEB-24: the caption bar that used to sit across the bottom of
     every media slot. Its markup went on 21 Aug, and WEB-23A had to suppress
     it by hand the moment a real photograph landed underneath: a caption
     announcing that photography is pending, printed over a photograph, is
     worse than no rule at all. */
}
/* ===================== end WEB-20 brand direction ====================== */


/* ===================== WEB-24 STYLE LIVE, start ====================== */
/* ==========================================================================
   WEB-24  STYLE GOES LIVE
   --------------------------------------------------------------------------
   Rich's ruling 23 Aug 2026: the panes ship as deliberate designed panels.
   A photograph lands in each one later, pane by pane, on his own clock.

   THE PAGE-GROUND LOCK IS NOT TOUCHED. WEB-19 holds every PAGE ground to
   within 0.3 RGB of its locked value and that stays exactly as it is. What
   changes here is PANE INTERIORS, which the 19 Aug surface ruling calls
   media surfaces and lets run the Ink-to-Concrete range. Every pane below
   grounds between #3E4247 and #4C5055, inside that range, so a pane reads
   as worked concrete rather than as a hole where an image failed to load.

   THE GRAIN MATHS is documented in the builder that wrote this block. Short
   version: the WEB-19 v4 remap is unchanged, so the mean of each pane is
   exactly its ground colour; two turbulence bands are averaged inside the
   filter so the texture carries both room-distance mottle and close-up
   aggregate; and the SVG is stretched to the pane rather than tiled, so
   low-frequency noise cannot show a repeat seam. Amplitudes are 0.090 to
   0.105, roughly 23 to 27 RGB either side of the ground, against 0.044 on
   the Paper page ground. That is the visible amplitude the order asks for.

   FIRST ATTEMPT, KEPT AS A NOTE. A single high-frequency band on a 220px
   tile, plus the WEB-20 47px technical grid, rendered as graph paper over a
   flat grey wash: at 100% the fine speckle averaged out and the only thing
   left was the grid. The grid came off the hero pane and the low-frequency
   band went in.

   PRINT ISOLATION, unchanged and still two independent guards: this whole
   block is inside @media screen, which paged media never matches, and every
   selector is scoped body:not(.docbody). Both governed documents are
   <body class="docbody">.

   ADDRESSABLE PANES. Every pane carries a stable data-pane id and draws its
   surface from var(--pane-img, var(--pane-grain)). A photograph arrives as
   ONE line in the PANE MAP at the end of this block. Nothing else moves.
   ========================================================================== */
@media screen{

  /* ---------------------------------------------- A. THE PANE MECHANISM
     One rule serves every pane. --pane-img is unset until a photograph is
     placed, so the fallback, the grain, is what renders today. */
  body:not(.docbody) [data-pane]{
    background-image:var(--pane-img,var(--pane-grain));
    background-size:var(--pane-size,100% 100%);
    background-repeat:var(--pane-repeat,no-repeat);
    background-position:var(--pane-pos,center)}

  /* pane hero: off-form concrete, board-marked, raking light
     ground #575B61, amplitude 0.105 (~27 RGB either side of the ground) */
  body:not(.docbody) [data-pane="hero"]{
    background-color:#575B61;
    --pane-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='640' height='640' viewBox='0 0 640 640' preserveAspectRatio='none'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.016' numOctaves='4' seed='7' stitchTiles='stitch' result='a'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' seed='12' stitchTiles='stitch' result='b'/%3E%3CfeColorMatrix in='a' type='saturate' values='0' result='ag'/%3E%3CfeColorMatrix in='b' type='saturate' values='0' result='bg'/%3E%3CfeComposite in='ag' in2='bg' operator='arithmetic' k1='0' k2='0.66' k3='0.34' k4='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='0.2100' intercept='0.2362'/%3E%3CfeFuncG type='linear' slope='0.2100' intercept='0.2519'/%3E%3CfeFuncB type='linear' slope='0.2100' intercept='0.2754'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E")}

  /* pane s1: commercial: tighter aggregate, flatter face
     ground #474B51, amplitude 0.095 (~24 RGB either side of the ground) */
  body:not(.docbody) [data-pane="s1"]{
    background-color:#474B51;
    --pane-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270' viewBox='0 0 480 270' preserveAspectRatio='none'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.022' numOctaves='4' seed='11' stitchTiles='stitch' result='a'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.0' numOctaves='3' seed='16' stitchTiles='stitch' result='b'/%3E%3CfeColorMatrix in='a' type='saturate' values='0' result='ag'/%3E%3CfeColorMatrix in='b' type='saturate' values='0' result='bg'/%3E%3CfeComposite in='ag' in2='bg' operator='arithmetic' k1='0' k2='0.62' k3='0.38' k4='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='0.1900' intercept='0.1834'/%3E%3CfeFuncG type='linear' slope='0.1900' intercept='0.1991'/%3E%3CfeFuncB type='linear' slope='0.1900' intercept='0.2226'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E")}

  /* pane s2: industrial: coarse, large volume, deeper shadow
     ground #3E4247, amplitude 0.100 (~26 RGB either side of the ground) */
  body:not(.docbody) [data-pane="s2"]{
    background-color:#3E4247;
    --pane-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270' viewBox='0 0 480 270' preserveAspectRatio='none'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.014' numOctaves='4' seed='19' stitchTiles='stitch' result='a'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' seed='24' stitchTiles='stitch' result='b'/%3E%3CfeColorMatrix in='a' type='saturate' values='0' result='ag'/%3E%3CfeColorMatrix in='b' type='saturate' values='0' result='bg'/%3E%3CfeComposite in='ag' in2='bg' operator='arithmetic' k1='0' k2='0.7' k3='0.3' k4='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='0.2000' intercept='0.1431'/%3E%3CfeFuncG type='linear' slope='0.2000' intercept='0.1588'/%3E%3CfeFuncB type='linear' slope='0.2000' intercept='0.1784'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E")}

  /* pane s3: hospitality: finer grain, closer detail
     ground #4C5055, amplitude 0.090 (~23 RGB either side of the ground) */
  body:not(.docbody) [data-pane="s3"]{
    background-color:#4C5055;
    --pane-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270' viewBox='0 0 480 270' preserveAspectRatio='none'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.03' numOctaves='3' seed='23' stitchTiles='stitch' result='a'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.2' numOctaves='3' seed='28' stitchTiles='stitch' result='b'/%3E%3CfeColorMatrix in='a' type='saturate' values='0' result='ag'/%3E%3CfeColorMatrix in='b' type='saturate' values='0' result='bg'/%3E%3CfeComposite in='ag' in2='bg' operator='arithmetic' k1='0' k2='0.58' k3='0.42' k4='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='0.1800' intercept='0.2080'/%3E%3CfeFuncG type='linear' slope='0.1800' intercept='0.2237'/%3E%3CfeFuncB type='linear' slope='0.1800' intercept='0.2433'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E")}

  /* pane s4: multi-residential: mid aggregate, repeated bay
     ground #43474C, amplitude 0.098 (~25 RGB either side of the ground) */
  body:not(.docbody) [data-pane="s4"]{
    background-color:#43474C;
    --pane-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='480' height='270' viewBox='0 0 480 270' preserveAspectRatio='none'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.019' numOctaves='4' seed='29' stitchTiles='stitch' result='a'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='3' seed='34' stitchTiles='stitch' result='b'/%3E%3CfeColorMatrix in='a' type='saturate' values='0' result='ag'/%3E%3CfeColorMatrix in='b' type='saturate' values='0' result='bg'/%3E%3CfeComposite in='ag' in2='bg' operator='arithmetic' k1='0' k2='0.64' k3='0.36' k4='0' result='m'/%3E%3CfeComponentTransfer in='m'%3E%3CfeFuncR type='linear' slope='0.1960' intercept='0.1647'/%3E%3CfeFuncG type='linear' slope='0.1960' intercept='0.1804'/%3E%3CfeFuncB type='linear' slope='0.1960' intercept='0.2000'/%3E%3CfeFuncA type='linear' slope='0' intercept='1'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E")}

  /* ------------------------------------------------------ B. HERO PANE
     Full height of the hero band, filling the right column, composed as
     the future photograph's home: concrete ground, one raking light fall,
     three faint form-board joints, and exactly ONE copper hairline datum,
     which is the existing .mslot-datum and is left where WEB-17 put it.

     THE 47px TECHNICAL GRID IS OFF THIS PANE. It was drawn for a much
     darker ground; against concrete it read as graph paper and was the
     single loudest thing in the hero. It stays on the evidence plates,
     where the ground it was drawn for has not moved. */
  body:not(.docbody) .hero-image [data-pane="hero"]{
    position:absolute;inset:0;
    background-image:
      linear-gradient(212deg,rgba(244,241,235,.17) 0%,rgba(244,241,235,.04) 46%,rgba(17,19,22,.14) 100%),
      linear-gradient(0deg,transparent 0 25.7%,rgba(17,19,22,.11) 25.9%,transparent 26.2%),
      linear-gradient(0deg,transparent 0 51.7%,rgba(17,19,22,.11) 51.9%,transparent 52.2%),
      linear-gradient(0deg,transparent 0 78.7%,rgba(17,19,22,.11) 78.9%,transparent 79.2%),
      var(--pane-img,var(--pane-grain));
    background-size:auto,auto,auto,auto,var(--pane-size,100% 100%);
    background-repeat:no-repeat,no-repeat,no-repeat,no-repeat,var(--pane-repeat,no-repeat);
    background-position:0 0,0 0,0 0,0 0,var(--pane-pos,center)}
  /* THE MARK COMES OFF THIS PANE, AND IT IS NOT A DIMMING PROBLEM.
     .mslot-mk is an oversized pillar fragment drawn at 5.5% Paper for a
     near-black slot. Against concrete all eight pillars read as a column
     grid: dimming it to 2.8% made a fainter grid, and enlarging it to 330%
     made a coarser one. Both were rendered and measured before this line
     was written. It is hidden here for a second reason as well: the mark is
     a CHILD of the pane, so the day a photograph lands in --pane-img the
     mark would sit on top of it. WEB-23A hit exactly that and had to
     suppress it by hand. Hiding it now makes the one-line photo drop clean.
     The mark is untouched everywhere else: header wordmark, evidence
     plates, every other slot on the site. */
  body:not(.docbody) .hero-image [data-pane="hero"] .mslot-mk{display:none}
  body:not(.docbody) .hero-image [data-pane="hero"] .mslot-datum{opacity:.6}
  /* the hero pane is a panel, not a photograph, so the bottom scrim that
     exists to protect caption legibility is eased back until one lands */
  body:not(.docbody) .hero-image::after{
    background:linear-gradient(90deg,rgba(23,25,28,.10),transparent 26%),
               linear-gradient(0deg,rgba(17,19,22,.42),transparent 34%)}

  /* -------------------------------------------------- C. SECTOR PANES
     Four ruled labels, four addressable panes. Card proportion and gap are
     measured off the locked concept: cards there run 1.84:1 with a 6px
     gutter, and 16/9 is that ratio rounded to a format a camera actually
     shoots, so Rich can crop to a named size rather than to a decimal.
     The copper datum on these cards is the 2px top rule WEB-20 already
     gave them. A second copper line inside the card was tried and cut: on
     a 175px card two copper lines is not a datum, it is a pattern. */
  body:not(.docbody) .w20-grid{gap:6px}
  body:not(.docbody) .w20-cell{
    aspect-ratio:16/9;min-height:150px;
    border-top:2px solid var(--copper)}
  body:not(.docbody) .w20-cell::after{
    content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
    background:linear-gradient(208deg,rgba(244,241,235,.11) 0%,rgba(244,241,235,0) 54%)}

  /* ---------------------------------------- D. WHAT IS NOT A WEB-24 PANE
     The three evidence plates are deliberately untouched. They already
     carry their own WEB-17 material treatments, formwork lines and drawing
     grid, so they were never the flat rectangles this order exists to fix,
     and they stay addressable through the existing SLOT MAP and
     docs/MEDIA_SLOTS.md. Replacing designed gradients with grain would have
     been a downgrade dressed up as consistency. */

  /* ---------------------------------------------------- E. TYPE PRESENCE
     MEASURED against the locked concept, not assumed:
       concept H1 cap height 48px on a 1672px-wide render, so ~66px type
       live H1 at the same width, before this pass:            68px
     The size was ALREADY at concept scale. Two things were not:
       1. WEIGHT. The concept sets a heavy grotesque, stems near .13em.
          The live H1 was 600. It is 700 here.
       2. LINE COUNT. max-width:15.5ch broke the ruled second line into
          "STRUCTURE" / "FIRST." at every desktop width, so the hero read as
          three ragged lines against the concept's two. The cap rises to
          19ch, which holds "Structure first." on one line from 681px up and
          still wraps before the column edge. The 4.6vw rate keeps the same
          fit at 1180 and 1652.
     Below 681px nothing changes: the existing mobile rules still own the
     H1 and are re-stated here so this block cannot silently outrank them. */
  @media (min-width:681px){
    body:not(.docbody) .hero h1{
      font-weight:700;font-size:clamp(40px,4.6vw,76px);line-height:.95;
      max-width:19ch}
  }

  /* --------------------------------------------- F. THE COPPER HAIRLINE
     The concept sets the eyebrow in copper on a light photograph. On Ink
     copper measures 3.07:1 and fails AA for text, and the A1 ruling of
     21 Aug licences copper text on Paper grounds only. So copper takes the
     role it is allowed to take on Ink, a non-text hairline at the 3:1
     threshold, and the eyebrow sits beneath it in the ground-named token. */
  body:not(.docbody) .hero .hero-copy-inner::before{
    content:"";display:block;width:56px;height:2px;background:var(--copper);
    margin:0 0 20px;flex:none}

  /* ------------------------------------------------ G. THE SINGLE BUTTON
     One button in the hero, outlined. Copper carries the border, which is
     non-text UI and clears the 3:1 threshold at 3.07:1; the label is Paper
     on Ink at 14.4:1. Hover fills to the solid copper the site already
     uses, so the previous treatment is one hover away, not deleted. */
  body:not(.docbody) .hero .hero-actions .button-copper{
    background:transparent;color:var(--paper);border-color:var(--copper)}
  body:not(.docbody) .hero .hero-actions .button-copper:hover{
    background:var(--copper);color:var(--paper);border-color:var(--copper)}

  /* ------------------------------------------------------------ H. RHYTHM
     The hero band carried 128px of section padding at 1422px on TOP of the
     hero-copy's own 100px, because main>section:not(.phead):not(.paper)
     :not(.band) catches .hero. That is what made the display type look
     small: 258px of dead band around a 720px row.

     THE SELECTORS BELOW LOOK DEFENSIVE ON PURPOSE. That generic rule scores
     (0,3,2), which is why a plain body:not(.docbody) .w20-sectors at (0,2,1)
     has never applied: the sector band has been rendering at 128px, not the
     99.5px its own WEB-20 rule asks for, since 20 Aug. Every rhythm selector
     here is written to (0,3,3) so it actually lands. */
  body:not(.docbody) main>section.hero:not(.phead){
    padding-block:0;min-height:min(calc(100svh - 76px),720px)}
  body:not(.docbody) .hero-copy{min-height:620px}
  /* below 960 the hero is a single column and sizes to its content; the
     existing min-height:auto there is re-stated so this block cannot
     silently outrank it. */
  @media (max-width:959.98px){
    body:not(.docbody) main>section.hero:not(.phead){min-height:auto}
  }
  body:not(.docbody) main>section.project-evidence:not(.phead),
  body:not(.docbody) main>section.final-cta:not(.phead),
  body:not(.docbody) main>section.control-system:not(.phead){
    padding-block:clamp(66px,7.4vw,118px)}
  body:not(.docbody) main>section.w20-sectors:not(.phead){
    padding-block:clamp(58px,6.2vw,96px)}
  body:not(.docbody) .w20-sectors .w20-head{margin-bottom:clamp(18px,1.9vw,26px)}

  /* ------------------------------- I. HIGH CONTRAST: PANELS GO FLAT
     Same posture as WEB-19. A reader who asks for more contrast gets the
     flat pane ground, not the grain. The panel is still a panel. */
  @media (prefers-contrast:more){
    body:not(.docbody) [data-pane]{background-image:none}
    body:not(.docbody) .hero-image [data-pane="hero"]{background-image:none}
  }

  /* ============================================================ PANE MAP
     ONE LINE PER PANE. This is the whole of a "WEB-24P PANE <id>: <path>"
     order. Point --pane-img at the file and the grain steps aside:

       body:not(.docbody) [data-pane="s1"]{
         --pane-img:url(/assets/photos/web24/commercial-1200.jpg);
         --pane-size:cover;--pane-pos:center}

     Rendered sizes, aspect ratios, intended subjects and the crop rule are
     in AI_BRIDGE\FROM_CODE\WEB24_PANE_MAP.md. The panes are:
       hero  s1 Commercial  s2 Industrial  s3 Hospitality  s4 Multi-residential
     The evidence plates are NOT panes: they use the SLOT MAP above.
     ==================================================================== */
}
/* ====================== WEB-24 STYLE LIVE, end ======================= */


/* ===================== WEB-25W CAPABILITY BAND, start ===================== */
/* ==========================================================================
   WEB-25W  THE ONE PAPER BAND ON /construction
   --------------------------------------------------------------------------
   Sol 4.3 asks for the existing Paper treatment on WHAT WE BUILD, using only
   existing tokens. It does not work on its own, and the contrast gate is what
   found that rather than the eye.

   .paper already carries the right descendant rules, including
   .paper .g4>div{background:transparent}. But WEB-19 sets the ink-raised grain
   on body:not(.docbody) .g4>div at the same (0,2,1) specificity and LATER in
   the file, so it wins and the cells stay dark. Ink headings then sit on an
   ink-raised ground: measured 1.01:1, and 1.46:1 after the first fix attempt.
   No .paper section on this site had ever contained a .g4, so nothing had hit
   the collision before.

   This block does one thing: it lets that band actually be Paper. Every value
   is an existing token, nothing new is defined, and the scope is one class that
   appears on exactly one section. Both print guards hold: @media screen, which
   paged media never matches, and body:not(.docbody).
   ========================================================================== */
@media screen{
  body:not(.docbody) .cap-band .g4>div{
    background-color:transparent;background-image:none;border-color:var(--rule-light)}
  body:not(.docbody) .cap-band .g4,
  body:not(.docbody) .cap-band .g4>div{border-color:var(--rule-light)}
  body:not(.docbody) .cap-band h2,
  body:not(.docbody) .cap-band .g4 h3{color:var(--ink)}
  body:not(.docbody) .cap-band .g4 p{color:var(--body-on-paper)}
  body:not(.docbody) .cap-band .eyebrow,
  body:not(.docbody) .cap-band .eyebrow.dim{color:var(--label-on-paper)}
}
/* ====================== WEB-25W CAPABILITY BAND, end ====================== */
