/* ============================================================
   HIGHLANDS — PREMIUM LAYER
   Loaded after hcc.css. Adds craft on top of the existing build:
   display typography, grain, scroll furniture, richer hover and
   entrance behaviour. Everything here is token-driven so both
   grounds keep working, and everything is disabled under
   prefers-reduced-motion.
   ============================================================ */

:root {
  --hcc-ease: cubic-bezier(.16, 1, .3, 1);
  --hcc-slow: 1.1s;
  --sp: 0;
}

/* ---------- display typography ------------------------------ */
h1, h2, h3, .capgrid b, .email {
  font-family: "Archivo", "Inter", system-ui, sans-serif;
  font-variation-settings: "wdth" 104;
  letter-spacing: -.028em;
}
h2 { font-weight: 700; }
.kicker {
  display: flex;
  align-items: center;
  gap: 14px;
  font-weight: 500;
  color: var(--ink-3);
}
.kicker::before {
  content: "";
  width: clamp(22px, 3vw, 54px);
  height: 1px;
  background: currentColor;
  opacity: .55;
  flex: none;
}
.capgrid b, .capgrid .num { font-variant-numeric: tabular-nums; }
.lede, .body { text-wrap: pretty; }

/* ---------- film grain -------------------------------------- */
/* ⚠ FIX 4 Sep: inset was -50%, a fixed layer FOUR times the screen (200vw x
   200vh) held as a blended backing store for the life of the page. The shift
   animation only ever moves it 2% of its own size, so 8% of overhang covers
   the screen at every keyframe; the keyframes below are rescaled so the
   grain still moves the same distance on screen. Identical picture. */
.hcc-grain {
  position: fixed;
  inset: -8%;
  z-index: 900;
  pointer-events: none;
  opacity: .05;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
  will-change: transform;
  animation: hcc-grain-shift 6s steps(6) infinite;
}
@keyframes hcc-grain-shift {
  0%   { transform: translate3d(0, 0, 0); }
  20%  { transform: translate3d(-3.45%, 1.72%, 0); }
  40%  { transform: translate3d(1.72%, -3.45%, 0); }
  60%  { transform: translate3d(-1.72%, 3.45%, 0); }
  80%  { transform: translate3d(3.45%, 1.72%, 0); }
  100% { transform: translate3d(0, 0, 0); }
}

/* ---------- scroll progress --------------------------------- */
.hcc-prog {
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 2px;
  z-index: 1600;
  pointer-events: none;
  background: linear-gradient(90deg, rgba(255,255,255,.25), #fff);
  transform: scaleX(var(--sp));
  transform-origin: 0 50%;
  opacity: 0;
  transition: opacity .4s ease;
}
html.hcc-scrolled .hcc-prog { opacity: .9; }

/* ---------- opening curtain --------------------------------- */
.hcc-veil {
  position: fixed;
  inset: 0;
  z-index: 3000;
  display: grid;
  place-items: center;
  background: #000;
  transition: opacity .8s var(--hcc-ease), visibility .8s;
}
.hcc-veil svg { width: min(240px, 46vw); color: #fff; opacity: .92; }
.hcc-veil { animation: hcc-veil-auto 1.6s forwards; }
@keyframes hcc-veil-auto { 0%,60% { opacity: 1; } 100% { opacity: 0; visibility: hidden; } }
.hcc-veil i {
  position: absolute;
  left: 50%; bottom: 22%;
  width: min(240px, 46vw);
  height: 1px;
  translate: -50% 0;
  background: rgba(255, 255, 255, .22);
  overflow: hidden;
}
.hcc-veil i::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #fff;
  transform-origin: 0 50%;
  animation: hcc-load 1.1s var(--hcc-ease) forwards;
}
@keyframes hcc-load { from { transform: scaleX(0); } to { transform: scaleX(1); } }
.hcc-veil.gone { animation: none; opacity: 0; visibility: hidden; }

/* ---------- navigation -------------------------------------- */
#nav {
  transition: transform .5s var(--hcc-ease), background-color .4s ease,
              backdrop-filter .4s ease, box-shadow .4s ease;
}
html.hcc-scrolled #nav {
  background: color-mix(in oklab, var(--ground) 72%, transparent);
  backdrop-filter: saturate(140%) blur(16px);
  -webkit-backdrop-filter: saturate(140%) blur(16px);
  box-shadow: 0 1px 0 var(--line), 0 18px 40px -32px rgba(0, 0, 0, .9);
}
html.hcc-nav-hidden #nav { transform: translate3d(0, -110%, 0); }

#nav ul a:not(.cta) { position: relative; }
#nav ul a:not(.cta)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -6px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 100% 50%;
  transition: transform .5s var(--hcc-ease);
}
#nav ul a:not(.cta):hover::after,
#nav ul a:not(.cta):focus-visible::after {
  transform: scaleX(1);
  transform-origin: 0 50%;
}

.cta {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: color .45s var(--hcc-ease), transform .35s var(--hcc-ease);
}
.cta::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ink-1);
  transform: translate3d(0, 101%, 0);
  transition: transform .55s var(--hcc-ease);
}
.cta:hover::before, .cta:focus-visible::before { transform: translate3d(0, 0, 0); }
.cta:hover, .cta:focus-visible { color: var(--ground); }

/* ---------- hero -------------------------------------------- */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background:
    radial-gradient(120% 80% at 50% 40%, transparent 40%, rgba(0, 0, 0, .55) 100%),
    linear-gradient(180deg, rgba(0, 0, 0, .5), transparent 26%);
}
.hero video, .hero .poster {
  animation: hcc-hero-drift 26s ease-in-out infinite alternate;
}
@keyframes hcc-hero-drift {
  from { transform: scale(1.02); }
  to   { transform: scale(1.09); }
}
.hero-ctl button {
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  background: rgba(0, 0, 0, .28);
  transition: color .35s ease, border-color .35s ease, background-color .35s ease, transform .35s var(--hcc-ease);
}
.hero-ctl button:hover { transform: translateY(-2px); background: rgba(0, 0, 0, .5); }

/* ---------- photography ------------------------------------- */
.proj-media figure img,
.svc-media figure img,
.plate-frame img,
.lead-fig img {
  transition: transform 1.4s var(--hcc-ease), filter 1.4s var(--hcc-ease);
}
.proj-media figure.on img,
.svc-media figure.on img { transform: scale(1.035); }
.plate-frame:hover img { transform: scale(1.04); }

/* ---------- numbers ----------------------------------------- */
.capgrid > div {
  position: relative;
  transition: background-color .5s var(--hcc-ease);
}
.capgrid > div::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: 1px;
  background: var(--ink-1);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .7s var(--hcc-ease);
}
.capgrid > div:hover::after { transform: scaleX(1); }
.capgrid > div:hover { background: color-mix(in oklab, var(--ink-1) 5%, var(--ground)); }

/* ---------- contact ----------------------------------------- */
.contact { position: relative; overflow: hidden; }
.contact::before {
  content: none;
}
.contact .inner { position: relative; }
.email {
  position: relative;
  display: inline-block;
  text-decoration: none;
  padding-bottom: .12em;
}
.email::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: currentColor;
  transform: scaleX(1);
  transform-origin: 100% 50%;
  transition: transform .6s var(--hcc-ease);
}
.email:hover::after { transform: scaleX(0); }
.cmeta dt { color: var(--ink-3); }
.cmeta dd { color: var(--ink-1); }

/* ---------- mobile enquiry bar ------------------------------ */
.hcc-bar {
  position: fixed;
  left: 12px; right: 12px;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 1500;
  display: none;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 13px 16px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .96);
  color: #000;
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, .8);
  text-decoration: none;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  transform: translate3d(0, 160%, 0);
  transition: transform .6s var(--hcc-ease), opacity .4s ease;
}
.hcc-bar span:last-child { font-size: 15px; }
@media (max-width: 860px) {
  .hcc-bar { display: flex; }
  html.hcc-scrolled .hcc-bar { transform: translate3d(0, 0, 0); }
  html.hcc-at-contact .hcc-bar { transform: translate3d(0, 160%, 0); }
  .mobile-menu a { font-family: "Archivo", "Inter", sans-serif; }
  .kicker::before { width: 20px; }
}

/* ---------- footer ------------------------------------------ */
footer .fsoc a {
  display: inline-flex;
  transition: transform .4s var(--hcc-ease), opacity .4s ease;
}
footer .fsoc a:hover { transform: translateY(-3px); opacity: 1; }

/* ---------- entrances --------------------------------------- */
.hcc-rise {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition: opacity .9s var(--hcc-ease), transform .9s var(--hcc-ease);
}
.hcc-rise.in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .hcc-grain, .hero video, .hero .poster { animation: none; }
  .hcc-rise { opacity: 1; transform: none; transition: none; }
  .hcc-veil { display: none; }
}

/* ============================================================
   MOBILE HERO LOCKUP
   ============================================================ */
.hlock { display: none; }
@media (max-width: 860px) {
  .hlock {
    display: block;
    position: absolute;
    left: 0; right: 0;
    bottom: clamp(104px, 13vh, 140px);
    padding: 0 22px;
    z-index: 4;
    text-align: left;
    animation: hlockIn 1.4s var(--hcc-ease) .55s both;
  }
  .hlock-word {
    width: min(82vw, 420px);
    height: auto;
    display: block;
    fill: #fff;
    filter: drop-shadow(0 12px 40px rgba(0, 0, 0, .55));
  }
  .hlock-line {
    margin: 0 0 20px;
    max-width: 30ch;
    color: rgba(255, 255, 255, .82);
    font-size: 14.5px;
    line-height: 1.5;
    letter-spacing: .002em;
    text-wrap: pretty;
  }
  .hlock-cta {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 13px 20px;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, .34);
    background: rgba(255, 255, 255, .1);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    backdrop-filter: blur(14px) saturate(150%);
    color: #fff;
    text-decoration: none;
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: .11em;
    text-transform: uppercase;
  }
  .hlock-cta:active { transform: scale(.97); }
  .hero::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 62%;
    background: linear-gradient(to top, rgba(0, 0, 0, .78), rgba(0, 0, 0, .34) 46%, transparent);
    pointer-events: none;
    z-index: 3;
  }
  .hero-cue { z-index: 4; }
}
@keyframes hlockIn {
  from { opacity: 0; transform: translate3d(0, 26px, 0); filter: blur(6px); }
  to { opacity: 1; transform: none; filter: none; }
}

/* ============================================================
   ENQUIRY FORM
   ============================================================ */
/* ⚠ the rest of the page is flat, square and hairline-ruled. The form
   follows that language: no card, no pills, no rounded corners. */
.enq {
  margin-top: clamp(56px, 8vw, 110px);
  padding-top: clamp(26px, 3vw, 42px);
  border-top: 1px solid color-mix(in oklab, var(--ink-1) 18%, transparent);
  max-width: 1000px;
}
.enq-kick { display: block; margin: 0 0 clamp(10px, 1.2vw, 16px); }
.enq-h {
  margin: 0 0 clamp(26px, 3.4vw, 46px);
  max-width: 24ch;
  font-size: clamp(26px, 4vw, 54px);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--ink-1);
}
.enq-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(20px, 2.4vw, 34px) clamp(24px, 3vw, 48px);
}
.fld-wide { grid-column: 1 / -1; }
.fld { display: block; }
.fld > span {
  display: block;
  margin-bottom: 10px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .22em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.fld input, .fld select, .fld textarea {
  width: 100%;
  padding: 10px 0 12px;
  border: 0;
  border-bottom: 1px solid color-mix(in oklab, var(--ink-1) 22%, transparent);
  border-radius: 0;
  background: transparent;
  color: var(--ink-1);
  font: inherit;
  font-size: clamp(15px, 1.15vw, 18px);
  letter-spacing: -.01em;
  transition: border-color .4s var(--hcc-ease);
}
.fld select { appearance: none; -webkit-appearance: none; cursor: pointer; }
.fld textarea { resize: vertical; min-height: 74px; line-height: 1.6; }
.fld input::placeholder, .fld textarea::placeholder { color: color-mix(in oklab, var(--ink-3) 62%, transparent); }
.fld input:focus, .fld select:focus, .fld textarea:focus {
  outline: none;
  border-bottom-color: var(--ink-1);
}
/* keyboard focus gets a stronger indicator than a 1px underline colour change:
   WCAG 2.4.7 / 2.4.11 - a 2px white underline is a visible, 3:1 change on black */
.fld input:focus-visible, .fld select:focus-visible, .fld textarea:focus-visible {
  border-bottom-color: var(--ink-1);
  box-shadow: 0 2px 0 var(--ink-1);   /* thickens the underline without moving the layout */
}
.fld.bad input, .fld.bad textarea { border-bottom-color: #c2432f; }
.enq-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px 32px;
  flex-wrap: wrap;
  margin-top: clamp(30px, 3.6vw, 52px);
  padding-top: clamp(20px, 2.4vw, 30px);
  border-top: 1px solid color-mix(in oklab, var(--ink-1) 12%, transparent);
}
.enq-send {
  display: inline-flex !important;
  align-items: center;
  gap: 14px;
  cursor: pointer;
  padding: 18px 30px;
  border: 0;
  border-radius: 0;
  background: var(--ink-1);
  color: var(--bg-1, #0b0b0b);
  text-decoration: none !important;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  transition: background .4s ease, color .4s ease, opacity .3s ease;
}
.enq-send::after, .enq-send::before { display: none !important; }
.enq-send:hover { background: color-mix(in oklab, var(--ink-1) 78%, var(--ground)); }
.enq-send span { transition: transform .4s var(--hcc-ease); }
.enq-send:hover span { transform: translateX(5px); }
.enq-note { margin: 0; font-size: 12.5px; line-height: 1.6; color: var(--ink-3); max-width: 32ch; }
.enq-status { margin: 18px 0 0; font-size: 14px; min-height: 1em; color: var(--ink-2); }
.enq-status.ok { color: var(--ink-1); font-weight: 600; }
.enq-status.err { color: #c2432f; }
@media (max-width: 700px) {
  .enq-grid { grid-template-columns: 1fr; }
  .enq-foot { flex-direction: column; align-items: flex-start; }
  .enq-send { width: 100%; justify-content: space-between; }
}


/* ============================================================
   PREMIUM PASS 2 — desktop + mobile design pass
   Editorial hero plate, ambient light, chapter furniture,
   masked reveals, marquee band, refined details.
   ============================================================ */

:root { --hcc-mx: 50%; --hcc-my: 40%; }

/* ⚠ REVERTED (Daniel, 4 Sep): the desktop hero is the film, full bleed,
   exactly as originally built. No framed plate, no ambient canvas, no
   editorial lockup over the top of it. Do not reintroduce. */
@media (min-width: 860.02px) {
  .hlock { display: none !important; }
}


@keyframes hccLineUp {
  from { transform: translate3d(0, 108%, 0); }
  to { transform: none; }
}

/* mobile: give the lockup a kicker and keep the rest as built */
@media (max-width: 860px) {
  .hlock-meta { display: none; }
  .hlock { bottom: clamp(118px, 17vh, 190px) !important; }
  .hlock-title {
    margin: 0 0 14px;
    font-family: "Archivo", "Inter", system-ui, sans-serif;
    font-variation-settings: "wdth" 104;
    font-weight: 700;
    font-size: clamp(30px, 9.2vw, 46px);
    line-height: .95;
    letter-spacing: -.035em;
    color: #fff;
  }
  .hlock-title > span { display: block; overflow: hidden; }
  .hlock-title span > i {
    display: block;
    font-style: normal;
    animation: hccLineUp 1.1s var(--hcc-ease) both;
    animation-delay: calc(.6s + var(--i, 0) * .1s);
  }
  .hlock-title span:nth-child(2) > i { color: rgba(255, 255, 255, .52); }
  .hlock-kick {
    display: flex;
    align-items: center;
    gap: 10px;
    margin: 0 0 14px;
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: .26em;
    text-transform: uppercase;
    color: rgba(255, 255, 255, .66);
  }
  .hlock-kick::before { content: ""; width: 24px; height: 1px; background: currentColor; opacity: .7; }
  .hlock-line { font-size: 15px; color: rgba(255, 255, 255, .86); }
}

/* ---------- chapter furniture on the wide grounds ------------ */
@media (min-width: 1024px) {
  .statement .inner, .lead .lead-info, .proc .inner, .slab .inner { position: relative; }
  /* an oversized outlined watermark fills the dead right half of the
     statement chapter without adding a single request */
  .statement .inner::after {
    content: "2009";
    position: absolute;
    right: 26%;
    top: 82%;
    translate: 0 0;
    font-family: "Archivo", sans-serif;
    font-weight: 700;
    font-size: clamp(110px, 12vw, 210px);
    line-height: .8;
    letter-spacing: -.05em;
    color: transparent;
    -webkit-text-stroke: 1px var(--line-strong);
    opacity: .16;
    pointer-events: none;
    z-index: 0;
    transform: none;
  }
  .statement .inner > .kicker,
  .statement .inner > h2,
  .statement .inner > .pin-steps { position: relative; z-index: 1; }
  .statement .pin-steps { max-width: 52ch; }

  .chapter-meta {
    position: absolute;
    right: 0;
    bottom: 4px;
    z-index: 1;
    display: grid; /* base rule below hides it under 1024px */
    gap: 18px;
    width: min(24vw, 300px);
    padding-left: 26px;
    border-left: 1px solid var(--line);
  }
  .chapter-meta div { display: grid; gap: 6px; }
  .chapter-meta span {
    font-size: 9.5px;
    letter-spacing: .24em;
    text-transform: uppercase;
    color: var(--ink-3);
  }
  .chapter-meta b { font-size: 14px; font-weight: 600; color: var(--ink-1); letter-spacing: -.01em; }
}

/* ---------- rail refinement ---------------------------------- */
.rail-track i { box-shadow: 0 0 18px color-mix(in oklab, var(--ink-1) 55%, transparent); }
.rail-n { font-variant-numeric: tabular-nums; }

/* ---------- pointer spotlight on the dark grounds ------------ */

/* ---------- masked reveals ----------------------------------- */
.hcc-mask { display: block; overflow: hidden; }
.hcc-mask > i {
  display: block;
  font-style: normal;
  transform: translate3d(0, 105%, 0);
  transition: transform 1.05s var(--hcc-ease);
  transition-delay: calc(var(--i, 0) * .08s);
}
.hcc-mask.in > i { transform: none; }

/* ---------- contact + footer detail ------------------------- */
.email {
  position: relative;
  display: inline-block;
  transition: opacity .4s ease;
}
.email::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -.08em;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform .7s var(--hcc-ease);
}
.email:hover::after { transform: none; }
.fld > span i { font-style: normal; color: var(--ink-1); }

@media (prefers-reduced-motion: reduce) {
  .hcc-mask > i { transform: none; transition: none; }
  .hero-amb { display: none; }
  .hlock-kick, .hlock-title span > i, .hlock-line, .hlock-cta, .hlock-meta { animation: none; }
  .svc-index li { transform: none; transition: none; }
}

/* ============================================================
   PREMIUM PASS 3 — hover states, reveals, focus
   ============================================================ */

/* ---------- smooth anchors ---------------------------------- */
html { scroll-behavior: smooth; }
html.hcc-resetting { scroll-snap-type: none !important; scroll-behavior: auto !important; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* ---------- service index hover previews -------------------- */
@media (min-width: 860.02px) {
  .svc-index {
    position: relative;
    list-style: none;
    padding: 0;
  }
  .svc-index li {
    position: relative;
    cursor: pointer;
    transition: color .35s ease, transform .35s var(--hcc-ease);
  }
  .svc-index li::before {
    content: "";
    position: absolute;
    left: -12px; top: 50%;
    width: 4px; height: 0;
    translate: 0 -50%;
    border-radius: 2px;
    background: var(--ink-1);
    transition: height .4s var(--hcc-ease);
  }
  .svc-index li:hover { color: var(--ink-1); transform: translateX(8px); }
  .svc-index li:hover::before { height: 70%; }
  .svc-index li.active { color: var(--ink-1); }
  .svc-index li.active::before { height: 100%; }

}

/* ---------- text scramble hover ----------------------------- */
.hcc-scramble {
  display: inline-block;
  position: relative;
}
.hcc-scramble .orig, .hcc-scramble .ghost {
  display: inline;
}
.hcc-scramble .ghost {
  position: absolute;
  left: 0; top: 0;
  opacity: 0;
  pointer-events: none;
}

/* ---------- mobile hamburger animation -------------------- */
.burger span {
  display: block;
  width: 22px; height: 2px;
  background: currentColor;
  transition: transform .4s var(--hcc-ease), opacity .25s ease, width .3s ease;
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; width: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

/* ---------- project image reveal mask ---------------------- */
.proj-media figure,
.lead-visual,
.plate-frame,
.plant-visual {
  overflow: hidden;
}
.proj-media figure img,
.lead-visual img,
.plate-frame img,
.plant-visual img {
  transform: scale(1.12);
  transition: transform 1.6s var(--hcc-ease), opacity 1s ease;
}
.proj-media figure.revealed img,
.lead-visual.revealed img,
.plate-frame.revealed img,
.plant-visual.revealed img {
  transform: scale(1);
}

/* ---------- enhanced focus states --------------------------- */
a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

/* ---------- magnetic button refinement ---------------------- */
.cta, .hlock-cta, .enq-send {
  transition: transform .25s var(--hcc-ease), box-shadow .35s ease, color .45s var(--hcc-ease);
}

/* ============================================================
   PASS 4 — Daniel, 4 Sep (phone):
   1. the header never hides on a phone
   2. the projects chapter head stays on screen through every project
   3. no floating "start a conversation" bar (Contact lives in the header)
   4. every snapping page is exactly one small-viewport screen: a swipe
      lands on a whole page, never a half or a quarter
   5. the slab drawing turns in 3D as it assembles
   ============================================================ */
.hcc-bar { display: none !important; }

@media (max-width: 860px) {
  /* 1 — the header stays put */
  html.hcc-nav-hidden #nav { transform: none; }
  #nav { background: color-mix(in oklab, #000 62%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(140%); backdrop-filter: blur(14px) saturate(140%); }
  html.hcc-scrolled #nav { background: rgba(0,0,0,.88); }

  /* 2 — the projects head is sticky under the header for the whole chapter */
  html.stack .proj-head {
    position: sticky; top: 0; z-index: 6;
    padding: calc(92px + env(safe-area-inset-top)) var(--gutter) 12px;
    background: linear-gradient(180deg, #000 0, #000 82%, rgba(0,0,0,0) 100%);
  }
  html.stack .proj-head h2 { display: block; }
  /* every project page after the first clears the sticky head */
  html.stack .proj .blk { padding-top: var(--proj-head-h, 118px); }
  html.stack .proj .proj-steps > .blk:first-child { padding-top: 0; }

  /* 4 — one gesture, one whole page: no snap area may exceed the screen */
  html.stack .blk { height: 100vh; height: 100svh; min-height: 0; overflow: hidden; }
  html.stack .svc { scroll-snap-align: none; }
  html.stack .svc .inner > div:first-child {
    position: sticky; top: 0; z-index: 7;
    min-height: var(--svc-head-h, 138px);
    padding: calc(78px + env(safe-area-inset-top)) var(--gutter) 16px;
    background: #000;
    border-bottom: 1px solid var(--line);
  }
  html.stack .svc .inner > div:first-child .kicker { margin-bottom: 7px; }
  html.stack .svc-h2 { font-size: clamp(21px, 6.2vw, 27px); line-height: 1.02; }
  html.stack .svc .blk {
    height: 100vh; height: 100svh; min-height: 0; overflow: hidden;
    scroll-snap-align: start; scroll-snap-stop: always;
  }
  html.stack .svc .blk > figure { position: absolute; inset: 0; height: 100%; aspect-ratio: auto; }
  html.stack .svc .blk > figure img { width: 100%; height: 100%; object-fit: cover; }
  html.stack .numbers .wrap,
  html.stack .proc .inner { min-height: 0; height: 100vh; height: 100svh; overflow: hidden; }
  html.stack .statement .pin-vp,
  html.stack .lead .pin-vp { overflow: hidden; }
}

/* 5 — the slab chapter keeps its original flat exploded view.
   ⚠ Daniel, 4 Sep: no 3D rotation here. Do not add it back. */

/* ============================================================
   PASS 5 — Daniel, 4 Sep
   1. the hero copy runs on BOTH grounds, and waits for the film
      (nothing over the black-and-H title card)
   2. no "request a quote" button on the film
   3. the process chapter fits its phone page: the head is never clipped
   ============================================================ */

/* 1 — the lockup holds at zero until hcc-plus.js adds .lit */
.hlock { opacity: 0; visibility: hidden; animation: none !important; }
.hlock.lit { visibility: visible; }
.hlock.lit .hlock-kick,
.hlock.lit .hlock-title span > i,
.hlock.lit .hlock-line { animation-play-state: running; }
.hlock { transition: opacity 1.1s var(--hcc-ease); }
.hlock.lit { opacity: 1; }

/* desktop: the same editorial lockup, set low-left over the film */
@media (min-width: 860.02px) {
  .hlock {
    display: block !important;
    position: absolute;
    left: 0; right: auto;
    bottom: clamp(132px, 15vh, 186px);
    padding: 0 clamp(28px, 4.4vw, 78px);
    z-index: 4;
    text-align: left;
    max-width: min(58vw, 780px);
  }
  .hlock-kick {
    display: flex; align-items: center; gap: 12px;
    margin: 0 0 18px;
    font-size: 10px; font-weight: 700;
    letter-spacing: .3em; text-transform: uppercase;
    color: rgba(255, 255, 255, .7);
  }
  .hlock-kick::before { content: ""; width: 42px; height: 1px; background: currentColor; opacity: .75; }
  .hlock-title {
    margin: 0 0 18px;
    font-family: "Archivo", "Inter", system-ui, sans-serif;
    font-weight: 700;
    font-size: clamp(44px, 5.4vw, 88px);
    line-height: .94;
    letter-spacing: -.038em;
    color: #fff;
    text-shadow: 0 18px 60px rgba(0, 0, 0, .45);
  }
  .hlock-title > span { display: block; overflow: hidden; }
  .hlock-title span > i { display: block; font-style: normal; }
  .hlock-title span:nth-child(2) > i { color: rgba(255, 255, 255, .5); }
  .hlock-line {
    margin: 0;
    max-width: 46ch;
    font-size: clamp(14px, 1.05vw, 17px);
    line-height: 1.6;
    color: rgba(255, 255, 255, .8);
  }
  .hero::after {
    content: "";
    position: absolute; inset: auto 0 0 0; height: 56%;
    background: linear-gradient(to top, rgba(0, 0, 0, .72), rgba(0, 0, 0, .26) 48%, transparent);
    pointer-events: none; z-index: 3;
  }
}

/* 2 — the button is gone for good */
.hlock-cta { display: none !important; }

/* 3 — the process chapter is ONE phone page: head plus the five steps,
   the track is no longer its own full-screen snap area (that pushed the
   head off the top of the page and clipped it). */
@media (max-width: 860px) {
  html.stack .proc .inner {
    height: 100vh; height: 100svh; min-height: 0;
    display: flex; flex-direction: column; justify-content: center;
    gap: 14px;
    padding: calc(92px + env(safe-area-inset-top)) var(--gutter) 26px;
    overflow: hidden;
  }
  html.stack .proc-head { flex: 0 0 auto; padding: 0; }
  html.stack .proc-head h2 { font-size: clamp(26px, 7.6vw, 34px); margin: 8px 0 0; }
  html.stack .proc-track.blk {
    height: auto !important; min-height: 0; overflow: visible;
    scroll-snap-align: none; scroll-snap-stop: normal;
    flex: 0 1 auto;
  }
  html.stack .proc-track > div { padding: 9px 0; }
  html.stack .proc-track b { margin: 4px 0 3px; }
}

/* ============================================================
   PASS 6 — per-letter drop-in headings (Daniel, 4 Sep: "each
   letter drops in vertically, doo-doo-doo, like construction").
   Letters are split by hcc-plus.js (which sets html.hcc-ltr);
   the drop fires off the chapter's existing .rv.in / .hlock.lit.
   ============================================================ */

/* section headings: the line itself no longer rises — its letters do.
   overflow:hidden on the line turns each line into a slot the letters
   drop into. */
html.js.hcc-ltr h2.rv .ln {
  opacity: 1;
  transform: none;
  transition: none;
  overflow: hidden;
}
/* ⚠ FIX 4 Sep: no will-change here. It was on every letter of every
   heading (and on every project word and every poured word below): ~800
   spans on the phone, ~1,000 on the desktop, each promoted to its own
   compositor layer for the life of the page. The drop is a one-shot
   transition; the browser promotes it while it runs and releases it after.
   Same motion, a fraction of the GPU memory. */
.hcc-l {
  display: inline-block;
  opacity: 0;
  transform: translateY(-112%);
}
h2.rv.in .hcc-l {
  opacity: 1;
  transform: translateY(0);
  transition:
    transform .58s cubic-bezier(.22, 1.05, .32, 1) calc(var(--li, 0) * .026s),
    opacity .22s linear calc(var(--li, 0) * .026s);
}

/* hero lockup: same pour, released when the film's title card clears */
html.hcc-ltr .hlock.lit .hlock-title span > i,
html.hcc-ltr .hlock .hlock-title span > i {
  animation: none;
  opacity: 1;
  transform: none;
}
html.hcc-ltr .hlock.lit .hlock-title .hcc-l {
  opacity: 1;
  transform: translateY(0);
  transition:
    transform .62s cubic-bezier(.22, 1.05, .32, 1) calc(var(--li, 0) * .034s),
    opacity .22s linear calc(var(--li, 0) * .034s);
}

/* ============================================================
   PASS 7 — Daniel, 4 Sep
   1. no word may break across two lines (letter spans are wrapped
      per word by hcc-plus.js; this keeps the wrapper unbreakable)
   2. the hero lockup never collapses into a vertical column
   3. mobile services: full photographs, ordinary scrolling — no
      full-screen tiles, no blown-up crops
   ============================================================ */

.hcc-wd { display: inline-block; white-space: nowrap; }
h2.rv .ln, .hlock-title span > i {
  overflow-wrap: normal;
  word-break: keep-all;
  hyphens: none;
}
.hcc-l { vertical-align: top; }

/* 2 — the film copy: honest line lengths on every ground */
.hlock-title { text-wrap: balance; }
.hlock-title > span { display: block; }
.hlock-title span > i { display: block; white-space: nowrap; font-style: normal; }
/* ⚠ Daniel, 4 Sep: "STRAIGHT STRUCTURES." was being cut off on the PC.
   The line spans clip (overflow:hidden for the reveal) and the line itself
   is nowrap, so the lockup box has to be wide enough for the LONGEST line
   at the chosen size. 20 characters of Archivo 700 measure ~10.4x the font
   size, so the box is widened and the size is also capped against the
   available width — the words can never run past the crop again. */
@media (min-width: 860.02px) {
  .hlock {
    --hlock-pad: clamp(28px, 4.4vw, 78px);
    --hlock-box: min(88vw, 1320px);
    max-width: var(--hlock-box);
  }
  .hlock-title {
    font-size: min(
      clamp(44px, 5.4vw, 88px),
      calc((var(--hlock-box) - 2 * var(--hlock-pad)) / 12.4)
    );
  }
}
@media (max-width: 860px) {
  .hlock { max-width: none; }
  .hlock-title span > i { white-space: normal; }
  .hlock-title { font-size: clamp(24px, 8.4vw, 40px); line-height: 1.04; }
}


/* 3 — mobile services back to photographs, scrolled normally */
@media (max-width: 860px) {
  html.stack .svc .inner > div:first-child {
    position: static;
    min-height: 0;
    padding: calc(104px + env(safe-area-inset-top)) var(--gutter) 30px;
    border-bottom: 1px solid var(--line);
    background: #000;
  }
  html.stack .svc .inner > div:first-child .kicker { margin-bottom: 10px; }
  html.stack .svc-h2 { font-size: clamp(24px, 7vw, 32px); line-height: 1.04; }
  /* ⚠ Daniel, 4 Sep: with the page on `scroll-snap-type:y mandatory`, a
     chapter with NO snap area inside it gets skipped entirely — one swipe
     flew from About straight past all six disciplines. Each discipline is
     a snap area again (it just isn't a full screen any more), and the
     chapter head is one too, so the chapter cannot be jumped over. */
  html.stack .svc { scroll-snap-align: start; scroll-snap-stop: always; }
  html.stack .svc .inner > div:first-child { scroll-snap-align: start; }
  html.stack .svc .blk {
    height: auto !important;
    min-height: 0;
    overflow: visible;
    scroll-snap-align: start;
    scroll-snap-stop: always;
    margin: 0 0 3px;
  }

  html.stack .svc .blk > figure {
    position: relative;
    inset: auto;
    height: auto;
    aspect-ratio: 4 / 3;
  }
  html.stack .svc .blk > figure img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

/* ============================================================
   PASS 8 — Daniel, 4 Sep
   1. the void between the last discipline and "How we deliver":
      every service tile now DISSOLVES into black at its foot (and
      out of black at its head), the way a project photograph does,
      and the process chapter's content anchors to the top of its
      page instead of floating in the vertical centre — the big
      dead band of black is gone
   2. the Allen machine's bleed was a straight-line ramp and read
      as two hard bands; it now runs the same EASED curve the
      project scrims use (many stops, slow start, soft finish)
   ============================================================ */
@media (max-width: 860px) {
  /* 1a — the tile's foot melts to true black, its head rises out of it */
  html.stack .svc .blk > figure::after {
    background: linear-gradient(180deg,
      rgba(0,0,0,.88) 0,
      rgba(0,0,0,.52) 12%,
      rgba(0,0,0,.42) 26%,
      rgba(0,0,0,.60) 42%,
      rgba(0,0,0,.72) 55%,
      rgba(0,0,0,.60) 68%,
      rgba(0,0,0,.80) 84%,
      #000 100%);
  }
  /* 1b — content to the top of the process page, not the middle */
  html.stack .proc .inner { justify-content: flex-start; }
  html.stack .proc-head { padding-top: clamp(28px, 5vh, 48px); }
}

/* 2 — the eased bleed. Smoothstep-shaped: opaque ground, then long
   soft shoulders. Same feel as .proj-scrim's ramp (0/.55/.92/1). */
@media (max-width: 860px) {
  .plate-feather {
    background: linear-gradient(180deg,
      var(--ground) 0,
      rgba(0,0,0,.92) 5%,
      rgba(0,0,0,.72) 11%,
      rgba(0,0,0,.45) 18%,
      rgba(0,0,0,.22) 25%,
      rgba(0,0,0,.07) 32%,
      rgba(0,0,0,0) 38%,
      rgba(0,0,0,0) 62%,
      rgba(0,0,0,.07) 68%,
      rgba(0,0,0,.22) 75%,
      rgba(0,0,0,.45) 82%,
      rgba(0,0,0,.72) 89%,
      rgba(0,0,0,.92) 95%,
      var(--ground) 100%);
  }
}

/* ============================================================
   PASS 9 — Daniel, 4 Sep
   1. the services chapter head is LOCKED on screen for the whole
      run of the six disciplines (it was made static in pass 7 and
      scrolled away with the first photograph)
   2. every project's words arrive with the same vertical drop the
      headings use — letters for the title, whole words for the
      paragraph, softer and slower than a heading
   ============================================================ */

/* 1 — locked services tile ---------------------------------- */
@media (max-width: 860px) {
  html.stack .svc .inner > div:first-child {
    position: sticky;
    top: 0;
    z-index: 7;
    padding: calc(92px + env(safe-area-inset-top)) var(--gutter) 34px;
    background: linear-gradient(180deg, #000 0, #000 88%, rgba(0,0,0,.92) 96%, rgba(0,0,0,0) 100%);
    border-bottom: 0;
  }
  /* the photographs run underneath it, so each tile clears its height */
  html.stack .svc .blk { margin-top: 0; }
  html.stack .svc .blk > figure { z-index: 1; }
}

/* 2 — project copy: the soft pour ---------------------------- */
.hcc-sl, .hcc-sw { display: inline-block; }
.proj-steps .pin-step .hcc-sl,
.proj-steps .pin-step .hcc-sw {
  opacity: 0;
  transform: translateY(-58%);
}
.proj-steps .pin-step p .hcc-sw { transform: translateY(-34%); }
.proj-steps .pin-step h3, .proj-steps .pin-step p { overflow: hidden; }

.proj-steps .pin-step.on .hcc-sl,
.proj-steps .pin-step.drop-in .hcc-sl {
  opacity: 1;
  transform: translateY(0);
  transition:
    transform .72s cubic-bezier(.2, .9, .24, 1) calc(var(--li, 0) * .022s),
    opacity .34s linear calc(var(--li, 0) * .022s);
}
.proj-steps .pin-step.on p .hcc-sw,
.proj-steps .pin-step.drop-in p .hcc-sw {
  opacity: 1;
  transform: translateY(0);
  transition:
    transform .8s cubic-bezier(.2, .9, .24, 1) calc(.22s + var(--li, 0) * .028s),
    opacity .46s linear calc(.22s + var(--li, 0) * .028s);
}

@media (prefers-reduced-motion: reduce) {
  .proj-steps .pin-step .hcc-sl,
  .proj-steps .pin-step .hcc-sw { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   PASS 10 — Daniel, 4 Sep: the plant chapter HOLDS on a wide
   ground. Two paragraphs, two beats: the first line of copy sits
   on its own, the second replaces it, and only then does the page
   release to the contact chapter. Phone is untouched (it snaps).
   ============================================================ */
@media (min-width: 860.02px) {
  html.js .plate-pin { position: relative; height: 300vh; }
  html.js .plate-pin > .plate {
    position: sticky; top: 0;
    height: 100vh; min-height: 100vh;
  }
  html.js .plate-pin .plate-steps { position: relative; display: grid; }
  html.js .plate-pin .plate-steps > p {
    grid-area: 1 / 1;
    margin-top: 0;
    opacity: 0;
    visibility: hidden;
    transform: translateY(22px);
    transition: opacity .45s linear, transform .8s cubic-bezier(.2,.9,.24,1), visibility 0s linear .8s;
  }
  html.js .plate-pin .plate-steps > p.on {
    opacity: 1; visibility: visible; transform: none;
    transition: opacity .5s linear .06s, transform .8s cubic-bezier(.2,.9,.24,1) .06s, visibility 0s;
  }
  /* the beat counter beside the copy */
  html.js .plate-pin .plate-beats {
    display: flex; gap: 8px; margin-top: clamp(22px, 2.4vw, 34px);
  }
  html.js .plate-pin .plate-beats i {
    width: 26px; height: 2px; background: rgba(255,255,255,.22);
    transition: background .4s ease;
  }
  html.js .plate-pin .plate-beats i.on { background: rgba(255,255,255,.85); }
}
@media (prefers-reduced-motion: reduce) {
  html.js .plate-pin { height: auto; }
  html.js .plate-pin > .plate { position: static; height: auto; }
  html.js .plate-pin .plate-steps > p { opacity: 1; visibility: visible; transform: none; }
  html.js .plate-pin .plate-beats { display: none; }
}

/* ============================================================
   PASS 11 — Daniel, 4 Sep, chose direction A from /hold-demo:
   "pour in, then release". On a wide ground the About, Leadership
   and plant chapters no longer STEP one paragraph at a time —
   all the copy sits on the page at once and pours in word by word
   as the chapter is held, then the page releases. The phone is
   untouched (it scrolls/snaps as before).
   ============================================================ */
@media (min-width: 860.02px) {
  html.js .pin.pour-hold .pin-steps { display: block; min-height: 0; }
  html.js.pin-on .pin.pour-hold .pin-step,
  html.js .pin.pour-hold .pin-step {
    position: static; grid-area: auto;
    opacity: 1; visibility: visible; transform: none; translate: none;
  }
  html.js.pin-on .pin.pour-hold .pin-step.on ~ .pin-step { transform: none; }
  html.js.pin-on .pin.pour-hold .pin-step + .pin-step,
  html.js .pin.pour-hold .pin-step + .pin-step { margin-top: clamp(14px, 1.3vw, 24px); }
  html.js .pin.pour-hold .pin-step p { font-size: clamp(15px, 1.06vw, 19px); line-height: 1.62; }
  html.js .pin.pour-hold [data-count] { opacity: 0; }

  /* About carries three long paragraphs: set them in two columns so the
     whole chapter still sits inside one held screen instead of running
     off the foot of it. */
  html.js .statement.pour-hold .pin-steps {
    columns: 2; column-gap: clamp(28px, 3vw, 56px);
    max-width: min(900px, 64vw);
    margin-top: clamp(24px, 2.4vw, 40px);
  }
  html.js .statement.pour-hold .pin-step { break-inside: avoid; }
  html.js .statement.pour-hold .pin-step + .pin-step { margin-top: clamp(12px, 1vw, 18px); }

  /* the plant chapter: both paragraphs at once, poured */
  html.js .plate-pin.pour-hold .plate-steps { display: block; }
  html.js .plate-pin.pour-hold .plate-steps > p {
    grid-area: auto; position: static;
    opacity: 1; visibility: visible; transform: none; transition: none;
  }
  html.js .plate-pin.pour-hold .plate-steps > p + p { margin-top: clamp(12px, 1.1vw, 20px); }
  html.js .plate-pin.pour-hold .plate-beats { display: none; }

  /* the word itself */
  html.js .pour-hold .pw {
    display: inline-block;
    opacity: .14;
    transform: translateY(.3em);
    transition: opacity .5s ease, transform .6s cubic-bezier(.2,.9,.24,1);
  }
  html.js .pour-hold .pw.lit { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  html.js .pour-hold .pw { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   PASS 13 — Daniel, 4 Sep
   1. About is ONE screen again on the phone, copy trimmed to fit
   2. every discipline tile is the same size, formwork included
   ============================================================ */
@media (max-width: 860px) {
  html.stack .pin.statement .pin-vp {
    box-sizing: border-box;
    min-height: 100vh;
    min-height: 100svh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    padding-top: calc(88px + env(safe-area-inset-top));
    padding-bottom: clamp(28px, 7vh, 56px);
    scroll-snap-align: start;
    scroll-snap-stop: always;
  }
  html.stack .statement .inner { width: 100%; }
  html.stack .pin.statement .pin-steps { margin-top: clamp(16px, 3.4vw, 24px); }
  html.stack .pin.statement .pin-steps > .pin-step + .pin-step { margin-top: clamp(14px, 3vw, 22px); }
  html.stack .pin.statement .pin-step .lede {
    font-size: clamp(13.5px, 3.5vw, 16px);
    line-height: 1.5;
  }
  html.stack .statement h2 { margin-bottom: 0; }
}

/* ============================================================
   PASS 14 — 4 Sep, bug pass (Claude). No design changes.
   1. phone services: the tile a swipe lands on sat UNDER the sticky
      chapter head with its title hidden (the head is ~200px tall and
      every tile snaps to the top of the screen). scroll-margin-top
      moves each tile's snap point down by the head's measured height
      (--svc-head-h, written by hcc.js), so the tile lands just below
      the head, whole. Same head, same tiles, same snapping.
   2. phone process on a short screen (iPhone SE, or an iPhone with both
      Safari bars showing, 553-667px): the fifth row ran 19px past the
      clipped page. Padding is trimmed under 700px tall; the copy is
      unchanged.
   3. the About credential column is a wide-ground element; a window
      resized under 1024px after load used to show it unstyled.
   ============================================================ */
@media (max-width: 860px) {
  html.stack .svc .blk { scroll-margin-top: var(--svc-head-h, 200px); }
  /* 4. LOCKED PAGES (Daniel, 4 Sep, from his phone). Every full-page block
     is exactly the visible screen at rest: --page-h is the measured
     innerHeight, written by hcc.js only when the scroll is still (see the
     note there). 100svh was ~190px short once Safari's bars collapsed, so
     the next project peeked in and a drag moved inside the gap. */
  html.stack .proj .blk { height: var(--page-h, 100svh); }
  html.stack .proj .proj-steps > .blk:first-child { height: calc(var(--page-h, 100svh) - var(--proj-head-h, 118px)); }
  html.stack .numbers .wrap,
  html.stack .proc .inner { height: var(--page-h, 100svh); }
}
@media (max-width: 860px) and (max-height: 700px) {
  /* the kicker must clear the fixed header (~76px with the button): 84 + 4 */
  html.stack .proc .inner { padding-top: calc(84px + env(safe-area-inset-top)); padding-bottom: 14px; gap: 6px; }
  html.stack .proc-head { padding-top: 4px; padding-bottom: 4px; }
  html.stack .proc-track > div { padding: 7px 0; }
}
@media (max-width: 1023.98px) {
  .chapter-meta { display: none; }
}
/* 2b. below 600px tall (an iPhone SE with both Safari bars is 553px) the five
   rows do not fit one screen at any honest size: the page is allowed to run
   its natural height and the reader scrolls the last rows in, instead of
   losing them behind overflow:hidden. Snapping still lands on its top. */
@media (max-width: 860px) and (max-height: 600px) {
  html.stack .proc .inner { height: auto; min-height: 100vh; min-height: 100svh; overflow: visible; }
}

/* ============================================================
   PASS 15 — CREDENTIALS (10 Sep 2026). The compliance half of a
   capability statement, mirroring the one-page PDF exactly. No new
   visual language: the site's own hairline grid, kicker, --d2 heading
   and .cta, so it reads as another chapter rather than a bolt-on.
   ⚠ If a line changes here it must change in the PDF too
   (capstat/build.py), or the two contradict each other in front of a
   client.
   ============================================================ */
/* ============================================================
   PASS 22 — CREDENTIALS IS A PINNED HOLD.
   Daniel, 13 Sep: "the paperwork before the pour, I wanted to lock on that
   screen as well and have its own scroll, maybe like two scrolls, goes down
   into our own machines. Make sure you have that scroll line at the bottom of
   that credential section as well."

   ⚠ IT IS THE BY-THE-NUMBERS PATTERN, NOT A NEW ONE: `.pin` + `data-hold` with
   NO `data-steps`. The whole band stays on screen and the hold exists so the
   four columns land in sequence and the download button is read rather than
   scrolled past. See the note at MECHANIC 1 in hcc.js: "a chapter may HOLD
   WITHOUT STEPPING".

   ⚠ THE RELEASE GUARD IS THE POINT OF THIS BLOCK. `.cred-grid` drops to two
   columns at 1080px, which doubles the band's height and it no longer fits a
   screen; a short screen fails for the same reason. Pinning a frame taller
   than the viewport is how content ends up under the nav and behind the rail.
   Released there, exactly as `.numbers` releases, and the rail goes with it.
   ============================================================ */
/* ⚠ THE FRAME MUST BE TOLD WHAT IT MAY NOT USE. The nav sits over the top and
   the rail over the bottom, so the band is given both back as padding. Without
   this the download button finished 36 to 43px INSIDE the rail at 1366x768,
   1280x720 and 1100x720 - measured, not guessed. Same treatment the slab
   chapter used on its own frame. */
html.js.pin-on .creds .pin-vp {
  align-content: center;
  /* ⚠ the nav hides on scroll-down, so the top reserve only has to cover it
     reappearing on a scroll-up; 8.6vh was over-generous and cost 26px of
     frame at 1536x864, which was the tightest size on the site. */
  padding-block: clamp(58px, 6.8vh, 96px) clamp(54px, 6.6vh, 88px);
}
/* ⚠⚠ THE BAND OUTGREW THE FRAME AND THE CAUSE WAS WIDTH, NOT HEIGHT.
   Every vertical measure in this chapter is a vw clamp, so the content gets
   TALLER as the screen gets WIDER while a pinned frame only grows with height.
   Measured content against available frame before any of this:
       1920x1080  966 in 903  (-64)
       1600x900   874 in 752  (-121)
       1536x864   843 in 722  (-121)
       1440x900   778 in 752  (-26)
   So nothing fitted, not even the most generous desktop. These four rules cap
   the growth IN THE PINNED STATE ONLY - the released layout below 1080px or
   821px tall is untouched and still reads at full size. Re-run
   /tmp/creds4.mjs after any change to this chapter's spacing. */
html.js.pin-on .creds h2 { font-size: clamp(32px, 3.4vw, 62px); max-width: 15em; }
html.js.pin-on .creds .cred-grid { margin-top: clamp(18px, 1.7vw, 28px); }
html.js.pin-on .creds .cred-grid > div { padding: clamp(16px, 1.4vw, 22px) clamp(20px, 1.8vw, 32px); }
html.js.pin-on .creds .cred-foot { margin-top: clamp(16px, 1.6vw, 26px); }

/* ⚠ 820px, not 700px. The band needs about 800px of frame once the nav and the
   rail are reserved; at 768 and 720 tall it collided with the rail even with
   the padding above, so those heights release instead of squeezing. */
@media (max-width: 1080px), (max-height: 820px) {
  html.js.pin-on .creds { height: auto; }
  html.js.pin-on .creds .pin-vp { position: static; height: auto; min-height: 0; overflow: visible; display: block; }
  html.js.pin-on .creds .rail { display: none; }
}

.creds .wrap { max-width: var(--max); }
.creds h2 { font-size: var(--d2); max-width: 16em; }
.creds h2 .dim { color: var(--ink-3); }

.cred-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 1px;
  margin: clamp(30px, 3.4vw, 58px) 0 0;
  background: var(--line);
  border-block: 1px solid var(--line);
}
.cred-grid > div { background: var(--ground); padding: clamp(24px, 2.4vw, 40px) clamp(20px, 1.8vw, 32px); }
.cred-grid dt {
  font-size: 10px; font-weight: 600; letter-spacing: var(--track-label);
  text-transform: uppercase; color: var(--ink-3);
  margin-bottom: clamp(14px, 1.4vw, 22px);
}
.cred-grid dd { margin: 0; }
.cred-grid dd b {
  display: block; font-weight: 500; color: var(--ink-1);
  font-size: clamp(13.5px, .95vw, 16px); line-height: 1.45;
}
.cred-grid dd b + b { margin-top: 8px; }
.cred-grid dd span {
  display: block; margin-top: 10px;
  font-size: clamp(12px, .82vw, 13.5px); line-height: 1.55; color: var(--ink-3);
}
.cred-grid dd b + span { margin-top: 14px; }

.cred-foot {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 18px 32px;
  margin-top: clamp(28px, 3vw, 48px);
}
.cred-foot p { font-size: clamp(14px, 1vw, 18px); line-height: 1.6; color: var(--ink-2); max-width: 34em; margin: 0; }
.cred-foot .cta {
  display: inline-flex; align-items: center; gap: 14px;
  background: var(--ink-1); color: var(--ground);
  padding: 17px 28px; text-decoration: none;
  font-size: 11px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase;
}
.cred-foot .cta span { transition: transform .4s var(--hcc-ease); }
.cred-foot .cta:hover span { transform: translateY(3px); }

@media (max-width: 1080px) { .cred-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* phone: its own snapping page like every other chapter, two columns, tight */
@media (max-width: 860px) {
  html.stack .creds .wrap {
    box-sizing: border-box; min-height: 100vh; min-height: var(--page-h, 100svh);
    display: flex; flex-direction: column; justify-content: center;
    padding: calc(92px + env(safe-area-inset-top)) var(--gutter) clamp(26px, 6vh, 48px);
    max-width: none;
    scroll-snap-align: start; scroll-snap-stop: always;
  }
  html.stack .creds h2 { font-size: clamp(24px, 7vw, 32px); max-width: none; }
  html.stack .cred-grid { margin-top: clamp(18px, 3.4vw, 26px); border: 1px solid var(--line); }
  html.stack .cred-grid > div { padding: 13px 12px 14px; }
  html.stack .cred-grid dt { font-size: 8.5px; letter-spacing: .12em; margin-bottom: 8px; }
  html.stack .cred-grid dd b { font-size: 11.5px; line-height: 1.35; }
  html.stack .cred-grid dd b + b { margin-top: 5px; }
  html.stack .cred-grid dd span { margin-top: 7px; font-size: 10px; line-height: 1.4; }
  html.stack .cred-grid dd b + span { margin-top: 8px; }
  html.stack .cred-foot { margin-top: clamp(16px, 3vh, 26px); flex-direction: column; align-items: stretch; gap: 14px; }
  html.stack .cred-foot p { font-size: 13px; }
  html.stack .cred-foot .cta { justify-content: space-between; padding: 15px 20px; font-size: 10px; }
}
@media (max-width: 860px) and (max-height: 700px) {
  html.stack .creds .wrap { padding-top: calc(80px + env(safe-area-inset-top)); }
  html.stack .cred-grid > div { padding: 10px 10px 11px; }
  html.stack .cred-grid dd b { font-size: 11px; }
  html.stack .cred-foot p { display: none; }
}
/* an iPhone SE with both Safari bars is 553px: the download button is the point
   of this chapter, so it may never fall below the fold */
@media (max-width: 860px) and (max-height: 600px) {
  html.stack .creds .wrap { padding-top: calc(70px + env(safe-area-inset-top)); padding-bottom: 18px; }
  html.stack .creds .kicker { margin-bottom: 10px; }
  html.stack .creds h2 { font-size: clamp(18px, 5.2vw, 22px); }
  html.stack .cred-grid { margin-top: 12px; }
  html.stack .cred-grid > div { padding: 8px 9px 9px; }
  html.stack .cred-grid dt { font-size: 8px; margin-bottom: 5px; }
  html.stack .cred-grid dd b { font-size: 10.2px; line-height: 1.3; }
  html.stack .cred-grid dd b + b { margin-top: 3px; }
  html.stack .cred-grid dd span { margin-top: 5px; font-size: 9px; line-height: 1.35; }
  html.stack .cred-grid dd b + span { margin-top: 6px; }
  html.stack .cred-foot { margin-top: 12px; }
  html.stack .cred-foot .cta { padding: 12px 16px; font-size: 9.5px; }
}

/* ============================================================
   PASS 16 - PROJECT LOCATIONS (Daniel, 10 Sep). Every one of the eleven
   projects now names a suburb and state. This is the moment the site started
   identifying jobs: a sector plus a suburb names the project. Daniel took
   that decision knowingly, having already named the rink and its builder on
   LinkedIn in August. Read claude/project-locations-and-act.md before
   removing or extending it - the Canberra/ACT wording in particular was a
   deliberate call, not a default.

   ⚠⚠ THIS IS THE REVERTED VERSION AND THAT IS ON PURPOSE.
   Daniel asked for the location to sit on the photograph itself, three
   attempts were built, and on 10 Sep he stopped it: "revert back to before
   you started putting locations on the photos. then, show me at least 5
   examples on how you can do this on mobile and PC." So the shipped pack is
   back to the first version, which he had approved: the suburb is a kicker
   in the copy, above the project title, on both screens. Nothing is painted
   on the picture.
   The three on-photograph attempts and their failure modes (a radial
   gradient that showed its corners, drop-shadows too thin to read, a
   floating pool that read as a smudge) are recorded in that same project
   doc, with the measurements. Do not rebuild any of them from memory.

   ⚠ <span class="proj-loc"> IS STILL IN index.html INSIDE EACH FIGURE and is
   hidden here. It is aria-hidden, it costs nothing, and it is the hook any
   on-photograph option would switch back on. Do not delete it, and if a
   suburb ever changes, change it in BOTH places - the span and the step's
   data-loc - or the two will disagree the day one of them is switched on.
   ============================================================ */
.proj figure .proj-loc { display: none; }

/* The kicker. Same language as every other label on the site: small, tracked
   uppercase, --ink-3, sitting above the thing it names. On the wide ground
   .pin-step is a flex column of just the H3 and the P, so ::before lands
   exactly where it should with no help. */
.proj-steps .pin-step[data-loc]::before {
  content: attr(data-loc);
  display: block;
  font-size: 11px;
  font-weight: 500;
  letter-spacing: var(--track-label);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: clamp(9px, .9vw, 14px);
}

@media (max-width: 860px) {
  /* ⚠ ORDER IS LOAD-BEARING ON THE PHONE. hcc.js rebuilds each step into a
     flex column of [figure, .blk-copy, .blk-rail], and generated content
     always paints before every child - so without these three lines the
     suburb prints ABOVE the photograph, adrift at the top of the screen,
     instead of above the title. It is a flex item here, so it can be
     reordered into the copy, and it carries the copy's own side padding
     because it is a sibling of .blk-copy, not a child of it. */
  html.stack .proj .pin-step[data-loc]::before {
    order: 1;
    flex: none;
    padding: 16px var(--gutter) 0;
    margin-bottom: 0;
  }
  html.stack .proj .blk > figure { order: 0; }
  html.stack .proj .blk-copy { order: 2; padding-top: 8px; }
  html.stack .proj .blk-rail { order: 3; }

  /* ⚠ KEPT FROM THE LOCATION WORK ON PURPOSE, and Daniel was told. It has
     nothing to do with locations. This band was solid black for
     `100lvh - 100svh` plus 96px, to cover the strip a collapsing Safari bar
     could expose back when every block was sized to 100svh. Blocks are now
     the measured visible height (--page-h, pass 14) inside overflow:hidden,
     so nothing can peek and nothing needs covering - but the rule stayed,
     and on a real iPhone with both bars showing that term is about 190px,
     which meant the top 286px of every project photograph was painted out.
     Reduced to a slim fade that keeps the soft top edge and gives the
     photograph back. */
  html.stack .proj .blk > figure::before {
    height: 58px;
    background: linear-gradient(180deg, rgba(0,0,0,.55) 0, rgba(0,0,0,.22) 46%, rgba(0,0,0,0) 100%);
  }
}

/* ============================================================
   ⚠⚠ THE LINE THE KICKER COSTS. THIS IS NOT A STYLE PREFERENCE.
   Putting the suburb in the copy adds a line to every project page, and the
   longest paragraph on the site is Civic & Community Buildings. Measured
   with this block removed, its text ran UNDER the progress rail by 15px at
   393x660, 13px at 390x664 and 26px at 360x640 - all real iPhones with both
   Safari bars showing - and Aged Care cleared it by 1px at 360x640, which is
   not clearance. With it: every one of the eleven clears at every size.
   ⚠ These rules were briefly deleted on 10 Sep along with the on-photograph
   experiment, because they had been written during it. That was wrong: they
   belong to the KICKER, not to the overlay, and the overlay is the version
   that does not need them. If an on-photograph option is ever adopted the
   copy gets its line back and these can go - but not before, and check
   Civic at 360x640 either way.
   ⚠ The 700px ceiling is deliberate and is not the 600px used elsewhere in
   this file. The collision starts between the two.
   ============================================================ */
@media (max-width: 860px) and (max-height: 700px) {
  html.stack .proj-steps h3 { font-size: clamp(20px, 5.6vw, 24px); margin-bottom: 10px; }
  html.stack .proj .blk p { font-size: 14px; line-height: 1.58; }
  html.stack .proj .pin-step[data-loc]::before { padding-top: 12px; }
  html.stack .proj .blk-copy { padding-top: 6px; }
}

/* ============================================================
   PASS 17 — NOT TAKEN. THE SERVICES HEADING STAYS OFF ON A WIDE SCREEN.
   Daniel, 10 Sep, first: "on the services page on PC, you're missing the what
   we do six disciplines heading just like the mobile. that's a pretty big
   deal." Then, on being shown that hcc.css hides it deliberately and the
   markup says so ("phone only: the desktop has the index list as its
   heading"): "ok then dont adjust the services heading if thats per design."

   ⚠ IT WAS BUILT, MEASURED AND THEN REMOVED, so nobody has to do that work
   again to find out whether it fits. It does:
     clamp(28px, 3.2vw, 48px), max-width 13em, .dim on the second line;
     column height 573px in a 900px pinned frame, 438 in 700, centred, and
     the six-item index list stays fully inside the frame at 1440x900,
     1920x1080, 1280x800, 1024x768 and 900x700. No wrap at any of them.
   The size has to be clamped to the COLUMN, not to --d2: the column is 445px
   at 1440 and 347px at 1024, "Six disciplines." is fifteen characters, so the
   ceiling is roughly column width / 8.4.

   ⚠ Position recorded for whoever asks next, because it will come up again:
   the six disciplines are NOT missing from the PC, they are the index list.
   What is missing is the sentence naming them, and Services is the only
   chapter on the site that opens without one. That is a real inconsistency,
   and the reason it is not fixed here is that Daniel chose the existing
   design once he knew it was a design. It is one uncommented rule away.
   ============================================================ */

/* ============================================================
   PASS 18 — "OUR OWN MACHINES. OUR OWN STANDARD." WAS WRAPPING.
   Daniel, 10 Sep: "OUR OWN MACHINES OUR OWN STANDARD - the text is a tad too
   big and loud."

   ⚠ IT IS NOT OVERSIZED RELATIVE TO THE SITE. Measured, it renders at 70px at
   1440 where the statement, process and credentials headings are all 72px, and
   on a phone it is the SMALLEST heading on the site (25px against 34px). Left
   there, "make it smaller" would have broken the one scale every chapter
   shares. The fault is elsewhere.

   ⚠ THE FAULT IS THAT EACH LINE NO LONGER FITS ON A LINE. The chapter is built
   on two sentences, one per .ln span, and the desktop rule sized the type as
   `(--cend - 26px - gutter) / 7.7` - room for about 7.7 characters. "Our own
   machines." is seventeen. So each sentence wrapped in half and the heading
   came out as FOUR lines of 70px type filling the whole column, with the body
   copy pushed hard up under it. That reads as shouting because it is: the
   words are the same, the block is twice the height it was drawn to be.
   The phone rule uses 14.3 for the same string and has always been fine, which
   is the proof that 7.7 was simply wrong rather than a deliberate crop.

   11.2 gives the seventeen characters their line with air to spare (about
   10.5em is the true requirement in this face at this weight), and the heading
   lands near 48px at 1440. Smaller than its neighbours, deliberately: it is
   the only chapter statement set inside a half-width column rather than across
   the page, so matching their point size was never matching their presence.
   ⚠ If the sentences are ever rewritten, re-check the divisor against the
   LONGEST of them, and check that .ln reports one client rect, not two.
   ============================================================ */
@media (min-width: 860.02px) {
  .plate-head h2 {
    font-size: min(var(--d2), calc((var(--cend) - 26px - var(--gutter)) / 11.8));
    /* ⚠ 11.8, NOT 11.2. The 11.2 note above was measured with the FALLBACK font
       (Google Fonts is blocked in the build container). With the real Archivo at
       wdth 104, 11.2 still broke "OUR OWN STANDARD." onto a third line at every
       desktop width. 11.8 is the smallest divisor that holds two, 1100-2560. */
    line-height: 1.06;
  }
  /* ⚠ THE AIR GOES ON .plate-steps, NOT ON THE PARAGRAPHS. When the chapter
     is pinned, .plate-steps becomes a grid and all three paragraphs are
     stacked in the SAME cell so they can cross-fade, so a margin-top on the
     paragraph moves all three together and the gap under the heading stays
     at zero. Measured: 0px before this, which is why the copy read as welded
     to the heading. */
  .plate-head .plate-steps { margin-top: clamp(26px, 2.6vw, 40px); }
}

/* ============================================================
   PASS 19 — THE ONE LINK OUT OF THE SINGLE PAGE.
   Daniel, 10 Sep: "THE CASE STUDY IS NICE! LOVE IT. IMPLEMENT IT."

   ⚠ THIS IS THE SECOND TIME A CASE STUDY HAS BEEN LINKED FROM THIS SITE. The
   first, `project-ice-rink.html`, was removed on 24 Aug at his instruction
   ("remove the case study all together please") and every trace came out of
   index.html: the @view-transition rules, #featured-rink, the
   view-transition-name, .project a.visual and .cs-link styles, and the link
   wrapper on the photograph. See claude/ice-rink-case-study-source.md. What is
   here now is deliberately far less than that was: no view transition, no
   photograph wrapped in an anchor, no featured treatment on the row. One text
   link, on one project, in the site's own kicker language.

   ⚠ IT SITS ON THE RINK STEP ONLY, and there is no styling that would let a
   second one appear by accident: the anchor has to be written into a step.
   If another case study is ever built, add the anchor to that step; do not
   generalise this rule into something that decorates every project.

   ⚠ On a phone hcc.js's wrapCopy moves every child of the step into .blk-copy,
   so the link travels with the words and needs no phone-specific placement.
   What it DOES need is the height it costs: the rink is the FIRST project, and
   the first block is shorter than the rest by --proj-head-h because it shares
   its page with the chapter head. Checked against the progress rail at 393x852,
   393x660, 390x664, 375x553 and 360x640 after adding it.

   ⚠⚠ REVISED 10 SEP: IT IS A BUTTON NOW, NOT A TEXT LINK.
   Daniel: "in the actual website, how you can click on the case study in the
   ice rink project section, can you make that more obvious to people to click
   on it? I want it to really stand out." The restrained text link above was
   his own earlier instinct and he has overruled it.

   ⚠ It is deliberately NOT a new button design. It copies `.cred-foot .cta`
   exactly (solid --ink-1 ground, --ground text, 11px/700/.2em uppercase, the
   arrow animating on hover), so the strongest thing on the Projects chapter is
   still the site's own language rather than a bolt-on. If .cred-foot .cta is
   ever restyled, restyle this with it.

   ⚠ WHAT IT COSTS IN HEIGHT: a text link was ~32-41px, this is ~45-56px. The
   rink is the FIRST project step and the shortest, so that height comes off
   the tightest block on the page. Re-verified at 393x852, 393x660, 390x664,
   375x553 and 360x640 against the progress rail. Do not enlarge it further
   without re-running that check.

   ⚠ THE PULSE FIRES TWICE AND STOPS. It is an attention cue on arrival, not a
   permanently animating element, and it is off entirely under
   prefers-reduced-motion.
   ============================================================ */
.proj-steps .cs-link {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 14px;
  margin-top: clamp(15px, 1.7vw, 24px);
  padding: 15px 24px;
  background: var(--ink-1);
  color: var(--ground);
  font-family: inherit;
  font-weight: 700;
  font-size: 11px;
  letter-spacing: .2em;
  text-transform: uppercase;
  text-decoration: none;
  border-bottom: 0;
  transition: transform .35s var(--hcc-ease), box-shadow .35s var(--hcc-ease);
}
.proj-steps .cs-link span {
  font-size: 13px;
  line-height: 0;
  transform: translate3d(0, -.5px, 0);
  transition: transform .4s var(--hcc-ease);
}
.proj-steps .cs-link:hover,
.proj-steps .cs-link:focus-visible { transform: translate3d(0, -2px, 0); }
.proj-steps .cs-link:hover span,
.proj-steps .cs-link:focus-visible span { transform: translate3d(5px, -.5px, 0); }
.proj-steps .cs-link:focus-visible { outline: 2px solid var(--ink-1); outline-offset: 4px; }

/* the arrival cue: a ring that opens out of the button twice, then stops */
.proj-steps .cs-link::after {
  content: "";
  position: absolute;
  inset: 0;
  border: 1px solid var(--ink-1);
  opacity: 0;
  pointer-events: none;
}
.proj-steps .pin-step.on .cs-link::after {
  animation: csRing 1.9s var(--hcc-ease) .45s 2 both;
}
@keyframes csRing {
  0%   { opacity: .85; transform: scale(1); }
  70%  { opacity: 0;   transform: scale(1.18); }
  100% { opacity: 0;   transform: scale(1.18); }
}
@media (prefers-reduced-motion: reduce) {
  .proj-steps .cs-link,
  .proj-steps .cs-link span { transition: none; }
  .proj-steps .pin-step.on .cs-link::after { animation: none; }
}
@media (max-width: 860px) {
  html.stack .proj-steps .cs-link { margin-top: 14px; padding: 13px 20px; font-size: 10px; gap: 12px; }
}
/* ⚠ SHORT PHONES ONLY, AND IT IS NOT COSMETIC. The rink is the first project
   block and the shortest, and .blk-copy reserves 64px at its foot for the
   .blk-rail progress row. At 375x553 the button as sized above finished 14px
   INTO the rail's own line. Measured, not guessed: button bottom 518 against a
   rail line at ~510. These numbers put it back at ~504. If the button, the
   paragraph or the rail gutter ever changes, re-run /tmp/rail4.mjs. */
@media (max-width: 860px) and (max-height: 620px) {
  html.stack .proj-steps .cs-link { margin-top: 8px; padding: 9px 16px; font-size: 9.5px; gap: 10px; }
}

/* ============================================================
   PASS 20 — THE STATEMENT AND LEADERSHIP COPY FALLS IN AS ONE.
   Daniel, 10 Sep: "text pops up, text goes back. I don't want that ... I want
   all the text to fall in the page ... from below all at once, nice and
   softly, leaving the heading as is."
   The mechanism, and why only these two chapters, is written out in full at
   MECHANIC 2a in hcc.js. Read that before changing anything here.

   ⚠ WIDE SCREENS ONLY. On a phone these chapters are already ordinary stacked
   blocks with no pin and no swapping, so there is nothing to fix and this must
   not reach them.
   ⚠ The pin engine keeps adding and removing .on all through the run. Every
   rule below exists to make .on mean nothing visual here, so the copy cannot
   flicker as the rail counts. If you add a rule that reads .on in a .fall
   chapter, you have put the flicker back.
   ============================================================ */
@media (min-width: 860.02px) {
  html.js.pin-on .fall .pin-steps { display: block; min-height: 0; }
  html.js.pin-on .fall .pin-step {
    grid-area: auto;
    position: static;
    display: block;
    visibility: visible;
    opacity: 0;
    transform: translateY(26px);
    transition: opacity .72s var(--ease), transform .72s var(--ease);
    transition-delay: calc(var(--i, 0) * 90ms);
  }
  html.js.pin-on .fall .pin-steps.in .pin-step { opacity: 1; transform: none; }
  html.js.pin-on .fall .pin-step + .pin-step { margin-top: clamp(18px, 1.7vw, 30px); }
  /* .on and the sibling rule that trailed steps 26px below: both neutralised */
  html.js.pin-on .fall .pin-step.on,
  html.js.pin-on .fall .pin-step.on ~ .pin-step { transform: none; }
  html.js.pin-on .fall .pin-steps.in .pin-step.on ~ .pin-step { opacity: 1; }
  /* the stacked grid needed reserved height; flowed copy sets its own */
  html.js.pin-on .fall.statement .pin-steps,
  html.js.pin-on .fall .lead-steps { min-height: 0; }
}
@media (prefers-reduced-motion: reduce) {
  html.js.pin-on .fall .pin-step { opacity: 1; transform: none; transition: none; }
}

/* ============================================================
   PASS 21 — A RAIL UNDER THE PLANT CHAPTER, AND CREDENTIALS MOVED.
   Daniel, 10 Sep: "on our own machines, our standard, make sure you put a
   scroll line underneath that as well because you don't have anything before
   it goes into let's pour something permanent."

   ⚠ THE PLANT CHAPTER IS NOT A PIN, which is why it never had one. Every
   other rail is drawn by `html.js.pin-on .pin .rail{display:flex}` and driven
   by the pin engine's own run maths. This one is a plain section, so it needs
   both halves written out: the display rule here, and a small driver in
   hcc-plus.js (search plate-rail) that maps scroll through the section onto
   the track width. It carries no "1 / 3" counter because there are no steps
   to count; the right-hand label reads "Company owned" instead.
   ⚠ Wide screens only. On a phone the chapter is an ordinary block with the
   photograph below the words and nothing to track.
   ============================================================ */
@media (min-width: 860.02px) {
  html.js .plate { position: relative; }
  html.js .plate .plate-rail { display: flex; bottom: 38px; }  /* same seat as every other rail (hcc.css .rail) - was 11px low */
}

/* ⚠ CREDENTIALS NOW SITS BETWEEN PROJECTS AND THE PLANT CHAPTER, not after
   both of them. Daniel, 10 Sep: "I can put that just after projects, then
   have company owned machines after that, so it splits up the photo
   sections." Projects and Plant are the two most photographic chapters on
   the site and they used to run back to back. The move is in index.html;
   nothing here depends on the order, but if a future ground-colour rule ever
   assumes an alternating dark/light run, note that Plant and Contact are now
   both data-ground="light" back to back. */

/* ============================================================
   PASS 23 — CONTACT CLOSES IN TWO COLUMNS ON A WIDE SCREEN.
   Before this the close was a single column: the display line, the email,
   the meta, and then the whole form underneath it, so the last screen of
   the site was mostly empty dark on the right and the form only arrived
   after a scroll. Now, from 1280px up, the words sit left and the form sits
   right, and the whole close fits one screen at 1440x900 (section ~1030px
   instead of ~1500px). Under 1280px nothing changes.
   ⚠ The display line is sized by the COLUMN, not the viewport: the widest
   word is "PERMANENT." and it must not wrap or clip. The calc below is the
   left column's width (the .inner max-width, minus the two gutters, minus
   the column gap, split 1 : 1.15) divided by 15.1, which is the measured
   width of "PERMANENT." in Archivo 700 per px of font-size, with a little
   air. Checked with the real Archivo at 1280, 1440, 1680, 1920 and 2560.
   ============================================================ */
@media (min-width: 1280px) {
  .contact .inner {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    gap: 0 clamp(56px, 6vw, 120px);
    align-items: start;
    padding-block: clamp(96px, 10vw, 160px) clamp(96px, 8vw, 160px);
  }
  .contact .kicker, .contact h2, .contact-row { grid-column: 1; }
  .contact h2 {
    max-width: none;
    font-size: min(var(--d1), 100px, calc((clamp(1280px, 74vw, 2560px) - 80px - clamp(56px, 6vw, 120px)) / 15.1));
  }
  .contact-row { margin-top: clamp(28px, 3vw, 48px); }
  .cmeta { flex-direction: column; gap: 18px; }
  .enq { grid-column: 2; grid-row: 1 / span 3; margin-top: 0; padding-top: 0; border-top: 0; max-width: none; }
  .enq-h { font-size: clamp(22px, 2.2vw, 30px); }
}

/* ============================================================
   PASS 25 — THE SIX DISCIPLINES NO LONGER SNAP TILE BY TILE ON A PHONE.
   Daniel, 13 Sep, from his phone: the chapter "swipes each discipline
   without touching it ... scrolls all the way down to Five steps by
   itself". Six snap stops a third of a screen apart, each with
   scroll-snap-stop:always and a scroll-margin tied to a sticky head, plus
   Safari's address bar changing the viewport height on every snap: each
   re-snap moved the bars, each bar move re-snapped. The chapter itself is
   still a snap stop (one swipe from About lands on its head, the 4 Sep
   rule), and because the chapter is taller than the screen the page may
   rest anywhere inside it; the tiles just are not stops of their own any
   more. hcc.js's seat has the matching rule (a tall leaf area is a place
   to read, not a misseat) and it is what also fixes the contact chapter
   "snapping away" from the reader.
   ============================================================ */
@media (max-width: 860px) {
  html.stack .svc .blk { scroll-snap-align: none; scroll-snap-stop: normal; }
  html.stack .svc .inner > div:first-child { scroll-snap-align: none; }
}

/* ⚠ 13 Sep, later, Daniel: "just before you get to the end, it doesn't let
   you see Formwork and steel fixing, it quickly smashes into Five steps."
   The chapter is one tall snap area, and the page may rest anywhere inside
   it ONLY while the area still fills the screen. With the last tile hard
   against the chapter's end, the last legal resting place put Formwork at
   the very bottom of the screen, and the next millimetre of scroll was a
   mandatory snap to Process. Room after the last tile: enough that the
   sixth discipline can sit whole under the sticky head with the page at
   rest, and the jump to Process only comes on the next deliberate swipe.
   Sized from the measured screen (--page-h) and head (--svc-head-h) so it
   is the same on a 553px iPhone SE and an 852px Pro. */
@media (max-width: 860px) {
  html.stack .svc .blk:last-child { margin-bottom: 0; }
  /* 14 Sep: the room after Formwork is gone again. In the pager it read as the end of the site (Daniel). The chapter foot (PASS 31) does the job instead. */
  html.stack .svc .inner { padding-bottom: 0; }
}

/* ============================================================
   PASS 26 — THE PAGER (13 Sep, evening). See the matching note in hcc.js.
   On a phone the document no longer scrolls: <main> lives inside .pgr, a
   fixed full-screen box whose scroll position hcc.js moves one page at a
   time. No scroll-snap anywhere on the phone any more (the rules that set
   it above are neutralised here rather than deleted, so their history
   stays readable). A .pg is one chapter page, exactly one screen tall; a
   .pg.tall is a chapter taller than the screen, which scrolls inside its
   page with native touch scrolling. The footer is moved into the contact
   chapter by hcc.js so the last page ends with it.
   ============================================================ */
@media (max-width: 860px) {
  html.stack.pager, html.stack.pager body { height: 100%; overflow: hidden; overscroll-behavior: none; scroll-snap-type: none !important; }
  html.stack.pager .pgr { position: fixed; inset: 0; overflow-y: auto; overflow-x: hidden; touch-action: pinch-zoom; background: #000; scrollbar-width: none; overscroll-behavior: none; scroll-behavior: auto; }
  html.stack.pager .pgr::-webkit-scrollbar { display: none; }
  html.stack.pager main > section, html.stack.pager footer, html.stack.pager .blk, html.stack.pager .wrap,
  html.stack.pager .inner, html.stack.pager .pin-vp { scroll-snap-align: none !important; scroll-snap-stop: normal !important; }
  html.stack.pager .pg { height: var(--page-h, 100dvh); overflow: hidden; box-sizing: border-box; }
  html.stack.pager .pg.tall { overflow-y: auto; touch-action: pan-y; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
  html.stack.pager .contact footer { position: static; }
}

/* ============================================================
   PASS 27 — PAGE TURNS WITHOUT HESITATION, AND NO GREY SQUARES.
   Daniel, after the pager: "perfect ... only one criticism: the smoothness
   of each scroll ... little hesitations", and "a split-second grey box
   before the numbers come up in each box".
   1. THE GREY BOX. The six number tiles faded in from transparent, and the
      grid behind them is --line grey (it draws the hairlines between
      tiles). A transparent tile on a grey grid IS a grey square until the
      fade completes. The tile now stays opaque and its contents fade in
      with the same stagger. (The five-step rows have no background, so
      they never had this; what shows there is their hairlines fading in.)
   2. THE HESITATION. Two full-screen effects were being recomputed on every
      frame of every turn: the film-grain overlay (a fixed layer with
      mix-blend-mode over the whole screen) and the header's backdrop blur
      over content moving underneath it. The grain is off on phones: at 5%
      opacity on a phone screen it was doing nothing a reader could see and
      costing a blend pass per frame. The blur is lifted for the duration of
      a turn (html.turning, set by hcc.js) and returns on landing. hcc.js
      also stops telling every scroll-watcher to re-measure mid-turn.
   ============================================================ */
@media (max-width: 860px) {
  html.stack .capgrid > div { opacity: 1 !important; transform: none !important; transition: none !important; }
  html.stack .capgrid > div > * {
    opacity: 0; transform: translateY(14px);
    transition: opacity .55s var(--ease) calc(var(--i, 0) * .07s), transform .55s var(--ease) calc(var(--i, 0) * .07s);
  }
  html.stack .numbers .blk.in .capgrid > div > * { opacity: 1; transform: none; }
  html.stack .hcc-grain { display: none; }
  html.stack.pager.turning #nav { -webkit-backdrop-filter: none; backdrop-filter: none; background: rgba(0,0,0,.9); transition: none; }
  html.stack.pager .pgr { will-change: scroll-position; }
  html.stack.pager .pg { contain: layout paint; }
}

/* ============================================================
   PASS 28 — THE ABOUT PARAGRAPH THAT MOVED WHILE DANIEL WATCHED (13 Sep).
   "A whole paragraph that was on the left, in front of my eyes, shifted
   to the right." About's three paragraphs sit in two CSS columns, and a
   column layout re-balances the moment anything changes a paragraph's
   height. Two things did, both on a cold load:
   1. `pour-hold` (which switches About from one-paragraph-at-a-time to
      all three in columns) was added by hcc-plus.js, which arrives after
      first paint on a slow connection. It is now in the markup, so the
      chapter is laid out its final way from the first frame.
   2. Inter arrives after first paint (font-display: swap) and the fallback
      face has different metrics, so line counts change and paragraph two
      hops columns. The two body weights are now preloaded from <head>,
      and the fallback below is Arial re-metricated to Inter's proportions
      (the values are the standard Inter/Arial overrides), so even where
      the swap is visible the lines break in the same places.
   ============================================================ */
@font-face {
  font-family: "Inter Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Helvetica");
  ascent-override: 90.20%;
  descent-override: 22.48%;
  line-gap-override: 0%;
  size-adjust: 107.40%;
}
body { font-family: 'Inter', 'Inter Fallback', system-ui, -apple-system, sans-serif; }

/* ============================================================
   PASS 29 — LEADERSHIP FITS ONE PHONE SCREEN.
   Daniel: "the first swipe on Leadership doesn't let you swipe away; the
   second does." It was 841px tall on a 664px screen: taller than the
   screen, so it was a scrolling page, and the first swipe scrolled it the
   177px to its bottom. Nothing to read in those 177px but padding. Now
   the chapter is a column that fills the page exactly: the copy takes
   what it needs, the photograph takes the rest (never under 96px), so on
   an ordinary phone it is one page and one swipe. On an iPhone SE (553px)
   the copy alone is more than the screen; there it still scrolls, which
   is the right behaviour for a page that does not fit.
   ============================================================ */
@media (max-width: 860px) {
  html.stack.pager .lead .pin-vp { min-height: var(--page-h, 100svh); height: auto; box-sizing: border-box; padding-top: 64px; display: flex; flex-direction: column; overflow: visible; }
  html.stack.pager .lead .lead-grid { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
  html.stack.pager .lead .lead-visual { flex: 1 1 auto; height: auto; min-height: 96px; max-height: 62.5vw; align-self: stretch; }
  html.stack.pager .lead .lead-info { flex: none; padding: 20px var(--gutter) 22px; justify-content: flex-start; }
  html.stack.pager .lead .lead-info h2 { margin-bottom: 12px; }
  html.stack.pager .lead .lead-steps .pin-step + .pin-step { margin-top: 12px; }
  html.stack.pager .lead .lead-meta { margin-top: 16px; }
  html.stack.pager .lead.tall .lead-visual { flex: none; height: 40vw; }
}
/* on a phone screen under 720px tall the copy is set a size smaller so the
   photograph keeps enough of the frame to show two faces, not two hats */
@media (max-width: 860px) and (max-height: 720px) {
  html.stack.pager .lead .lead-info { padding: 14px var(--gutter) 16px; }
  html.stack.pager .lead .lead-info h2 { font-size: clamp(20px, 5.6vw, 24px); margin-bottom: 8px; }
  html.stack.pager .lead .lead-steps .pin-step p { font-size: 14px; line-height: 1.45; }
  html.stack.pager .lead .lead-steps .pin-step + .pin-step { margin-top: 8px; }
  html.stack.pager .lead .lead-meta { margin-top: 10px; }
  html.stack.pager .lead .lead-meta dd { margin-top: 3px; }
  html.stack.pager .lead .lead-visual img { object-position: 50% 30%; }
}

/* ============================================================
   PASS 30 — THE SETTLE STAYS ON PHONES, WITHOUT THE FLICKER.
   Daniel: "the pictures in Services start as normal, then you see the
   black bleed come on slowly, they flicker; same with the plant photo."
   Then: "I like the settling-in. Make it work without bugs."
   Why it flickered, and what changes:
   1. The FIGURE faded up as a group (opacity on the container). A group
      fade over a child that is itself being transformed makes WebKit
      render the whole figure offscreen every frame. Now the IMAGE fades
      and settles, and the figure never changes opacity. The bleed sits
      over black while the picture rises under it, so it is there from
      the first frame instead of arriving a beat later.
   2. The gradient bleed (figure::after, .plate-feather) was promoted to
      its own drawing layer only once the image beneath it started moving:
      that late promotion is the flash. Both are promoted up front
      (translateZ(0)), as is the image (will-change), so no layer is
      created or dropped mid-animation.
   The desktop is untouched.
   ============================================================ */
@media (max-width: 860px) {
  html.stack .blk > figure { opacity: 1 !important; transition: none !important; }
  html.stack .blk > figure img {
    opacity: 0; transform: scale(1.04);
    transition: opacity .85s var(--ease), transform 1.4s var(--ease);
    will-change: transform, opacity; backface-visibility: hidden;
  }
  html.stack .blk.in > figure img { opacity: 1; transform: none; }
  html.stack .blk > figure::after,
  html.stack .plate-feather { transform: translateZ(0); will-change: transform; }
  html.stack .plate-frame img,
  html.stack .lead-visual img { will-change: transform; backface-visibility: hidden; }
}
@media (max-width: 860px) and (prefers-reduced-motion: reduce) {
  html.stack .blk > figure img { opacity: 1; transform: none; transition: none; }
}
@media (max-width: 860px) { html.stack.pager .pg.tall:focus { outline: none; } html.stack.pager .pg.tall:focus-visible { outline: 2px solid var(--ink-1); outline-offset: -2px; } }

/* ============================================================
   PASS 31 — A FOOT ON EVERY CHAPTER THAT SCROLLS (14 Sep, pre go-live).
   Daniel: at the bottom of Services "it may appear to the user that that's
   the end of the scrolling". A chapter that scrolls inside its page now
   ends with a hairline and "Next: <the next chapter's title>" in the rail
   language, so the reader knows the next swipe is a page turn. hcc.js adds
   the element to every chapter page; this rule shows it only while the
   chapter is actually taller than the screen (.tall), so it never appears
   on a page that turns in one swipe. Contact ends with the footer and is
   the last page, so it gets none.
   ============================================================ */
@media (max-width: 860px) {
  .pg-next { display: none; }
  html.stack.pager .pg.tall > .pg-next {
    display: flex; align-items: center; gap: 14px;
    padding: 22px var(--gutter) 30px;
    font-size: 10px; letter-spacing: var(--track-label); text-transform: uppercase;
    color: var(--ink-3); white-space: nowrap;
  }
  html.stack.pager .pg-next i { flex: 1; height: 1px; background: var(--line); display: block; }
  html.stack.pager .pg-next b { color: var(--ink-1); font-weight: 600; }
  html.stack.pager .pg-next em { font-style: normal; color: var(--ink-1); }
}

/* ============================================================
   PASS 32 — ABOUT SITS STILL (14 Sep, PC only).
   Daniel: on the About screen "the paragraphs shift and move around in
   front of your eyes... they readjust". Three causes, all removed:
   1. The two columns were CSS multicol (columns: 2), which the browser
      RE-BALANCES on any reflow, so a late font, a re-wrapped heading or a
      resize could carry a whole paragraph from the left column to the
      right. The columns are now a fixed grid: paragraph one owns the left
      column, two and three the right. Nothing can change column.
   2. The .fall reveal slid each paragraph up 26px, staggered, on top of the
      word pour. Two motions on the same copy in two columns read as the
      layout readjusting. The slide is gone here; the fade and the word pour
      stay. Leadership keeps its own .fall untouched.
   3. Archivo came from Google Fonts after first paint (hcc.css now serves
      it from assets/fonts, preloaded), so the heading no longer swaps font
      and re-wraps above the copy.
   ⚠ WIDE SCREENS ONLY. The phone is the pager and is not touched.
   ⚠ Specificity: these carry .fall and .pin-on on purpose so they outrank
   both the pour-hold block and the .fall block above, in either order.
   ============================================================ */
@media (min-width: 860.02px) {
  html.js .statement.pour-hold .pin-steps,
  html.js.pin-on .statement.pour-hold.fall .pin-steps {
    columns: auto;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto 1fr;
    column-gap: clamp(28px, 3vw, 56px);
    row-gap: clamp(12px, 1vw, 18px);
    align-items: start;
    align-content: start;
    min-height: 0;
  }
  html.js .statement.pour-hold .pin-step,
  html.js.pin-on .statement.pour-hold.fall .pin-step { margin: 0; break-inside: auto; min-width: 0; }
  html.js .statement.pour-hold .pin-step:nth-child(1),
  html.js.pin-on .statement.pour-hold.fall .pin-step:nth-child(1) { grid-column: 1; grid-row: 1 / span 2; }
  html.js .statement.pour-hold .pin-step:nth-child(2),
  html.js.pin-on .statement.pour-hold.fall .pin-step:nth-child(2) { grid-column: 2; grid-row: 1; }
  html.js .statement.pour-hold .pin-step:nth-child(3),
  html.js.pin-on .statement.pour-hold.fall .pin-step:nth-child(3) { grid-column: 2; grid-row: 2; }
  /* the paragraph fades in where it sits; it no longer slides */
  html.js.pin-on .statement.pour-hold.fall .pin-step,
  html.js.pin-on .statement.pour-hold.fall .pin-steps.in .pin-step { transform: none; }
}

/* ============================================================
   PASS 33 — A RULE BETWEEN CREDENTIALS AND THE PLANT (14 Sep, PC only).
   Daniel: "there should be a separator between credentials and company
   owned plant, like there is on mobile". Both chapters are black, so on a
   wide screen the boundary was a black gap with nothing in it. A hairline
   in the rail's own line colour now sits across the top of the plant
   chapter, gutter to gutter, and rides up with it as it arrives. Once the
   chapter is held it sits under the nav, so it marks the join and nothing
   else. The phone shows its own foot there (PASS 31) and is untouched.
   ============================================================ */
@media (min-width: 860.02px) {
  html.js .plate::before {
    content: ""; position: absolute; z-index: 3;
    left: var(--gutter); right: var(--gutter); top: 0; height: 1px;
    background: var(--line);
  }
}
