/* Site-wide additions from the homepage rebuild: the utility bar above the
 * nav, and the credentials plate that appears on both home and about.
 *
 * THIS IS THE SOURCE. It lives in src/ and build.sh copies it to
 * assets/chrome.css before build-assets.js runs, for the same reason plot.css
 * does - build-assets.js content-hashes and RENAMES anything under assets/,
 * so a hand-edit there is written to a filename that no longer exists.
 *
 * The bar is site-wide rather than home-only on purpose. It carries the phone
 * number, and chrome that appears on one page and vanishes on the next reads
 * as a bug. The proposed homepage only showed it because that was the only
 * page drawn.
 */

/* ---- utility bar -------------------------------------------------------- */
/* Sits in normal flow at the very top, so every page starts with a solid dark
   strip and the hero begins below it. .nav is position:absolute against the
   initial containing block, so it does NOT move out of the way on its own -
   it is pushed down by exactly this height further below. */
:root{ --ubar-h:40px; }

/* ---- display face ------------------------------------------------------- */
/* The headline face, swapped from Saira Stencil One to Archivo Black.
 *
 * Overridden here rather than at its declaration because site.css has no source
 * under src/ - it exists only as the hashed asset - and hand-editing that is the
 * trap the plot.css header describes. chrome.css loads after site.css, so this
 * wins on order.
 *
 * A single-weight face is the right shape for this slot: every rule that uses
 * --disp declares `font:400 ...`, because Saira Stencil One only ever had a 400.
 * Archivo Black is also a 400-only family that renders heavy, so it drops in
 * without touching any of those five declarations. Archivo (variable) is the
 * fallback in case Black fails to load, since it at least has the same skeleton.
 *
 * Archivo Black is wider and has a much larger x-height than the stencil, so the
 * tracking comes off and the ch-based measures are retuned below - the same
 * character count in this face is a considerably longer line. */
:root{ --disp:'Archivo Black','Archivo',system-ui,sans-serif; }

.ubar{
  position:relative; z-index:22;
  height:var(--ubar-h);
  background:var(--bg-2);
  border-bottom:1px solid rgba(255,255,255,.07);
}
.ubar__in{
  max-width:calc(1240px + 12vw); margin:0 auto; height:100%;
  padding:0 6vw; display:flex; align-items:center; justify-content:space-between; gap:24px;
}
.ubar__facts{
  list-style:none; margin:0; padding:0;
  display:flex; align-items:center; gap:28px; min-width:0;
}
.ubar__facts li{
  font:600 11px/1 ui-monospace,Menlo,monospace; letter-spacing:.14em;
  text-transform:uppercase; color:var(--steel-dim); white-space:nowrap;
}
.ubar__tel{
  display:inline-flex; align-items:center; gap:9px; flex:none;
  text-decoration:none; color:var(--ink);
  font:600 13px/1 ui-monospace,Menlo,monospace; letter-spacing:.04em;
}
.ubar__tel svg{flex:none; display:block}
.ubar__tel:hover{color:var(--accent-ink)}

/* The facts are the first thing to go: the phone number is the reason the bar
   exists, and three mono strings do not survive a phone's width. */
@media (max-width:900px){
  .ubar__facts li:nth-child(n+3){display:none}
  .ubar__facts{gap:18px}
}
@media (max-width:620px){
  .ubar__facts{display:none}
  .ubar__in{justify-content:flex-end}
}

/* ---- nav, pushed clear of the bar --------------------------------------- */
/* .nav is `inset:0 0 auto` against the initial containing block. Left alone it
   would paint straight over the utility bar. */
.nav{top:var(--ubar-h)}

/* The hero asks for 100svh. With a bar above it that makes the first screen
   taller than the viewport and invents a scrollbar before any content. */
.hero{min-height:calc(100svh - var(--ubar-h))}

/* ---- header CTA --------------------------------------------------------- */
/* The phone number moved up into the utility bar, so this slot is free for the
   action the page actually wants. Solid rather than outlined: it is the primary
   call to action in the chrome and the proposed homepage drew it that way. */
.nav__cta{
  background:var(--accent); border-color:var(--accent); color:#fff;
  text-transform:uppercase; letter-spacing:.04em;
}
.nav__cta:hover{background:#1b57bb; border-color:#1b57bb; color:#fff}

/* ---- credentials plate -------------------------------------------------- */
/* What replaced four loose .chips on home and about.
 *
 * The chips were sized by phrase length, so the row came out ragged - one
 * short tag, one very long one - and read as leftover metadata rather than as
 * something the company stands behind. On the about page they were also
 * OUTSIDE the wrap holding the prose, which is why they hung off the left
 * edge with nothing to line up against.
 *
 * Built as a ruled plate instead: equal cells, hairline dividers, one border
 * around the set. That is the footer title block's instrument, reused, which
 * is the right register here and means the page carries two of the same thing
 * rather than two different ones.
 *
 * No width of its own: it fills whatever it is placed in. On about that is a
 * plain .wrap, the same box the prose above it sits in, so the two edges line
 * up. Setting a width here instead looked right until plot.css was accounted
 * for - it forces `.sec--art > .wrap` to 1240px with !important, so the prose
 * is wider than its own max-width rule claims and anything hardcoded to match
 * that rule lands ~100px short on each side. */
/* The dividers are drawn by the grid gap, not by a border on each cell.
 *
 * Per-cell borders need to know which cell ends a row, and the column count
 * here is not fixed - it is whatever auto-fit produces in the box it lands in
 * (four across under the prose on about, two inside the story pane on home).
 * nth-child rules written for one of those are wrong in the other, and the
 * homepage showed it: a right border hanging off the end of row one and no
 * rule at all between the two rows.
 *
 * A 1px gap over a ruled ground draws every divider in both axes, whatever the
 * column count, with no arithmetic. */
.creds{
  list-style:none; padding:0;
  margin:clamp(30px,3.6vw,46px) 0 0;
  border:1px solid var(--line);
  display:grid; grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:1px; background:var(--line-soft);
}
.creds li{
  background:var(--bg-card);
  padding:22px; min-height:64px;
  display:flex; align-items:center; gap:14px; min-width:0;
}
/* The tick is what makes this read at a glance rather than on reading. Accent
   ink so it carries without the label having to shout. */
.creds svg{flex:none; color:var(--accent-ink)}
.creds b{
  font:600 12px/1.4 ui-monospace,Menlo,monospace;
  letter-spacing:.12em; text-transform:uppercase; color:var(--ink);
  overflow-wrap:anywhere;
}

/* Between roughly 460 and 900 auto-fit lands on three columns, which leaves
   the fourth cell alone on a row. Two-up is the better shape for four items. */
@media (max-width:900px){ .creds{grid-template-columns:repeat(2,1fr)} }
@media (max-width:460px){ .creds{grid-template-columns:1fr} }

/* ---- provenance line ---------------------------------------------------- */
/* Sits above the footer's bottom bar, quiet but readable. It is a disclosure,
   so it is deliberately not hidden behind a link or set below the copyright:
   it reads at the same weight as the rest of the footer's fine print. */
.provenance{
  max-width:1240px; margin:36px auto 0;
  font:500 13px/1.6 var(--body); color:var(--ink-faint);
  text-wrap:pretty;
}

/* ---- card surfaces ------------------------------------------------------ */
/* Every card on the site read as a flat fill inside a hairline rectangle: the
 * same ground as its neighbour, the same edge on all four sides, and no
 * separation from the page. On a dark site that is what makes a grid of cards
 * look like one grey field with lines ruled across it rather than a set of
 * objects, which is the "flat and desegmented" complaint exactly.
 *
 * Three things fix it, and none of them is a border-radius or a blur:
 *
 *   1. A ground that is not uniform. A card lit from above is lighter at the
 *      top; a flat fill is the one thing a real surface never is.
 *   2. An edge that is not uniform either. A real object catches light on its
 *      top edge and loses it at the bottom, so the top hairline is brighter
 *      than the border and the bottom is darker.
 *   3. Two shadows, not one. A tight contact shadow says the card is close to
 *      the page; a wide ambient one says there is space behind it. A single
 *      soft shadow reads as a sticker.
 *
 * All paint. Nothing here samples the backdrop, so the fixed drawing sheet
 * behind the page costs nothing extra to composite - the same constraint the
 * earlier glass work was built under.
 */
.svcx__stage,
.workx__card,
.creds,
.areax__panel,
.svccard,
.work figure,
.spec__g,
.cols3 > div{
  background-image:linear-gradient(180deg, rgba(255,255,255,.055) 0%, rgba(255,255,255,.012) 38%, rgba(0,0,0,.06) 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.10),
    inset 0 -1px 0 rgba(0,0,0,.30),
    0 1px 2px rgba(0,0,0,.34),
    0 14px 30px -12px rgba(0,0,0,.55);
}

/* The plate is one object, so its cells must not each pick up the treatment -
   the gradient belongs to the plate and the cells only supply their ground. */
.creds li{background-image:none; box-shadow:none}

/* Hover is where a card earns the depth: it lifts rather than merely changing
   its border colour. Kept off the plate and the panel, which are not links. */
.workx__card,
.svccard{transition:border-color .2s ease, box-shadow .25s ease, transform .25s cubic-bezier(.16,.84,.24,1)}
.workx__card:hover,
.svccard:hover{
  transform:translateY(-3px);
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.16),
    inset 0 -1px 0 rgba(0,0,0,.30),
    0 2px 4px rgba(0,0,0,.36),
    0 26px 46px -14px rgba(0,0,0,.68);
}
@media (prefers-reduced-motion:reduce){
  .workx__card,.svccard{transition:border-color .2s ease}
  .workx__card:hover,.svccard:hover{transform:none}
}

/* ---- brand mark --------------------------------------------------------- */
/* 34px to 66px, in two steps the owner asked for: +30%, then +50% again.
 *
 * plot.css sets `.nav__brand .nav__mark{height:34px}`, which already overrides
 * the 27px in hero.css. Matching that selector rather than the bare `.nav__mark`
 * is what makes this stick: on equal specificity the later sheet wins, and
 * chrome.css is loaded after plot.css. A bare `.nav__mark{}` here would lose.
 *
 * The header does not grow with it. `.nav__brand` already reserves a 44px
 * min-height for the touch target, so the mark now fills that box exactly
 * rather than sitting inside it, and the nav's own padding is untouched. */
.nav__brand .nav__mark{height:66px}

/* ---- page hero, blended into the page ----------------------------------- */
/* Every inner page opened with a photograph that simply stopped: a hard edge
 * where the image ended and the page began. The scrim only reached 74% black at
 * its foot, so the last band of photograph was still bright right up to the cut.
 *
 * The first attempt masked the image out to fully transparent, which softened
 * the edge but let the structural sheet show THROUGH the photograph - the
 * drawing bleeding up into the picture. Right for the homepage, where the plate
 * below wants to come through; wrong here, where there is nothing behind the
 * hero but the page.
 *
 * So no mask. The scrim carries a third gradient instead, running from clear
 * through to the page colour itself, and the hero resolves into a flat ground
 * with nothing drawn over the photograph. */
.phero__img{-webkit-mask-image:none; mask-image:none}
/* The scrim used to finish on solid var(--bg), which meant the hero painted
 * its own opaque floor. The page behind it carries the drawing sheet, so the
 * sheet was hidden for the height of the hero and then cut back in the instant
 * the hero ended - the hard line under the photograph. Same fault as the strip
 * on the home page, same fix: the hero resolves into the real ground instead of
 * covering it, and the photograph and the scrim are masked out together over
 * the last 120px so there is no edge left to see. */
.phero__img,
.phero__scrim{
  -webkit-mask-image:linear-gradient(to top, transparent 0, rgba(0,0,0,.5) 44px, #000 120px);
          mask-image:linear-gradient(to top, transparent 0, rgba(0,0,0,.5) 44px, #000 120px);
}
.phero__scrim{
  background:
    linear-gradient(to bottom,
      rgba(14,16,19,0) 42%,
      rgba(14,16,19,.44) 66%,
      rgba(14,16,19,.80) 84%,
      rgba(14,16,19,.94) 100%),
    linear-gradient(to right, rgba(5,7,11,.90) 0%, rgba(5,7,11,.64) 46%, rgba(5,7,11,.20) 82%, rgba(5,7,11,.12) 100%),
    linear-gradient(to top, rgba(5,7,11,.62) 0%, rgba(5,7,11,.10) 52%, rgba(5,7,11,.42) 100%);
}

/* ---- homepage hero, dissolved at the foot -------------------------------- */
/* The inner pages got this when the page heroes were blended; the homepage hero
 * did not, so it still ended on a cut straight into the band below it.
 *
 * .hero__prog goes with it. It is a 2px bar at bottom:0 spanning the full
 * width, which put a hard rule exactly on the seam the fade is trying to
 * dissolve - the line visible along the top of the strip. The dots beside the
 * readout already say which slide is up; the bar only said how long was left,
 * and it cost more than it was worth sitting there. */
.heroslide{
  -webkit-mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.30) calc(var(--seam) * .40),
    rgba(0,0,0,.80) calc(var(--seam) * .76), #000 var(--seam));
          mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.30) calc(var(--seam) * .40),
    rgba(0,0,0,.80) calc(var(--seam) * .76), #000 var(--seam));
}
.hero__prog{display:none}

/* The overlap, the photograph's fade and the scrim's release are the same
 * distance, so they are one number. Before this the mask was a percentage of
 * the hero and the overlap was a clamp, which meant the photograph started
 * dissolving ~90px above where the strip began - and in that gap it dissolved
 * onto the drawing sheet, which is why the blueprint was coming up over the
 * hero. Tied together, the photograph is solid until the strip is behind it
 * and there is no gap for the sheet to show through. */
:root{--seam:clamp(120px,12vw,190px)}

/* The scrim has to let go at the same edge the photograph does.
 *
 * It is the last thing painting inside the hero - z-index auto, so above the
 * media - and site.css runs its bottom stop to 74% black. Fading the slides
 * alone therefore left the strip behind the hero seen through that 74%, and the
 * strip below the hero seen clean: identical gradient, two different results,
 * meeting on a hard line at the hero's foot. That line was the seam, not the
 * gradient.
 *
 * Masked from the bottom in px off the same clamp as the overlap, rather than
 * in percentages. The copy sits about 100px up on a wide screen and about 93px
 * up on a phone, while the hero itself changes height a lot between them - a
 * percentage that clears the copy on one clears nothing on the other. In px the
 * scrim is still solid everywhere the headline and buttons sit, and gone by the
 * time it reaches the strip. */
.hero__scrim{
  -webkit-mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.5) calc(var(--seam) * .40), #000 calc(var(--seam) * .92));
          mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.5) calc(var(--seam) * .40), #000 calc(var(--seam) * .92));
}

/* The cloud has to rise INTO the photo, so it needs a layer that paints above
 * both the slides and the scrim. Read site.css alone and .hero__scrim looks
 * like it sits behind the photo - it is given z-index:-1 there - but hero.css
 * resets that to auto, so the scrim actually paints after .hero__media and
 * buries anything layered inside it. An earlier attempt hung the ramp on
 * .hero__media::after: it showed on desktop and vanished on the phone, where
 * the scrim's pool of shadow is far heavier. The ramp was under the scrim at
 * both widths - only the wide crop was faint enough to look right.
 *
 * So the ramp takes an explicit z-index rather than relying on paint order.
 * .hero__inner is z-index:10, so 5 clears both photo and scrim while leaving
 * the headline and both buttons crisp on top.
 *
 * Its height is tied to the same clamp as the band's overlap so the ramp and
 * the band it ramps into always begin together; the 1.35 feathers it a little
 * past the seam. Fading the slide to nothing is not enough by itself - the
 * photo's dark concrete stays legible as a smear long after it is mostly
 * transparent, which read as a stain across the cloud. Ending opaque in the
 * band's own colour is what clears it. */

/* ---- contractor marquee (moved here from home.css) ---------------------- */
/* This lived in home.css while the marquee was the homepage's alone. The about
 * page runs it too now, and home.css is loaded only on the homepage - so the
 * about strip had no .marq rules at all and rendered as a vertical stack of
 * logos, twice over, about 500px tall. Shared markup needs shared styles.
 */
/* ---- contractor marquee ------------------------------------------------- */
/* The proposal ran GC names as scrolling text. We have the actual marks, so
   this scrolls those instead - a logo carries recognition that a name does not.
   The strip keeps its light ground: every one of these marks was drawn for
   white paper, and half of them go invisible on graphite. */
.marq{padding:clamp(28px,3.4vw,42px) 0}
.marq__head{
  max-width:1240px; margin:0 auto clamp(22px,2.6vw,32px);
  padding:0 6vw; display:flex; align-items:center; gap:20px;
}
.marq__lab{
  margin:0; flex:none;
  font:600 11px/1 ui-monospace,Menlo,monospace; letter-spacing:.16em;
  text-transform:uppercase; color:rgba(18,24,34,.62); white-space:nowrap;
}
.marq__ln{flex:1 1 auto; height:1px; background:rgba(0,0,0,.14)}

/* The track holds the logo list twice. The animation travels exactly -50%,
   which lands copy two where copy one began, so the loop has no seam. */
.marq__vp{position:relative; overflow:hidden}
.marq__track{display:flex; width:max-content; animation:marq 46s linear infinite}
.marq__track:hover{animation-play-state:paused}
@keyframes marq{from{transform:translate3d(0,0,0)} to{transform:translate3d(-50%,0,0)}}
.marq__set{
  list-style:none; margin:0; padding:0 clamp(46px,6vw,88px) 0 0;
  display:flex; align-items:center; flex:none; gap:clamp(46px,6vw,88px);
}
.marq__set li{display:flex; align-items:center; justify-content:center; min-height:66px}
.marq__set img{
  display:block; width:auto; height:auto;
  max-height:var(--lh,48px); max-width:var(--lw,240px);
  object-fit:contain; opacity:.94;
}
/* Fades at both ends, so logos enter and leave rather than being cut by an edge. */
.marq__vp::before,.marq__vp::after{
  content:""; position:absolute; top:0; bottom:0; width:clamp(60px,9vw,140px);
  z-index:2; pointer-events:none;
}
.marq__vp::before{left:0;  background:linear-gradient(90deg,#C3CAD4,rgba(195,202,212,0))}
.marq__vp::after {right:0; background:linear-gradient(270deg,#C3CAD4,rgba(195,202,212,0))}
@media (prefers-reduced-motion:reduce){
  .marq__track{animation:none}
  .marq__vp{overflow-x:auto}
  /* The track carries the list twice so the moving loop has no seam. Standing
     still in a scrollable viewport, that second copy is reachable - and it is
     aria-hidden with empty alts, so it reads as every logo printed twice. One
     copy is all a static strip needs. */
  .marq__set[aria-hidden="true"]{display:none}
}

/* ---- the contractor strip, eased in and out ----------------------------- */
/* The band is steel, not paper.
 *
 * A near-white strip on a graphite page is two hard edges however it is
 * feathered: bridging #EEF1F6 to #0E1013 runs through the whole grey scale, and
 * showing much of that ramp looks like a chrome bar laid across the page. The
 * fix is not a better fade, it is less distance to fade across.
 *
 * The ground is --steel now, which is the tone taken off the chrome in the
 * company's own mark, so the band reads as a lit steel plate set into the page
 * rather than a sheet of paper stapled onto it. The marks still need a light
 * ground - they were drawn for white paper and the navy and black ones
 * disappear on graphite - and --steel is light enough to carry them while
 * sitting far closer to the page than white ever could.
 *
 * Because the distance is short, the transition can be long: a generous fade
 * either side now passes through a few related steel tones instead of the
 * entire grey scale, which is what makes it read as light falling across a
 * surface rather than a band stuck on top of one. */
.logoband,
.marq{
  background:
    linear-gradient(to bottom,
      rgba(206,213,223,0) 0%,
      rgba(180,189,201,.42) 8%,
      rgba(198,206,217,.92) 20%,
      rgba(206,213,223,.99) 34%,
      #D6DBE2 50%,
      rgba(206,213,223,.99) 66%,
      rgba(172,181,194,.90) 80%,
      rgba(96,107,122,.62) 91%,
      var(--bg) 100%);
  border-block:0;
  padding-block:clamp(58px,6.6vw,92px);
  position:relative;
}

/* The hero hands straight to the plate.
 *
 * Before this the order down the page was hero, then a band of page dark, then
 * the plate - because the hero masked out to nothing and the band then opened
 * on var(--bg). Two transitions where there should be one.
 *
 * The plate is pulled up under the hero's fade and sits BEHIND it: the hero
 * keeps the higher stacking order, so where its mask goes transparent the steel
 * is what shows through. Image dissolves into cloud, cloud settles into page.
 * The padding gains back what the margin takes, so the label and the marks stay
 * where they were - only the gradient reaches up. */
/* hero.css gives .hero its own opaque ground (--bg-2). With that in place the
   masked slides fade to reveal THAT, not the plate behind - which is why the
   overlap changed nothing and the edge stayed hard. The slides cover the
   section completely, so the section does not need a ground of its own. */
.hero{position:relative; z-index:2; background:transparent}

/* On the homepage the band no longer has to fade itself in: the hero's ramp
 * above it does that, and ends opaque in this exact colour. Leaving the shared
 * fade-in underneath meant the two met at different values - the ramp finished
 * at #D6DBE2 while the band was only ~20% into its own rise - and that mismatch
 * was the pale line still visible along the seam. So the homepage band starts
 * at full strength and only fades OUT at the bottom.
 *
 * Scoped to .hero + .marq deliberately. The about page runs the same strip with
 * no hero above it, where the fade-in IS the transition; starting that one
 * opaque would cut a hard edge into the graphite. */
.hero + .marq{
  /* One vertical gradient, full width, and nothing else.
   *
   * No radial: a radial has a bright core and falls away from it, which on a
   * strip this wide reads as a lens sitting in the middle - the bulge.
   *
   * No mask on the sides either. That was meant to soften the ends and did the
   * opposite: a ramp that reaches full strength at 14% and then meets flat
   * light has a corner in it, and a corner across a uniform plateau is a box.
   * The light runs the whole width now, so there are no vertical edges to see
   * at all - the same reason the top reads well, which is that it only ever
   * changes in one direction.
   *
   * And no plateau. The stops below are a smooth hump, close enough together
   * that no single one shows as a line: it rises out of the photograph, peaks
   * across the marks and falls back into the page. */
  background:linear-gradient(to bottom,
    rgba(112,124,142,0)   0,
    rgba(118,130,147,.22) calc(var(--seam) * .35),
    rgba(134,146,163,.55) calc(var(--seam) * .72),
    rgba(166,177,192,.82) var(--seam),
    rgba(192,201,214,.92) 46%,
    rgba(207,214,225,.96) 60%,
    rgba(203,211,222,.95) 72%,
    rgba(176,186,200,.80) 82%,
    rgba(134,146,163,.50) 90%,
    rgba(70,79,93,.20)    96%,
    rgba(14,16,19,0)      100%);
}

.hero + .marq{
  position:relative; z-index:1;
  margin-top:calc(-1 * var(--seam));
  padding-top:calc(var(--seam) + clamp(20px,2.4vw,34px));
  padding-bottom:clamp(96px,11vw,168px);
}

/* One hairline of light along the top rim, the way a plate catches it. */
.logoband::before{
  content:""; position:absolute; left:0; right:0; top:0; height:1px;
  background:linear-gradient(to right, transparent, rgba(255,255,255,.18), transparent);
  z-index:2; pointer-events:none;
}

/* Bigger marks, and centred in the band rather than sitting high in it. */
.marq__set li{min-height:96px}
.marq__set img{max-height:calc(var(--lh,48px) * 1.4)}
.logos li{min-height:96px}
.logos img{max-height:calc(var(--lh,48px) * 1.4)}
@media (max-width:640px){
  .marq__set img,
  .logos img{max-height:calc(var(--lh,48px) * 1.05)}
}

/* Centred over the marks, and no rule beside it. The rule was a leftover from
   the static band, where it filled the width the label did not use; centred,
   there is no width left over for it to fill. */
.marq__head{
  justify-content:center;
  margin-bottom:clamp(26px,3vw,38px);
}
.marq__lab{
  font:700 clamp(15px,1.5vw,20px)/1.2 var(--disp-2);
  letter-spacing:.22em;
  /* Solid, not .82. At 15px on a phone the label sits on the dimmer shoulder of
     the light, and the 18% of ground bleeding through the type was the whole
     difference between 3.6:1 and passing. */
  color:#101620;
}
.marq__ln{display:none}

/* ---- no sideways scroll ------------------------------------------------- */
/* site.css reveals two-column blocks by sliding the halves in from opposite
 * sides: [data-anim=part] starts at translateX(-30px) and :nth-child(even) at
 * +30px. The half coming from the right hangs 30px past the layout until its
 * reveal fires, and below 1440px the gutter is no longer wide enough to absorb
 * it - so every page carrying one of those blocks could be dragged sideways,
 * by 4px at 1280 and the full 30px on a phone. Predates this work; it is fixed
 * here because it ships on eight of the twelve pages.
 *
 * clip, not hidden: hidden would make this a scroll container, which changes
 * how position:sticky resolves and how the scroll-panned drawing sheet reads
 * its offsets. clip cuts the overflow off and leaves scrolling alone.
 *
 * Fixed from here rather than in site.css because site.css has no source under
 * src/ - it exists only as a hashed asset, so a build would overwrite the edit.
 * chrome.css loads after it, which is enough. */
html,
body{overflow-x:clip}

/* ---- gallery without captions ------------------------------------------- */
/* The shop trio on the about page reads as three photographs, not three
 * catalogued items, so it drops the label strip. Hidden rather than removed:
 * the text is still in the DOM for the lightbox caption and for the aria-label
 * lightbox.js builds from it, and display:none does not stop textContent. */
.work--nocap figcaption{display:none}

/* ---- specification block ------------------------------------------------ */
/* Four groups of hard facts on each service page: the codes the work answers
 * to, the material, the tolerances and what the client gets handed. A GC or an
 * estimator reads this before they read any of the prose, so it is set as a
 * plate of cells rather than more paragraphs - scannable, and it holds its
 * shape at four, two or one column. */
.spec{
  display:grid; gap:clamp(14px,1.6vw,20px);
  grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  margin-top:clamp(26px,3vw,38px);
}
@media (max-width:1100px){ .spec{grid-template-columns:repeat(2,1fr)} }
@media (max-width:620px){ .spec{grid-template-columns:1fr} }

.spec__g{
  background:var(--bg-card); border:1px solid var(--line);
  padding:clamp(18px,2vw,24px);
}
.spec__g h3{
  margin:0 0 14px;
  font:700 clamp(12px,1.1vw,13px)/1.3 var(--disp-2);
  letter-spacing:.16em; text-transform:uppercase; color:var(--accent-ink);
}
.spec__g ul{margin:0; padding:0; list-style:none}
.spec__g li{
  position:relative; padding-left:18px;
  font:400 clamp(13.5px,1.15vw,15px)/1.55 var(--body);
  color:var(--steel);
}
.spec__g li + li{margin-top:10px; padding-top:10px; border-top:1px solid rgba(255,255,255,.06)}
/* The mark sits on the first line of a wrapped item, not centred on the block. */
.spec__g li::before{
  content:""; position:absolute; left:0; top:.62em;
  width:8px; height:1px; background:var(--accent-ink); opacity:.75;
}
.spec__g li + li::before{top:calc(10px + .62em)}

/* ---- services page: cards that are not links --------------------------- */
/* The four cards used to open a page each. They do not any more, so the
 * affordances that promised it have to go with the href - a card that lifts
 * and lights its outline under the cursor is telling you it will do something.
 * The scope list takes the place of "See this service": the page is now where
 * the answer is, not a menu pointing somewhere else. */
.svccard:hover{background:var(--bg-card); outline-color:var(--line); transform:none}

.svccard__scope{
  margin:4px 0 0; padding:0; list-style:none;
  border-top:1px solid rgba(255,255,255,.07);
}
.svccard__scope li{
  position:relative; padding:9px 0 0 17px;
  font:400 clamp(13px,1.1vw,14.5px)/1.45 var(--body);
  color:var(--steel);
}
.svccard__scope li::before{
  content:""; position:absolute; left:0; top:calc(9px + .62em);
  width:8px; height:1px; background:var(--accent-ink); opacity:.7;
}

/* Footer scope names: text, matching the links they sit above. */
.ftsvc{
  margin:0; padding:2px 0;
  font:inherit; line-height:2.05;
  color:rgba(242,244,247,.72);
}

/* ---- the marks ---------------------------------------------------------- */
/* Two of the eight - Meridian and Swinerton - are supplied on an opaque white
 * ground rather than a transparent one, so they scrolled across the strip as
 * white rectangles with hard corners. That was the "box". multiply drops any
 * white in a mark to whatever is behind it while leaving the ink alone, so the
 * grounds disappear and Meridian's black bar and reversed type still read.
 * It costs nothing on the six that were already transparent. */
/* The marquee no longer masks or blends anything itself.
 *
 * mix-blend-mode:multiply here looked right in Chromium and did nothing at all
 * in WebKit, so every white logo plate was still on screen in Safari while the
 * tests said they were gone. A blend mode was the wrong tool anyway: the two
 * files that shipped with an opaque plate - Meridian and Swinerton - have had
 * it removed from the artwork, which is browser-independent, and the soft
 * edges now come from a mask on the section so the marks and the ground they
 * sit on fade together. */

/* ---- studio credit ------------------------------------------------------ */
/* Last thing on the page, under the legal row. Built from the vocabulary the
 * rest of the site already uses - hairline rule, the mono micro-label, the
 * display face for the name and the accent as a single machined dot - so it
 * reads as part of the drawing rather than a badge stuck on the end of it.
 * Deliberately quiet: it sits below the copyright and carries no colour of its
 * own beyond that dot. */
.bpill{
  margin:clamp(26px,3.2vw,42px) auto 0;
  display:flex; width:max-content; align-items:center; gap:11px;
  padding:9px 17px;
  border:1px solid var(--line); border-radius:999px;
  background-color:var(--bg-card);
  background-image:linear-gradient(180deg, rgba(255,255,255,.055) 0%, rgba(255,255,255,.012) 42%, rgba(0,0,0,.07) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.09), 0 1px 2px rgba(0,0,0,.30);
}
.bpill__k{
  font:600 10.5px/1 ui-monospace,Menlo,monospace;
  letter-spacing:.19em; text-transform:uppercase; color:rgba(242,244,247,.42);
}
.bpill__d{
  width:5px; height:5px; flex:none; border-radius:50%;
  background:var(--accent-ink); opacity:.85;
}
.bpill__b{
  font:700 12.5px/1 var(--disp-2);
  letter-spacing:.13em; text-transform:uppercase; color:var(--steel);
}
@media (max-width:420px){
  .bpill{gap:9px; padding:8px 14px}
  .bpill__k{font-size:9.5px; letter-spacing:.15em}
  .bpill__b{font-size:11.5px}
}


/* ---- contact form ------------------------------------------------------- */
/* The form used to be the left half of a .split, with a list of what to send
 * in the right half. The list is gone, so the grid is gone with it: one child
 * in an auto-fit grid stretches to the full wrap, which would have put a
 * thousand-pixel name field on the page. The form gets a measured width
 * instead, and the four contact details come out of the card and run across
 * underneath it - inside it they sat below the submit button and read as more
 * form to fill in. */
.contactwrap{display:block}
.pane--form{
  max-width:720px; margin-inline:auto;
  padding:clamp(26px,3.4vw,40px);
}
.pane--form > h2{font-size:clamp(26px,3.4vw,40px); margin-top:0}
.pane--form > p{margin-bottom:clamp(24px,3vw,32px)}

/* Fields. They were 48px tall with an 11px label at 60% opacity - the label
   was harder to read than the placeholder it described. */
.lead{gap:18px}
.lead label{
  gap:9px;
  font:600 11.5px/1 var(--mono, ui-monospace, Menlo, monospace);
  letter-spacing:.15em; color:rgba(242,244,247,.72);
}
.lead input,
.lead select,
.lead textarea{
  min-height:54px; padding:14px 16px;
  background:rgba(5,7,11,.55);
  border:1px solid rgba(255,255,255,.13);
  font:500 17px/1.45 var(--body);
  transition:border-color .16s ease, background .16s ease;
}
.lead input:hover,
.lead select:hover,
.lead textarea:hover{border-color:rgba(255,255,255,.24)}
.lead input:focus,
.lead select:focus,
.lead textarea:focus{
  background:rgba(5,7,11,.8); border-color:var(--accent-ink);
}
.lead textarea{min-height:150px; line-height:1.55}

/* The select carried the platform arrow, which is the one control on the page
   that looked like it came from somewhere else. */
.lead select{
  -webkit-appearance:none; appearance:none;
  padding-right:44px;
  background-image:linear-gradient(45deg,transparent 50%,rgba(242,244,247,.7) 50%),
                   linear-gradient(135deg,rgba(242,244,247,.7) 50%,transparent 50%);
  background-position:calc(100% - 21px) calc(50% + 1px), calc(100% - 15px) calc(50% + 1px);
  background-size:6px 6px, 6px 6px;
  background-repeat:no-repeat;
}

/* Fine print, set as fine print: on its own ground, off to the side of the
   flow, so it stops competing with the questions above it. */
.lead label.lead__consent{
  padding:16px 18px; margin-top:4px;
  background:rgba(255,255,255,.028);
  border:1px solid rgba(255,255,255,.07);
  font:400 13.5px/1.62 var(--body);
  color:rgba(242,244,247,.6);
}
.lead label.lead__consent input{margin-top:1px}

.lead button{
  min-height:56px; padding-inline:30px; align-self:stretch;
  font-size:15px; letter-spacing:.1em;
}
@media (min-width:560px){ .lead button{align-self:flex-start} }

.lead__alt{font-size:15.5px; color:rgba(242,244,247,.6)}

/* The four details, across the foot of the form. */
.cdeck{
  max-width:720px; margin:clamp(22px,2.6vw,30px) auto 0;
  /* Two by two, not auto-fit. At 720px a 210px minimum resolved to three
     columns, which left the fourth detail alone on its own row beside two empty
     cells with the divider lines still drawn through them. */
  display:grid; grid-template-columns:repeat(auto-fit,minmax(285px,1fr)); gap:1px;
  background:var(--line);
  border:1px solid var(--line);
}
.cdeck__row{
  display:flex; flex-direction:column; gap:7px;
  padding:16px 18px; background:var(--bg-card);
  text-decoration:none; color:inherit;
  transition:background .16s ease;
}
a.cdeck__row:hover{background:var(--bg-card-2, #1D2127)}
.cdeck__k{
  font:600 10.5px/1 var(--mono, ui-monospace, Menlo, monospace);
  letter-spacing:.19em; text-transform:uppercase; color:var(--steel);
}
.cdeck__v{font:700 clamp(15px,1.5vw,17px)/1.3 var(--disp-2); letter-spacing:.02em; color:var(--ink)}
a.cdeck__row .cdeck__v{color:var(--accent-ink)}

/* plot.css gives .lead the full glass treatment - sheen, body tint, bevel,
 * lift - because it used to be one of the reading blocks. Inside .pane, which
 * has the heavier version of the same treatment, that is a glass card sitting
 * in a glass card: two speculars crossing in the top-left corner, which is what
 * made the head of the form look washed and muddy. The pane is the surface; the
 * form is just its contents. */
.pane--form .lead{
  background:none; border:0; border-radius:0; box-shadow:none;
}

/* site.css caps h2.big at 17ch, which was right when the form was one half of a
 * two-column split. In a 638px column it broke the headline over four lines
 * with a ragged edge. */
.pane--form > h2{max-width:none}

/* ---- closing block: long headlines --------------------------------------- */
/* The copy column was an inline max-width:640px, which is fine for a headline
 * of five or six words. "Here's how to get turnkey steel services for your
 * project" is fifty-seven characters, and at the block's 72px Archivo Black
 * that is six lines of shouting. The block takes a size step rather than the
 * sentence being cut down to fit it. */
.cta__copy{max-width:640px}
.cta--long .cta__copy{max-width:760px}
.cta--long h2{font-size:clamp(26px,3.4vw,44px); line-height:1.04}

/* ---- story bullets ------------------------------------------------------ */
/* The list used to run inside the sentence - "on multi-family, retail and
   mixed-use, light industrial and other work" - where the commas were doing
   two jobs at once and you could not tell where one market ended and the next
   began. Same dash mark as the specification plate, so the two lists read as
   the same kind of thing. */
.storylist{
  margin:16px 0 0; padding:0; list-style:none;
  display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:9px 22px;
}
.storylist li{
  position:relative; padding-left:19px;
  font:600 clamp(15px,1.5vw,17px)/1.45 var(--body);
  color:var(--ink);
}
.storylist li::before{
  content:""; position:absolute; left:0; top:.66em;
  width:10px; height:2px; background:var(--accent-ink); opacity:.85;
}

/* ---- label and secondary-heading scale ---------------------------------- */
/* Everything in the tier below a headline was set small enough to skim past:
   the carousel tabs at 16px, the numbered eyebrows at 11px, the card captions
   at 11.5px. They are the labels that tell you where you are, so they carry
   more weight and a size step each. Sizes are clamped rather than fixed so the
   increase does not land on a phone as an overflow. */

/* the carousel tabs are bumped in home.css - it loads after this sheet */

/* the numbered rules - "01 - SERVICES", "THE SHOP" - used site-wide */
.rule .n{font-size:clamp(12px,1.15vw,13.5px); font-weight:700; letter-spacing:.2em}

/* closing block eyebrow */
.cta .eye{font-size:clamp(12px,1.1vw,13px); font-weight:700}

/* specification plate group headings */
.spec__g h3{font-size:clamp(13.5px,1.2vw,15px); letter-spacing:.14em}

/* gallery captions and the contact details keys */
.work figcaption{font-size:clamp(12px,1.05vw,13px); font-weight:600; letter-spacing:.11em}
.cdeck__k{font-size:12px; font-weight:700}

/* form labels */
.lead label{font-size:clamp(12px,1.1vw,13px); font-weight:700}

/* the reading tiers: scope rows, process steps, questions, service cards */
.scope li strong{font-size:clamp(18px,1.85vw,21px)}
.steps li h3{font-size:clamp(20px,2vw,24px)}
.faq summary{font-size:clamp(18px,1.85vw,22px)}
.svccard__body h2{font-size:clamp(20px,2.2vw,27px)}

/* The goals label used to follow a <p> and took its spacing from that. After a
   list it sat right on the last bullet. */
.storylist + .story__sub{margin-top:clamp(24px,2.6vw,32px)}

/* ---- submitted confirmation --------------------------------------------- */
/* Built by lead.js on success rather than shipped in the markup, so it cannot
 * be found by anyone who has not submitted, and so any page carrying the form
 * gets it without the template repeating itself. */
.sent{
  padding:0; border:0; background:transparent;
  max-width:min(560px, calc(100vw - 32px));
  color:var(--ink);
}
.sent::backdrop{background:rgba(5,7,11,.78)}
.sent__in{
  padding:clamp(26px,3.4vw,40px);
  background-color:var(--bg-card);
  background-image:linear-gradient(168deg, rgba(46,58,76,.70) 0%, rgba(20,26,35,.78) 46%, rgba(11,15,20,.84) 100%);
  border:1px solid rgba(255,255,255,.13);
  border-radius:10px;
  box-shadow:inset 0 1px 0 rgba(255,255,255,.28), 0 30px 70px -30px rgba(0,0,0,.95);
}
.sent__eye{
  margin:0 0 12px;
  font:700 clamp(12px,1.1vw,13px)/1 var(--mono, ui-monospace, Menlo, monospace);
  letter-spacing:.2em; text-transform:uppercase; color:var(--accent-ink);
}
.sent__h{
  margin:0 0 14px;
  font:400 clamp(23px,3vw,34px)/1.08 var(--disp);
  text-transform:uppercase; letter-spacing:.005em; text-wrap:balance;
}
.sent__b{
  margin:0 0 clamp(22px,2.6vw,28px);
  font:400 clamp(15px,1.4vw,16.5px)/1.6 var(--body);
  color:var(--steel);
}
.sent__row{display:flex; gap:12px; flex-wrap:wrap}
.sent__row .btn{min-height:50px}

@media (prefers-reduced-motion: no-preference){
  .sent[open]{animation:sent-in .22s cubic-bezier(.16,.84,.44,1)}
  @keyframes sent-in{from{opacity:0; transform:translateY(10px) scale(.985)} to{opacity:1; transform:none}}
}

/* ---- page heroes: taller, and dissolved over a longer run ---------------- */
/* site.css sets these at clamp(340px,46vh,520px), which on a laptop resolves to
 * about 414px - a letterbox strip of a photograph rather than a photograph. The
 * frames are 4:3 and were being cropped to roughly 3.5:1. More of the picture
 * shows now, without the hero taking the whole screen and pushing the page
 * below the fold. */
.phero{min-height:clamp(380px,46vh,520px)}
@media (max-width:760px){ .phero{min-height:clamp(300px,42vh,380px)} }

/* And the dissolve runs over 220px instead of 120. At 120 the photograph was
 * still carrying visible texture within a few pixels of its own edge, so it
 * read as a cut with a soft top rather than as a fade. The extra stops keep the
 * ramp from showing a corner where it meets the page. */
.phero__img,
.phero__scrim{
  -webkit-mask-image:linear-gradient(to top,
    transparent 0, rgba(0,0,0,.18) 46px, rgba(0,0,0,.55) 108px,
    rgba(0,0,0,.86) 168px, #000 220px);
          mask-image:linear-gradient(to top,
    transparent 0, rgba(0,0,0,.18) 46px, rgba(0,0,0,.55) 108px,
    rgba(0,0,0,.86) 168px, #000 220px);
}

/* ---- page heroes: something to dissolve INTO ----------------------------- */
/* The home page hero works because a strip of light sits under it: the
 * photograph fades onto a surface, and that surface then fades into the page.
 * The inner heroes had no such surface, so however long the fade ran, a
 * midtone photograph was resolving straight to near-black - which the eye reads
 * as a cut no matter how gradual it is. Length was never going to fix it.
 *
 * So they get the same construction, in steel rather than the near-white the
 * strip uses. That one is pale because eight logos have to be legible on it;
 * there is nothing sitting on this, so it only has to be light enough to catch
 * the foot of the photograph.
 *
 * No opaque base, for the same reason as the strip: a solid ground here would
 * hide the drawing sheet and cut it back in the moment the band ended. */
:root{--pseam:clamp(150px,17vw,250px)}

.phero{position:relative; z-index:2}
.phero__img,
.phero__scrim{
  -webkit-mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.20) calc(var(--pseam) * .22),
    rgba(0,0,0,.58) calc(var(--pseam) * .52),
    rgba(0,0,0,.88) calc(var(--pseam) * .80), #000 var(--pseam));
          mask-image:linear-gradient(to top, transparent 0,
    rgba(0,0,0,.20) calc(var(--pseam) * .22),
    rgba(0,0,0,.58) calc(var(--pseam) * .52),
    rgba(0,0,0,.88) calc(var(--pseam) * .80), #000 var(--pseam));
}

.pherofade{
  position:relative; z-index:1; pointer-events:none;
  margin-top:calc(-1 * var(--pseam));
  height:calc(var(--pseam) + clamp(70px,7vw,120px));
  /* The band has to be strong BEFORE the photograph starts thinning, not after.
     Weak at the top, the drawing sheet behind it read straight through the
     half-faded photograph - blueprint lines across a jobsite picture, which is
     the part that looked cheap. It is near-solid across the whole overlap now,
     so the photograph dissolves onto steel and onto nothing else, and it only
     starts letting go once the photograph has gone. Below that it falls away
     over another 120px, so the sheet returns to the page too gradually to
     catch the eye. */
  /* Mid steel, not light steel. Opacity is what hides the sheet, but the first
     pass used a pale grey at that opacity and the result was a bright panel
     sitting under every hero - the same cheap slab the home page strip was
     until the light moved. A darker steel at the same opacity hides the sheet
     just as completely and reads as haze the photograph sinks into. */
  background:linear-gradient(to bottom,
    rgba(84,96,114,.60)   0,
    rgba(104,116,134,.88) calc(var(--pseam) * .34),
    rgba(116,128,146,.94) calc(var(--pseam) * .72),
    rgba(110,122,140,.92) var(--pseam),
    rgba(80,92,110,.55)   72%,
    rgba(40,48,60,.18)    89%,
    rgba(14,16,19,0)      100%);
}

/* The headline reveal was the slowest on the site: a full second, against .7-.8
 * for everything around it, on the largest elements on the page. Even once the
 * trigger was fixed it still arrived after the text and images beside it. */
.anim [data-anim=wipe]{transition-duration:.55s, .55s}
