/* Metal Craft Engineering — site styles. Plain CSS, no build step. */
/* slop-ok: default-font Geist is the owner's chosen brand face, carried through consistently */

@font-face{font-family:"Geist";src:url(assets/fonts/geist-latin.woff2) format("woff2");font-weight:100 900;font-style:normal;font-display:swap}
@font-face{font-family:"Geist Mono";src:url(assets/fonts/geist-mono-latin.woff2) format("woff2");font-weight:100 900;font-style:normal;font-display:swap}

/* Theme tokens. Every colour and chamfer in this file comes from this table (and the Railway Bridges block after it),
   so a theme is an override of these names in a stylesheet linked after this one. Two families of accent:
   - --accent, --accent-2 and the --accent-a* alphas colour the interface: buttons, links, the nav underline, the focus
     ring, selected and targeted items, field focus and errors, the page-head wash. A theme sets them in both :root and
     html[data-segment="bridges"] (that rule is more specific than :root), so the division switch still works.
   - --red, --red-2 and --warm are the hot metal: the Forge's brand colour, the carousel and scrolling-window
     embers and the reticle cursor read them live, so a theme leaves them alone unless it wants different embers. */
:root{
  --bg:#09090a;--bg-2:#0e0e10;--surface:#131316;--surface-2:#19191d;
  --line:rgba(255,255,255,.08);--line-2:rgba(255,255,255,.15);
  --txt:#f4f4f2;--txt-2:#b6b6bc;--txt-3:#8d8d95;
  --red:#e0243d;--red-2:#c41d34;
  /* The interface accent: the division's tone (the bridges rule below sets it again, so it follows the switch). */
  --accent:var(--red);--accent-2:var(--red-2);
  /* Fixed brand red: the header logo and the intro mark's fallback; never moved by a division tone. */
  --brand-red:#e0243d;
  /* Division tones, read by the entry chooser and the header tabs; html[data-segment] below copies one into --red. */
  --tone-engineering:#e0243d;
  --tone-bridges:#bd5a1a;
  /* The interface accent at the alphas the rules below use: page-head wash, row target (no rule reads a22 since the
     field focus became a plain edge; kept because the themes set it). */
  --accent-a05:rgba(224,36,61,.05);--accent-a10:rgba(224,36,61,.1);--accent-a22:rgba(224,36,61,.22);
  /* The lit client rows' wash (tabs.css): --accent-a08; --red-a08 is its old name, kept until tabs.css moves to the new
     one. */
  --accent-a08:rgba(224,36,61,.08);--red-a08:rgba(224,36,61,.08);
  /* The accent lightened for small text on the dark background (7:1 or more): the current division sub-row link. */
  --red-txt:#eb7081;
  /* Hot metal: the edge of a hovered plate and the far end of the nav's ember underline. */
  --warm:rgba(255,122,82,.6);
  /* Surfaces: under photos and video, the photo viewer, form fields, the phone menu, the label over a video, the
     photo viewer's backdrop, the opening screen, the footer band. */
  --media-bg:#050506;--dialog-bg:#0c0c0e;--field-bg:#0b0b0d;--menu-bg:rgba(9,9,10,.985);--chip-bg:rgba(9,9,10,.72);
  --backdrop:rgba(5,5,6,.9);--intro-bg:#070708;--ftr-bg:var(--bg);
  /* Text roles: the hero's subline, field placeholders, text on the accent (the selection, a hovered button). */
  --txt-hero:#d6d6da;--txt-ph:#7d7d85;--on-accent:#fff;
  /* Edges and washes: a hovered field's border, the keyboard focus ring, the page-head wash. */
  --field-edge-hover:rgba(255,255,255,.26);--focus:var(--accent);--wash:var(--accent-a05);
  /* Plate fills: a ghost plate, a hovered ghost plate, a hovered icon or viewer button, a ghost plate over footage. */
  --plate-ghost:rgba(255,255,255,.04);--plate-ghost-hover:rgba(255,255,255,.1);--plate-hover:rgba(255,255,255,.08);
  --plate-dark:rgba(18,18,21,.62);
  /* Fades: the background's channels, for the hero shades and the tile caption fade (rgba(var(--fade-rgb),alpha)). */
  --fade-rgb:9,9,10;
  /* Embers and the mark: the colour wash on footage, the intro mark's heat (its hover glow and scale were removed in
     round B3, QA Q7 item 13). */
  --media-tint:#8e1426;--ember-heat:rgba(255,110,60,.55);
  /* The reticle (restored 11:27): its spark core and the case under its stroke. */
  --spark-core:#ffe4c0;--ret-case:#09090a;
  /* Shadows: the opening screen's vignette, the photo viewer's drop shadow, the shader warm-up box (drawn at 1%). */
  --vignette:rgba(0,0,0,.6);--dialog-shadow:rgba(0,0,0,.6);--shader-warm:#000;
  /* Cut-corner plates (every button, tag, switch, card and panel): a rectangle with its top-left and bottom-right corners
     chamfered by --cut, as a cut steel plate. The cut scales with the plate (see Cut-corner plates below): 6 px on tags
     and small plates, 8 px on buttons, 12 px on cards and tiles, 14 px on panels, the form and large media. */
  --cut-sm:6px;--cut-md:8px;--cut-lg:12px;--cut-xl:14px;--cut:var(--cut-md);
  --sans:"Geist",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;
  --mono:"Geist Mono",ui-monospace,"SF Mono",Consolas,monospace;
  --ease:cubic-bezier(.2,.7,0,1);
  /* Fluid container: the gutter is 4vw up to 1400 px wide (as before), then 9vw - 70px, so content spans about 89% of a
     wide screen; capped at 160 px and a 3040 px column (2720 px of content). Grid gaps grow from 16 px past 1455 px. */
  --wrap:3040px;--gut:clamp(20px,max(4vw,9vw - 70px),160px);--gap:clamp(16px,1.1vw,28px);--hdr:72px
}
/* Railway Bridges division: oxide copper in place of the ember red, at the red's weight on the dark background (4.4:1
   against 4.3:1; white on it 4.5:1 against 4.7:1). Only the accent tokens move; --brand-red and the logo rules stay red.
   The hot-metal tone warms from coral to amber so it still reads as hotter than the accent. Set on <html> by boot.js / the tabs. */
html[data-segment="bridges"]{
  --red:#bd5a1a;--red-2:#a54c16;--accent:var(--red);--accent-2:var(--red-2);
  --accent-a05:rgba(189,90,26,.05);--accent-a10:rgba(189,90,26,.1);--accent-a22:rgba(189,90,26,.22);--red-txt:#d4946a;
  --accent-a08:rgba(189,90,26,.08);--red-a08:rgba(189,90,26,.08);
  --warm:rgba(255,178,96,.6)
}

/* Base */
*,*::before,*::after{box-sizing:border-box}
/* Root type: 16 px up to 1440 px wide or 810 px tall (every phone, tablet and laptop), then it follows the smaller of width and height, so
   a 2000x1060 screen gets 17.2 px and anything from 2560x1440 up 19 px (an ultrawide follows its height). Component
   sizes are in rem, so they scale with it. */
html{font-size:clamp(1rem,.75rem + min(.2777vw,.4938vh),1.1875rem);background:var(--bg);color-scheme:dark;-webkit-text-size-adjust:100%;scroll-padding-top:calc(var(--hdr) + 24px)}
body{margin:0;background:var(--bg);color:var(--txt);font-family:var(--sans);font-size:1rem;line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility}
img,video,svg,canvas{display:block;max-width:100%}
/* <picture> only adds a WebP source: it takes no box, so every img rule and grid below applies as if the img stood alone. */
picture{display:contents}
a{color:inherit;text-decoration:none}
h1,h2,h3,p,dl,dd,ul,ol,figure,blockquote{margin:0}
ul,ol{padding:0;list-style:none}
button,input,textarea,select{font:inherit;color:inherit}
::selection{background:var(--accent);color:var(--on-accent)}
:focus-visible{outline:2px solid var(--focus);outline-offset:3px}
.skip-link{position:fixed;left:16px;top:12px;z-index:400;padding:10px 16px;background:var(--txt);color:var(--bg);font-weight:500;transform:translateY(-200%);transition:transform .2s}
.skip-link:focus{transform:none}
.visually-hidden{position:absolute!important;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* slop-ok: outline-none replaced by designed :focus-visible (main only takes focus from the skip link, as a scroll target) */
main{display:block;transition:opacity .25s ease}main:focus{outline:none}
body.leaving main{opacity:0}
.wrap{width:100%;max-width:var(--wrap);margin-inline:auto;padding-inline:var(--gut)}

/* Type */
/* slop-ok: eyebrow original-site section labels kept verbatim by the wording rule; page categories and division names carry information */
.eyebrow{display:inline-flex;align-items:center;font:500 .75rem/1.2 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--txt-2)}
h1{font-size:clamp(2.6rem,6.4vw,5.8rem);font-weight:500;line-height:.98;letter-spacing:-.048em}
h2{font-size:clamp(2rem,4.2vw,3.5rem);font-weight:500;line-height:1.03;letter-spacing:-.038em}
h3{font-size:1.2rem;font-weight:500;line-height:1.3;letter-spacing:-.015em}
.lede{max-width:54ch;font-size:clamp(1.05rem,1.35vw,1.22rem);line-height:1.55;color:var(--txt-2)}
.dim{color:var(--txt-3)}
.line{display:block;overflow:hidden;padding-bottom:.08em;margin-bottom:-.08em}.line>span{display:block}

/* Cut-corner plates. Every button, the project filters, the icon and photo viewer buttons, the menu button (and the
   division tabs, in tabs.css) are one shape: --plate, clipped. The plate is filled with
   --fill and edged with --edge, --ew wide; the edge is drawn by the background itself (four straight lines and the two
   chamfers), so it follows the cut corners, and a translucent fill never doubles up under it. A hover or a focus changes
   only these values, which fade (@property below). Keyboard focus thickens the edge to 2 px, light on an outlined plate
   and in the accent on a filled one: an outline would be clipped away with the corners. */
@property --edge{syntax:"<color>";inherits:false;initial-value:transparent}
@property --fill{syntax:"<color>";inherits:false;initial-value:transparent}
@property --ew{syntax:"<length>";inherits:false;initial-value:1px}
/* The outline, cut by each plate's own --cut, so the chamfer scales with the plate: 6 px on tags and small plates, 8 px on
   buttons, 12 px on cards and tiles, 14 px on panels, the form and large media. Set on every plate (--cut inherits, and
   a button inside the form must not take the form's cut). :where() keeps all of this at zero specificity. */
:where(.btn,.filters button,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a,.chip,.cap,.feature-media,.proc-media,.media-pair>picture,.tile,.pitem,.gallery a,.panel,.form,.nav-dd-list){
  clip-path:polygon(var(--cut) 0,100% 0,100% calc(100% - var(--cut)),calc(100% - var(--cut)) 100%,0 100%,0 var(--cut))}
:where(.btn,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a,.nav-dd-list){--cut:var(--cut-md)}
:where(.chip,.filters button,.hdr-quote){--cut:var(--cut-sm)}
:where(.cap,.tile,.pitem,.gallery a){--cut:var(--cut-lg)}
:where(.feature-media,.proc-media,.media-pair>picture,.panel,.form){--cut:var(--cut-xl)}
/* Each element's own fill and edge below (and in tabs.css) apply over these. */
:where(.btn,.filters button,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a,.panel,.form,.nav-dd-list){
  --edge:transparent;--fill:transparent;--ew:1px;border:0;border-radius:0;
  background:linear-gradient(var(--edge),var(--edge)) top/100% var(--ew) no-repeat,linear-gradient(var(--edge),var(--edge)) bottom/100% var(--ew) no-repeat,
    linear-gradient(var(--edge),var(--edge)) left/var(--ew) 100% no-repeat,linear-gradient(var(--edge),var(--edge)) right/var(--ew) 100% no-repeat,
    linear-gradient(to bottom right,transparent 50%,var(--edge) 0 calc(50% + var(--ew)),transparent 0) top left/var(--cut) var(--cut) no-repeat,
    linear-gradient(to bottom right,transparent calc(50% - var(--ew)),var(--edge) 0 50%,transparent 0) bottom right/var(--cut) var(--cut) no-repeat,var(--fill);
  transition:--edge .25s,--fill .25s,color .25s}
/* slop-ok: outline-none replaced by designed :focus-visible (a 2 px light edge in the plate's own shape) */
:is(.btn,.filters button,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a):focus-visible{outline:none;--edge:var(--txt);--ew:2px}
:is(.btn:not(.btn-ghost),.filters button[aria-pressed="true"]):focus-visible{--edge:var(--accent)}
/* Photo plates (cards, media, tiles, gallery photos): the photo fills the plate, so the edge is drawn over it by ::before,
   with the same gradients as above (four straight lines and the two chamfers): --rim, --rim-w wide. Tiles and gallery
   photos have no edge at rest. Keyboard focus thickens the edge to 2 px, light, in the plate's own shape. */
:where(.cap,.feature-media,.proc-media,.pitem,.media-pair>picture,.tile,.gallery a){position:relative;--rim:var(--line);--rim-w:1px}
:where(.tile,.gallery a){--rim:transparent}
:is(.cap,.feature-media,.proc-media,.pitem,.media-pair>picture,.tile,.gallery a)::before{content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  background:linear-gradient(var(--rim),var(--rim)) top/100% var(--rim-w) no-repeat,linear-gradient(var(--rim),var(--rim)) bottom/100% var(--rim-w) no-repeat,
    linear-gradient(var(--rim),var(--rim)) left/var(--rim-w) 100% no-repeat,linear-gradient(var(--rim),var(--rim)) right/var(--rim-w) 100% no-repeat,
    linear-gradient(to bottom right,transparent 50%,var(--rim) 0 calc(50% + var(--rim-w)),transparent 0) top left/var(--cut) var(--cut) no-repeat,
    linear-gradient(to bottom right,transparent calc(50% - var(--rim-w)),var(--rim) 0 50%,transparent 0) bottom right/var(--cut) var(--cut) no-repeat}
:is(.cap,.tile,.gallery a):focus-visible,.pitem:has(>a:focus-visible){--rim:var(--txt);--rim-w:2px}
/* slop-ok: outline-none replaced by designed :focus-visible (the plate's own edge, 2 px and light, above) */
:is(.cap,.tile,.gallery a,.pitem>a):focus-visible{outline:none}
/* High Contrast: the system draws its own colours and drops the background; a plain bordered box with a normal outline. */
@media (forced-colors:active){:is(.btn,.filters button,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a,.chip,.cap,.feature-media,.proc-media,.media-pair>picture,.pitem,.panel,.form,.nav-dd-list){clip-path:none;border:1px solid ButtonText}:is(.btn,.filters button,.icon-btn,.menu-btn,.photo-viewer button,.photo-viewer a):focus-visible{outline:2px solid Highlight}}

/* Buttons and links */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:10px;height:3rem;padding:0 1.375rem;--fill:var(--txt);color:var(--bg);font-size:.95rem;font-weight:500;letter-spacing:-.01em;white-space:nowrap;cursor:pointer}
.btn:hover{--fill:var(--accent);color:var(--on-accent)}
.btn svg,.link svg{width:16px;height:16px;flex:none;transition:transform .3s var(--ease)}
.btn:hover svg,.link:hover svg{transform:translateX(3px)}
.btn-ghost{--fill:var(--plate-ghost);--edge:var(--line-2);color:var(--txt)}
.btn-ghost:hover{--fill:var(--plate-ghost-hover);--edge:var(--warm);color:var(--txt)}
.link{display:inline-flex;align-items:center;gap:8px;padding-bottom:5px;border-bottom:1px solid var(--line-2);font-weight:500;transition:border-color .25s}
.link:hover{border-color:var(--accent)}
/* In-text links inside running copy: the same underline as .prose and .info links, but inline so lines wrap normally. */
.tlink{color:inherit;border-bottom:1px solid var(--line-2);transition:border-color .25s}.tlink:hover{border-color:var(--accent)}
.actions{display:flex;flex-wrap:wrap;gap:12px}.page-actions{margin-top:36px}

/* Header */
.hdr{position:fixed;inset:0 0 auto;z-index:120;border-bottom:1px solid transparent;transition:background .35s,border-color .35s}
/* The scrolled header is solid (opaque, the page background colour, so nothing under it shows through) with no backdrop blur, on every page: a blur is redrawn on every scrolled
   frame (measured 13-19 ms of GPU time per second of scrolling, and about a fifth more over the live Forge canvas), and
   one look everywhere keeps the header still across the page cross-fade. */
.hdr.scrolled,body.menu-open .hdr{background:var(--bg);border-bottom-color:var(--line)}
.hdr-in{display:flex;align-items:center;gap:28px;height:var(--hdr)}
/* The logo, 18% larger from round B3 (owner, 25 Oct: "enlarge the logo somewhat in all sections"): the wordmark 1.05 to
   1.24rem, the header mark 44x22 to 52x26 px, the footer mark 38 to 45 px wide and the opening screen's no-WebGL mark
   (Home intro) all by the same step; the opening screen's 3D mark grows inside its canvas (logo3d.js, B6), so its box
   stays as it was. */
.logo{display:inline-flex;align-items:center;gap:17px;font-size:1.6rem;font-weight:600;letter-spacing:-.02em}   /* owner 26 Oct 16:50: logo bigger, header and footer (1.24rem -> 1.6rem) */
.logo svg{width:58px;height:auto;fill:var(--brand-red)}
/* Header mark: pre-rendered 3D frames of the intro mark (120-frame sway, 60-frame spin on hover), stepped by CSS alone.
   The sheets are 12x10 and 10x6 frames of 52x26 px (drawn at 2x, matted for a light ground and colour-graded in the
   file, so no CSS filter: A6, round B3; the sway and spin sheets lossy since v4, 99 and 48 KB, the still v3). app.js fetches the spin sheet once the page is idle, so the first hover spins
   at once. */
/* The sheet slides under a 44x22 window by transform (x on translate, y on transform), which the compositor steps on its
   own: a background-position animation would restyle, repaint and re-raster the header on the main thread 20 times a second. */
.mark3d{position:relative;flex:none;width:52px;height:26px;margin:0 -3.5px;overflow:hidden;zoom:1.3}   /* 52x26 sprite frames shown at 68x34 */
@media (min-width:961px) and (max-width:1439px){.hdr .logo{font-size:1.42rem;gap:15px}.hdr .mark3d{zoom:1.16}}   /* the one-line desktop header stays inside its width */
@media (min-width:961px) and (max-width:1099px){.hdr .logo{font-size:1.3rem;gap:14px}.hdr .mark3d{zoom:1.08}}
/* The first paint shows one still frame; the sway sheet takes over once app.js has it decoded (html.m3-live) and plays
   one full sway, 120 frames in 4.2 s, then rests on frame 0, which is the still and the sheet's first frame, so it ends
   without a jump (WCAG 2.2.2: nothing moves for more than 5 s after load; coordinator ruling, round B3). A hover or
   keyboard focus spins it once (1.2 s). */
.mark3d::before{content:"";position:absolute;left:0;top:0;width:52px;height:26px;background:url(assets/img/mark3d-still-v3.webp) 0 0/52px 26px no-repeat}
html.m3-live .mark3d::before{width:624px;height:260px;background:url(assets/img/mark3d-sway-v4.webp) 0 0/624px 260px no-repeat;animation:m3-x .42s steps(12) 10,m3-y 4.2s steps(10) 1}
/* Under a data-saving request (html.save-data, app.js) the scripts fetch neither sheet, so the hover spin is off too:
   it would fetch the spin sheet on its own; the mark keeps its still frame. */
html:not(.save-data) .logo:hover .mark3d::before,html:not(.save-data) .logo:focus-visible .mark3d::before{width:520px;height:156px;background-image:url(assets/img/mark3d-spin-v4.webp);background-size:520px 156px;translate:-468px;transform:translateY(-130px);animation:m3-sx .2s steps(10) 6,m3-sy 1.2s steps(6) 1}
@keyframes m3-x{from{translate:0}to{translate:-624px}}
@keyframes m3-y{from{transform:translateY(0)}to{transform:translateY(-260px)}}
@keyframes m3-sx{from{translate:0}to{translate:-520px}}
@keyframes m3-sy{from{transform:translateY(0)}to{transform:translateY(-156px)}}
@media (prefers-reduced-motion:reduce){:is(html.m3-live,html) :is(.mark3d,.logo:hover .mark3d,.logo:focus-visible .mark3d)::before{animation:none;width:52px;height:26px;background-image:url(assets/img/mark3d-still-v3.webp);background-size:52px 26px;translate:none;transform:none}}
/* The nav links are plain text. An ember bar (accent to hot metal, no glow) sits under the current page's link; on hover
   it sweeps in under the hovered link from its left and out to the right, and the current page's bar steps back while
   another link is hovered, so one bar shows at a time. The Certifications item (round B3.1) is a drop-down button
   (.nav-dd-btn) that takes the same look and bar; its bar shows while its list holds the current page (.is-current). */
.nav{position:relative;display:flex;gap:2px;margin-left:auto}
.nav a,.nav-dd-btn{position:relative;padding:.5rem .875rem;font-size:.92rem;color:var(--txt-2);transition:color .2s}
.nav a::after,.nav-dd-btn::after{content:"";position:absolute;left:.875rem;right:.875rem;bottom:.125rem;height:2px;background:linear-gradient(90deg,var(--accent),var(--warm));transform:scaleX(0);transform-origin:right;opacity:0;transition:transform .14s var(--ease),opacity .14s linear}
.nav a:hover,.nav a[aria-current="page"],.nav-dd-btn:hover,.nav-dd.is-current>.nav-dd-btn{color:var(--txt)}
.nav a:is(:hover,:focus-visible,[aria-current="page"])::after,.nav-dd-btn:is(:hover,:focus-visible)::after,.nav-dd.is-current>.nav-dd-btn::after{transform:none;transform-origin:left;opacity:1;transition:transform .35s var(--ease),opacity 0s}
.nav:hover a[aria-current="page"]:not(:hover)::after,.nav:hover .nav-dd.is-current>.nav-dd-btn:not(:hover)::after{transform:scaleX(0);opacity:0;transition:transform .14s var(--ease),opacity .14s linear}
/* From 961 to 1199 px tabs.css slims the nav links to .6rem a side, and to .45rem up to 1099 px; the bar keeps to the
   label. */
@media (min-width:961px) and (max-width:1199px){.hdr .nav a::after,.hdr .nav-dd-btn::after{left:.6rem;right:.6rem}.hdr .nav .nav-dd-btn::after{right:calc(.6rem + 12px + .3rem)}}
@media (min-width:961px) and (max-width:1099px){.hdr .nav a::after,.hdr .nav-dd-btn::after{left:.45rem;right:.45rem}.hdr .nav .nav-dd-btn::after{right:calc(.45rem + 12px + .3rem)}}
/* The Certifications drop-down (round B3.1, app.js; contract B7-CONTRACTS.md "nav"). The item carries a small chevron
   (an inline stroke, no icon font) that turns while the list is open; the list hangs from the item as an outlined plate
   on the board (the 3:1 control edge, the button cut, 8 px), its text lined up with the item's, one 44 px row per page,
   the current page's in the accent. A 6 px strip under the item (.nav-dd::after) keeps the hover alive on the way down
   to the list. Scripts make the item a button (aria-expanded) and hide the list while it is closed ([hidden]); without
   them the item stays a link to Certifications and the list opens on hover or keyboard focus inside it. */
.nav-dd{position:relative;display:flex}
.nav-dd::after{content:"";position:absolute;left:0;right:0;top:100%;height:6px}
.nav-dd-btn{display:inline-flex;align-items:center;gap:.3rem;margin:0;border:0;background:none;font-family:inherit;line-height:inherit;cursor:pointer}
.nav .nav-dd-btn::after{right:calc(.875rem + 12px + .3rem)}
.nav-dd-chev{width:12px;height:12px;flex:none;transition:rotate .2s var(--ease)}
.nav-dd.is-open .nav-dd-chev{rotate:180deg}
.nav-dd-list{position:absolute;left:0;top:calc(100% + 6px);z-index:1;display:grid;min-width:100%;padding:6px 0;--edge:var(--line-3);--fill:var(--bg)}
.nav-dd-list[hidden]{display:none}
.nav .nav-dd-list a{display:flex;align-items:center;min-height:44px;padding:0 1.5rem 0 .875rem;white-space:nowrap;transition:color .2s,background-color .2s}
.nav .nav-dd-list a::after{content:none}
.nav .nav-dd-list a:hover{background-color:var(--plate-hover)}
.nav .nav-dd-list a[aria-current="page"]{color:var(--accent-txt)}
.nav .nav-dd-list a:focus-visible{outline-offset:-3px}
html:not(.js) .nav-dd-list{display:none}
html:not(.js) .nav-dd:is(:hover,:focus-within) .nav-dd-list{display:grid}
/* The phone menu's group (.menu-dd): the item is a menu row with the chevron at its end, open by default; its two pages
   follow as an indented group in a smaller size, each row 44 px. */
.menu-dd-btn{display:flex;justify-content:space-between;align-items:center;width:100%;margin:0;padding:10px 0;border:0;background:none;font:500 clamp(1.75rem,min(8vw,6.5svh),3rem)/1.2 var(--sans);letter-spacing:-.04em;color:inherit;text-align:left;cursor:pointer}
.menu-dd-btn svg{width:22px;height:22px;flex:none;color:var(--txt-3);transition:rotate .2s var(--ease)}
.menu-dd.is-open .menu-dd-btn svg{rotate:180deg}
.menu-dd-list{display:grid;padding:0 0 10px 1.25rem}
.menu-dd-list[hidden]{display:none}
.menu nav .menu-dd-list a{min-height:44px;padding:4px 0;font-size:clamp(1.25rem,min(5.5vw,4.5svh),1.75rem);letter-spacing:-.025em}
/* The header quote plate. Below 961 px it is in the menu (Responsive); from 961 to 1239 px tabs.css hides it, so the
   bar with the division tabs keeps to one line. */
.hdr-quote{flex:none;height:2.5rem;padding:0 1rem;font-size:.88rem}
.menu-btn{display:none;place-items:center;width:44px;height:44px;margin-left:auto;padding:0;--fill:var(--plate-ghost);--edge:var(--line-2);cursor:pointer}
.menu-btn:hover{--edge:var(--warm)}
.menu-btn span{grid-area:1/1;display:block;width:16px;height:1.5px;background:currentColor;transition:transform .3s var(--ease)}
.menu-btn span:first-child{transform:translateY(-3px)}.menu-btn span:last-child{transform:translateY(3px)}
.menu-btn[aria-expanded="true"] span:first-child{transform:rotate(45deg)}.menu-btn[aria-expanded="true"] span:last-child{transform:rotate(-45deg)}
/* No backdrop blur under the open menu: behind a 98.5% opaque panel it cannot be seen, yet it would re-blur the whole
   screen for every frame of the fade and of any video still playing underneath. */
.menu{position:fixed;inset:0;z-index:110;display:flex;flex-direction:column;justify-content:space-between;gap:40px;padding:calc(var(--hdr) + 32px) var(--gut) 40px;background:var(--menu-bg);overflow-y:auto;opacity:0;visibility:hidden;transition:opacity .3s,visibility .3s}
.menu.open{opacity:1;visibility:visible;transition:opacity .3s,visibility 0s}
/* The link type also follows the screen height, so the whole menu and its quote button fit on short phones. */
/* An arrow, where a menu link carries one. */
.menu nav a svg{width:22px;height:22px;flex:none;color:var(--txt-3)}
.menu nav a{display:flex;justify-content:space-between;align-items:center;padding:10px 0;font-size:clamp(1.75rem,min(8vw,6.5svh),3rem);font-weight:500;line-height:1.2;letter-spacing:-.04em}
.menu nav a[aria-current="page"]{color:var(--accent)}
.menu-foot{display:grid;gap:10px;justify-items:start;color:var(--txt-2)}
body.menu-open{overflow:hidden}

/* Home hero */
.hero{position:relative;isolation:isolate;display:flex;flex-direction:column;justify-content:flex-end;min-height:100svh;overflow:hidden}
.hero-media{position:absolute;inset:0;z-index:-2;background:var(--media-bg)}
.hero-media video,.hero-media img{width:100%;height:100%;object-fit:cover;filter:brightness(.74) contrast(1.05)}
/* The bridges projects header shows its poster under the video (a 58 KB centre crop on phones, above 640 px the still
   at 1920 x 1080, so that the poster and not the video's first frame stays the page's largest paint; each preloaded for
   its screens in the page's head); the video, transparent until its first frame, covers it. */
.hero-media>video{position:absolute;inset:0}
.hero-media--work::before{content:"";position:absolute;inset:0;background:url(assets/img/projects-loop-poster-1920-v1.webp) 50% 50%/cover no-repeat;filter:brightness(.74) contrast(1.05)}
@media (max-width:640px){.hero-media--work::before{background-image:url(assets/img/projects-loop-poster-p-v1.webp)}}
.hero-media::after{content:"";position:absolute;inset:0;background:var(--media-tint);mix-blend-mode:color;opacity:.32}
.hero-shade{position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,rgba(var(--fade-rgb),.6) 0%,rgba(var(--fade-rgb),0) 26%,rgba(var(--fade-rgb),.12) 52%,rgba(var(--fade-rgb),.94) 100%),linear-gradient(90deg,rgba(var(--fade-rgb),.82) 0%,rgba(var(--fade-rgb),.5) 42%,rgba(var(--fade-rgb),0) 78%)}
.hero-in{padding-top:calc(var(--hdr) + 80px);padding-bottom:clamp(72px,13vh,140px)}
.hero h1{max-width:none;margin:24px 0 22px;font-size:clamp(2.8rem,6.6vw,6.6rem);line-height:.95;letter-spacing:-.055em}
.hero-sub{font-size:clamp(1.25rem,2.1vw,1.85rem);font-weight:400;line-height:1.3;letter-spacing:-.025em;color:var(--txt-hero)}
.hero .actions{margin-top:36px}
/* Over footage, the loop and the closing call to action (and in the header, which is clear over the hero) a ghost
   plate takes a darker fill, so its label stays legible without any blur. */
:is(.hero,.cta,.hdr) .btn-ghost:not(:hover){--fill:var(--plate-dark)}
/* Home hero over the Forge loop (forge/engine.js frames the loop's subject into the space the text leaves).
   Landscape: the text block is centred in the height under the header, and the subject sits beside it, centred on it,
   within the content column (which is fluid, so on wide screens the subject grows; the engine trims its region to
   about the screen height). Portrait: the text stays low and the band under the header holds the subject. */
@media (orientation:landscape){
  .forge~main .hero{justify-content:center}
  .forge~main .hero-in{padding-top:calc(var(--hdr) + clamp(16px,4vh,48px));padding-bottom:clamp(16px,4vh,48px)}
  /* The headline stops growing a little earlier, so the two columns stay about even on wide screens. */
  .forge~main .hero h1{font-size:clamp(2.8rem,6.6vw,5.8rem)}
  /* The shade keeps the text legible and clears before the subject beside it. */
  .forge~main .hero-shade{background:linear-gradient(180deg,rgba(var(--fade-rgb),.55) 0%,rgba(var(--fade-rgb),0) 16%,rgba(var(--fade-rgb),0) 80%,rgba(var(--fade-rgb),.9) 100%),linear-gradient(90deg,rgba(var(--fade-rgb),.75) 0%,rgba(var(--fade-rgb),.45) 35%,rgba(var(--fade-rgb),0) 55%)}
}
/* Short landscape screens (phones on their side): the type steps down so the whole block fits under the header. */
@media (orientation:landscape) and (max-height:560px){
  .forge~main .hero h1{margin:14px 0 12px;font-size:clamp(2rem,10.5vh,6.6rem)}
  .forge~main .hero-sub{font-size:clamp(1rem,4.6vh,1.85rem)}
  .forge~main .hero .actions{margin-top:18px}
}
/* Wider landscape screens: the subject's region starts right after the text, so the shade follows the text instead of
   the screen. --tx is the text's right edge (the column's left edge plus 6.7 x the headline size: the widest line is
   6.67 em); the side shade is about .13 there, as before, and clears where the subject's box begins. The bottom fade
   starts at 86% so tall scenes are not swallowed by it. */
@media (orientation:landscape) and (min-width:1200px) and (min-height:561px){
  .forge~main .hero{--tx:calc(max(var(--gut),50% - var(--wrap) / 2 + var(--gut)) + clamp(2.8rem,6.6vw,5.8rem) * 6.7)}
  .forge~main .hero-shade{background:linear-gradient(180deg,rgba(var(--fade-rgb),.55) 0%,rgba(var(--fade-rgb),0) 16%,rgba(var(--fade-rgb),0) 86%,rgba(var(--fade-rgb),.9) 100%),linear-gradient(90deg,rgba(var(--fade-rgb),.75) 0%,rgba(var(--fade-rgb),.45) calc(var(--tx) * .7),rgba(var(--fade-rgb),0) calc(var(--tx) + clamp(40px,5vw,96px)))}
}
@media (orientation:portrait){
  .forge~main .hero-in{padding-bottom:max(clamp(24px,4.5vh,64px),env(safe-area-inset-bottom))}
  .forge~main .hero-shade{background:linear-gradient(180deg,rgba(var(--fade-rgb),.55) 0%,rgba(var(--fade-rgb),0) 12%,rgba(var(--fade-rgb),0) 48%,rgba(var(--fade-rgb),.9) 100%)}
}

/* Page heads */
.page-head{position:relative;isolation:isolate;padding:calc(var(--hdr) + clamp(72px,12vh,140px)) 0 clamp(56px,8vw,104px);border-bottom:1px solid var(--line);overflow:hidden}
.page-head:not(.page-head--media)::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--wash)}
.page-head h1{max-width:15ch;margin:24px 0}
.page-head .lede{max-width:52ch}
/* Breadcrumb (round B3.1; only the Vendor Approval RDSO page has one, B1): a small line at the top of the page head,
   above the eyebrow, in --txt-2 (6.5:1 on the gridded wash), the current page in --txt; links underlined on hover in the
   page's tone; the separators are plain glyphs (B1's spans), spaced by the gap; it wraps on a phone. On touch screens
   each link has a 44 px tall tap area without moving the line. */
.crumbs{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .45em;margin-bottom:20px;font-size:.85rem;line-height:1.5;color:var(--txt-2)}
.crumbs a{text-decoration:underline 1px transparent;text-underline-offset:3px;transition:text-decoration-color .2s}
.crumbs a:hover,.crumbs a.touch-hover{text-decoration-color:var(--accent)}
.crumbs [aria-current="page"]{color:var(--txt)}
@media (pointer:coarse){.crumbs a{position:relative}.crumbs a::before{content:"";position:absolute;inset:-12px -4px}}
/* Touch screens (Q16): arrow text links are 44 px tall boxes, the short footer links 44 px wide. */
@media (pointer:coarse){.link{min-height:44px}.ftr li a{min-width:44px}}
@media (max-width:640px){.crumbs{font-size:14px}}
.page-head--media{display:flex;align-items:flex-end;min-height:78svh}
.page-head--media .hero-media::after{opacity:.28}
/* Stretched renders. A renderer that stretches its viewport to the page height (as Google's does) would make the hero
   and this head grow with every stretch. A viewport at least 4000 px tall and at least twice as tall as wide is such a
   render; a visitor's window gets there only as a narrow column on a tall portrait monitor zoomed out (to 50% on a
   1440 x 2560 one, or to 90% on a 4K one run without display scaling). There the hero stops at about one screen for
   its width (a phone's height at 412 px wide, 1080 px at 1920 px wide) and this head at 78% of that. */
@media (min-height:4000px) and (max-aspect-ratio:1/2){
  .hero{min-height:max(56.25vw,min(216vw,915px))}
  .page-head--media{min-height:calc(max(56.25vw,min(216vw,915px)) * .78)}
}

/* Sections */
.sec{padding:clamp(88px,11vw,168px) 0}
.sec--line{border-top:1px solid var(--line)}
.sec--alt{background:var(--bg-2);border-top:1px solid var(--line);border-bottom:1px solid var(--line)}
.sec-head{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:24px 64px;margin-bottom:clamp(40px,5vw,72px)}
.sec-head h2{margin-top:18px;max-width:16ch}
.sec-head .lede{max-width:44ch}
.statement{max-width:30ch;font-size:clamp(1.7rem,3.5vw,3.1rem);font-weight:400;line-height:1.16;letter-spacing:-.034em}
.statement .dim{color:var(--txt-3)}
.statement-grid{display:grid;grid-template-columns:minmax(180px,.35fr) 1fr;gap:40px;align-items:start}

/* Capability cards */
.caps{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--gap)}
.cap{display:flex;flex-direction:column;overflow:hidden;background:var(--surface)}
.cap-img{aspect-ratio:4/5;overflow:hidden;background:var(--media-bg)}
.cap-img img{width:100%;height:100%;object-fit:cover;transition:transform 1.4s var(--ease)}
.cap:hover .cap-img img{transform:scale(1.05)}
.cap-body{display:grid;gap:8px;padding:1.375rem 1.375rem 1.625rem}
/* slop-ok: numbered-markers the numbers follow the owner's dictated list order, not a process sequence */
.cap-n{font:500 .72rem var(--mono);letter-spacing:.06em;color:var(--txt-3)}
.cap p{font-size:.95rem;line-height:1.55;color:var(--txt-2)}

/* Feature: media beside copy */
.feature{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);gap:clamp(36px,6vw,6rem);align-items:center}
.feature-media{overflow:hidden;aspect-ratio:16/10;background:var(--media-bg)}
.feature-media video,.feature-media img{width:100%;height:100%;object-fit:cover}
/* No backdrop blur on the label over the video: it would be re-blurred on every video frame. A darker glass instead. */
.chip{position:absolute;left:16px;top:16px;z-index:1;display:inline-flex;align-items:center;padding:7px 12px;background:var(--chip-bg);font:500 .7rem/1 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--txt)}
.feature-copy h2{margin:18px 0 20px}.who-title{margin:18px 0 24px}
.feature-copy .lede{margin-bottom:28px}
.ticks{margin-bottom:32px;border-bottom:1px solid var(--line)}
.ticks li{position:relative;padding:14px 0 14px 26px;border-top:1px solid var(--line);color:var(--txt-2)}
.ticks li::before{content:"";position:absolute;left:0;top:1.35em;width:12px;height:1px;background:var(--line-2)}
/* The pair belongs to the process above it: that process gives up its bottom padding (below) and the pair carries it. */
.media-pair{display:grid;grid-template-columns:1fr 1fr;gap:var(--gap);margin-top:var(--gap);margin-bottom:clamp(64px,8vw,112px)}
/* The picture is the grid item here (it fades and rises as a [data-stagger] child), so it keeps a box. */
.media-pair>picture{display:block}
/* height:auto: the img's height attribute would otherwise override the 3/2 box (it drew 1066 px tall). */
.media-pair img{width:100%;height:auto;aspect-ratio:3/2;object-fit:cover}

/* Numbers */
.stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));border-top:1px solid var(--line-2)}
.stat{padding:30px 24px 0}
.stat+.stat{border-left:1px solid var(--line)}
.stat:first-child{padding-left:0}
.stat-v{display:flex;align-items:baseline;gap:8px;white-space:nowrap}
.stat-n{font-size:clamp(2.6rem,5.2vw,4.8rem);font-weight:400;line-height:1;letter-spacing:-.05em;font-variant-numeric:tabular-nums}
.stat-u{font:500 .78rem var(--mono);letter-spacing:.04em;color:var(--txt-2)}
.stat p{margin-top:14px;max-width:24ch;font-size:.92rem;color:var(--txt-3)}

/* Project tiles */
.tiles{display:grid;grid-template-columns:1.35fr 1fr;grid-template-rows:repeat(2,minmax(240px,1fr));gap:var(--gap)}
.tile{position:relative;isolation:isolate;display:block;overflow:hidden;min-height:240px;background:var(--media-bg)}
.tile:first-child{grid-row:span 2;min-height:clamp(520px,min(36vw,62vh),900px)}
.tile img{position:absolute;inset:0;z-index:-2;width:100%;height:100%;object-fit:cover;transition:transform 1.4s var(--ease)}
.tile::after{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,transparent 45%,rgba(var(--fade-rgb),.86))}
.tile:hover img{transform:scale(1.04)}
.tile-cap{position:absolute;left:0;right:0;bottom:0;display:flex;align-items:flex-end;justify-content:space-between;gap:16px;padding:1.5rem}
.tile-cap small{display:block;margin-bottom:6px;font:500 .7rem var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--txt-2)}
.tile-cap .tile-t{font-size:1.2rem}
.tile-cap svg{width:18px;height:18px;flex:none}

/* Client logos */
.logos{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));border-top:1px solid var(--line);border-left:1px solid var(--line)}
.logos>*{display:grid;place-items:center;min-height:7.375rem;padding:1.375rem;border-right:1px solid var(--line);border-bottom:1px solid var(--line);transition:background .3s}
.logos a:hover{background:var(--surface)}
/* The logo files are pre-graded white on transparent, so no filter or blend here:
   those cost a filter pass and an isolated layer per logo. height:auto undoes the img's height attribute. */
.logos img{width:auto;height:auto;max-width:100%;max-height:2.625rem;object-fit:contain;opacity:.86}
.logos b{font-size:1rem;font-weight:600;letter-spacing:.02em;color:var(--txt-2)}
/* Clientele, Railway Bridges (round B3, anti-slop 10): the one client logo as a single cell of the wall, at the left;
   the other clients follow as a linked line of full names (.logo-also). */
.logos.logos--few{grid-template-columns:minmax(0,14rem);width:fit-content;max-width:100%}
/* The line of the clients with no logo, under the home logo strip and the Clientele wall (anti-slop 11). */
.logo-also{max-width:75ch;margin-top:16px;color:var(--txt-2)}
.logo-also+:is(.rows,.win){margin-top:var(--gap)}

/* Call to action */
.cta{position:relative;isolation:isolate;padding:clamp(96px,12vw,176px) 0;border-top:1px solid var(--line);overflow:hidden}
.cta h2{max-width:18ch;margin:18px 0 32px}.cta h2:first-child{margin-top:0}

/* Footer */
.ftr{padding:4.5rem 0 2rem;border-top:1px solid var(--line);background:var(--ftr-bg);font-size:.92rem;color:var(--txt-2)}
.ftr-grid{display:grid;grid-template-columns:1.5fr repeat(3,1fr);gap:40px}
.ftr-brand p{max-width:32ch;margin-top:18px;color:var(--txt-3)}
.ftr :is(h2,.ft-title){min-height:2.4em;margin-bottom:16px;font:500 .72rem/1.2 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--txt-3)}
.ftr li+li{margin-top:10px}
.ftr a{transition:color .2s}.ftr a:hover{color:var(--txt)}
.ftr address{font-style:normal;line-height:1.7}.ftr address+ul{margin-top:12px}
.ftr-base{display:flex;flex-wrap:wrap;justify-content:space-between;gap:12px 24px;margin-top:4rem;padding-top:24px;border-top:1px solid var(--line);font-size:.85rem;color:var(--txt-3)}

/* Capabilities page */
.proc{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(36px,6vw,6rem);align-items:center;padding:clamp(64px,8vw,112px) 0;border-top:1px solid var(--line)}
.proc:first-child{border-top:0;padding-top:0}
.proc:has(+.media-pair){padding-bottom:0}
/* Of type: the .media-pair between two processes must not break the alternation. */
.proc:nth-of-type(even) .proc-media{order:2}
.proc-media{overflow:hidden;aspect-ratio:4/3;background:var(--media-bg)}
.proc-media img,.proc-media video{width:100%;height:100%;object-fit:cover}
.proc-media--small{width:100%;max-width:500px;justify-self:center}
.proc h2{margin:18px 0 20px;font-size:clamp(1.9rem,3.4vw,2.9rem)}
.proc .lede{margin-bottom:28px}

/* Projects page */
.filters{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:32px}
.filters button{height:2.5rem;padding:0 1.125rem;--edge:var(--line-2);color:var(--txt-2);font-size:.9rem;cursor:pointer}
.filters button:hover{color:var(--txt);--edge:var(--warm)}
.filters button[aria-pressed="true"]{--fill:var(--txt);--edge:var(--txt);color:var(--bg)}
.pgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap)}
.pitem{overflow:hidden;background:var(--surface);scroll-margin-top:calc(var(--hdr) + 24px)}
.pitem[hidden]{display:none}
.pitem>a{display:block;aspect-ratio:5/4;overflow:hidden;background:var(--media-bg)}
.pitem img{width:100%;height:100%;object-fit:cover;transition:transform 1.2s var(--ease)}
.pitem>a:hover img{transform:scale(1.04)}
.pitem-body{display:grid;gap:6px;padding:1.125rem 1.25rem 1.375rem}
.meta{display:flex;justify-content:space-between;gap:12px;font:500 .7rem/1.4 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--txt-3)}
/* In Clientele rows and project cards the meta line reads as words: the text face, as written, untracked. */
:where(.rowi,.pitem) .meta{font:500 .82rem/1.4 var(--sans);letter-spacing:0;text-transform:none;font-variant-numeric:tabular-nums}
.pitem :is(h3,.pitem-t){font-size:1.05rem}
.pitem-t,.rowi-t{display:block;font-weight:500;line-height:1.3;letter-spacing:-.015em;color:var(--txt)}
.pitem p{font-size:.92rem;color:var(--txt-2)}
.pitem:target{--rim:var(--accent)}
.gallery{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap)}
.gallery a{display:block;overflow:hidden;aspect-ratio:4/3;background:var(--media-bg)}
.gallery img{width:100%;height:100%;object-fit:cover;transition:transform 1.2s var(--ease)}
.gallery a:hover img{transform:scale(1.04)}
.pgrid+.gallery{margin-top:var(--gap)}

/* Row lists (clients, orders). The clients page opens its first section with a lead paragraph, its name in bold. */
.sec-lead{margin-bottom:clamp(40px,5vw,72px)}
.sec-lead strong{font-weight:500;color:var(--txt)}
.rows{border-top:1px solid var(--line-2)}
.faq{border-top:1px solid var(--line-2);max-width:61.25rem}
.faq-i{border-bottom:1px solid var(--line)}
.faq-i summary{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:22px 0;cursor:pointer;list-style:none}
.faq-i summary::-webkit-details-marker{display:none}
.faq-i summary::after{content:"";flex:none;width:12px;height:12px;border-right:1.5px solid var(--txt-2);border-bottom:1.5px solid var(--txt-2);transform:translateY(-3px) rotate(45deg);transition:transform .3s var(--ease)}
.faq-i[open] summary::after{transform:translateY(3px) rotate(-135deg)}
.faq-i summary :is(h3,.faq-q){font-size:1.08rem}
.faq-i summary:hover :is(h3,.faq-q){color:var(--txt)}
.faq-i p{max-width:70ch;padding:0 0 24px;color:var(--txt-2)}
.rowi{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.6fr) 8.75rem;gap:24px;align-items:baseline;padding:22px 0;border-bottom:1px solid var(--line);scroll-margin-top:calc(var(--hdr) + 24px)}
.rowi :is(h3,.rowi-t){font-size:1.08rem}
.rowi p{color:var(--txt-2)}
.rowi .meta{justify-content:flex-end;text-align:right}
.rowi:target{background:linear-gradient(90deg,var(--accent-a10),transparent 70%)}
.rowi a.link{font-size:.85rem}
.rowi p .link{margin-left:.5em}
.rowi p .link+.link{margin-left:.75em}

/* About page */
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:clamp(36px,6vw,6rem);align-items:start}
.facts>div{display:grid;grid-template-columns:8.75rem 1fr;gap:20px;padding:18px 0;border-top:1px solid var(--line)}
.facts>div:last-child{border-bottom:1px solid var(--line)}
.facts dt{font:500 .72rem/1.9 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--txt-3)}
.facts dd{color:var(--txt-2)}
/* The About fact card (round B3, Q1-3): the facts on one 14 px cut plate, edged 1 px in its own shape, with no height
   cap (it was a scrolling window that clipped its last rows). The plate is the frame, so the list loses its outer rules. */
.facts-card{--cut:var(--cut-xl);--edge:var(--line);--fill:var(--surface);--ew:1px;padding:4px clamp(20px,2.6vw,36px);
  clip-path:polygon(var(--cut) 0,100% 0,100% calc(100% - var(--cut)),calc(100% - var(--cut)) 100%,0 100%,0 var(--cut));
  background:linear-gradient(var(--edge),var(--edge)) top/100% var(--ew) no-repeat,linear-gradient(var(--edge),var(--edge)) bottom/100% var(--ew) no-repeat,
    linear-gradient(var(--edge),var(--edge)) left/var(--ew) 100% no-repeat,linear-gradient(var(--edge),var(--edge)) right/var(--ew) 100% no-repeat,
    linear-gradient(to bottom right,transparent 50%,var(--edge) 0 calc(50% + var(--ew)),transparent 0) top left/var(--cut) var(--cut) no-repeat,
    linear-gradient(to bottom right,transparent calc(50% - var(--ew)),var(--edge) 0 50%,transparent 0) bottom right/var(--cut) var(--cut) no-repeat,var(--fill)}
.facts-card .facts>div:first-child{border-top:0}
.facts-card .facts>div:last-child{border-bottom:0}
@media (forced-colors:active){.facts-card{clip-path:none;border:1px solid CanvasText}}
@media print{.facts-card{clip-path:none;background:none;padding:0}}
.panels{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap)}
.panel{padding:1.875rem;--edge:var(--line);--fill:var(--surface)}
.panel h3{margin-bottom:12px}
.panel p{max-width:52ch;color:var(--txt-2)}
.carousel{position:relative}
.car-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;scrollbar-width:none;border:1px solid var(--line);background:var(--media-bg)}
.car-track::-webkit-scrollbar{display:none}
.car-slide{position:relative;flex:0 0 100%;margin:0;scroll-snap-align:start}
.car-slide img{width:100%;height:clamp(280px,54vw,660px);object-fit:contain}
.car-ctrl{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:20px}
.car-cap{font-size:.95rem;color:var(--txt-2)}
.car-btns{display:flex;align-items:center;gap:14px}
.car-count{font:500 .8rem var(--mono);color:var(--txt-3);font-variant-numeric:tabular-nums;white-space:nowrap}
.icon-btn{display:grid;place-items:center;width:3rem;height:3rem;padding:0;--edge:var(--line-2);cursor:pointer}
.icon-btn:hover{--fill:var(--plate-hover);--edge:var(--warm)}
.icon-btn svg{width:18px;height:18px}

/* Contact */
.contact{display:grid;grid-template-columns:minmax(0,.9fr) minmax(0,1.2fr);gap:clamp(40px,6vw,6rem);align-items:start}
/* The enquiry form comes first in the page and the Tab order (F1 item 90, Q5-5); on wide screens it keeps its place on
   the right of the address column, and in one column (below) it comes first. */
.contact--form-first>.form{grid-column:2;grid-row:1}
.contact--form-first>div{grid-column:1;grid-row:1}
.info>div{padding:20px 0;border-top:1px solid var(--line)}
.info>div:last-child{border-bottom:1px solid var(--line)}
.map-link{margin-top:28px}
.info dt{margin-bottom:6px;font:500 .72rem/1.2 var(--mono);letter-spacing:.06em;text-transform:uppercase;color:var(--txt-3)}
.info dd{font-size:1.05rem}
.info a{border-bottom:1px solid var(--line-2)}.info a:hover{border-color:var(--accent)}
.form{display:grid;grid-template-columns:1fr 1fr;gap:18px;width:100%;max-width:60rem;justify-self:end;padding:clamp(24px,3vw,40px);--edge:var(--line);--fill:var(--surface)}
.form h2{grid-column:1/-1;font-size:1.6rem;letter-spacing:-.025em}
.field{display:grid;align-content:start;gap:8px;min-width:0}
.field.wide,.form .note,.form .actions{grid-column:1/-1}
.field label{font-size:.88rem;color:var(--txt-2)}
.field input,.field textarea{width:100%;min-width:0;padding:.8125rem .875rem;border:1px solid var(--line-2);background:var(--field-bg);font-size:.96rem;color:var(--txt);transition:border-color .2s,box-shadow .2s}
.field textarea{min-height:150px;resize:vertical}
.field input::placeholder,.field textarea::placeholder{color:var(--txt-ph)}
.field input:hover,.field textarea:hover{border-color:var(--field-edge-hover)}
/* slop-ok: outline-none replaced by designed :focus-visible (the border in the type colour, doubled inside the field) */
.field input:focus-visible,.field textarea:focus-visible,.field select:focus-visible{outline:none;border-color:var(--txt);box-shadow:inset 0 0 0 1px var(--txt)}
.form .note{font-size:.85rem;color:var(--txt-3)}
.form .note a{color:var(--txt-2);border-bottom:1px solid var(--line-2)}
.honey{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}
/* A field that fails validation (app.js): a red border and a short message under it, linked by aria-describedby. The
   message text stays light for contrast; the red is carried by the border. */
.field :is(input,textarea)[aria-invalid="true"]{border-color:var(--accent)}
.field-err{font-size:.82rem;line-height:1.4;color:var(--txt)}
.field-err[hidden]{display:none}

/* Legal pages */
.prose{max-width:47.5rem}
.prose h2{margin:52px 0 14px;font-size:clamp(1.3rem,2vw,1.6rem);letter-spacing:-.02em}
.prose h2:first-child{margin-top:0}
.prose p,.prose li{max-width:70ch;color:var(--txt-2)}
.prose p+p{margin-top:14px}
.prose ul{margin:14px 0;padding-left:1.2em;list-style:disc}
.prose li+li{margin-top:8px}
.prose a{color:var(--txt);border-bottom:1px solid var(--line-2)}
.prose strong{font-weight:500;color:var(--txt)}
.prose code{font:.88em var(--mono)}


/* Reticle cursor (app.js), for a mouse only. Restored from B2-bright on the owner's instruction (26 Oct 11:27: no
   circle cursor; the corner brackets that extend to frame a button are wanted); class names in
   build-patches\CURSOR-1127.md. Round B3, 26 Oct 13:35 (B17, owner: "something normal looking but with the corner snapping"):
   the system arrow shows everywhere again (links and buttons get the pointer hand) and the rest plate and drag chevrons
   are hidden, so only the four corner brackets are drawn: over a control (a button, link, tab or filter; never a photo,
   a media box or the opening screen's mark) they frame it in the page's tone. Moved by transforms only, above
   every layer (app.js moves it into an open modal dialog). */
.reticle{position:fixed;left:0;top:0;z-index:2147483000;width:0;height:0;pointer-events:none;opacity:0;--ret-tone:var(--red);transition:opacity .15s linear}
.reticle.is-on{opacity:1}
.reticle.is-native{opacity:0}
.reticle>span{position:absolute;left:0;top:0;pointer-events:none;will-change:transform}
.ret-plate{width:12px;height:12px;color:var(--txt);transition:opacity .09s linear}
.ret-chev{width:6px;height:10px;color:var(--txt);opacity:0}
:is(.ret-plate,.ret-chev) svg{display:block;width:100%;height:100%;overflow:visible;fill:none;stroke:currentColor;stroke-width:1.5;stroke-opacity:.7}
.ret-plate .ret-tick{stroke-width:1}
:is(.ret-plate,.ret-chev) .ret-case{stroke:var(--ret-case);stroke-width:3;stroke-opacity:.45}
.reticle.is-frame .ret-plate{opacity:0}
.reticle.is-drag .ret-chev{opacity:1}
.ret-plate,.ret-chev{display:none}
.ret-b{width:8px;height:8px;border:0 solid var(--ret-tone);opacity:0;transition:opacity .09s linear}
.ret-b--tl{border-width:1.5px 0 0 1.5px}
.ret-b--tr{border-width:1.5px 1.5px 0 0}
.ret-b--bl{border-width:0 0 1.5px 1.5px}
.ret-b--br{border-width:0 1.5px 1.5px 0}
.reticle.is-frame .ret-b{opacity:1}
/* The click's ember: one short streak off the top right bracket, warm core to the tone (app.js animates it once). */
.ret-spark{position:absolute;left:calc(100% + 1.5px);top:-1.5px;width:14px;height:1.5px;opacity:0;transform-origin:0 50%;background:linear-gradient(90deg,var(--spark-core),var(--warm) 35%,var(--ret-tone) 70%,transparent)}
@media (prefers-reduced-motion:reduce){.reticle,.reticle>span{transition:none}.ret-spark{display:none}}
@media not ((hover:hover) and (pointer:fine)){.reticle{display:none}}
:is(a[href],button,summary,[role="button"],[role="tab"]):not(:disabled,[aria-disabled="true"]){cursor:pointer}
@media (forced-colors:active){.reticle{display:none}}
/* High Contrast, after the base rules they override: the menu button's bars in the button colour, a 3 px focus ring
   inside the photo cards and tiles, and the client logos (white on transparent) drawn black on a light scheme, as on
   paper (Print). */
@media (forced-colors:active){
  .menu-btn span{forced-color-adjust:none;background:ButtonText}
  /* Form fields (Q14 S2): the focus is a Highlight outline, as every other control's (the field's own cue is a border
     change the system colours flatten). */
  .field :is(input,textarea,select):focus-visible{outline:2px solid Highlight;outline-offset:2px}
  /* The hero (Q14 S2): the Forge's light still and loop are decoration and stood behind the buttons in a dark scheme;
     in forced colours the hero sits on Canvas. */
  .forge::before{display:none!important}
  .forge canvas{visibility:hidden}
  :is(.cap,.tile,.gallery a,.pitem>a):focus-visible{outline:3px solid Highlight;outline-offset:-3px}
}
@media (forced-colors:active) and (prefers-color-scheme:light){.logos img{filter:brightness(0)}}
/* Photo viewer */
body.photo-open{overflow:hidden}
.photo-viewer{width:min(1180px,94vw);max-width:none;max-height:94dvh;padding:0;border:1px solid var(--line-2);background:var(--dialog-bg);color:var(--txt);box-shadow:0 12px 32px -12px var(--dialog-shadow)}
/* slop-ok: glass overlay over content (the dialog's backdrop dims and blurs the page behind the photo) */
.photo-viewer::backdrop{background:var(--backdrop);-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px)}
.viewer-bar,.viewer-controls{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px}
.viewer-bar{border-bottom:1px solid var(--line)}
.viewer-controls{border-top:1px solid var(--line)}
.viewer-count{font:500 .8rem var(--mono);color:var(--txt-3)}
.photo-viewer figure{background:var(--media-bg)}
/* The photo box follows the photo's shape (no black band around a landscape photo on a portrait phone), 4/3 until
   the file arrives so the dialog does not open collapsed. */
.photo-viewer figure img{width:100%;height:auto;aspect-ratio:auto 4/3;max-height:62dvh;object-fit:contain}
.photo-viewer figcaption{padding:14px 18px;font-size:.95rem;color:var(--txt-2)}
.photo-viewer button,.photo-viewer a{display:inline-flex;align-items:center;min-height:44px;min-width:44px;padding:0 16px;--edge:var(--line-2);font-size:.88rem;color:var(--txt);cursor:pointer}
.photo-viewer button:hover,.photo-viewer a:hover{--fill:var(--plate-hover);--edge:var(--warm)}
/* Round B3, F2 (Q2-1, Q2-2, Q5): a control the viewer hides (Download PDF on a photo, Previous and Next on a one-page
   document) never shows; the plate rules give every viewer control a display, which outranks [hidden]. On a phone the
   controls stay on screen: the picture takes at most half the screen, the bar and the control row hold the dialog's top
   and bottom while a long caption scrolls between them, and the plates keep to one row. */
.photo-viewer [hidden]{display:none!important}
@media (max-width:640px),(max-height:500px){
  .photo-viewer figure img{max-height:50dvh}
  .photo-viewer :is(.viewer-bar,.viewer-controls){position:sticky;z-index:1;background:var(--dialog-bg)}
  .photo-viewer .viewer-bar{top:0}
  .photo-viewer .viewer-controls{bottom:0;gap:8px;padding:10px 12px}
  .photo-viewer .viewer-controls :is(button,a){flex:none;padding:0 12px;white-space:nowrap}
}
/* A certificate page on a phone: pinch to zoom, or Download PDF; its full-size link would not fit the row. */
@media (max-width:640px){.photo-viewer.is-doc .viewer-original{display:none}}

/* Home intro */
body.intro-on{overflow:hidden}
.mc-intro{position:fixed;inset:0;z-index:300;display:grid;place-items:center;background:var(--intro-bg);transition:opacity .7s ease,visibility .7s}
.mc-intro.gone{opacity:0;visibility:hidden}
.mc-intro::after{content:"";position:absolute;inset:0;z-index:1;pointer-events:none;background:radial-gradient(ellipse at 50% 48%,transparent 50%,var(--vignette))}
.intro-stage{position:relative;z-index:2;display:grid;justify-items:center;gap:26px;transition:opacity .6s var(--ease),transform .9s var(--ease),filter .6s}
.mc-intro.entered .intro-stage{pointer-events:none}
.mc-intro.entered .intro-hint{opacity:0;transition:opacity .4s}
/* On the click the solid mark burns out as its particles ignite on the Forge canvas beneath it. */
.mc-intro.entered .intro-logo canvas{opacity:0;filter:brightness(2.4) blur(2px);transition:opacity .5s ease .08s,filter .45s ease}
/* That animated blur makes the browser compile a GPU shader for each blur radius it passes through, the first time it
   draws one: about 150 ms of stalled frames at the hand-off. So while the page is still booting (the engine loading, on
   the animated path only), a box the size of the mark, under it and at 1% black for under a second, runs the same
   brightness and blur sweep once: the shaders are built before the opening screen settles and the burn-out runs smooth. */
.intro-logo::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--shader-warm);opacity:0;pointer-events:none}
html.intro-live:not(.forge-still) .intro-logo::before{animation:mc-warm .8s linear .1s}
@keyframes mc-warm{from{opacity:.01}to{opacity:.01;filter:brightness(2.4) blur(2px)}}
.mc-intro.armed .intro-logo canvas{animation:mc-heat .9s ease-in-out infinite alternate}
@keyframes mc-heat{to{filter:drop-shadow(0 0 36px var(--ember-heat)) brightness(1.15)}}
.intro-logo{position:relative;display:block;width:min(74vw,560px,calc((100svh - 150px) * 1.9));aspect-ratio:1.9;padding:0;border:0;background:none;cursor:pointer;-webkit-tap-highlight-color:transparent}
/* slop-ok: outline-none replaced by designed :focus-visible (2 px, light, clear of the mark) */
.intro-logo:focus{outline:none}
.intro-logo:focus-visible{outline:2px solid var(--txt);outline-offset:10px}
/* Focus stays on the mark while the intro plays (there is no Skip button to take it); no ring over the animation. A
   transparent colour rather than none, so forced-colours (High Contrast) users still see where focus is. */
.mc-intro.entered .intro-logo:focus-visible{outline-color:transparent}
.intro-logo canvas{width:100%;height:100%;transition:transform .6s var(--ease),filter .6s}
.intro-logo .fallback{position:absolute;inset:0;display:none;width:82%;margin:auto;fill:var(--brand-red)}
.intro-hint{display:grid;gap:6px;text-align:center;font-size:.95rem;font-weight:500;letter-spacing:.01em}
.intro-hint span{font-size:.82rem;font-weight:400;color:var(--txt-3)}

/* Wide screens (1600 px and up; phones, tablets and laptops keep the rules above exactly). These blocks must stay after
   the base rules: a media query adds no specificity, so placed earlier the base rules would win. */
/* Vertical rhythm: section spacing follows the screen height, with rem caps, so a large screen gets breathing room
   rather than half-empty bands (.proc:first-child keeps its zero top padding: higher specificity). */
@media (min-width:1600px){
  .sec{padding:clamp(5.5rem,min(11vw,12vh),8rem) 0}
  .hero+.sec{padding:clamp(5.5rem,min(11vw,10vh),7.5rem) 0}
  .sec-head{margin-bottom:clamp(2.5rem,min(5vw,6vh),4.5rem)}
  .cta{padding:clamp(6rem,min(12vw,12vh),8.5rem) 0}
  .page-head{padding-bottom:clamp(3.5rem,min(8vw,9vh),6.5rem)}
  .proc{padding:clamp(4rem,min(8vw,9vh),7rem) 0}
  .media-pair{margin-bottom:clamp(4rem,min(8vw,9vh),7rem)}
}
/* Grids use the extra width without stretching: the four capability cards stay four and their images flatten
   (4/5, then 1/1, then 4/3); project cards gain columns so the 500 px photos are never upscaled much; the mosaic's big
   tile keeps its photo's shape; the statement widens to meet the content edge; the video boxes move to the video's own
   16/9 (or close to it), so less of the 1280x720 source is cropped and upscaled. Client logos stay six to a row, drawn
   larger. The statement sits flush with the content edge; the carousel is as wide as a 16/9 photo at its height (centred,
   controls under it), not a black slab; the FAQ and client rows use the column rather than stopping short of it. */
@media (min-width:1600px){
  .cap-img{aspect-ratio:1}
  .pgrid{grid-template-columns:repeat(4,minmax(0,1fr))}
  .tiles{grid-template-columns:1.6fr 1fr}
  .tile:first-child{min-height:clamp(520px,min(36vw,68vh),900px)}
  .statement-grid{grid-template-columns:1fr auto}
  .statement{max-width:36ch}
  .feature-media{aspect-ratio:16/9}
  .proc-media{aspect-ratio:16/10}
  .carousel{--car-h:clamp(560px,70vh,900px);max-width:calc(var(--car-h) * 16 / 9);margin-inline:auto}
  .car-slide img{height:var(--car-h)}
  .logos img{max-height:4rem}
  .logos b{line-height:1}
  .faq{max-width:none}
  .rowi{grid-template-columns:minmax(0,22rem) minmax(0,1fr) 8.75rem}
}
/* Fifteen project cards fill three rows of five (four columns left a hole in the last row). */
@media (min-width:1800px){
  .pgrid{grid-template-columns:repeat(5,minmax(0,1fr))}
}
/* The work gallery keeps three columns (at six its 1600 px photos drew smaller than the thumbnails above them); the
   contact form spans its column so the page is not two islands. */
@media (min-width:2400px){
  .cap-img{aspect-ratio:4/3}
  .form{max-width:none}
}

/* Responsive */
@media (max-width:1100px){
  .caps{grid-template-columns:repeat(2,minmax(0,1fr))}
  .logos{grid-template-columns:repeat(4,minmax(0,1fr))}
  .ftr-grid{grid-template-columns:1fr 1fr}
}
@media (max-width:960px){
  .nav,.hdr-quote{display:none}
  .menu-btn{display:grid}
  .feature,.proc,.split,.contact,.statement-grid{grid-template-columns:1fr}
  .contact.contact--form-first>*{grid-column:auto;grid-row:auto}
  .proc:nth-of-type(even) .proc-media{order:0}
  .tiles{grid-template-columns:1fr;grid-template-rows:none}
  .tile:first-child{grid-row:auto;min-height:420px}
  .tile{min-height:300px}
  .pgrid,.gallery,.panels{grid-template-columns:repeat(2,minmax(0,1fr))}
  .rowi{grid-template-columns:1fr;gap:6px}
  .rowi .meta{justify-content:flex-start;text-align:left}
  /* An odd last panel spans the row instead of sitting alone in it. */
  .panel:last-child:nth-child(odd){grid-column:1/-1}
}
@media (max-width:640px){
  :root{--hdr:64px}
  body{font-size:15px}
  .caps,.pgrid,.gallery,.panels,.media-pair{grid-template-columns:1fr}
  .cap-img{aspect-ratio:16/11}
  .logos{grid-template-columns:repeat(2,minmax(0,1fr))}
  .form{grid-template-columns:1fr}
  /* 16 px text in the fields, so iOS does not zoom the page when one is focused. */
  .field input,.field textarea{font-size:16px}
  .facts>div{grid-template-columns:1fr;gap:4px}
  .stats{grid-template-columns:1fr}
  .stat,.stat:first-child{padding:26px 0 0}
  .stat+.stat{border-left:0;border-top:1px solid var(--line);margin-top:26px}
  .ftr-grid{grid-template-columns:1fr}
  .actions .btn{flex:1 1 auto}
  .car-ctrl{flex-direction:column;align-items:flex-start}
}
/* Short landscape screens (phones on their side). After the rules above, which they override: every media box fits
   under the header in one screen, the carousel with its arrows (as wide as a 16/9 photo at that height, as on large
   screens), and the menu sets its links beside the quote button. */
@media (orientation:landscape) and (max-height:560px){
  /* width:100%, or the grid items' aspect ratio would turn the height cap into a narrower box. */
  .cap-img,.feature-media,.proc-media{width:100%;max-height:78svh}
  .tile:first-child{min-height:0;height:78svh}
  .carousel{--car-h:calc(100svh - var(--hdr) - 90px);max-width:calc(var(--car-h) * 16 / 9);margin-inline:auto}
  .car-slide img{height:var(--car-h)}
  .menu{flex-direction:row;align-items:flex-end;padding-top:calc(var(--hdr) + 8px)}
  .menu nav{flex:1}
  .menu nav a{font-size:1.375rem;padding:6px 0}
}
/* Short screens: the photo viewer's buttons stay on screen. */
@media (max-height:560px){
  .photo-viewer figure img{max-height:calc(94dvh - 193px)}
}
/* Touch screens: at least 44 px to tap for the logo, inline text links and footer links, without changing their look;
   the filter chips grow to 44 px. The two stacked contact numbers get a smaller margin so their
   targets do not cover each other's digits. */
@media (pointer:coarse){
  .logo{min-height:44px}
  .link{position:relative}
  .link::before{content:"";position:absolute;inset:-8px -6px}
  .filters button{height:2.75rem}
  .ftr li a{display:inline-block;padding-block:10px;margin-block:-10px}
  .info dd a{position:relative}
  .info dd a::before{content:"";position:absolute;inset:-10px -4px}
}
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
}

/* Page changes: the browser cross-fades the two pages natively and keeps the old one on screen until the
   new one can paint, so there is no blank frame. Both sides share one timing so the unchanged header holds still. */
@view-transition{navigation:auto}
::view-transition-old(root),::view-transition-new(root){animation-duration:.25s}
/* The fade layer lets the pointer through, so links and buttons on the new page respond during the fade. */
::view-transition{pointer-events:none}
@media (prefers-reduced-motion:reduce){@view-transition{navigation:none}}

/* Entrance and scroll reveals. Only opacity, translate and scale are animated, and in CSS, so the compositor
   runs them even while the page is still loading. boot.js adds .js to <html> before the first paint. */
@keyframes mc-rise{from{translate:0 110%}}
@keyframes mc-up{from{opacity:0;translate:0 16px}}
@keyframes mc-fade{from{opacity:0}}
@media (prefers-reduced-motion:no-preference){
  html.js :is(.hero,.page-head){--lag:0s}
  /* After the intro the model glides from its full-screen framing to its place beside the text; the headline waits for
     it, so it never rises over the model. */
  html.js:not(.from-site) .hero{--lag:.8s}
  html.js :is(.hero,.page-head) h1 .line>span{animation:mc-rise .4s cubic-bezier(.16,1,.3,1) calc(var(--lag) + var(--i,0s)) backwards}
  html.js :is(.hero,.page-head) h1 .line:nth-child(2)>span{--i:.04s}
  html.js :is(.hero,.page-head) h1 .line:nth-child(3)>span{--i:.08s}
  html.js :is(.hero,.page-head) :is(.eyebrow,.hero-sub,.lede,.actions){animation:mc-up .35s cubic-bezier(.25,1,.5,1) calc(var(--lag) + var(--i,0s)) backwards}
  html.js :is(.hero,.page-head) :is(.hero-sub,.lede){--i:.08s}
  html.js :is(.hero,.page-head) .actions{--i:.1s}
  /* The home hero holds its first frame under the intro until the intro hands over. */
  html.js:not(.from-site):not(.intro-done) .hero :is(h1 .line>span,.eyebrow,.hero-sub,.actions){animation-play-state:paused}
  /* After the intro the header, the hero's shade and the sections below fade up over the loop; not <main> as a whole,
     which would make the browser composite the entire page through an extra full-screen pass for the fade. */
  html.intro-done:not(.from-site) :is(.hdr,.hero-shade,main>.hero~*){animation:mc-fade 1.4s cubic-bezier(.33,1,.68,1) backwards}
  /* Scroll reveals (app.js): a 0.3 s fade, started half a screen before the block scrolls in. Long lists (more than four
     items) only fade, they never move. */
  html.js :is([data-reveal],[data-stagger]>*):not(.rv-done){opacity:0;translate:0 28px}
  html.js [data-stagger]:has(>:nth-child(5))>:not(.rv-done){translate:none}
  html.js :is([data-reveal],[data-stagger]>*).rv-in:not(.rv-done){opacity:1;translate:none;transition:opacity .3s cubic-bezier(.25,1,.5,1) var(--rd,0s),translate .3s cubic-bezier(.25,1,.5,1) var(--rd,0s)}
}
/* In a stretched render (see Page heads) nothing scrolls, so the blocks show without the reveal. */
@media (prefers-reduced-motion:no-preference) and (min-height:4000px) and (max-aspect-ratio:1/2){
  html.js :is([data-reveal],[data-stagger]>*):not(.rv-done){opacity:1;translate:none}
}
html.from-site .mc-intro{display:none}
/* No JavaScript (boot.js never added .js): the opening screen could never be left, so it is not shown. */
html:not(.js) .mc-intro{display:none}
/* Without scripts (boot.js never adds .js). The phone menu needs the script, so on phones and tablets the nav links sit
   in a wrapped row under the division strip (and the sub-tab row), 44 px tall, in an opaque header that --hdr now
   includes (one row from 641 to 960 px, two below). Videos give way to the poster, drawn here; the project filters,
   which only the script can run, are left out. */
html:not(.js) video{display:none}
html:not(.js) :is(.feature-media,.proc-media):has(>video){background:var(--media-bg) url(assets/img/cnc-poster-v2.webp) 50% 50%/cover no-repeat}
html:not(.js) .filters{display:none}
@media (max-width:960px){
  html:not(.js){--hdr-nav:44px;--hdr:calc(var(--hdr-bar) + var(--hdr-seg) + var(--hdr-sub) + var(--hdr-nav))}
  html:not(.js) .hdr{min-height:var(--hdr);background:var(--bg);border-bottom-color:var(--line)}
  html:not(.js) .menu-btn{display:none}
  html:not(.js) .nav{position:absolute;left:0;right:0;top:calc(var(--hdr-bar) + var(--hdr-seg) + var(--hdr-sub));display:flex;flex-wrap:wrap;gap:0 2px;margin:0;padding:0 var(--gut)}
  html:not(.js) .nav a{display:inline-flex;align-items:center;min-height:44px}
}
@media (max-width:640px){html:not(.js){--hdr-nav:88px}}

/* Forge stage (home page): the Three.js process loop, fixed behind the hero. The hero is transparent; everything after
   it is opaque, so the loop only shows behind the hero. During the intro it sits under the overlay and its mark. */
.forge{position:fixed;left:0;top:0;z-index:0;width:100vw;height:100vh;height:100lvh;background:var(--bg);pointer-events:none;contain:strict}
.forge canvas{display:block;width:100%;height:100%;opacity:0;transition:opacity .8s ease}
html.forge-on .forge canvas{opacity:1}
.forge~:is(main,.ftr){position:relative;z-index:1}
:where(.forge~main>.hero~*,.forge~.ftr){background-color:var(--bg)}
html.intro-live .forge{z-index:290}
html.forge-on .mc-intro{background:transparent}
html.forge-away .forge{visibility:hidden}
/* Home from another page of the site (no opening screen): the still (the remembered division's, else engineering) holds
   the hero from the first paint until the loop is up, then the canvas, stacked over it, fades in as the still fades out
   over the same 0.8 s, as on the division homes (tabs.css: the pages with the sub-tab row, left out here). The intro
   path is untouched (no .from-site). */
html.js.from-site:not(.forge-still):not(:has(.seg-sub)) .forge::before{content:"";position:absolute;inset:0;background:var(--bg) url(assets/img/forge/still-v6-eng-1600.webp) 40% 40%/cover no-repeat}
html.js.from-site[data-segment="bridges"]:not(.forge-still):not(:has(.seg-sub)) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-1600.webp)}
@media (min-width:1700px){html.js.from-site:not(.forge-still):not(:has(.seg-sub)) .forge::before{background-image:url(assets/img/forge/still-v6-eng-2560.webp)}html.js.from-site[data-segment="bridges"]:not(.forge-still):not(:has(.seg-sub)) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-2560.webp)}}
@media (orientation:portrait){html.js.from-site:not(.forge-still):not(:has(.seg-sub)) .forge::before{background-image:url(assets/img/forge/still-v6-eng-p-1080.webp);background-position:50% 40%}html.js.from-site[data-segment="bridges"]:not(.forge-still):not(:has(.seg-sub)) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-p-1080.webp)}}
html.js.from-site:not(:has(.seg-sub)) .forge canvas{position:relative}
html.forge-on.from-site:not(.forge-still):not(:has(.seg-sub)) .forge::before{opacity:0;transition:opacity .8s}
html:is(:not(.js),.forge-still) .forge::before{content:"";position:absolute;inset:0;background:var(--bg) url(assets/img/forge/still-v6-eng-1600.webp) 40% 40%/cover no-repeat}
html[data-segment="bridges"]:is(:not(.js),.forge-still) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-1600.webp)}
@media (min-width:1700px){html:is(:not(.js),.forge-still) .forge::before{background-image:url(assets/img/forge/still-v6-eng-2560.webp)}html[data-segment="bridges"]:is(:not(.js),.forge-still) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-2560.webp)}}
@media (orientation:portrait){html:is(:not(.js),.forge-still) .forge::before{background-image:url(assets/img/forge/still-v6-eng-p-1080.webp);background-position:50% 40%}html[data-segment="bridges"]:is(:not(.js),.forge-still) .forge::before{background-image:url(assets/img/forge/still-v6-bridges-p-1080.webp)}}

/* Touch screens: no blue tap box. While a finger is on a button or link (tapping or starting a scroll) it gets the same
   highlight a mouse hover gives it on a computer: app.js sets .touch-hover on it until the finger lifts. */
html{-webkit-tap-highlight-color:transparent}
.btn.touch-hover{--fill:var(--accent);color:var(--on-accent)}
.btn.touch-hover svg,.link.touch-hover svg{transform:translateX(3px)}
.btn-ghost.touch-hover,:is(.hero,.cta,.hdr) .btn-ghost.touch-hover{--fill:var(--plate-ghost-hover);--edge:var(--warm);color:var(--txt)}
.link.touch-hover,.tlink.touch-hover,.info a.touch-hover{border-color:var(--accent)}
.nav a.touch-hover{color:var(--txt)}
.nav a.touch-hover::after{transform:none;transform-origin:left;opacity:1}
.cap.touch-hover .cap-img img{transform:scale(1.05)}
.tile.touch-hover img,.pitem>a.touch-hover img,.gallery a.touch-hover img{transform:scale(1.04)}
.logos a.touch-hover{background:var(--surface)}
.ftr a.touch-hover{color:var(--txt)}
.filters button.touch-hover{color:var(--txt);--edge:var(--warm)}
.faq-i summary.touch-hover :is(h3,.faq-q){color:var(--txt)}
.icon-btn.touch-hover,.photo-viewer :is(button,a).touch-hover{--fill:var(--plate-hover);--edge:var(--warm)}
.menu-btn.touch-hover{--edge:var(--warm)}

/* Phones (640 px and below): a 14 px floor for text (eyebrows 13 px), 44 px tall link targets that leave each underline
   where it is, the footer's lists two to a row (the brand, the contact block and the list just before it span the row,
   so the order on screen stays the order in the page) and an opaque menu. */
@media (max-width:640px){
  :root{--menu-bg:var(--bg)}
  .eyebrow{font-size:13px}
  .ftr :is(h2,.ft-title),.facts dt,.info dt,.meta,.chip,.cap-n,.tile-cap small,.stat-u,.car-count,.viewer-count,.ftr-base,
  .form .note,.rowi a.link,.field-err,.prose code{font-size:14px}
  .link{position:relative}
  .link::before{content:"";position:absolute;inset:-9px -6px}
  /* Inline text links (the "Also:" lines, the RDSO contact card, links in running text) and the short footer links
     (Q2-5): a 44 px tap area round the word, the look unchanged. */
  .tlink,.form .note a{position:relative}
  .tlink::before,.form .note a::before{content:"";position:absolute;inset:-12px -6px}
  .info dd a::before{inset:-12px -4px}
  .form .note a::before{inset:-13px -6px}
  .ftr li a{min-width:44px}
  .menu-foot{gap:0}
  .menu-foot>a:not(.btn){display:inline-flex;align-items:center;min-height:44px}
  .ftr-grid{grid-template-columns:1fr 1fr;gap:32px 24px}
  .ftr-grid>:is(div,nav:has(+div)){grid-column:1/-1}
}
/* Small phones (F2, Q2-3): eyebrows to the 14 px floor too. */
@media (max-width:480px){.eyebrow{font-size:14px}}

/* Paused motion (html.mc-paused; round B3 removed the footer's motion button, the state stays: tabs.js keeps
   window.mcSetMotion, window.mcMotionPaused and the mc:motion event for the scripts' calm checks): the same page as the
   system's reduced-motion setting, so each @media (prefers-reduced-motion:reduce) rule above and in tabs.css,
   carousel.css and scrollwin.css is repeated here for html.mc-paused (the page cross-fade, an at-rule, is skipped by
   tabs.js instead). Also: the scroll reveals show at once (their hidden start state is a no-preference rule), and the
   Forge canvas is hidden while its still stands in. */
html.mc-paused,html.mc-paused *,html.mc-paused *::before,html.mc-paused *::after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important;scroll-behavior:auto!important}
html.mc-paused :is(.mark3d,.logo:hover .mark3d,.logo:focus-visible .mark3d)::before{animation:none;width:52px;height:26px;background-image:url(assets/img/mark3d-still-v3.webp);background-size:52px 26px;translate:none;transform:none}
html.mc-paused :is(.reticle,.reticle>span,.carousel--cards .car-track,.seg-switch a,.seg-tab,.seg-sub a,.hdr .seg-sub),html.js.mc-paused .win-scroll{transition:none}
html.mc-paused :is(.ret-spark,.car-sparks){display:none}
html.js.mc-paused :is([data-reveal],[data-stagger]>*):not(.rv-done){opacity:1;translate:none}
html.mc-paused .forge canvas{visibility:hidden}

/* Print: white paper, near-black type. The dark tokens are swapped here (html:root outranks a theme's :root), the
   moving and decorative layers are left out, every tab panel, window and carousel prints in full, and plates print as
   outlines so no label depends on a background being printed. Chrome prints link text only, with no URLs. */
@media print{
  html:root{
    --bg:#fff;--bg-2:#fff;--surface:#fff;--surface-2:#f2f2f2;--line:rgba(0,0,0,.14);--line-2:rgba(0,0,0,.3);
    --txt:#111;--txt-2:#333;--txt-3:#555;--txt-hero:#222;--txt-ph:#555;
    --media-bg:#fff;--dialog-bg:#fff;--field-bg:#fff;--menu-bg:#fff;--chip-bg:#fff;--backdrop:#fff;--intro-bg:#fff;--ftr-bg:#fff;
    --plate-ghost:transparent;--plate-ghost-hover:transparent;--plate-hover:transparent;--plate-dark:transparent;
    --fade-rgb:255,255,255;--hdr:0px;color-scheme:light;-webkit-print-color-adjust:exact;print-color-adjust:exact}
  html,body{overflow:visible!important;background:var(--bg)!important}
  *,*::before,*::after{animation:none!important;transition:none!important}
  /* Left out: the Forge stage and every canvas (embers, sparks), the opening screen, the cursor, the skip link, the
     header's links, tabs, quote plate and menu, the tab row, carousel controls, filters, the footage behind heads and
     the enquiry form (the contact details still print). */
  .forge,canvas,.mc-intro,.reticle,.skip-link,.nav,.seg-switch,.seg-sub,.hdr-quote,.menu-btn,.menu,.seg-tabs,
  .car-ctrl,.car-btns,.car-sparks,.win-sparks,.filters,.hero-media,.hero-shade,.page-head::before,.tile::after,.form{display:none!important}
  .hdr{position:static!important;background:none!important;border-bottom:1px solid var(--line)}
  /* The header mark: the flat logo in place of the 3D sprite (a 52x26 frame file, the mark placed as in the sprite). */
  .mark3d{filter:none!important;overflow:visible}
  html .logo .mark3d::before{content:url(assets/img/logo-mark-frame.svg)!important;width:52px!important;height:26px!important;background:none!important;translate:none!important;transform:none!important}
  .hero,.page-head--media{min-height:0!important}
  .hero-in{padding-top:2rem;padding-bottom:2rem}
  .sec,.cta{padding:3rem 0}
  .ftr{padding:2rem 0 0}
  .ftr-base{margin-top:1.5rem;padding-top:12px}
  /* Revealed and entered: nothing waits for a scroll or the intro on paper. */
  html.js :is([data-reveal],[data-stagger]>*),.line>span{opacity:1!important;translate:none!important}
  /* Plates as outlines: the label in the type colour, the edge in the plate's own shape. */
  .btn,.btn-ghost,.photo-viewer :is(button,a){--fill:transparent!important;--edge:var(--txt)!important;color:var(--txt)!important}
  /* Both home division panels, one after the other. */
  .seg-panel,.seg-panel[hidden]{display:block!important}
  .seg-panel+.seg-panel{margin-top:2rem;padding-top:2rem;border-top:1px solid var(--line)}
  /* Scrolling windows: no cap, no fade, no clipping. */
  .win,.win-scroll{max-height:none!important;height:auto!important;overflow:visible!important;-webkit-mask-image:none!important;mask-image:none!important}
  /* Carousels: every card (or photo) in a wrapped grid, captions under them. */
  .car-track{display:grid!important;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--gap);overflow:visible!important;border:0}
  .car-slide{min-width:0}
  .car-slide img{height:auto!important}
  /* Photo tiles: the photo, then its caption under it. */
  .tiles{grid-template-rows:none}
  .tile,.tile:first-child{grid-row:auto;min-height:0!important}
  .tile img{position:static;height:auto;aspect-ratio:4/3}
  .tile-cap{position:static;padding:.75rem 0}
  /* FAQ answers print with their questions (where the browser supports it). */
  .faq-i::details-content{content-visibility:visible;display:block}
  .logos img{filter:brightness(0)}
  /* Photo and video boxes take their picture's own height and drop the frame: a picture that does not fit moves whole
     to the next sheet instead of leaving an empty frame behind on this one. */
  :is(.feature-media,.proc-media){aspect-ratio:auto;overflow:visible}
  :is(.feature-media,.proc-media) :is(img,video){height:auto}
  :is(.feature-media,.proc-media,.media-pair>picture)::before{display:none}
  :is(.cap,.pitem,.tile,.rowi,.faq-i,.stat,.car-slide,.feature-media,.media-pair>picture,.info>div,.facts>div){break-inside:avoid;page-break-inside:avoid}
  /* Q14 S3: buttons, field rows, certificate plates and resource cards never split; a label or heading never ends a sheet. */
  :is(.btn,.rd-field,.rd-cert-card,.res-card,.facts-card){break-inside:avoid;page-break-inside:avoid}
  :is(.eyebrow,h2,h3){break-after:avoid;page-break-after:avoid}
}
