/* ============================================================================
   RANCO concept demos — SHARED skeleton.
   Load order: ibix-base.css -> shared.css -> motion-kit.css -> [theme].css
   This file carries fonts, the fluid type scale, the spacing/ease tokens and
   every structural class the three concepts share. Each theme file supplies
   ONLY the colour tokens + section backgrounds + its one signature move — the
   skeleton (header, hero, stat band, services list, trust wall, QHSE, contact,
   footer) is written once here so the three concepts are the same building
   with different paint, not three different buildings.

   Token contract every [theme].css must define before this file's classes
   make sense:
     --ink --ink-2 --ink-3        dark surfaces
     --paper --paper-2            light surfaces
     --txt --txt-dim --txt-faint  on-dark text
     --ink-txt --ink-dim          on-paper text
     --navy --blue                RANCO's own brand colours (logo — never edited)
     --blue-link                  on-paper small text/links (brand blue fails
                                   4.5:1 on paper, see CLIENT-FACTS.md)
     --accent --accent-hi --accent-btn   the concept's own signal colour, in a
                                   large/decorative shade, an on-dark small-text
                                   shade and a white-text-safe button shade
     --line --line-ink            hairlines on dark / on paper
   Every ratio below was produced by design/snippets/contrast-check.py — see
   the @contrast annotations in each [theme].css, not typed from memory (D-16).
   ========================================================================= */

/* --- 1 · fonts (self-hosted, Latin only, these demos are English-only by
   owner instruction 2026-08-04: "remove arabic as there is no need for now".
   RANCO's own Arabic wordmark still ships inside the logo SVG — that is part
   of the mark, not page copy, and is untouched.

   Typeface pair, changed 2026-08-05 (owner: "change fonts for all 3, pick a
   good font you believe will be best"). IBM Plex Sans is OUT.

   Display: SCHIBSTED GROTESK. Body: INTER. Both self-hosted variable OFL
   files (~95KB combined — inside the DP12 budget). Why this pair: Schibsted
   Grotesk carries authority at 80px+ display sizes where IBM Plex Sans went
   flat and wide; Inter is the most legible face on the house list at the
   14–17px sizes the service rows and register content live at.

   Body face changed Inter -> FIGTREE 2026-08-05, on request. Settled — do not
   "correct" it back; the reasoning, including what was traded away, is in
   clients/ranco/STATUS.md, which is where a note about anyone's decision
   belongs. Figtree is also markedly lighter: 20KB against Inter's 48KB.

   Each file is a variable font, so it gets ONE @font-face with a font-weight
   RANGE — never one rule per weight, which would make the browser download
   the same file twice or synthesise a fake bold. Ranges below match the
   file's own fvar axis, checked with fontTools: Schibsted Grotesk's Latin
   subset only carries 400–900 (no light weights in this subset); Inter
   carries the full 100–900. Fallback metrics generated with fontpie against
   these exact files — pasted verbatim, not estimated. */
@font-face{
  font-family:'Schibsted Grotesk';
  font-style:normal; font-weight:400 900; font-display:swap;
  src:url('../fonts/schibsted-grotesk-var-latin.woff2') format('woff2');
}
@font-face{
  font-family:'Schibsted Grotesk Fallback';
  font-style:normal; font-weight:400; src:local('Arial');
  ascent-override:92.54%; descent-override:24.43%; line-gap-override:0.00%; size-adjust:105.53%;
}
@font-face{
  font-family:'Figtree';
  font-style:normal; font-weight:300 900; font-display:swap;
  src:url('../fonts/figtree-var-latin.woff2') format('woff2');
}
@font-face{
  font-family:'Figtree Fallback';
  font-style:normal; font-weight:400; src:local('Arial');
  ascent-override:95.06%; descent-override:25.02%; line-gap-override:0.00%; size-adjust:99.94%;
}

/* --- 2 · tokens shared by all three concepts ------------------------------- */
:root{
  --body-latin:'Figtree','Figtree Fallback',Arial,sans-serif;
  --display-latin:'Schibsted Grotesk','Schibsted Grotesk Fallback',Arial,sans-serif;
  --mono:ui-monospace,'Cascadia Mono','SF Mono',Consolas,'Liberation Mono',monospace;
  --body: var(--body-latin);
  --display: var(--display-latin);

  --ease:cubic-bezier(.22,1,.36,1);
  --wrap:1280px;
  --gut:clamp(20px,4.5vw,64px);
  --tb-h:76px; /* .tb-in's own fixed height — shared so the mobile-menu panel
                  can sit flush under the header without a second magic number */

  /* spacing scale (design/snippets/layout-primitives.css, R 01 §2) */
  --space-3:1rem; --space-4:1.5rem; --space-5:2rem; --space-6:3rem;
  --space-7:4rem; --space-8:6rem; --space-9:8rem;

  /* Section-level padding, from the TOP of the scale above (component-
     internal spacing stays on the --space-3..5 end, see .split-lead etc.
     below). Fixed 2026-08-05: every content section EXCEPT #stats and
     #site-footer shipped with NO padding-block at all — measured `#about`,
     `#services`, `#trust`, `#qhse`, `#careers`, `#contact` all at 0px top
     AND bottom, so section content butted straight against the boundary
     with the next section's differently-coloured band. That is the root
     cause of "too close together" — not a component-spacing problem, a
     missing-section-frame problem. Two tiers, not one repeated value:
     --pad-section for the utility/list sections (Services' own row dividers
     already carry internal rhythm, so the section doesn't need to lean on
     external air as hard); --pad-section-lg for the single-focus feature
     sections (About's pull-quote, QHSE's card trio) that read better with
     more room around them. */
  --pad-section:    clamp(4.5rem, 9vh, 6.5rem);   /* --space-7 -> --space-8 */
  --pad-section-lg: clamp(6rem, 11vh, 8rem);      /* --space-8 -> --space-9 */

  /* fluid type ladder (design/snippets/type-scale.css, verified @360/@1440) */
  --step-0:  clamp(1rem, calc(0.958rem + 0.185vw), 1.125rem);      /* 16 / 18   body */
  --step-1:  clamp(1.188rem, calc(1.125rem + 0.278vw), 1.375rem);  /* 19 / 22   lead */
  --step-2:  clamp(1.375rem, calc(1.25rem + 0.556vw), 1.75rem);    /* 22 / 28   h3 */
  --step-3:  clamp(1.625rem, calc(1.438rem + 0.833vw), 2.188rem);  /* 26 / 35   h2 */
  --step-4:  clamp(1.938rem, calc(1.667rem + 1.204vw), 2.75rem);   /* 31 / 44   h1 inner */
  --step-5:  clamp(2.25rem, calc(1.854rem + 1.759vw), 3.438rem);   /* 36 / 55   statement */
  --lh-latin:1.5; --lh-display:1.05;

  --z-header:50; --z-overlay:60;
}

html{ font-size:100% }
body{ line-height:var(--lh-latin) }

body{
  font-family:var(--body); font-size:var(--step-0);
  background:var(--paper); color:var(--ink-txt);
  font-optical-sizing:auto; text-rendering:optimizeLegibility;
}
h1,h2,h3{ font-family:var(--display); font-weight:700; letter-spacing:-.02em; line-height:var(--lh-display) }
::selection{ background:var(--accent); color:#fff }
:focus-visible{ outline:2px solid var(--accent-hi, var(--accent)); outline-offset:3px }

/* Section colour context. Every content section carries ONE of these so every
   child component (chips, cards, rows, stats) resolves its muted/line/border
   tokens against the surface it is ACTUALLY painted on — never a single
   global "muted grey" guessed to work everywhere. Background colour itself is
   still set per section, per theme, in [theme].css. */
.on-dark{ color:var(--txt); --body-muted:var(--txt-dim); --card-line:var(--line);
  --card-bg:transparent; --row-line:var(--line); --chip-line:var(--line); --chip-txt:var(--txt);
  --stat-line:var(--line); --stat-label:var(--txt-dim); --logo-grid-line:var(--line) }
.on-paper{ color:var(--ink-txt); --body-muted:var(--ink-dim); --card-line:var(--line-ink);
  --card-bg:transparent; --row-line:var(--line-ink); --chip-line:var(--line-ink); --chip-txt:var(--ink-txt);
  --stat-line:var(--line-ink); --stat-label:var(--ink-dim); --logo-grid-line:var(--line-ink) }

.wrap{ max-width:var(--wrap); margin-inline:auto; padding-inline:var(--gut) }
.mono{ font-family:var(--mono); font-size:.72rem; letter-spacing:.14em; text-transform:uppercase }

.kicker{
  font-family:var(--mono); font-size:.72rem; letter-spacing:.18em; text-transform:uppercase;
  display:flex; align-items:center; gap:14px; margin-bottom:22px; color:var(--accent-hi, var(--accent));
}
.kicker::after{ content:""; height:1px; width:64px; background:currentColor; opacity:.55 }
.on-paper .kicker{ color:var(--navy) }

/* buttons — extends the base .btn (nowrap, min-height) */
.btn{ font-family:var(--display); font-weight:600; font-size:.95rem; letter-spacing:.01em;
  padding:15px 28px; border-radius:4px; min-height:48px }
.btn-solid{ background:var(--accent-btn, var(--accent)); color:#fff }
.btn-solid:hover{ filter:brightness(1.08); transform:translateY(-1px) }
.btn-navy{ background:var(--navy); color:#fff }
.btn-navy:hover{ filter:brightness(1.12); transform:translateY(-1px) }
.btn-ghost{ border-color:rgba(255,255,255,.5); color:#fff }
.btn-ghost:hover{ border-color:#fff; background:rgba(255,255,255,.08) }
.btn-outline-ink{ border-color:var(--card-line,var(--line-ink)); color:inherit }
.btn-outline-ink:hover{ background:var(--ink-txt); color:var(--paper); border-color:var(--ink-txt) }

/* --- 3 · header ------------------------------------------------------------- */
#top-bar{ position:sticky; top:0; z-index:var(--z-header); background:var(--header-bg,var(--paper));
  border-bottom:1px solid var(--header-line,var(--line-ink)) }
.tb-in{ max-width:var(--wrap); margin-inline:auto; padding-inline:var(--gut);
  height:var(--tb-h); display:flex; align-items:center; justify-content:space-between; gap:20px }
.brand{ display:flex; align-items:center; line-height:1 }
.brand img{ height:36px; width:auto }
/* Concept B: the logo sits in its own white lockup chip so it never touches
   the dark shell directly — the mark stays exactly as drawn either way. */
.brand-chip{ background:#fff; border-radius:6px; padding:9px 16px;
  box-shadow:0 1px 2px rgba(0,0,0,.08) }
.tb-nav{ display:flex; gap:28px }
.tb-nav a{ font-family:var(--display); font-weight:500; font-size:.9rem;
  color:var(--nav-txt,var(--ink-dim)); padding:8px 0; position:relative }
.tb-nav a::after{ content:""; position:absolute; inset-inline-start:0; bottom:2px; height:1.5px; width:0;
  background:var(--accent); transition:width .3s var(--ease) }
.tb-nav a:hover{ color:var(--nav-txt-hover,var(--ink-txt)) }
.tb-nav a:hover::after{ width:100% }
@media (max-width:860px){ .tb-nav{ display:none } }
.tb-right{ display:flex; align-items:center; gap:10px }
.tb-cta{ padding:11px 20px; min-height:42px; font-size:.86rem }
@media (max-width:560px){ .tb-cta{ display:none } }

/* --- 3b · smart header (hide on scroll down, reappear on scroll up) --------
   Transform-only, JS-driven (js/header-scroll.js) — the .hide class is NEVER
   present without JS, so a no-JS visitor simply never sees it move: the
   header's default state IS visible, this only ever takes it away. Honours
   prefers-reduced-motion via ibix-base.css's global transition-duration
   collapse (still shows/hides, just without the slide). */
#top-bar{ transition:transform .32s var(--ease); will-change:transform }
#top-bar.hide{ transform:translateY(-100%) }

/* --- 3c · "What we offer" nav dropdown --------------------------------------
   Real <a href="#services"> label (works with JS off — nothing is ever
   unreachable) plus a separate disclosure <button aria-expanded> that reveals
   the seven services. Opens on: pointer-fine hover (decoration only), a JS
   click/tap toggle (.open, robust for touch), and — when JS never ran at all —
   a :focus-within fallback so keyboard users still get SOMETHING. Escape and
   click-outside close it (js/nav-drop.js); the toggle itself never traps focus. */
.nav-drop{ position:relative; display:flex; align-items:center }
.nav-drop-link{ font-family:var(--display); font-weight:500; font-size:.9rem;
  color:var(--nav-txt,var(--ink-dim)); padding:8px 0; position:relative }
.nav-drop-link::after{ content:""; position:absolute; inset-inline-start:0; bottom:2px; height:1.5px; width:0;
  background:var(--accent); transition:width .3s var(--ease) }
.nav-drop-link:hover{ color:var(--nav-txt-hover,var(--ink-txt)) }
.nav-drop-link:hover::after{ width:100% }
.nav-drop-toggle{ display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; padding:0; margin-inline-start:1px; border-radius:4px;
  background:none; border:0; color:var(--nav-txt,var(--ink-dim)); cursor:pointer }
.nav-drop-toggle:hover{ color:var(--nav-txt-hover,var(--ink-txt)) }
.nav-drop-toggle::before{ content:""; width:0; height:0; margin:0 auto;
  border-inline:4px solid transparent; border-top:5px solid currentColor;
  transition:transform .2s var(--ease) }
.nav-drop.open .nav-drop-toggle::before{ transform:rotate(180deg) }
/* invisible bridge so the mouse can travel from the link row down into the
   panel without crossing a dead zone and losing :hover mid-way */
.nav-drop::after{ content:""; position:absolute; top:100%; inset-inline:0; height:14px }
.nav-drop-menu{ list-style:none; position:absolute; top:100%; inset-inline-start:0; margin-top:14px;
  min-width:250px; padding:8px; border-radius:8px;
  background:var(--header-bg,var(--paper)); border:1px solid var(--header-line,var(--line-ink));
  box-shadow:0 18px 40px -14px rgba(10,12,16,.35);
  opacity:0; visibility:hidden; transform:translateY(-6px);
  transition:opacity .18s var(--ease), transform .18s var(--ease), visibility 0s linear .18s;
  pointer-events:none; z-index:var(--z-overlay) }
.nav-drop-menu li + li{ margin-top:1px }
.nav-drop-menu a{ display:block; padding:10px 12px; border-radius:5px;
  font-family:var(--display); font-weight:500; font-size:.86rem; color:var(--nav-txt,var(--ink-dim)) }
.nav-drop-menu a:hover, .nav-drop-menu a:focus-visible{
  color:var(--nav-txt-hover,var(--ink-txt)); background:rgba(127,127,127,.12) }
@media (hover:hover) and (pointer:fine){
  .nav-drop:hover .nav-drop-menu{ opacity:1; visibility:visible; transform:none; pointer-events:auto; transition-delay:0s }
}
.nav-drop.open .nav-drop-menu{ opacity:1; visibility:visible; transform:none; pointer-events:auto; transition-delay:0s }
html:not(.js) .nav-drop:focus-within .nav-drop-menu{ opacity:1; visibility:visible; transform:none; pointer-events:auto; transition-delay:0s }

/* --- 3d · mobile menu --------------------------------------------------------
   Added 2026-08-05: below 860px `.tb-nav` (rule above) simply disappears —
   there was no replacement at all, so the "What we offer" services were
   unreachable from the header on any phone. A real disclosure button
   (aria-expanded/aria-controls) drives a fixed full-width panel
   (js/mobile-menu.js): Escape closes it and returns focus to the toggle, a
   tap on any link closes it and lets the anchor navigate, and it uses the
   owner-keyed position-fixed scroll lock from ibix-base.css
   (`body.scroll-locked` — `body{overflow:hidden}` alone is a documented
   no-op on this codebase). js/header-scroll.js already checks for
   `body.menu-open` and force-reveals the header while it is set, so the
   header can never hide itself out from under an open menu.
   No-JS fallback: the toggle button is useless without a script to drive it,
   so <head>'s noscript block hides it and unpins the panel into normal flow
   instead — the same nav, always reachable, whether or not JS runs. */
.menu-toggle{ display:none; background:none; border:0; border-radius:4px; padding:8px;
  min-width:44px; min-height:44px; align-items:center; justify-content:center;
  color:var(--nav-txt,var(--ink-dim)); cursor:pointer }
@media (max-width:860px){ .menu-toggle{ display:inline-flex } }
/* A STATIC three-bar mark, never redrawn on open/close — no morph to an X.
   Chased a morphing version through four rebuilds (::before/::after,
   animating `top`+rotate together, animating `transform` alone with the two
   diagonal bars pre-rotated from first paint, even a plain-text glyph) and
   EVERY version painted a corrupted, lopsided shape the instant it changed
   at the same moment `.mobile-menu.is-open` started its own opacity/
   transform transition (a full-viewport panel, in the same sticky header).
   Bisected across nine isolated test pages: every element involved reported
   byte-identical, geometrically-correct computed styles in the broken runs —
   this is a real compositor/paint race tied to that specific combination,
   not a CSS authoring mistake, and it survived being reduced to plain ASCII
   text with zero transforms anywhere in the chain. The open state is
   instead a background pill on the BUTTON itself (below) — a well-used,
   fully accessible pattern that needs no icon repaint at all, so there is
   nothing left in this component that can race. */
.menu-toggle-bars{ position:relative; display:block; width:22px; height:16px }
.menu-toggle-bars span{ position:absolute; inset-inline-start:0; width:22px; height:2px;
  background:currentColor }
.menu-toggle-bars span:nth-child(1){ top:0 }
.menu-toggle-bars span:nth-child(2){ top:7px }
.menu-toggle-bars span:nth-child(3){ top:14px }
/* OPEN state must read as CLOSE. It previously kept the three-bar mark and
   only added an accent pill, which reads as a broken minus, not a dismiss
   (owner, 2026-08-05). An ANIMATED bar-to-X morph is what was originally tried
   and it hit a reproducible compositor race, so this does the swap with NO
   transform and NO transition: bar 2 is hidden, bars 1 and 3 are rotated by a
   static rule that only ever applies in the open state. Nothing re-paints
   mid-transition because nothing transitions. */
.menu-toggle[aria-expanded="true"]{ background:var(--accent-btn,var(--accent)); color:#fff }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars span{ transition:none }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars span:nth-child(1){ top:7px; transform:rotate(45deg) }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars span:nth-child(2){ opacity:0 }
.menu-toggle[aria-expanded="true"] .menu-toggle-bars span:nth-child(3){ top:7px; transform:rotate(-45deg) }

.mobile-menu{ position:fixed; inset:var(--tb-h) 0 0 0; z-index:var(--z-overlay);
  background:var(--header-bg,var(--paper)); color:var(--nav-txt-hover,var(--ink-txt));
  overflow-y:auto; -webkit-overflow-scrolling:touch;
  padding:28px var(--gut) 40px;
  transform:translateY(-8px); opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .22s var(--ease), transform .22s var(--ease), visibility 0s linear .22s }
.mobile-menu.is-open{ transform:none; opacity:1; visibility:visible; pointer-events:auto; transition-delay:0s }
.mobile-menu nav{ display:flex; flex-direction:column }
.mobile-menu nav > a{ font-family:var(--display); font-weight:700; font-size:var(--step-2);
  padding:14px 2px; border-bottom:1px solid var(--header-line,var(--line-ink)) }
.mm-group{ display:flex; flex-direction:column; border-bottom:1px solid var(--header-line,var(--line-ink)) }
.mm-h{ font-family:var(--mono); font-size:.68rem; letter-spacing:.14em; text-transform:uppercase;
  color:var(--header-accent,var(--accent-hi,var(--accent))); padding:16px 2px 8px }
.mm-group a{ font-family:var(--display); font-weight:500; font-size:1rem; color:var(--nav-txt,var(--ink-dim));
  padding:10px 2px; min-height:24px; display:flex; align-items:center }
.mobile-menu a:hover, .mobile-menu a:focus-visible{ color:var(--header-accent,var(--accent)) }
.mobile-menu-actions{ margin-top:28px }
.mobile-menu-actions .btn{ width:100% }
@media (min-width:861px){ .mobile-menu{ display:none } }

/* --- 4 · hero ---------------------------------------------------------------- */
#hero{ position:relative; overflow:hidden; background:var(--ink); --header-h:76px }
/* #top-bar is position:sticky, not fixed — it still occupies its own row in
   flow, so a naive 100svh hero UNDER it always overhangs the fold by the
   header's own height (design/snippets README's documented defect #2).
   Subtract it here rather than trusting a fixed-header assumption. */
#hero.hero-full{ min-height:calc(100vh - var(--header-h)); min-height:calc(100svh - var(--header-h));
  display:flex; align-items:center }
/* short-laptop trim — placed here, AFTER the base padding rule below in the
   SAME file, so it cannot be silently outranked by source order the way a
   later theme file already did once on this project (see hero-in comment). */
.hero-media{ position:absolute; inset:0 }
.hero-media picture, .hero-media img{ width:100%; height:100%; object-fit:cover }
.hero-scrim{ position:absolute; inset:0; pointer-events:none }
.hero-grid{ position:absolute; inset:0; pointer-events:none; opacity:.35;
  background:
    repeating-linear-gradient(90deg, rgba(255,255,255,.035) 0 1px, transparent 1px 120px),
    repeating-linear-gradient(0deg, rgba(255,255,255,.035) 0 1px, transparent 1px 120px) }
.hero-in{ position:relative; z-index:2; width:100%; max-width:var(--wrap); margin-inline:auto;
  padding:clamp(120px,16vh,168px) var(--gut) clamp(150px,19vh,196px) }
/* Re-specified AFTER the rule above, in the SAME file, on purpose: a later
   file silently deleting this exact guardrail by source order is a named
   pitfall in design/snippets/README.md's own validation log. */
@media (max-height:820px){
  .hero-in{ padding-block:clamp(72px,11vh,110px) clamp(48px,7vh,72px) }
}
/* Hero display measure, fixed 2026-08-05 (owner: "text isn't nicely laid
   out here or on the other"). Measured on a-nightshift.html at 1440px before
   the fix: font-size 84.6px with line-height 82.9px — leading TIGHTER than
   the font size — inside a 15ch (761px) box, wrapping "Built for Saudi
   Arabia's heavy work." to "Built for Saudi / Arabia's / heavy work.",
   stranding "Arabia's" alone. text-wrap:balance was already on and did not
   fix it — the box was simply too narrow for a 6-word headline to break
   well, whatever the balance algorithm chooses inside it. Widened the
   measure (15ch -> 22ch) AND dropped the size a step so the natural wrap
   is a clean two-line break in both concept A and concept B's headline
   (different copy, same class) at every checked width (1440/1366/1024/768/
   390): "Built for Saudi / Arabia's heavy work." and "Our fleet. Our
   people. / Ready for the work." Leading moved from .98 (tighter than the
   type size) to 1.02, inside the 0.95–1.05 usable range for multi-line
   grotesk display. */
.hero-h{ color:#fff; font-size:clamp(2.05rem, min(6vw, 8.6vh), 5.4rem); line-height:1.02;
  letter-spacing:-.02em; max-width:22ch; text-wrap:balance }
/* Lead paragraph: comfortably looser leading than the display (1.7 vs the
   display's 1.02) and a measure that keeps lines inside the ~55–70 character
   readable range rather than running the full hero column. */
.hero-sub{ margin-top:22px; max-width:52ch; color:#DDE1E6; font-size:clamp(1rem,1.3vw,1.14rem); line-height:1.7 }
.hero-actions{ margin-top:34px; display:flex; flex-wrap:wrap; gap:16px; align-items:center }
.hero-tagline{ margin-top:30px; color:#fff; font-family:var(--mono); font-size:.7rem;
  letter-spacing:.18em; text-transform:uppercase; opacity:.78 }

/* numbered service strip along the hero's bottom edge — the tab-strip pattern
   from the reference site, static (no auto-advance JS): seven real anchors
   to #services */
.hero-foot{ position:relative; z-index:3; border-top:1px solid rgba(255,255,255,.14) }
/* Inside the full-bleed cinematic hero (concepts A/B) the strip must be
   PINNED to the hero's own bottom edge and taken out of flow — otherwise it
   is a second flex item beside .hero-in in #hero's row-flex container and
   both fight for width instead of stacking (measured: this was silently
   pushing the CTA below the fold at 1024x700, qa.py hero-fit). Concept C's
   #service-strip re-uses the same .hero-foot markup as an ordinary in-flow
   section, so the absolute rule is scoped to .hero-full only. */
#hero.hero-full .hero-foot{ position:absolute; inset-inline:0; inset-block-end:0 }
/* Mobile fix, 2026-08-05. The rule above pins the strip to the hero's own
   bottom edge so it can float over the photo without pushing the CTA row
   down. That only works when the hero is TALL enough to hold hero copy + two
   CTAs above the strip's own height — and the strip's height is NOT fixed:
   it grows from 2 grid rows to 4 as the columns drop (7 -> 4 -> 2, see
   "@media (max-width:1180px)" and "@media (max-width:560px)" below), which
   is exactly when width is also narrowest. Measured 2026-08-05 (Playwright
   matrix across 10 viewports): every narrow phone (390x780, 414x896,
   360x740) and every short viewport under ~720px tall (768x600, 700x600,
   860x700) put the strip's top edge ABOVE the CTA row's bottom edge — both
   CTAs sat behind the strip and the lead paragraph clipped mid-sentence.
   MUST come after the rule above: same selector, same specificity, so
   source order is what lets this win. Comma = OR: either condition alone is
   enough to take the strip out of absolute flow and let it stack normally
   below the hero content instead, which is guaranteed never to overlap
   anything, at any content length, at any width. */
@media (max-width:760px), (max-height:720px){
  #hero.hero-full{ min-height:auto; display:block }
  #hero.hero-full .hero-foot{ position:static; inset:auto }
}
.hs-tabs{ max-width:var(--wrap); margin-inline:auto; padding:0 var(--gut);
  display:grid; grid-template-columns:repeat(7,1fr); column-gap:clamp(8px,1.6vw,20px) }
/* padding-inline-start is NOT optional here. Each cell carries a hairline rule
   on its inline-start edge, and with `padding:16px 0` the label sat 1px from
   that rule — measured [0,1,1,1,1,1,1] across the seven cells, i.e. the text
   was touching the divider. The owner's words: "its fully on the left ...
   needs a gap from the left" (2026-08-05). The FIRST cell keeps zero inset so
   its label still aligns with the headline and the page gutter above it; the
   other six are inset from their own rule. */
.hs-tab{ display:flex; flex-direction:column; gap:8px; padding:16px 0 18px; color:#fff;
  padding-inline-start:20px;
  border-inline-start:1px solid rgba(255,255,255,.16) }
.hs-tab:first-child{ border-inline-start:0; padding-inline-start:0 }
.hs-tab-i{ font-family:var(--mono); font-size:.66rem; letter-spacing:.12em; color:var(--accent-hi,var(--accent)) }
/* text-wrap:balance added 2026-08-05 (same typography pass as .hero-h): at
   seven columns the longer service names ("Construction & Contracting",
   "Transportation & Logistics") wrap, and the default greedy wrap stranded
   the LAST word alone ("Construction &" / "Contracting"). Balance moves the
   break to the ampersand instead ("Construction" / "& Contracting") so the
   second line is never a single orphaned word. */
/* ...and `balance` alone was NOT enough (owner, 2026-08-05: "still same issue
   with the strip"). Measured cause: `.hs-tabs` is capped at `max-width:var(--wrap)`
   (1280px), so from 1280px upward the seven columns are frozen at ~147px no
   matter how wide the screen gets — at 1920px the label box is still 147px.
   "Construction & Contracting" needs ~175px at this size, so two of the seven
   wrap and five do not: measured cell heights were [16, 32], which is exactly
   the ragged row the owner could see.

   The font cannot go smaller (12.5px is already the floor for a label), and
   widening the strip past --wrap would break alignment with every other section.
   So instead every label RESERVES two lines. The wrapped ones then sit in a row
   that matches the rest, and the strip reads as a deliberate grid rather than an
   accident. Below 1180px the grid drops to four columns, each label fits on one
   line, and the reservation is released so the strip does not carry dead space
   on a phone. */
.hs-tab-t{ font-family:var(--display); font-weight:600; font-size:.78rem; line-height:1.3;
  text-wrap:balance; min-block-size:2.6em }
@media (max-width:1180px){ .hs-tab-t{ min-block-size:0 } }
.hs-tab:hover .hs-tab-t{ color:var(--accent-hi,var(--accent)) }
/* Breakpoint moved 900px -> 1180px 2026-08-05: at 1024px-wide viewports the
   seven-column grid left ~146px per tab, which balance alone could not save
   for three-word names ("Heavy Equipment Rental", "Pipeline & Maintenance")
   — there is no even split of three words that avoids a short word landing
   alone. Four columns at 1024 give each tab enough room that every label
   (measured 1440/1366/1024/768/390) is a single line or an ampersand-led
   pair, never a lone word. */
@media (max-width:1180px){
  .hs-tabs{ grid-template-columns:repeat(4,1fr); row-gap:4px }
  .hs-tab:nth-child(4n+1){ border-inline-start:0 }
}
@media (max-width:560px){
  .hs-tabs{ grid-template-columns:repeat(2,1fr) }
  .hs-tab:nth-child(odd){ border-inline-start:0 }
}

/* --- 5 · stat band ------------------------------------------------------------ */
#stats{ padding-block-start:clamp(56px,8vh,84px); padding-block-end:clamp(56px,8vh,84px) }
.stats-grid{ display:grid; grid-template-columns:repeat(4,1fr); gap:var(--space-6) var(--space-4) }
@media (max-width:860px){ .stats-grid{ grid-template-columns:repeat(2,1fr) } }
.stat{ border-inline-start:2px solid var(--stat-line,var(--line)); padding-inline-start:22px }
.stat b{ display:block; font-family:var(--display); font-weight:700; letter-spacing:-.02em;
  font-size:clamp(1.7rem,2.9vw,2.6rem); line-height:1; font-variant-numeric:tabular-nums; min-height:1em }
/* :not(.stat-num) is load-bearing. The label markup nests a span inside a
   span (<span><em>Years</em> <span>…</span></span>) and both rely on this
   rule's display:block — but a bare `.stat span` ALSO matches `.stat-num`,
   which lives inside the <b> (<b><span class="stat-num">22</span>+</b>).
   Measured 2026-08-05: with the broad selector, the <b> computed 41.6px
   while .stat-num inside it computed 13.76px (.86rem, this rule's own
   font-size) and display:block pushed the "+" onto its own line — the
   entire "why is 22 so small" bug. Excluding .stat-num fixes both the
   font-size steal and the forced line-break in one rule; the label spans
   are untouched. */
.stat span:not(.stat-num){ display:block; margin-top:10px; font-size:.86rem; line-height:1.5; color:var(--stat-label,var(--txt-dim)) }
.stat em{ font-style:normal; color:var(--accent-hi,var(--accent)) }
.stat--pending{ border-style:dashed; opacity:.85 }
.stat--pending b{ letter-spacing:.06em }
/* .stat--list — for a value that is a LIST, not a numeral (the three ISO
   standards). Fixed 2026-08-05: "9001 · 14001 · 45001" set at the same
   display size as the other three cells wrapped to three lines (measured
   125px tall against "Approved"'s 42px), which is what made the row read as
   broken — a list crammed into a numeral slot rather than a genuine
   mismatch of content types. Each standard gets its own line at a size that
   suits a label, not a headline number, so the row's four cells go back to
   reading as one row. */
.stat--list b{ font-size:clamp(1rem,1.6vw,1.25rem); letter-spacing:0; line-height:1.45; min-height:0 }

/* --- 6 · split section (about / QHSE image pairs) ----------------------------- */
/* #about, #qhse: feature sections built around one pull-quote / one card
   trio — the --pad-section-lg tier (see the tokens' own comment, §2). */
#about, #qhse{ padding-block-start:var(--pad-section-lg); padding-block-end:var(--pad-section-lg) }
.split{ display:grid; grid-template-columns:minmax(0,5.6fr) minmax(0,6.4fr); gap:clamp(36px,5vw,80px); align-items:start }
.split.split-rev{ grid-template-columns:minmax(0,6.4fr) minmax(0,5.6fr) }
.split.split-rev .split-fig{ order:2 }
@media (max-width:860px){ .split, .split.split-rev{ grid-template-columns:1fr } .split.split-rev .split-fig{ order:0 } }
.split-copy h2{ font-size:var(--step-4); max-width:17ch }
.split-lead{ margin-top:24px; font-size:var(--step-1); line-height:1.65; font-weight:500; max-width:52ch }
.split-body{ margin-top:16px; max-width:58ch; color:var(--body-muted); line-height:1.65 }
/* Pull-quote rule widened 2026-08-05 (owner: spacing "feels off" — measured
   the rule sitting as little as 16px from the text at the clamp's own
   floor). Floor raised 16px -> 22px so the rule never crowds the text at any
   width, ceiling left alone since it already read fine at desktop. */
.pull{ margin-top:32px; font-family:var(--display); font-weight:600; font-size:var(--step-2);
  line-height:1.32; max-width:30ch; border-inline-start:3px solid var(--accent); padding-inline-start:clamp(22px,2.6vw,28px) }
/* Attribution, split out of an inline style 2026-08-05. Was margin-top:14px
   directly under a step-2 pull-quote (22-28px, line-height 1.32) with no
   visual break — read as "tight... no separation" (owner). --space-5 (32px)
   puts clear, deliberate air under the quote instead. Copy is untouched. */
.pull-attrib{ margin-top:var(--space-5); font-weight:600 }
.chip-rail{ margin-top:30px; display:flex; flex-wrap:wrap; gap:10px }
.chip{ font-family:var(--mono); font-size:.68rem; letter-spacing:.12em; text-transform:uppercase;
  border:1px solid var(--chip-line,var(--line-ink)); border-radius:999px; padding:9px 16px;
  display:inline-flex; align-items:center; min-height:24px; color:var(--chip-txt,inherit) }
.split-fig figure{ border-radius:6px; overflow:hidden; background:var(--paper-2); aspect-ratio:4/3 }
.split-fig img{ width:100%; height:100%; object-fit:cover }

/* --- 7 · services index -------------------------------------------------------- */
/* Dense list content — its own row dividers (.svc-row's border-top) already
   carry internal rhythm, so the --pad-section tier (not -lg) is enough. */
#services{ padding-block-start:var(--pad-section); padding-block-end:var(--pad-section) }
#services .svc-head{ display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-end;
  gap:18px; margin-bottom:32px }
#services .svc-head h2{ font-size:var(--step-3); max-width:22ch }
.svc-index{ list-style:none }
.svc-row{ display:grid; grid-template-columns:44px 1fr; gap:20px; align-items:start;
  padding-block:24px; border-top:1px solid var(--row-line,var(--line-ink)) }
.svc-row:last-child{ border-bottom:1px solid var(--row-line,var(--line-ink)) }
.svc-row.has-tile{ grid-template-columns:44px 1fr auto }
@media (max-width:640px){ .svc-row.has-tile{ grid-template-columns:44px 1fr } .svc-tile{ display:none } }
.svc-i{ font-family:var(--mono); font-size:.78rem; letter-spacing:.1em; padding-top:4px; color:var(--accent-hi,var(--accent)) }
.svc-t h3{ font-size:var(--step-2); font-weight:600 }
.svc-t p{ margin-top:8px; max-width:56ch; color:var(--body-muted); line-height:1.6; font-size:.96rem }
.svc-tile{ width:104px; border-radius:6px; overflow:hidden; aspect-ratio:4/3 }
.svc-tile img{ width:100%; height:100%; object-fit:cover }

/* --- 8 · trust / logo wall ------------------------------------------------------ */
#trust{ padding-block-start:var(--pad-section); padding-block-end:var(--pad-section) }
#trust .trust-head{ margin-bottom:34px }
#trust .trust-head h2{ font-size:var(--step-3); max-width:26ch }
#trust .trust-lead{ margin-top:16px; max-width:58ch; color:var(--body-muted) }
.logo-wall{ margin-top:32px; display:grid; grid-template-columns:repeat(auto-fit,minmax(140px,1fr)); gap:2px;
  background:var(--logo-grid-line,var(--line-ink)); border:1px solid var(--logo-grid-line,var(--line-ink)) }
.logo-tile{ background:#fff; display:flex; align-items:center; justify-content:center; padding:22px 18px; min-height:96px }
.logo-tile img{ max-height:38px; width:auto; max-width:100% }
.trust-foot{ margin-top:28px; display:flex; flex-wrap:wrap; gap:12px; align-items:center }

/* --- 9 · QHSE ------------------------------------------------------------------- */
#qhse .qhse-cards{ margin-top:32px; display:flex; flex-direction:column; gap:14px }
.q-card{ border:1px solid var(--card-line,var(--line-ink)); border-inline-start:3px solid var(--accent);
  border-radius:6px; padding:22px 24px; background:var(--card-bg,transparent) }
.q-card h3{ font-size:1.02rem; font-weight:700; display:flex; gap:14px; align-items:baseline }
.q-card h3 i{ font-style:normal; font-family:var(--mono); font-size:.64rem; letter-spacing:.12em; color:var(--accent-hi,var(--accent)) }
.q-card p{ margin-top:8px; color:var(--body-muted); font-size:.92rem; line-height:1.6 }

/* --- 10 · contact ----------------------------------------------------------------- */
#contact{ padding-block-start:var(--pad-section); padding-block-end:var(--pad-section) }
#contact .con-head h2{ font-size:var(--step-4) }
#contact .con-head p{ margin-top:14px; max-width:52ch; color:var(--body-muted) }
.con-grid{ margin-top:44px; display:grid; grid-template-columns:repeat(3,1fr); gap:18px }
@media (max-width:860px){ .con-grid{ grid-template-columns:1fr } }
.con-card{ border:1px solid var(--card-line,var(--line-ink)); border-radius:6px; padding:26px; background:var(--card-bg,transparent) }
.con-card .mono{ display:block; margin-bottom:14px; color:var(--accent-hi,var(--accent)) }
.con-card h3{ font-size:1.04rem }
.con-num{ display:block; margin-top:10px; font-family:var(--display); font-weight:700;
  font-size:clamp(1.1rem,1.7vw,1.4rem); font-variant-numeric:tabular-nums }
.con-acts{ margin-top:18px; display:flex; flex-wrap:wrap; gap:10px }
.con-mail{ display:block; margin-top:8px; word-break:break-word }
.con-office{ margin-top:36px; display:flex; flex-wrap:wrap; gap:10px 28px; color:var(--body-muted); font-size:.9rem }

/* --- 11 · footer -------------------------------------------------------------------- */
/* Grid rule (playbook): brand + link columns on row 1, legal line spans
   1/-1 on row 2 — never 1fr auto 1fr with the legal line in the middle. */
#site-footer{ padding-block-start:var(--space-7); padding-block-end:var(--space-5) }
.foot-grid{ display:grid; grid-template-columns:minmax(220px,1.3fr) repeat(3,minmax(150px,1fr));
  gap:var(--space-6) var(--space-5) }
@media (max-width:860px){
  .foot-grid{ grid-template-columns:1fr 1fr }
  .foot-brand{ grid-column:1/-1 }
}
@media (max-width:560px){ .foot-grid{ grid-template-columns:1fr } }

.foot-logo{ display:inline-flex; line-height:1; border-radius:6px }
.foot-logo img{ height:34px; width:auto }
.foot-tagline{ margin-top:16px; font-family:var(--mono); font-size:.68rem;
  letter-spacing:.16em; text-transform:uppercase; color:var(--footer-muted,inherit) }
.foot-creds{ margin-top:14px; max-width:34ch; font-size:.78rem; line-height:1.6; color:var(--footer-muted,inherit) }

.foot-h{ font-family:var(--display); font-weight:700; font-size:.82rem; letter-spacing:.02em; margin-bottom:14px }
/* #site-footer-scoped, not a bare .foot-col — two of these columns are <nav>
   elements, and ibix-base.css's own landmark reset (`#site-footer nav{
   display:block }`, (1,0,1)) otherwise outranks a plain class rule (0,1,0)
   and collapses the column back to inline running text. */
#site-footer .foot-col{ display:flex; flex-direction:column; gap:6px; min-width:0 }
/* WCAG 2.2 target minimum is 24x24 and the design brief treats it as
   never-traded. A footer link at .86rem renders ~19px tall, so the row
   needs an explicit floor — same pattern as .ibix-credit below. The gap
   is reduced to 6px so the columns do not grow taller for it. */
.foot-col a{ font-size:.86rem; color:var(--footer-muted,inherit); line-height:1.4;
  display:inline-flex; align-items:center; min-height:24px }
.foot-col a:hover{ color:var(--footer-accent,var(--accent)) }

.foot-addr{ font-size:.86rem; line-height:1.6; color:var(--footer-muted,inherit); max-width:30ch }
/* contact card address continuation line. Was an inline style="margin-top:10px"
   on three pages - the CSP has no unsafe-inline, so that attribute is silently
   IGNORED in production while looking fine on localhost (2026-08-05). */
.con-addr-line{ margin-top:10px }
.foot-link-plain{ display:inline-flex; align-items:center; min-height:24px;
  margin-top:10px; font-size:.86rem; color:var(--footer-muted,inherit);
  word-break:break-word }
.foot-link-plain:hover{ color:var(--footer-accent,var(--accent)) }

.foot-legal-row{ grid-column:1/-1; margin-top:var(--space-5); padding-top:var(--space-4);
  border-top:1px solid var(--footer-line,var(--line-ink));
  display:flex; flex-wrap:wrap; justify-content:space-between; align-items:center; gap:14px 24px }
.foot-legal{ font-size:.84rem; color:var(--footer-muted,inherit) }
/* mandatory footer credit — whole phrase is the link, visible underline at
   rest, hover state, clears the 24px WCAG target (playbook rule, all four
   points enforced by qa.py's ibix-credit check) */
.ibix-credit{ display:inline-flex; align-items:center; min-height:24px;
  text-decoration:underline; text-underline-offset:2px; color:inherit }
.ibix-credit:hover{ color:var(--footer-accent,var(--accent)) }

/* --- 11b · careers band ------------------------------------------------------------- */
#careers{ padding-block-start:var(--pad-section); padding-block-end:var(--pad-section) }
#careers .careers-in{ display:grid; grid-template-columns:minmax(0,6.4fr) minmax(0,5.6fr);
  gap:clamp(28px,4vw,64px); align-items:center }
@media (max-width:860px){ #careers .careers-in{ grid-template-columns:1fr } }
#careers h2{ font-size:var(--step-4); max-width:18ch }
#careers .careers-lead{ margin-top:18px; max-width:56ch; font-size:var(--step-1); line-height:1.6;
  color:var(--body-muted) }
#careers .careers-themes{ margin-top:26px; display:flex; flex-wrap:wrap; gap:10px }
#careers .careers-actions{ margin-top:8px; display:flex; align-items:center }
@media (max-width:860px){ #careers .careers-actions{ margin-top:28px } }

/* --- 12 · concept chrome (demo only) --------------------------------------------------- */
/* Demo chrome — a STATIC bar at the very top of the document, not a fixed
   overlay. It was position:fixed bottom-left, which put it permanently on top
   of the hero's service strip: measured 2026-08-05 covering "Rig Moving" and
   "Heavy Equipment Rental" at desktop, and after being nudged up by 90px it
   simply collided with different items at 768px and 390px, because the strip's
   height changes with its column count. A fixed overlay above a variable-height
   element is a guess that has to be re-made at every breakpoint; putting the
   pill in normal flow removes the collision entirely, at any width, forever.
   It scrolls away with the page, which is right — it is our label, not RANCO's
   content, and the gallery link is in the footer too. */
.concept-pill{ margin:0; padding:10px var(--gut); text-align:center;
  background:#101319; color:#fff; font-family:var(--mono); font-size:.68rem;
  letter-spacing:.12em; text-transform:uppercase; }
.concept-pill a{ text-decoration:underline; color:#fff; display:inline-flex; align-items:center; min-height:24px }
@media (max-width:720px){ .concept-pill{ font-size:.62rem; padding:8px 12px } }

/* --- 13 · hero entrance utilities (animation pass, 2026-08-05 later) --------
   Opt-in via markup only — applied where a concept's own hero copy calls for
   it (see each [theme].html's <head> comment for which of A/B/C/D/E use
   which). Both patterns are plain CSS @keyframes with fill-mode `both`, NOT
   gated on html.js: a CSS animation runs whether or not any script arrives
   (matching the house precedent already shipped for b-field.css's own
   .hero-media img "field-settle" — that rule needs no JS either). Outside the
   prefers-reduced-motion:no-preference query neither class carries ANY
   hidden/offset state, so a no-JS visitor, a reduced-motion visitor, and a
   visitor whose browser is simply slow to first paint all see the finished
   text immediately — nothing here can ever leave the hero blank. */
.kicker-draw::after{ transform-origin:left center }
@media (prefers-reduced-motion:no-preference){
  .kicker-draw::after{
    transform:scaleX(0);
    animation:ibix-kicker-draw .7s var(--ease-emphasized-decel) .15s both;
  }
}
@keyframes ibix-kicker-draw{ to{ transform:scaleX(1) } }

/* Word-level headline rise. Markup contract:
     <h1><span class="hero-word">Word</span> <span class="hero-word">Word</span> …</h1>
   Real whitespace text nodes stay between the spans so word-spacing and text
   selection are untouched — only the words themselves are wrapped, never
   individual letters (playbook: no per-letter animation). Start opacity is
   .1, never 0 — a hero H1 is very often the LCP element, and design/
   PLAYBOOK's own hero-line-reveal rule is that the LCP candidate must have
   real, if faint, pixels on the very first frame, not a fully transparent
   one. Capped at 8 words; a 9th+ word inherits the 8th's delay rather than
   growing the animation indefinitely. */
.hero-word{ display:inline-block }
@media (prefers-reduced-motion:no-preference){
  .hero-word{ animation:ibix-word-rise .6s var(--ease-emphasized-decel) both }
  .hero-word:nth-child(1){ animation-delay:.15s }
  .hero-word:nth-child(2){ animation-delay:.2s }
  .hero-word:nth-child(3){ animation-delay:.25s }
  .hero-word:nth-child(4){ animation-delay:.3s }
  .hero-word:nth-child(5){ animation-delay:.35s }
  .hero-word:nth-child(6){ animation-delay:.4s }
  .hero-word:nth-child(7){ animation-delay:.45s }
  .hero-word:nth-child(n+8){ animation-delay:.5s }
}
@keyframes ibix-word-rise{ from{ opacity:.1; transform:translateY(.3em) } to{ opacity:1; transform:none } }

/* Whole-block quiet entrance — for the concepts whose hero reads better as
   one calm settle than a word-by-word rise (D, E). Same no-JS/no-hidden-
   state contract as the two rules above. */
.hero-settle{ }
@media (prefers-reduced-motion:no-preference){
  .hero-settle{ animation:ibix-hero-settle .8s var(--ease-emphasized-decel) .1s both }
}
@keyframes ibix-hero-settle{ from{ opacity:0; transform:translateY(14px) } to{ opacity:1; transform:none } }

/* --- 14 · register-row transitions (animation pass, 2026-08-05 later) ------
   Hover micro-interactions (transform/colour only, ~200ms) plus a tighter
   scroll-in stagger for the seven-row services index shared by A/B/C. Each
   row is independently `.reveal`-observed (not a `[data-stagger]` group) so
   it already staggers naturally as a user scrolls past it one row at a time
   — this only adds a small stepped delay so rows that enter the viewport
   TOGETHER (a fast scroll, or a short viewport where several rows are
   already visible on load) still read as a stagger rather than several rows
   fading in identically at once. */
.svc-row .svc-t h3{ transition:transform .2s var(--ease) }
.svc-row:hover .svc-t h3{ transform:translateX(4px) }
.svc-tile img{ transition:transform .3s var(--ease) }
.svc-row:hover .svc-tile img{ transform:scale(1.05) }
html.js .svc-row.reveal:nth-child(1){ transition-delay:0ms }
html.js .svc-row.reveal:nth-child(2){ transition-delay:60ms }
html.js .svc-row.reveal:nth-child(3){ transition-delay:120ms }
html.js .svc-row.reveal:nth-child(4){ transition-delay:180ms }
html.js .svc-row.reveal:nth-child(5){ transition-delay:240ms }
html.js .svc-row.reveal:nth-child(6){ transition-delay:300ms }
html.js .svc-row.reveal:nth-child(7){ transition-delay:360ms }
@media (prefers-reduced-motion:reduce){
  html.js .svc-row.reveal:nth-child(1), html.js .svc-row.reveal:nth-child(2),
  html.js .svc-row.reveal:nth-child(3), html.js .svc-row.reveal:nth-child(4),
  html.js .svc-row.reveal:nth-child(5), html.js .svc-row.reveal:nth-child(6),
  html.js .svc-row.reveal:nth-child(7){ transition-delay:0s }
}

/* Footer link hover — colour change already existed (§11); add a matching
   underline sweep so the hover state reads as more than a colour swap,
   consistent with the header nav's own ::after sweep (§3). Purely
   decorative ::after, no layout impact, tap target untouched. */
.foot-col a{ position:relative }
.foot-col a::after{ content:""; position:absolute; inset-inline-start:0; bottom:2px;
  height:1px; width:0; background:currentColor; transition:width .2s var(--ease) }
.foot-col a:hover::after{ width:100% }
