/* ============================================================
   TECHHOLDEN — the corporate site
   Rebuilt 2026-08-18 on four rulings:
     · Paper ground with Azure bands, not Ink throughout
     · the WebGL load field is cut, not quietened
     · conventional corporate section order, our own substance in it
     · homepage first, inner pages behind it in this same system

   What survives from the kinetic build: Archivo as the one family,
   the seven-step type scale, the eleven drawn marks, the published
   engagement model, and the rule that nothing on the page claims
   something nobody has confirmed.
   ============================================================ */

/* ---- ONE FAMILY. Archivo, self-hosted, weight 100–900 + width 62–125.
   The width axis is the point: "built to hold" is a structural claim and a
   face that goes genuinely expanded reads as engineered rather than as UI
   chrome. No second family; labels and figures are the same face at a
   different weight, tracking and size. ---- */
@font-face{
  font-family:'Archivo';
  src:url('./fonts/archivo-var.woff2') format('woff2-variations');
  font-weight:100 900; font-stretch:62% 125%; font-display:swap;
}

/* ---- THE ONE EXCEPTION TO ONE FAMILY: the wordmark.
   Brand law fixes the mark at Inter 750, "exempt and unalterable". Setting
   it in Archivo makes the wordmark construction, the 91-mark exploration
   and the brand PDF all stop describing what is on the page. Only weight
   750 is loaded, and only .th-wordmark and the beamlock ghost ask for it —
   body copy stays Archivo, which was a deliberate decision.
   font-display:block, not swap: a wordmark that reflows on load is worse
   than one that appears a moment late. ---- */
@font-face{
  font-family:'Inter';
  src:url('./fonts/inter-750.woff2') format('woff2');
  font-weight:750; font-style:normal; font-display:block;
}

/* ============================================================
   1 · GROUND TOKENS
   Paper is the page. Three bands sit on it — wash (pale Azure),
   ink, and azure — and each one re-points the same six tokens so
   every element inside inherits the right value without a single
   band-specific colour rule.
   ============================================================ */
:root{
  --fg:        var(--th-ink);
  --muted:     var(--th-mute-lt);
  --surface:   #FFFFFF;
  --hair:      color-mix(in srgb, var(--th-ink) 13%, var(--th-paper));
  --accent:    var(--th-azure-deep);
  --ground:    var(--th-paper);

  --wash:      color-mix(in srgb, var(--th-azure) 6%, var(--th-paper));
  --wash-hair: color-mix(in srgb, var(--th-azure) 17%, var(--th-paper));

  --shadow:    0 1px 2px rgba(11,14,17,.045), 0 10px 28px -14px rgba(11,14,17,.14);
  --shadow-up: 0 2px 4px rgba(11,14,17,.05), 0 20px 42px -18px rgba(11,14,17,.20);

  --gap:       clamp(16px, 1.6vw, 24px);
  --sec:       clamp(64px, 8vw, 118px);
}

.band--wash{ --ground:var(--wash); --hair:var(--wash-hair); background:var(--wash); }
.band--ink{
  --fg:var(--th-paper); --muted:var(--th-mute); --surface:var(--th-slate);
  --hair:var(--th-line); --accent:var(--th-azure-lift); --ground:var(--th-ink);
  --shadow:none; --shadow-up:none;
  background:var(--th-ink); color:var(--th-paper);
}
.band--azure{
  --fg:#fff; --muted:#F6FAFF; --surface:rgba(255,255,255,.10);
  --hair:rgba(255,255,255,.28); --accent:#fff; --ground:var(--th-azure);
  --shadow:none; --shadow-up:none;
  background:var(--th-azure); color:#fff;
}

/* ============================================================
   2 · RESET AND THE ONE CONTAINER
   Every band is full-bleed; every line of content sits inside the
   same .page. One left edge on the whole site, nothing opts out.
   ============================================================ */
*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; scroll-padding-top:88px; }
html,body{ margin:0; padding:0; }
body{
  background:var(--th-paper);
  color:var(--th-ink);
  font-family:'Archivo', system-ui, -apple-system, 'Segoe UI', Helvetica, Arial, sans-serif;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
img,svg{ max-width:100%; }
.page{ width:min(100% - 2*var(--gap), 1180px); margin-inline:auto; }
section.band{ padding-block:var(--sec); }
.skip{
  position:absolute; left:-9999px; top:0; z-index:60;
  background:var(--th-ink); color:var(--th-paper);
  padding:10px 16px; border-radius:8px; text-decoration:none;
}
.skip:focus{ left:var(--gap); top:10px; }
:where(a,button,summary):focus-visible{
  outline:2px solid var(--th-azure); outline-offset:3px; border-radius:4px;
}

/* ============================================================
   3 · TYPE — seven steps, carried over from the kinetic build.
   Nothing may sit between them.
   ============================================================ */
/* ---- The spacing scale, 4pt based, published 2026-08-18 ----
   Values were previously picked ad hoc: 22, 26, 30, 34, 52, 82 and 118px all
   appeared alongside the 4pt-friendly ones, so there was no set to check a new
   value against. These are the set. A 4pt base rather than 8pt, because 8pt is
   too coarse — 12px between 8 and 16 is needed constantly.

   Documented exceptions, deliberately NOT snapped: 1-3px hairlines and borders;
   the 5/6/9px optical nudges inside marks and tick bullets; and 13/17px, which
   are single-purpose optical corrections on the button and the beam lockup.
   Snapping those would move ink, not air. */
:root{
  --sp-1:  4px;   --sp-2:  8px;   --sp-3: 12px;  --sp-4: 16px;
  --sp-5: 20px;   --sp-6: 24px;   --sp-8: 32px;  --sp-10: 40px;
  --sp-12: 48px;  --sp-14: 56px;  --sp-16: 64px; --sp-18: 72px;
  --sp-20: 80px;  --sp-24: 96px;  --sp-30: 120px;
}

:root{
  --t-label:   12.5px;
  --t-small:   14px;
  --t-body:    16.5px;
  --t-lead:    19px;
  --t-sub:     22px;
  --t-head:    clamp(1.85rem, 3.2vw, 2.75rem);
  --t-display: clamp(2.5rem, 4.8vw, 4rem);
}
h1,h2,h3,p,ul,ol,dl,dd,figure{ margin:0; }
/* Additive leading, not a ratio. --t-display is fluid (40px on a phone, 64px
   on a desktop) and a single ratio cannot serve both: 1.03 leaves ~2px between
   a descender and the next cap-line at 64px, and the same ratio at 40px — where
   the headline runs to three lines — is tighter still, because small display
   type needs MORE relative leading, not the same. calc(em + rem) gives ~1.16 at
   64px and ~1.22 at 40px from one declaration.

   The ratio was set by measuring glyph ink, not by taste: Archivo's ascenders
   run 0.72em and its descenders 0.18em, so 0.90em of every line box is ink. At
   the first attempt (1.095) that left 12.4px of white between the p of
   "shipped," and the k/t of "kept" — 19% of the font size, which is why the two
   lines still read as one slab at display size. This leaves ~26%. The face is
   widened (wdth 112) and heavy, which is why it needs the room. */
h1{ font-size:var(--t-display); font-variation-settings:'wght' 700,'wdth' 112;
    line-height:calc(1.058em + 0.41rem); letter-spacing:-.028em; }
h2{ font-size:var(--t-head);    font-variation-settings:'wght' 660,'wdth' 108;
    line-height:1.1;  letter-spacing:-.021em; }
h3{ font-size:var(--t-sub);     font-variation-settings:'wght' 640,'wdth' 104;
    line-height:1.22; letter-spacing:-.012em; }
p{ font-size:var(--t-body); font-variation-settings:'wght' 430; }
.deck{ font-size:var(--t-lead); font-variation-settings:'wght' 430;
       line-height:1.5; color:var(--muted); max-width:62ch; margin-top:var(--gap); }
.lede{ font-size:var(--t-lead); font-variation-settings:'wght' 430;
       line-height:1.55; color:var(--fg); max-width:60ch; }
.label{
  font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; line-height:1.4;
  color:var(--accent); font-variant-numeric:tabular-nums;
}
.small{ font-size:var(--t-small); font-variation-settings:'wght' 460; color:var(--muted); }
.muted{ color:var(--muted); }
a{ color:var(--accent); }

/* Section heads: label, rule, heading, standfirst — one pattern, every band.

   The max-width is in rem, not ch, and that is deliberate. `ch` resolves
   against the element's OWN font-size, so `.sechead{max-width:64ch}` at 16px
   computed to 586px while the `.deck` inside it, declared NARROWER at 58ch but
   set at 19.4px, wanted 631px. The parent won, and the same standfirst rendered
   631px under a page hero and 586px under a section head — the width
   inconsistency read across the site. A rem cap is font-size-independent, so
   the container can no longer be accidentally narrower than its own child. */
.sechead{ max-width:41.25rem; }   /* 660px — comfortably clear of the 631px deck */
.sechead .label{ display:block; margin-bottom:var(--gap); }
.sechead h2 + .deck{ margin-top:var(--gap); }
/* Unused since 2026-08-18: the Azure conviction band was the only thing centred
   on the site, and it broke the one-left-edge rule §1.3 rests on. Kept as an
   available utility, not applied anywhere. */
.sechead--mid{ margin-inline:auto; text-align:center; }
.sechead--mid .deck{ margin-inline:auto; }

/* ============================================================
   4 · CONTROLS
   ============================================================ */
.btn{
  display:inline-flex; align-items:center; gap:.5em;
  font-size:var(--t-body); font-variation-settings:'wght' 600;
  letter-spacing:-.004em; text-decoration:none;
  padding:13px 24px; border-radius:999px; border:1px solid transparent;
  transition:background-color .18s ease, border-color .18s ease,
             transform .18s cubic-bezier(.2,.7,.2,1), box-shadow .18s ease;
}
.btn::after{ content:"→"; transition:transform .18s cubic-bezier(.2,.7,.2,1); }
.btn:hover::after{ transform:translateX(3px); }
.btn--primary{ background:var(--th-azure); color:#fff; box-shadow:var(--shadow); }
.btn--primary:hover{ background:var(--th-azure-deep); transform:translateY(-2px);
                     box-shadow:var(--shadow-up); }
.btn--ghost{ color:var(--fg); border-color:var(--hair); background:var(--surface); }
.btn--ghost:hover{ border-color:var(--th-azure); color:var(--accent); transform:translateY(-2px); }
.band--azure .btn--primary{ background:#fff; color:var(--th-azure-deep); }
.band--azure .btn--primary:hover{ background:#fff; }
.band--azure .btn--ghost{ background:transparent; color:#fff; border-color:rgba(255,255,255,.5); }
.band--azure .btn--ghost:hover{ border-color:#fff; color:#fff; }
.band--ink .btn--ghost{ background:transparent; }
.cta-row{ display:flex; flex-wrap:wrap; gap:12px; margin-top:calc(var(--gap) * 1.4); }
.textlink{
  font-size:var(--t-small); font-variation-settings:'wght' 580;
  text-decoration:none; letter-spacing:.005em;
}
.textlink::after{ content:" →"; }
.textlink:hover{ text-decoration:underline; }

/* ============================================================
   5 · HEADER
   ============================================================ */
.site-head{
  position:sticky; top:0; z-index:50;
  background:color-mix(in srgb, var(--th-paper) 88%, transparent);
  backdrop-filter:saturate(150%) blur(10px);
  border-bottom:1px solid transparent;
  transition:border-color .2s ease, box-shadow .2s ease;
}
.site-head[data-stuck]{ border-bottom-color:var(--hair); box-shadow:0 6px 20px -18px rgba(11,14,17,.5); }
.head-in{ display:flex; align-items:center; gap:var(--gap); padding-block:14px; }
/* font-family deliberately NOT set here: brand-tokens.css puts the mark on
   var(--th-sans) (Inter first) and overriding it to inherit was the defect. */
.th-wordmark{ font-size:20px; text-decoration:none; }
/* The beam is as wide as `tech` because the ghost copy of `tech` sets the
   lockup's width. The ghost must therefore be in the SAME face as the mark —
   measured in Archivo it produces a beam that fits no wordmark at all. */
.th-beamlock .th-ghost{ font-family:var(--th-sans); }
.site-nav{ margin-left:auto; display:flex; gap:calc(var(--gap) * 1.1); }
.site-nav a{
  font-size:var(--t-small); font-variation-settings:'wght' 540;
  color:var(--th-mute-lt); text-decoration:none; padding-block:6px;
  border-bottom:1.5px solid transparent; transition:color .15s ease, border-color .15s ease;
}
/* --fg, not --th-ink. Both resolve to Ink on a light ground, but --th-ink is
   also the dark page ground, so hovering blacked the item out entirely. The
   underline stays --th-azure: it measures 3.99:1 on the dark header, past the
   3:1 a non-text graphic needs, so it did not need touching. */
.site-nav a:hover{ color:var(--fg); border-bottom-color:var(--th-azure); }
.head-cta{ margin-left:var(--gap); padding:9px 17px; font-size:var(--t-small); }
.navtoggle{ display:none; }

/* ============================================================
   6 · HERO
   ============================================================ */
.hero{ padding-block:clamp(48px, 6.5vw, 92px) clamp(40px, 5vw, 72px); }
.hero-grid{
  display:grid; gap:clamp(28px, 4vw, 64px);
  grid-template-columns:minmax(0, 1.02fr) minmax(0, .98fr);
  align-items:center;
}
.hero h1{ margin-top:18px; }
.hero .deck{ font-size:clamp(1.05rem, 1.35vw, 1.24rem); max-width:52ch; }
.th-beamlock.lockup{ font-size:56px; }
.th-beamlock.lockup .th-beam{ background:var(--th-azure); }
.herofig{ width:100%; height:auto; color:color-mix(in srgb, var(--th-ink) 46%, var(--th-paper)); }

/* ============================================================
   7 · MARKS — the drawn illustration set
   Paint arrives from outside the sprite: stroke is currentColor,
   the dot reads --mk-az, the one occluding panel reads --mk-ground,
   and the stroke weight reads --mk-sw. CSS rules cannot reach inside
   the shadow tree a <use> builds; custom properties inherit into it.
   ============================================================ */
body{ --mk-az:var(--th-azure); --mk-ground:var(--th-paper); }
.band--ink{ --mk-az:var(--th-azure-lift); --mk-ground:var(--th-ink); }
.band--azure{ --mk-az:#fff; --mk-ground:var(--th-azure); }
.mark{
  display:block; flex:none; width:84px; height:84px;
  color:color-mix(in srgb, var(--fg) 62%, var(--ground));
  transition:color .18s ease;
}
.mark--sm{ width:44px; height:44px; --mk-sw:2.1; }
.mark--xs{ width:32px; height:32px; --mk-sw:3.2; }

/* ============================================================
   8 · CARDS
   ============================================================ */
.cards{
  display:grid; gap:var(--gap); align-items:stretch;
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 268px), 1fr));
  margin-top:clamp(32px, 3.6vw, 52px);
}
.cards--4{ grid-template-columns:repeat(auto-fit, minmax(min(100%, 230px), 1fr)); }
.card{
  position:relative;
  --mk-ground:var(--surface);
  background:var(--surface);
  border:1px solid var(--hair);
  border-radius:var(--th-radius);
  padding:clamp(24px, 2.2vw, 32px);
  display:flex; flex-direction:column; align-items:flex-start; gap:14px;
  box-shadow:var(--shadow);
  transition:border-color .18s ease, transform .18s cubic-bezier(.2,.7,.2,1), box-shadow .18s ease;
}
.card:hover{ border-color:color-mix(in srgb, var(--th-azure) 45%, var(--hair));
             transform:translateY(-3px); box-shadow:var(--shadow-up); }
.card:hover .mark{ color:var(--fg); }
.card h3{ margin:0; }
.card p{ margin:0; color:var(--muted); }
.card .textlink{ margin-top:auto; padding-top:4px; }
.card--lead{ border-color:color-mix(in srgb, var(--th-azure) 52%, transparent); }
.card--lead .mark{ color:var(--accent); }
.card__tag{
  position:absolute; top:clamp(22px, 2.2vw, 30px); right:clamp(22px, 2.2vw, 30px);
  font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--accent);
}
.card--span{ grid-column:1 / -1; flex-direction:row; align-items:center; gap:clamp(20px,2.4vw,32px); }
.card--span .card__body{ display:flex; flex-direction:column; gap:8px; }

/* ============================================================
   9 · THE COMPARISON — hire it / rent it / us
   ============================================================ */
.compare{ display:grid; gap:0; margin-top:clamp(28px, 3vw, 44px);
  border-top:1px solid var(--hair); }
.compare__row{
  display:grid; grid-template-columns:minmax(140px, 200px) 1fr;
  gap:var(--gap); padding-block:20px; border-bottom:1px solid var(--hair);
  align-items:baseline;
}
.compare__k{ font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
.compare__row--us .compare__k{ color:var(--accent); }
.compare__row--us .compare__v{ font-variation-settings:'wght' 520; }
.compare__v{ margin:0; max-width:62ch; }

/* ============================================================
   10 · THE SPECIFICATION MATRIX
   ============================================================ */
.spec{ margin-top:clamp(32px, 3.4vw, 48px); border-top:1px solid var(--hair);
  font-variant-numeric:tabular-nums; }
.spec__head, .spec__row{
  display:grid; grid-template-columns:minmax(120px, 1fr) repeat(3, minmax(0, 2fr));
  gap:var(--gap); padding-block:18px; border-bottom:1px solid var(--hair);
  align-items:start;
}
.spec__head span{ font-size:var(--t-sub); font-variation-settings:'wght' 640,'wdth' 104;
  letter-spacing:-.012em; }
.spec__head .is-lead{ color:var(--accent); }
.spec__head em{ display:block; font-style:normal; font-size:var(--t-label);
  font-variation-settings:'wght' 580; letter-spacing:.1em; text-transform:uppercase;
  color:var(--accent); margin-top:4px; }
.spec__k{ font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
.spec__row span:not(.spec__k){ font-size:var(--t-body); color:var(--muted); }
.spec__row .is-lead{ color:var(--fg); font-variation-settings:'wght' 500; }

/* ============================================================
   11 · PRODUCTS — the one Ink band
   ============================================================ */
.prod-grid{ display:grid; gap:clamp(20px, 2.4vw, 32px);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 380px), 1fr));
  margin-top:clamp(32px, 3.6vw, 52px); }
.shot{ margin:0; }
.shot img{ display:block; width:100%; height:auto;
  border:1px solid var(--th-line); border-radius:10px; }
.shot figcaption{ margin-top:12px; font-size:var(--t-small); color:var(--muted); }
.shot figcaption b{ color:var(--th-paper); font-variation-settings:'wght' 600; }
.statusrow{ display:flex; flex-wrap:wrap; gap:calc(var(--gap) * 1.6);
  margin-top:clamp(32px, 3.2vw, 44px); padding-top:24px; border-top:1px solid var(--hair); }
.statusrow div{ min-width:190px; }
.statusrow dt{ font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
.statusrow dd{ margin:5px 0 0; font-size:var(--t-body); }

/* ============================================================
   12 · WORKED PROBLEMS
   ============================================================ */
.cases{ display:grid; gap:clamp(28px, 3.4vw, 56px);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 400px), 1fr));
  margin-top:clamp(32px, 3.4vw, 48px); }
.case__head{ display:flex; align-items:center; gap:16px; }
.steps{ list-style:none; margin:var(--gap) 0 0; padding:0; }
.steps li{
  display:grid; grid-template-columns:34px 132px 1fr; gap:var(--gap);
  padding-block:15px; border-top:1px solid var(--hair); align-items:baseline;
}
.steps li:last-child{ border-bottom:1px solid var(--hair); }
.steps__n{ font-size:var(--t-label); font-variation-settings:'wght' 600;
  color:var(--accent); font-variant-numeric:tabular-nums; }
.steps__t{ font-size:var(--t-body); font-variation-settings:'wght' 620; }
.steps__d{ font-size:var(--t-body); color:var(--muted); }
.tag{
  display:inline-block; margin-top:var(--gap);
  font-size:var(--t-small); font-variation-settings:'wght' 500; color:var(--muted);
  border:1px dashed var(--hair); border-radius:999px; padding:5px 13px;
}

/* ============================================================
   13 · CONTACT + FOOTER
   ============================================================ */
.mailplain{ margin-top:var(--gap); color:var(--muted); font-size:var(--t-body); }
.sel{ color:var(--fg); font-variation-settings:'wght' 600; user-select:all; }
.site-foot{ background:var(--th-ink); color:var(--th-paper);
  --fg:var(--th-paper); --muted:var(--th-mute); --hair:var(--th-line); --accent:var(--th-azure-lift);
  padding-block:clamp(48px, 5vw, 76px) 32px; }
.foot-grid{ display:grid; gap:var(--gap);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 190px), 1fr)); }
.foot-mark{ font-size:24px; display:block; margin-bottom:10px; }
.foot-h{ font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted); margin-bottom:12px; }
.site-foot nav{ display:flex; flex-direction:column; gap:9px; align-items:flex-start; }
.site-foot a{ color:var(--th-paper); text-decoration:none; font-size:var(--t-small);
  font-variation-settings:'wght' 460; }
.site-foot a:hover{ color:var(--th-azure-lift); }
.foot-legal{ display:flex; flex-wrap:wrap; gap:8px var(--gap);
  justify-content:space-between; margin-top:clamp(36px, 4vw, 56px);
  padding-top:24px; border-top:1px solid var(--hair); }
.foot-legal p{ margin:0; font-size:var(--t-small); color:var(--muted); }

/* ============================================================
   14 · RESPONSIVE
   ============================================================ */
@media (max-width:960px){
  .hero-grid{ grid-template-columns:1fr; }
  .herofig{ max-width:560px; }
  .site-nav{ display:none; }
  .navtoggle{
    display:inline-flex; margin-left:auto; align-items:center; gap:8px;
    background:none; border:1px solid var(--hair); border-radius:999px;
    padding:8px 15px; font:inherit; font-size:var(--t-small);
    font-variation-settings:'wght' 560; color:var(--fg); cursor:pointer;
  }
  .site-head[data-open] .site-nav{
    display:flex; flex-direction:column; gap:0; width:100%; margin:0;
    order:3; border-top:1px solid var(--hair); padding-block:8px;
  }
  .site-head[data-open] .site-nav a{ padding-block:12px; border-bottom:0; }
  .head-in{ flex-wrap:wrap; }
  .head-cta{ margin-left:12px; }
}
@media (max-width:760px){
  .spec__head, .spec__row{ grid-template-columns:1fr; gap:6px; padding-block:16px; }
  .spec__head span{ font-size:var(--t-body); }
  .spec__head .is-lead{ font-size:var(--t-sub); }
  .spec__row span:not(.spec__k)::before{
    content:attr(data-col) " — "; font-variation-settings:'wght' 600; color:var(--fg);
  }
  .steps li{ grid-template-columns:34px 1fr; }
  .steps__d{ grid-column:2; }
  .card--span{ flex-direction:column; align-items:flex-start; }
  .compare__row{ grid-template-columns:1fr; gap:4px; }
  .mark{ width:64px; height:64px; }
}
@media (max-width:420px){ .head-cta{ display:none; } }

/* ============================================================
   15 · REDUCED MOTION — the base state, not an afterthought
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  html{ scroll-behavior:auto; }
  *,*::before,*::after{ transition:none !important; animation:none !important; }
  .card:hover, .btn:hover{ transform:none; }
}

/* ---- Print: the page as a document ---- */
@media print{
  .site-head, .cta-row, .btn{ display:none; }
  body{ background:#fff; }
  .band--ink, .band--azure{ background:#fff; color:#000; --fg:#000; --muted:#333; }
}

/* ============================================================
   16 · CORRECTIONS FROM THE FIRST RENDER
   ============================================================ */

/* The hero headline broke as "Software built to / hold", which strands the one
   word the whole positioning rests on. Balanced wrapping fixes it without
   hard-coding a break that would be wrong at another width. */
h1{ text-wrap:balance; }

/* The hero figure needs a block to sit in, or a line drawing floats in white
   space and reads as an afterthought. Wash panel, same family as the bands. */
.herofig-wrap{
  background:var(--wash);
  border:1px solid var(--wash-hair);
  border-radius:20px;
  padding:clamp(18px, 2.4vw, 32px);
  --mk-az:var(--th-azure);
}
.herofig{ color:color-mix(in srgb, var(--th-ink) 52%, var(--wash)); }

.lede--after{ margin-top:clamp(24px, 2.6vw, 36px); }
.cta-row--mid{ justify-content:center; }
.pname{ color:var(--th-paper); font-variation-settings:'wght' 600; }

/* The status row was a flex line, so the first long definition claimed the whole
   width and pushed the other two onto a second row. Three columns, always. */
.statusrow{
  display:grid; gap:var(--gap) calc(var(--gap) * 1.6);
  grid-template-columns:repeat(auto-fit, minmax(min(100%, 240px), 1fr));
}
.statusrow div{ min-width:0; }

/* "Leave the gauge" and "Share the on-call" wrapped in a column sized for
   shorter titles. */
.steps li{ grid-template-columns:34px 152px 1fr; }

/* Measured against the painted ground, not guessed. On the Azure band the deck
   started at #D3E2F8 → 3.70:1, under AA for 19px body. #EDF4FE only reached
   4.38:1 — still short. #F6FAFF clears it. White on Azure is 4.85:1, so the
   usable range for secondary text on this band is very narrow, and any future
   value here has to be measured rather than picked. */

/* ============================================================
   17 · THE REWRITE — 2026-08-18
   Rulings behind this pass: plain benefit-led copy · no hero
   illustration · no proof sections, because there is no proof
   yet · anonymous, so every "named engineer" promise is gone ·
   generic product names · Techholden LLC, New York, in the foot.
   ============================================================ */

/* The hero carries no figure now. A quiet Azure wash does the work the
   illustration was doing, which is what Intellectsoft — the closest
   comparable read in a browser on 2026-08-18 — runs behind its headline. */
.hero--plain{
  position:relative;
  padding-block:clamp(72px, 9vw, 148px) clamp(56px, 6.5vw, 104px);
  background:
    radial-gradient(90% 120% at 78% -10%,
      color-mix(in srgb, var(--th-azure) 13%, transparent) 0%, transparent 62%),
    radial-gradient(70% 90% at 4% 108%,
      color-mix(in srgb, var(--th-azure) 7%, transparent) 0%, transparent 60%);
}
.hero--plain h1{ margin-top:20px; max-width:21ch; }
.hero--plain .hero-deck{ max-width:56ch; margin-top:calc(var(--gap) * 1.2); }
.hero--plain .th-beamlock.lockup{ font-size:64px; }

/* ---- Industries: TNG's numbered 01–05 full-width pattern.
   Their five sectors, our sentences — lifting a named competitor's copy
   verbatim is a different thing from following their structure. ---- */
.inds{ margin-top:clamp(32px, 3.6vw, 52px); border-top:1px solid var(--hair); }
.ind{
  display:grid; grid-template-columns:minmax(64px, 92px) 1fr;
  gap:var(--gap); padding-block:clamp(24px, 2.4vw, 32px);
  border-bottom:1px solid var(--hair);
  transition:background-color .18s ease;
}
.ind:hover{ background:color-mix(in srgb, var(--th-azure) 4%, transparent); }
.ind__n{
  font-size:clamp(1.6rem, 2.4vw, 2.1rem);
  font-variation-settings:'wght' 650, 'wdth' 108;
  letter-spacing:-.02em; line-height:1; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.ind__b h3{ margin-bottom:8px; }
.ind__b p{ color:var(--muted); max-width:68ch; }

/* ---- Products: text one side, the interface the other, alternating ---- */
.prod{
  display:grid; gap:clamp(24px, 3.2vw, 56px);
  grid-template-columns:minmax(0, .82fr) minmax(0, 1.18fr);
  align-items:center;
  margin-top:clamp(40px, 4.4vw, 68px);
}
.prod--alt{
  margin-top:clamp(56px, 6vw, 96px);
  /* the columns swap with the order, or the second product renders smaller
     than the first for no reason a reader could name */
  grid-template-columns:minmax(0, 1.18fr) minmax(0, .82fr);
}
.prod--alt .prod__t{ order:2; }
.prod__t h3{ margin-bottom:12px; }
.prod__t p{ color:var(--muted); max-width:46ch; }
.prod .shot img{
  display:block; width:100%; height:auto;
  border:1px solid var(--hair); border-radius:14px;
  box-shadow:var(--shadow-up);
}

/* ---- Footer address ---- */
.site-foot address{ font-style:normal; display:flex; flex-direction:column; gap:10px; }
.site-foot address span{
  font-size:var(--t-small); color:var(--muted); line-height:1.6;
  font-variation-settings:'wght' 430;
}

@media (max-width:900px){
  .prod, .prod--alt{ grid-template-columns:1fr; }
  .prod--alt .prod__t{ order:0; }
  .hero--plain h1{ max-width:none; }
}
@media (max-width:640px){
  .ind{ grid-template-columns:1fr; gap:6px; }
  .ind__n{ font-size:1.2rem; }
}

/* ============================================================
   18 · TAGLINE AND THE SIX-STAGE MODEL — 2026-08-18
   "Concept to Continuity." locked to the wordmark in the header
   and the footer, on the owner's ruling — not as the hero
   headline, because a firm nobody has heard of still needs the
   headline to say what it sells.

   The section beneath it was measured before this pass: 19 text
   blocks on the page carried delivery language against 11 that
   carried continuity, and How we work ran four delivery stages
   to one. A tagline promising a span cannot sit above a section
   that only describes the first half of it, so the section is
   now six numbered stages running concept → continuity. The
   tagline and the model are the same claim.
   ============================================================ */
.lockline{
  font-size:var(--t-small);
  font-variation-settings:'wght' 500;
  letter-spacing:.005em;
  color:var(--muted);
  padding-left:14px;
  margin-left:2px;
  border-left:1px solid var(--hair);
  white-space:nowrap;
}
.lockline--foot{
  margin:10px 0 0; padding:0; border:0;
  color:var(--muted);
}

/* Six stages read as a sequence, so they hold a fixed three-across
   grid rather than reflowing to a ragged four-plus-two. */
.cards--6{ grid-template-columns:repeat(3, minmax(0, 1fr)); }
.card__n{
  font-size:var(--t-label);
  font-variation-settings:'wght' 620;
  letter-spacing:.1em; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.card__n + .mark{ margin-top:-4px; }

@media (max-width:1020px){ .cards--6{ grid-template-columns:repeat(2, minmax(0, 1fr)); } }
@media (max-width:960px){ .lockline{ display:none; } }
@media (max-width:620px){ .cards--6{ grid-template-columns:1fr; } }

/* ============================================================
   19 · A SITE, NOT A PAGE — 2026-08-18
   The audit that prompted this: 0 links to another page, 0 Open
   Graph tags, 0 structured data, no canonical, no manifest, no
   robots, no sitemap, no legal pages, no 404, 7 footer links.
   Against Intellectsoft's 25-plus footer links and a dozen
   service pages. Nothing about the design read as unfinished —
   the fact that it was one file did.

   Twelve pages now build from build.py. The patterns below are
   the ones the inner pages need and the homepage did not.
   ============================================================ */

/* ---- Page hero: smaller than the homepage's, same furniture ---- */
.pagehero{
  padding-block:clamp(56px, 6.5vw, 104px) clamp(36px, 4vw, 60px);
  background:radial-gradient(80% 120% at 82% -20%,
    color-mix(in srgb, var(--th-azure) 11%, transparent) 0%, transparent 60%);
}
.pagehero .label{ display:block; margin-bottom:var(--gap); }
.pagehero h1{ font-size:clamp(2.1rem, 4vw, 3.4rem); max-width:26ch; text-wrap:balance; }
.pagehero .deck{ max-width:58ch; }

/* ---- Service and industry detail rows ---- */
.svc, .indp{
  display:grid; gap:clamp(24px, 3.4vw, 56px);
  grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);
  align-items:start;
}
/* Single column since the decorative 01-05 numerals were dropped (2026-08-18):
   an industries list is not a sequence, so the numbers were scaffolding, and the
   column they sat in was pushing every industry heading 150px right of the left
   edge every other block on the page shares. */
.indp{ grid-template-columns:minmax(0, 1fr); }
.svc__t .mark{ margin-bottom:var(--gap); }
.svc__t h2{ margin-bottom:12px; }
.pricing{
  margin-top:var(--gap); font-size:var(--t-label);
  font-variation-settings:'wght' 620; letter-spacing:.1em;
  text-transform:uppercase; color:var(--accent);
}
.indp h2{ margin-bottom:12px; }

/* ---- Tick lists: the "what you get" pattern every firm uses ---- */
.ticks{ list-style:none; margin:var(--gap) 0 0; padding:0; }
.ticks li{
  position:relative; padding-left:32px; padding-block:9px;
  border-bottom:1px solid var(--hair); color:var(--muted);
  font-size:var(--t-body);
}
.ticks li:first-child{ border-top:1px solid var(--hair); }
.ticks li::before{
  content:""; position:absolute; left:2px; top:16px;
  width:13px; height:13px; border-radius:50%;
  border:1.5px solid var(--accent);
  background:radial-gradient(circle at 50% 50%, var(--accent) 0 3px, transparent 3px);
}
.ticks--2{ columns:2; column-gap:var(--gap); }
.ticks--2 li{ break-inside:avoid; }

/* ---- The six stages, one per band ---- */
.stage{
  display:grid; gap:clamp(20px, 3vw, 48px);
  grid-template-columns:minmax(72px, 120px) minmax(0, 1fr);
  align-items:start;
}
.stage__n span{
  display:block; font-size:clamp(1.8rem, 2.8vw, 2.4rem);
  font-variation-settings:'wght' 650, 'wdth' 108; letter-spacing:-.02em;
  line-height:1; color:var(--accent); font-variant-numeric:tabular-nums;
}
.stage__n .mark{ margin-top:var(--gap); }
.stage__b h2{ margin-bottom:12px; }
.stage__b p + p{ margin-top:var(--gap); color:var(--muted); max-width:66ch; }

/* ---- Running prose, for About and the legal pages ---- */
.prose{ max-width:68ch; }
.prose p + p{ margin-top:var(--gap); }
.prose p{ color:var(--muted); }
.prose--legal h2{
  font-size:var(--t-sub); margin-top:clamp(28px, 3vw, 44px); margin-bottom:10px; color:var(--fg);
}
.prose--legal h2:first-child{ margin-top:0; }
.draftnote{
  border:1px dashed var(--hair); border-radius:var(--th-radius);
  padding:14px 18px; margin-bottom:clamp(24px, 3vw, 40px);
  font-size:var(--t-small); color:var(--muted); max-width:68ch;
}
.draftnote b{ color:var(--fg); font-variation-settings:'wght' 620; }

/* ---- Company facts ---- */
.facts{ margin-top:clamp(24px, 3vw, 40px); border-top:1px solid var(--hair); }
.facts > div{
  display:grid; grid-template-columns:minmax(140px, 220px) 1fr; gap:var(--gap);
  padding-block:16px; border-bottom:1px solid var(--hair); align-items:baseline;
}
.facts dt{
  font-size:var(--t-label); font-variation-settings:'wght' 580;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted);
}
.facts dd{ margin:0; }

/* ---- Contact ---- */
.contact-grid{
  display:grid; gap:clamp(24px, 4vw, 64px);
  grid-template-columns:minmax(0, 1.3fr) minmax(0, .7fr);
}
.contact-big{ margin:10px 0 var(--gap); }
.contact-big a{
  font-size:clamp(1.3rem, 2.4vw, 1.9rem);
  font-variation-settings:'wght' 620, 'wdth' 104;
  letter-spacing:-.015em; text-decoration:none;
}
.contact-big a:hover{ text-decoration:underline; }
.contact-addr{ font-style:normal; margin-top:10px; color:var(--muted); line-height:1.75; }
.cards--3{ grid-template-columns:repeat(3, minmax(0, 1fr)); }

/* ---- The deep footer ---- */
.foot-grid--deep{ grid-template-columns:1.5fr repeat(3, 1fr); align-items:start; }
.foot-brand address{ margin-top:var(--gap); }
.foot-legal__links{ display:flex; flex-wrap:wrap; gap:8px; }
.foot-legal__links a{ font-size:var(--t-small); }

@media (max-width:1020px){
  .foot-grid--deep{ grid-template-columns:repeat(2, 1fr); }
  .cards--3{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}
@media (max-width:820px){
  .svc, .contact-grid{ grid-template-columns:1fr; }
  .ticks--2{ columns:1; }
}
@media (max-width:620px){
  .indp, .stage{ grid-template-columns:1fr; gap:10px; }
  .cards--3, .foot-grid--deep{ grid-template-columns:1fr; }
  .facts > div{ grid-template-columns:1fr; gap:4px; }
}

/* ============================================================
   20 · REVEAL ON SCROLL
   Hidden only when JavaScript is present — the `js` class is set
   by an inline script in the head, so with scripting off nothing
   is ever hidden and the page reads exactly as it does now.
   Switched off entirely under prefers-reduced-motion.
   ============================================================ */
.js .sechead, .js .card, .js .prod, .js .ind, .js .svc,
.js .stage, .js .indp, .js .shot, .js .prose, .js .facts,
.js .contact-grid, .js .draftnote, .js .cta-row{
  opacity:0; transform:translateY(12px);
}
.js .is-in{
  opacity:1; transform:none;
  transition:opacity .55s ease, transform .55s cubic-bezier(.2,.7,.2,1);
}
@media (prefers-reduced-motion:reduce){
  .js .sechead, .js .card, .js .prod, .js .ind, .js .svc,
  .js .stage, .js .indp, .js .shot, .js .prose, .js .facts,
  .js .contact-grid, .js .draftnote, .js .cta-row{ opacity:1; transform:none; }
  .js .is-in{ transition:none; }
}

/* ============================================================
   21 · THE HOMEPAGE, REBUILT — 2026-08-18
   Measured before this pass: the first image on the homepage sat
   4,362px down, and the page ran six bands alternating between
   two pale colours with no dark or saturated moment anywhere.
   Every firm homepage read in a browser has both — Netguru is
   near-black throughout, Simform dark purple, Intellectsoft a
   blue gradient, Net Solutions a full Azure band.

   Now: hero with the product alongside the headline, a strip, an
   Azure conviction band, a compact model, a compact industries
   row, and the products on Ink — where light product shots have
   something to sit against. First image at roughly 240px.
   ============================================================ */

.hero--split{
  padding-block:clamp(48px, 5.5vw, 84px) clamp(32px, 4vw, 56px);
  background:radial-gradient(70% 110% at 88% -18%,
    color-mix(in srgb, var(--th-azure) 13%, transparent) 0%, transparent 58%);
}
.hero--split .hero-grid{
  display:grid; gap:clamp(28px, 4vw, 60px);
  grid-template-columns:minmax(0, .92fr) minmax(0, 1.08fr);
  align-items:center;
}
.hero--split h1{ font-size:clamp(2.3rem, 4.2vw, 3.6rem); max-width:15ch; margin-top:18px; }
.hero--split .hero-deck{ max-width:46ch; font-size:clamp(1rem, 1.25vw, 1.16rem); }
.hero--split .th-beamlock.lockup{ font-size:52px; }

/* The shot runs wider than its column and is clipped by the wrapper, so it
   reads as a window onto something larger without ever crossing the viewport. */
.hero-shot{
  border-radius:var(--th-radius);
  overflow:hidden;
  border:1px solid var(--hair);
  box-shadow:var(--shadow-up);
  background:var(--wash);
  aspect-ratio:16 / 11;
}
/* Zoomed and offset so the application fills the frame and its floating panels
   crop against the edges. Shown at natural size the app sits small inside its
   own canvas margins and reads as a picture of a picture. */
.hero-shot img{
  display:block; width:172%; max-width:none; height:auto;
  margin-left:-13.5%; margin-top:-7%;
}

/* ---- The compact model: six stages as a scannable list ---- */
.mini{
  list-style:none; margin:clamp(24px, 3vw, 40px) 0 0; padding:0;
  display:grid; grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:0 var(--gap); border-top:1px solid var(--hair);
}
.mini li{
  display:grid; grid-template-columns:38px 1fr; align-items:baseline;
  gap:4px var(--gap); padding-block:16px; border-bottom:1px solid var(--hair);
}
.mini__n{
  grid-row:span 2; font-size:var(--t-label); font-variation-settings:'wght' 620;
  letter-spacing:.1em; color:var(--accent); font-variant-numeric:tabular-nums;
}
.mini b{ font-size:var(--t-body); font-variation-settings:'wght' 620; }
.mini span:not(.mini__n){ font-size:var(--t-small); color:var(--muted); line-height:1.5; }
.textlink--big{ display:inline-block; margin-top:var(--gap); font-size:var(--t-body); }

/* ---- Industries as one scannable row ---- */
.indrow{
  display:grid; grid-template-columns:repeat(5, minmax(0, 1fr));
  margin-top:clamp(24px, 3vw, 40px); border-top:1px solid var(--hair);
}
.indrow a{
  display:block; text-decoration:none; color:inherit;
  padding:24px var(--gap) 24px 0; border-bottom:2px solid transparent;
  transition:border-color .18s ease, color .18s ease;
}
.indrow a + a{ padding-left:var(--gap); border-left:1px solid var(--hair); }
.indrow a:hover{ border-bottom-color:var(--th-azure); }
.indrow b{
  display:block; font-size:var(--t-sub);
  font-variation-settings:'wght' 620,'wdth' 104; letter-spacing:-.012em; line-height:1.2;
}

/* ---- The three product links under the featured one ---- */
.prodrow{
  display:grid; grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:var(--gap); margin-top:clamp(32px, 3.4vw, 48px);
}
.prodcard{
  --mk-ground:var(--surface);
  display:flex; flex-direction:column; align-items:flex-start; gap:12px;
  background:var(--surface); border:1px solid var(--hair);
  border-radius:var(--th-radius); padding:clamp(20px, 2vw, 24px);
  text-decoration:none; color:inherit;
  transition:border-color .18s ease, transform .18s cubic-bezier(.2,.7,.2,1);
}
.prodcard:hover{ border-color:var(--th-azure); transform:translateY(-3px); }
.prodcard b{ font-size:var(--t-body); font-variation-settings:'wght' 640; }
.prodcard span{ font-size:var(--t-small); color:var(--muted); line-height:1.55; }
.prodcard::after{ content:"→"; color:var(--accent); margin-top:auto; padding-top:6px; }

/* The Ink band's product figure needs a lighter frame than the Paper one */
.band--ink .shot img{ border-color:var(--th-line); }

@media (max-width:1020px){
  .mini{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .indrow{ grid-template-columns:repeat(3, minmax(0, 1fr)); }
  .prodrow{ grid-template-columns:1fr; }
}
@media (max-width:900px){
  .hero--split .hero-grid{ grid-template-columns:1fr; }
  .hero--split h1{ max-width:none; }
  .hero-shot{ aspect-ratio:16 / 10; }
  .hero-shot img{ width:150%; margin-left:-11%; margin-top:-6%; }
}
@media (max-width:640px){
  .mini, .indrow{ grid-template-columns:1fr; }
  .indrow a + a{ padding-left:0; border-left:0; }
}

/* ============================================================
   22 · COLOUR — 2026-08-18
   The brand is Graphite & Azure: one accent, split into -deep for
   light grounds and -lift for dark, because the core Azure is
   marks-and-display only at 3.99:1 on Ink and 4.49:1 on Paper.
   Extending to six hues meant doing that arithmetic five more
   times rather than picking swatches that look nice and fail on
   the page. Every value below was solved for, not chosen:

     hue      deep      Paper   Wash  | lift      Ink
     azure    #1C69CE   4.91    4.54  | #2C7BE2   4.64
     teal     #0C7971   4.87    4.51  | #0E8B83   4.64
     violet   #7651CD   5.07    4.70  | #8969D3   4.63
     amber    #9B590D   5.08    4.70  | #B76910   4.65
     green    #127848   5.11    4.73  | #158E56   4.64
     rust     #B6462B   4.98    4.62  | #D05435   4.61

   Solved against the WASH ground (#EAEEF1) as well as Paper, not
   just Paper — the first pass cleared 4.5:1 on Paper and then
   failed at 4.2:1 the moment those numerals sat on a wash band,
   which is exactly where the industries row lives. All six clear
   4.5:1 on both light grounds and on Ink: the text bar, not the
   3:1 graphics bar, so the same value carries a label as well as
   a mark.

   COLOUR ENCODES WHAT, NOT DECORATION. A hue belongs to a
   concept and follows it everywhere: Run is teal in the hero
   strip, in the service card, on the services page and in the
   footer. Nothing is coloured for variety alone.

   NOTE FOR THE BRAND DOCUMENT: this extends Graphite & Azure
   from one accent to six and needs recording there. Azure
   remains primary and is the only hue on furniture — the beam,
   the wordmark dot, the buttons.

   Hues are never the only signifier: every mark also has its own
   drawing and a text label, so nothing here depends on colour
   vision.
   ============================================================ */
:root{
  --c-azure:#1C69CE; --c-teal:#0C7971; --c-violet:#7651CD;
  --c-amber:#9B590D; --c-green:#127848; --c-rust:#B6462B;
}
.band--ink{
  --c-azure:#2C7BE2; --c-teal:#0E8B83; --c-violet:#8969D3;
  --c-amber:#B76910; --c-green:#158E56; --c-rust:#D05435;
}
/* On the Azure band nothing else may compete, so every hue resolves to white */
.band--azure{
  --c-azure:#fff; --c-teal:#fff; --c-violet:#fff;
  --c-amber:#fff; --c-green:#fff; --c-rust:#fff;
}

.h-azure { --accent:var(--c-azure);  --mk-az:var(--c-azure);  }
.h-teal  { --accent:var(--c-teal);   --mk-az:var(--c-teal);   }
.h-violet{ --accent:var(--c-violet); --mk-az:var(--c-violet); }
.h-amber { --accent:var(--c-amber);  --mk-az:var(--c-amber);  }
.h-green { --accent:var(--c-green);  --mk-az:var(--c-green);  }
.h-rust  { --accent:var(--c-rust);   --mk-az:var(--c-rust);   }

/* The mark takes the hue; the dot inside it is already reading --mk-az. */
.tinted .mark{ color:var(--accent); }
.tinted:hover .mark{ color:var(--accent); }
.card.tinted:hover,
.prodcard.tinted:hover{ border-color:var(--accent); }
.indrow a.tinted:hover{ border-bottom-color:var(--accent); }
.mini li.tinted .mini__n{ color:var(--accent); }
.card.tinted .card__n,
.card.tinted .card__tag{ color:var(--accent); }
.svc.tinted .pricing,
.stage.tinted .stage__n span{ color:var(--accent); }
.ticks li::before{ border-color:var(--accent);
  background:radial-gradient(circle at 50% 50%, var(--accent) 0 3px, transparent 3px); }

/* Run keeps its lead-card outline, now in its own hue rather than Azure */
.card--lead.tinted{ border-color:color-mix(in srgb, var(--accent) 52%, transparent); }

/* ---- the hero returns to one column ---- */
.hero--plain .hero-t{ max-width:none; }

/* ---- Colour on surfaces, not just on numerals.
   A hue on a 12.5px numeral is technically present and visually absent. The
   same hue at 5% across a whole card reads as a coloured card while leaving
   the text on effectively the same ground — every value below was re-measured
   against the tinted ground rather than the untinted one. ---- */
.card.tinted{
  background:color-mix(in srgb, var(--accent) 5%, var(--surface));
  border-color:color-mix(in srgb, var(--accent) 24%, var(--hair));
  --mk-ground:color-mix(in srgb, var(--accent) 5%, var(--surface));
}
.prodcard.tinted{
  background:color-mix(in srgb, var(--accent) 9%, var(--surface));
  border-color:color-mix(in srgb, var(--accent) 30%, var(--hair));
  --mk-ground:color-mix(in srgb, var(--accent) 9%, var(--surface));
}
.card.tinted:hover, .prodcard.tinted:hover{
  border-color:color-mix(in srgb, var(--accent) 62%, transparent);
}

/* A coloured cap on each column, which is the clearest signal at this size */
.indrow a.tinted{ border-top:3px solid var(--accent); margin-top:-1px; }
/* No side stripe. A >1px coloured left border on list items is the single most
   reliable "AI made this" tell, and here it was the third decorative system on
   the same six items, stacked on the numerals and a six-colour rainbow. The
   numeral carries the sequence; the hairline rule carries the grouping. */
.mini li.tinted{ padding-left:0; }

/* The stage numerals carry the hue at a size where it registers. The industries
   row had these too until its numbering was dropped as decorative. */
.mini__n{ font-size:var(--t-small); letter-spacing:.06em; }

/* Detail pages: a coloured rule beside each stage and each industry */
/* Same removal as `.mini` — the stage numeral is the marker, not a stripe. */
.stage.tinted{ padding-left:0; }
.svc.tinted .mark{ color:var(--accent); }

/* ============================================================
   23 · NEGATIVE SPACE — 2026-08-18
   Measured before this pass: 54.9% of the homepage's vertical
   space was air, section padding was 115px, and the H1 sat 230px
   from the top of the document. Against the offsets recorded from
   the real firm homepages — Netguru puts its H1 at 381px, Net
   Solutions runs 244px between a section heading and the first
   card beneath it — that is tight everywhere at once.

   Everything below widens the same rhythm rather than adding air
   in one place: the band, the gap between a heading and what it
   introduces, the inside of a card, the height of a list row, and
   the leading inside a paragraph. Air added in only one of those
   reads as a gap; added across all of them it reads as space.
   ============================================================ */
:root{
  --gap:clamp(18px, 1.9vw, 28px);
  --sec:clamp(88px, 11vw, 168px);
}

/* ---- Heroes get the most, because the first screen sets the pace ---- */
.hero--plain{ padding-block:clamp(96px, 12vw, 196px) clamp(72px, 8vw, 128px); }
.pagehero{ padding-block:clamp(80px, 9vw, 148px) clamp(48px, 5.5vw, 84px); }
.hero--plain h1{ margin-top:24px; }
.hero--plain .hero-deck{ margin-top:calc(var(--gap) * 1.35); }
.hero .cta-row{ margin-top:calc(var(--gap) * 1.9); }

/* ---- A heading needs room before what it introduces ---- */
.sechead .label{ margin-bottom:calc(var(--gap) * 1.1); }
.sechead h2 + .deck, .sechead h1 + .deck{ margin-top:calc(var(--gap) * 1.15); }
.cards, .inds, .spec, .compare, .cases, .indrow, .mini, .ticks{
  margin-top:clamp(44px, 5.2vw, 80px);
}
.prod{ margin-top:clamp(52px, 6vw, 96px); }
.prod--alt{ margin-top:clamp(64px, 7.4vw, 120px); }
.prodrow{ margin-top:clamp(40px, 4.6vw, 72px); }
.facts, .contact-grid{ margin-top:clamp(36px, 4.2vw, 64px); }

/* ---- Inside the components ---- */
.card{ padding:clamp(24px, 2.9vw, 40px); gap:18px; }
.prodcard{ padding:clamp(24px, 2.6vw, 32px); gap:15px; }
.mini li{ padding-block:24px; }
.indrow a{ padding:32px var(--gap) 32px 0; }
.ticks li{ padding-block:14px; }
.spec__head, .spec__row{ padding-block:24px; }
.facts > div{ padding-block:24px; }
.stage{ gap:clamp(24px, 3.4vw, 56px); }
.stage__b p + p{ margin-top:calc(var(--gap) * 1.15); }
.svc, .indp{ gap:clamp(28px, 4vw, 64px); }

/* ---- Air inside the type, not only around it ---- */
body, p, li, dd{ line-height:1.68; }
.deck, .hero-deck, .lede{ line-height:1.58; max-width:58ch; }
.prose p + p{ margin-top:calc(var(--gap) * 1.15); }
.ticks li, .card p, .prodcard span{ line-height:1.6; }

/* ---- The footer gets the same treatment ---- */
.site-foot{ padding-block:clamp(64px, 7vw, 108px) 32px; }
.foot-grid{ gap:calc(var(--gap) * 1.4); }
.foot-legal{ margin-top:clamp(48px, 5.4vw, 80px); padding-top:28px; }

@media (max-width:640px){
  .indrow a{ padding:24px 0; }
  .card{ padding:24px; }
}

/* ============================================================
   24 · THE HOMEPAGE PASS — 2026-08-18
   Owner's rulings: the hero fills the viewport · a fourth
   engagement (Modernise) joins Build/Run/Augment, and the cards
   run four across on one row · digital
   forensics retired, leaving four industries · the three
   products each get their own zigzag section · the hero CTA
   pair removed.
   ============================================================ */

/* ---- The hero fills the first screen ----
   svh, not vh: on mobile the browser chrome collapses on scroll, and vh
   measures the tallest state, so a vh hero is cut off on arrival — which is
   the one moment the hero has to be whole. The header is sticky and sits
   above, so its height comes off the top. */
/* `section.hero--full`, not `.hero--full`: §2 sets the band padding as
   `section.band`, an element-plus-class selector that outspecifies a bare class
   no matter how late it is declared. Matching its shape is what makes this
   override actually land — the first attempt did not, and the hero stayed 83px
   past the fold while the stylesheet looked correct. */
/* `section.hero--full`, matching §2's `section.band` shape — see the note
   below. The hero is one screen of headline and nothing else. The four
   engagements used to be pinned to its bottom edge; that ate the height the
   hero had been given, so the headline was squeezed into whatever was left
   and the screen read as a hero plus a crammed footer rather than a hero.
   They now open the page below the fold, in their own band. */
section.hero--full{
  min-height:calc(100svh - var(--head-h, 62px));
  display:flex; align-items:center;
  /* .hero--plain's padding is sized for a band that ends where its content
     ends. Here the height is the screen and the centring supplies the air. */
  padding-block:clamp(24px, 3vw, 56px);
}
.hero--full .hero-stack{ width:min(100% - 2*var(--gap), 1180px); }

/* Short viewports — a laptop at 1440x720 with the chrome open, or a phone in
   landscape — get the hero's natural height instead of a squeezed screen. */
@media (max-height:600px){
  section.hero--full{ min-height:0; padding-block:clamp(32px, 5vw, 64px); }
}


/* ---- Four across, the full width of the row ----
   Explicit 4 x 1fr, not auto-fit: auto-fit would drop to three-plus-one at
   some widths. The base `.indrow` is still `repeat(5, 1fr)` from when there
   were five industries, so this override is what keeps the row full-width —
   without it four items sit in a five-column grid and the row stops 236px
   short of the right edge. */
.cards--row4{ grid-template-columns:repeat(4, minmax(0, 1fr)); }
.indrow--4{ grid-template-columns:repeat(4, minmax(0, 1fr)); }

/* Four cards in the width three used to have leaves less room for the measure,
   so the type steps down one notch rather than the padding collapsing. */
.cards--row4 .card{ padding:clamp(24px, 2.2vw, 32px); }
.cards--row4 .card p{ font-size:var(--t-small); }

@media (max-width:1080px){
  .cards--row4, .indrow--4{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .indrow--4 a:nth-child(odd){ padding-left:0; border-left:0; }
}
@media (max-width:620px){
  .cards--row4, .indrow--4{ grid-template-columns:1fr; }
  .indrow--4 a + a{ padding-left:0; border-left:0; border-top:1px solid var(--hair); }
}

/* ---- Three consecutive Ink bands read as one dark region ----
   Left alone they would stack two full section paddings between each product,
   which opens a gap wide enough to look like a mistake. Collapse the touching
   edges and keep the outer two. */
/* `section.` prefix required: §2 sets the band padding as `section.band`, an
   element-plus-class selector, so a bare `.band--ink-*` never wins however late
   it is declared. Without it these three bands each kept a full 158px top AND
   bottom, putting 316px of dead air between products that the comment above
   claims is collapsed. Measured, not assumed: section padding across the site
   was (158,158) on 51 of 52 sections. */
section.band--ink-open{ padding-bottom:clamp(28px, 3vw, 48px); }
section.band--ink-mid{ padding-block:clamp(28px, 3vw, 48px); }
section.band--ink-close{ padding-top:clamp(28px, 3vw, 48px); }

/* The first product follows a section head; the other two open their band, so
   they do not want the head's top margin. */
.band--ink-mid .prod, .band--ink-close .prod{ margin-top:0; }
.band--ink-close .textlink--big{ margin-top:clamp(32px, 3.6vw, 52px); display:inline-block; }

/* ============================================================
   25 · THE CRITIQUE PASS — 2026-08-18
   From the impeccable critique. Rulings taken: a CTA returns to
   the hero (34) · response times published (35) · industries go
   monochrome (36) · the Azure band earns its emphasis through
   scale rather than centring (37). The products band was
   explicitly left alone.
   ============================================================ */

/* ---- 37 · The page's one change of pace ----
   Every band ran the same rhythm: label, 44px h2, deck, grid. The Azure band
   used to break it by being centred, and centring was removed because it was
   the only block off the shared left edge. Emphasis now comes from scale, which
   costs nothing structurally: this heading steps up to the display size while
   staying on the same left edge as everything else.

   The container max-width is in rem, NOT ch. `ch` resolves against the
   container's own 16px while the heading inside it is 64px — the same inversion
   that made a standfirst 586px in one place and 631px in another (§24). Setting
   22ch here produced a ~200px column and wrapped this heading over six lines.
   The `ch` cap belongs on the h2, where the unit and the type size agree. */
.sechead--turn{ max-width:56rem; }          /* 896px — two lines at 64px */
.sechead--turn h2{
  font-size:var(--t-display);
  line-height:calc(0.98em + 0.42rem);
  letter-spacing:-.026em;
  font-variation-settings:'wght' 680,'wdth' 110;
  max-width:26ch;                            /* on the h2 itself, where ch is the right unit */
}
.sechead--turn .deck{ max-width:34rem; }

/* ---- 35 · Published response times ----
   A definition list, not cards: three short promises are a table of facts, and
   wrapping each in a card would be the reflex the register warns about. Reuses
   `.facts` from the about page so the pattern is the same one a reader has
   already met there. */
.facts--times{ margin-top:clamp(32px, 3.6vw, 52px); max-width:41.25rem; }
.facts--times dd{ font-variation-settings:'wght' 520; }
.band--wash .facts--times{ border-top-color:var(--hair); }

/* The hero CTA sits under the standfirst, on the same left edge. */
.hero--full .cta-row{ margin-top:clamp(24px, 2.6vw, 36px); }

/* ============================================================
   26 · DARK MODE — 2026-08-28
   Follows the system and nothing else. No toggle, no stored
   preference, no inline theme script: this site has no other
   state, and a switch would be the one thing on it that has to
   be remembered, plus a blocking script on twelve pages to stop
   the wrong theme flashing before first paint. The visitor has
   already told their OS what they want.

   The page is not one ground, it is four, and the alternation is
   the rhythm. Ink is ALREADY the dark contrast event, so simply
   darkening everything would flatten the ladder into one flat
   surface. Each band moves one step instead and the order of
   lightness is preserved: page, then wash, then the old ink band
   as a RAISED panel rather than a sunken one. Azure is untouched;
   it is the loud band in both themes and its contrast was solved
   against white long ago.

   Inverting the ink band to Paper was the alternative. A light
   band on a dark page is a glare bomb, so it was not taken.
   ============================================================ */
@media (prefers-color-scheme: dark){
  :root{
    color-scheme: dark;

    --fg:        var(--th-paper);
    --muted:     var(--th-mute);
    --surface:   #161C22;
    --hair:      var(--th-line);
    /* Not --th-azure-lift: that is solved against Ink and measures 4.28:1 on
       the lifted band, just under AA. This is the same azure from the sector
       set below, solved against the lightest dark ground. */
    --accent:    #5394E7;
    --ground:    #0B0E11;

    --wash:      #141A21;
    --wash-hair: #232C35;

    --shadow: none; --shadow-up: none;
  }

  body{
    background:#0B0E11; color:var(--th-paper);
    --mk-az:var(--th-azure-lift); --mk-ground:#0B0E11;
  }

  .band{ background:#0B0E11; }

  .band--wash{
    --ground:#141A21; --hair:#232C35; --mk-ground:#141A21;
    background:#141A21;
  }

  /* The former dark band becomes the lifted one, keeping its job as the
     page's one contrast event without turning into a glare panel. */
  .band--ink{
    --fg:var(--th-paper); --muted:#9AA3AE; --surface:#2C353E;
    --hair:#3A444E; --accent:#5394E7; --ground:#232B34;
    --mk-az:var(--th-azure-lift); --mk-ground:#232B34;
    background:#232B34; color:var(--th-paper);
  }

  .site-head{ background:color-mix(in srgb, #0B0E11 88%, transparent); }

  /* The nav reaches past --muted straight to --th-mute-lt, which is the
     "secondary text on light" token. On the dark ground it measures 3.09:1
     and fails AA. Route it through --muted, which the block above has
     already re-pointed. */
  .site-nav a{ color:var(--muted); }

  /* The six sector hues need a third set, not the ink band's.
     The deep set is for paper. The lift set was solved against Ink (#0B0E11)
     and reaches ~4.6:1 there. But dark mode's LIGHTEST ground is the lifted
     band at #232B34, where the lift set falls to ~3.44:1 and fails AA. These
     are the lift hues raised in lightness only, hue and saturation untouched,
     until each clears 4.6:1 on #232B34. Measured, not guessed:

       hue      on #232B34   on #141A21   on #0B0E11
       azure    4.60         5.62         6.22
       teal     4.62         5.64         6.24
       violet   4.61         5.64         6.24
       amber    4.62         5.65         6.24
       green    4.60         5.63         6.22
       rust     4.60         5.63         6.22
  */
  :root, .band, .band--wash{
    --c-azure:#5394E7; --c-teal:#10A49A; --c-violet:#9E84DB;
    --c-amber:#D77C13; --c-green:#19A765; --c-rust:#DA775E;
  }
  .band--ink{
    --c-azure:#5394E7; --c-teal:#10A49A; --c-violet:#9E84DB;
    --c-amber:#D77C13; --c-green:#19A765; --c-rust:#DA775E;
  }

  /* The header wordmark uses the --light variant, which pins "tech" to Ink
     for a light ground. On a dark page that is invisible and the logo reads
     "holden." Put it back to full strength and lift the dot. */
  .th-wordmark--light .th-a{ color:var(--th-paper); }
  .th-wordmark .th-dot{ color:var(--th-azure-lift); }
}
