@charset "UTF-8";
/* ==========================================================================
   WEBHELPER.COM - PROTOTYPE 003
   Video hero  |  mega menu  |  thin type  |  amber accent
   RULE: no transform/movement on nav, buttons or cards. Colour changes only.
   ASCII-only comments
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TOKENS
   -------------------------------------------------------------------------- */
:root {
  /* ground - very slightly cool, to sit with the blue video */
  --ink-0: #08090b;
  --ink-1: #0d0f13;
  --ink-2: #13161b;
  --ink-3: #1a1e24;

  --line:    rgba(255,255,255,0.09);
  --line-2:  rgba(255,255,255,0.16);
  /* Interactive borders - ghost button, PAUSE control, burger.
     SC 1.4.11 wants 3:1 for a control boundary, and the MEASURED floor on this
     ground is alpha 0.336. Was 0.44 (4.38:1); 0.36 is as soft as it can go and
     still hold 3:1 with a little headroom for brighter video frames.
     DO NOT drop this below 0.34. */
  --line-ui: rgba(255,255,255,0.36);

  --cream: #f0f1f3;
  --body:  #b6bcc4;
  --muted: #949aa3;

  /* ACCENT - swap these three lines to retheme the whole site.
     Alternates that also pass AA on --ink-0:
       teal    #4fbfa8 / #8fd9c9 / #0d2b26
       cyan    #58b6dd / #9ad6ee / #082430
       coral   #f4784f / #f9ab8f / #2b0f07          */
  --acc:      #f0a43c;
  --acc-soft: #f7c98a;
  --acc-ink:  #2a1804;   /* text colour ON the accent */

  --font-display: 'Outfit', 'Helvetica Neue', Arial, sans-serif;
  /* Raleway - lifted from Eterna Health, which uses it as its body face.
     Smaller x-height and lighter strokes than Inter, so it reads a size down
     at the same px value. See the body rule for the compensation. */
  --font-body:    'Raleway', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --shell: 1340px;
  --bleed: 48px;
  --nav-h: 86px;
  /* Scrolled-header plate: colour + opacity, both tunable from here.
     Below alpha 1 the backdrop-filter blur behind it shows and the bar reads
     as glass - but the lower it goes the more a bright work card bleeds
     through, so RE-MEASURE the nav after changing either.
     Amber is what breaks first, never white text.
     Tried: #2b2b2a needed 0.87+ (43,43,42 was too light to wear thin). */
  --hdr-plate: 30, 30, 30;   /* #1e1e1e - neutral dark grey */
  --hdr-alpha: 0.85;

  /* The amber shine hairline. Full strength where page content sits right
     under it (header, mega); softened where the line is isolated against
     near-black on BOTH sides (section bands into the footer), which otherwise
     reads as a heavier line even though it is the same single pixel. */
  --shine-soft: rgba(240,164,60,0.50);
  --ease: cubic-bezier(.22,.68,.24,1);
}

/* --------------------------------------------------------------------------
   2. BASE
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
html { background: var(--ink-0); scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ink-0);
  color: var(--body);
  font-family: var(--font-body);
  font-size: 17px;
  font-weight: 300;
  line-height: 1.72;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

img { max-width: 100%; height: auto; display: block; }
a { color: var(--acc); text-decoration: none; }
button { font: inherit; }

h1, h2, h3, h4 {
  margin: 0;
  font-family: var(--font-display);
  color: var(--cream);
  font-weight: 200;            /* thin and clear */
  letter-spacing: -0.02em;
  line-height: 1.12;
}
h3, h4 { font-weight: 300; }
p { margin: 0 0 20px; }
p:last-child { margin-bottom: 0; }
strong { font-weight: 500; color: var(--cream); }

::selection { background: var(--acc); color: var(--acc-ink); }
:focus-visible { outline: 3px solid var(--acc); outline-offset: 3px; }

.wh-skip {
  position: absolute; left: -9999px; top: 0; z-index: 100000;
  background: var(--acc); color: var(--acc-ink); padding: 14px 22px; font-weight: 500;
}
.wh-skip:focus { left: 12px; top: 12px; }

.wh-sr {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   3. PRIMITIVES
   -------------------------------------------------------------------------- */
.wh-shell { width: 100%; max-width: var(--shell); margin: 0 auto; padding: 0 var(--bleed); }
.wh-section { padding: 124px 0; position: relative; }
.wh-section--raised { background: var(--ink-1); }

.wh-eyebrow {
  display: block;
  font-family: var(--font-body);
  font-size: 11px; font-weight: 500;
  letter-spacing: 0.3em; text-transform: uppercase;
  color: var(--acc);
  margin-bottom: 22px;
}
/* eyebrow with a rule running in from the left. Grid, not flex - house rule.
   The line fades in from transparent, same idiom as the section hairlines, so
   it reads as light arriving rather than a hard tick mark. Note this INDENTS
   the eyebrow, so it no longer aligns with the h1 below it - that is the
   intent of the device, not a bug. */
.wh-eyebrow--rule {
  display: grid; grid-auto-flow: column; justify-content: start; align-items: center;
  gap: 20px;
}
.wh-eyebrow--rule::before {
  content: ""; width: clamp(32px, 4.5vw, 75px); height: 1px;
  background: linear-gradient(90deg, transparent, var(--acc));
}
/* centred section heads: the rule + text travel together as one group */
.wh-head--center .wh-eyebrow--rule { justify-content: center; }

.wh-h2 { font-size: clamp(32px, 4.6vw, 58px); letter-spacing: -0.025em; }
.wh-h2 em { font-style: normal; color: var(--acc); }
.wh-lede { margin-top: 22px; font-size: 19px; max-width: 62ch; }
/* Releases the 62ch measure so a lede fills its container. Deliberately opt-in:
   62ch is the readable-line-length cap, and at the full 1244px shell a 19px
   lede runs past 100 characters a line, which is well beyond comfortable. Use
   it where the container is already narrow (a split column) or where killing a
   bad wrap matters more than the measure. */
.wh-lede--wide { max-width: none; }
.wh-head { max-width: 780px; margin-bottom: 66px; }
/* Lets a head span the whole shell instead of the 780px measure. Used where a
   headline was breaking to a second line only because of the cap, not because
   it was actually long. Releases the lede's 62ch with it - see the note on
   .wh-lede--wide about what that costs in line length. */
.wh-head--wide { max-width: none; }
.wh-head--wide .wh-lede { max-width: none; }
.wh-head--center { margin-left: auto; margin-right: auto; text-align: center; }
.wh-head--center .wh-lede { margin-left: auto; margin-right: auto; }

.wh-hair { width: 54px; height: 1px; background: var(--acc); margin-bottom: 26px; }
.wh-head--center .wh-hair { margin-left: auto; margin-right: auto; }

/* --------------------------------------------------------------------------
   4. BUTTONS - colour change only, never movement
   -------------------------------------------------------------------------- */
.wh-button-primary,
.wh-button-ghost {
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 12px;
  padding: 16px 30px;
  font-family: var(--font-body); font-size: 15px; font-weight: 500;
  border: 1px solid transparent; border-radius: 2px;
  cursor: pointer;
  transition: background-color .3s var(--ease), color .3s var(--ease), border-color .3s var(--ease);
}
.wh-button-primary i, .wh-button-ghost i { font-style: normal; }

.wh-button-primary { background: var(--acc); color: var(--acc-ink); }
.wh-button-primary:hover, .wh-button-primary:focus-visible { background: var(--acc-soft); color: var(--acc-ink); }

.wh-button-ghost { background: transparent; color: var(--cream); border-color: var(--line-ui); }
.wh-button-ghost:hover, .wh-button-ghost:focus-visible { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }

/* Teal fill that picks up the Freshworks support tab, to tie it into the
   palette rather than leave it sitting there as a foreign object. Inverted
   against the normal ghost: it starts FILLED and empties on hover, where every
   other ghost starts empty and fills.
   Only the background moves - colour and border stay on the base rule - so the
   control boundary is exactly the one already measured for --line-ui.
   Measured: cream on #006063 is 6.51:1. The border against the brightest
   gold.mp4 frame is 2.50:1 at the --soft mid stop, but that is the ground the
   plain ghost already stands on (2.49:1); filled is better at the other two
   stops (4.83 / 5.35 vs 3.27 / 3.33). Not a regression either way.
   MUST stay after .wh-button-ghost:hover - same specificity, source order is
   the only thing stopping the amber hover from winning. */
/* Border matches the fill rather than being removed. Visually identical to no
   border - a clean teal edge like the support tab - but it KEEPS the 1px the
   base rule reserves, so the button does not shrink 2px when hover swaps back
   to the --line-ui outline. Section 4's rule: colour change only, never
   movement. */
.wh-button-ghost--teal { background: #006063; border-color: #006063; }
.wh-button-ghost--teal:hover,
.wh-button-ghost--teal:focus-visible {
  background: transparent; color: var(--cream); border-color: var(--line-ui);
}

.wh-actions { margin-top: 38px; display: grid; grid-auto-flow: column; justify-content: start; gap: 14px; }

/* text link with a static underline that changes colour, never shifts */
.wh-tlink {
  font-size: 14px; font-weight: 500; color: var(--acc);
  border-bottom: 1px solid rgba(240,164,60,0.4); padding-bottom: 2px;
  transition: color .28s var(--ease), border-color .28s var(--ease);
}
.wh-tlink:hover, .wh-tlink:focus-visible { color: var(--acc-soft); border-bottom-color: var(--acc-soft); }

/* --------------------------------------------------------------------------
   5. HEADER + MEGA MENU
   -------------------------------------------------------------------------- */
.wh-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 900;
  background: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color .4s var(--ease), border-color .4s var(--ease);
}
.wh-header.is-stuck, body.mega-open .wh-header {
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
}

/* the shine line under the bar - same idiom as the .wh-band section divider:
   amber at the centre, fading to nothing at both ends. Replaces the flat
   border, so keep border-bottom transparent or you get a double line.
   Sits at bottom:-1px, i.e. exactly where the transparent border is, so
   nothing shifts when it fades in. */
.wh-header::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; z-index: 3;
  background: linear-gradient(90deg, transparent, var(--acc), transparent);
  opacity: 0; transition: opacity .4s var(--ease);
  pointer-events: none;
}
.wh-header.is-stuck::after, body.mega-open .wh-header::after { opacity: 1; }

/* SCROLLED HEADER - charcoal with a sheen, rather than flat black.
   It lives on a pseudo-element so it can cross-fade: a gradient cannot be
   transitioned, but opacity can. Before scroll the bar stays fully transparent
   and the video shows through, exactly as before.
   Sheen peaks are deliberately capped at 0.055 / 0.045: any brighter and the
   muted caret glyph drops under 4.5:1 at the brightest point of the sweep.
   Measured on the lightest pixel - cream 12.06:1, amber 8.6:1, muted 4.81:1. */
.wh-header::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  opacity: 0; transition: opacity .4s var(--ease);
  background-color: rgba(20,23,28,var(--hdr-alpha));
  background-image:
    /* the sweep - a slow diagonal highlight, warmed very slightly toward amber */
    linear-gradient(100deg,
      rgba(255,232,205,0)     0%,
      rgba(255,232,205,0.030) 30%,
      rgba(255,232,205,0.055) 47%,
      rgba(255,232,205,0.022) 63%,
      rgba(255,232,205,0)     85%),
    /* the polish - catches light along the top, settles into the bottom edge */
    linear-gradient(180deg,
      rgba(255,255,255,0.045) 0%,
      rgba(255,255,255,0.012) 42%,
      rgba(0,0,0,0.20)        100%);
}
.wh-header.is-stuck::before, body.mega-open .wh-header::before { opacity: 1; }

/* VARIANT (?sheen=logo): the highlight originates behind the wordmark instead
   of sweeping the middle of the bar, so it reads as light catching the logo.
   The centre is pinned to the LOGO, not to a percentage of the viewport - the
   shell is capped at --shell and centred, so the wordmark drifts right as the
   window widens and a percentage would slide off it. Left edge of the shell is
   50% - shell/2, then + bleed + half the 166px logo.
   Peak is 0.075 (vs 0.055 for the centre sweep) and that is safe because the
   brightest point sits over the logo, where there is no nav text. Worst case
   still measured: muted caret 4.55:1, cream 11.40:1, amber 6.18:1. */
/* DEFAULT: flat #2b2b2a at --hdr-alpha, letting the backdrop blur through.
   No gradients, no image. */
.wh-header--flat::before {
  background-color: rgba(var(--hdr-plate), var(--hdr-alpha));
  background-image: none;
}

/* VARIANT (?hdr=warm): Highland Dell treatment - a heavily blurred warm image
   baked in, not a live blur of the page. This is what actually reads as
   "frosted": a backdrop-filter over a mostly-dark page has nothing to show.
   The image is 560x96 / 2KB and generated by the scratchpad script, exposure
   SOLVED so amber holds 5:1. Opaque on purpose - the blur IS the texture, so
   nothing is gained by letting the page through, and translucency would undo
   the solved contrast. */
.wh-header--warm::before {
  background-color: #1c1510;
  background-image:
    linear-gradient(180deg, rgba(0,0,0,0) 45%, rgba(0,0,0,0.30) 100%),
    url("../img/header-blur.jpg");
  background-size: auto, cover;
  background-position: center, center;
  background-repeat: no-repeat, no-repeat;
}
/* (the amber hairline is now on .wh-header::after for every variant) */

.wh-header--sheenlogo::before {
  background-image:
    radial-gradient(58% 400% at calc(50% - (var(--shell) / 2) + var(--bleed) + 83px) 46%,
      rgba(255,232,205,0.075) 0%,
      rgba(255,232,205,0.045) 24%,
      rgba(255,232,205,0.018) 50%,
      rgba(255,232,205,0)     74%),
    linear-gradient(180deg,
      rgba(255,255,255,0.045) 0%,
      rgba(255,255,255,0.012) 42%,
      rgba(0,0,0,0.20)        100%);
}

/* the bar must sit ABOVE the plate - ::before is positioned, the bar is not,
   so without this the charcoal would paint straight over the nav */
.wh-header__in {
  position: relative; z-index: 2;
  display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 30px; height: var(--nav-h);
}
.wh-brand img { width: 166px; }

.wh-nav { justify-self: end; }
.wh-nav > ul { margin: 0; padding: 0; list-style: none; display: grid; grid-auto-flow: column; gap: 8px; align-items: center; }
.wh-nav > ul > li { position: static; }

/* top-level item: colour + underline only, no movement */
.wh-nav__top {
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 8px;
  padding: 10px 16px;
  background: transparent; border: 0; border-radius: 2px;
  color: var(--cream); font-family: var(--font-body); font-size: 20px; font-weight: 400;
  cursor: pointer;
  transition: color .26s var(--ease), background-color .26s var(--ease);
}
/* --body, not --muted: at --hdr-alpha 0.9 a bright client screenshot showing
   through the glass took the muted caret to 4.39:1 (3.6:1 over pure white).
   --body holds 6.51:1 there and is barely distinguishable at 9px. */
/* fa-chevron-down, not the U+25BC solid triangle it used to be. A chevron is
   all stroke and no fill, so it reads lighter than a filled glyph of the same
   size - it wants to run a touch large. Scaled with the label when that went
   15px -> 20px; at the old 10px it looked like a stray mark beside 20px text. */
.wh-nav__top .caret { font-size: 12px; color: var(--body); transition: color .26s var(--ease); }
.wh-nav__top:hover, .wh-nav__top:focus-visible,
.wh-nav__top[aria-expanded="true"] { color: var(--acc); background: rgba(255,255,255,0.05); }
.wh-nav__top:hover .caret, .wh-nav__top[aria-expanded="true"] .caret { color: var(--acc); }
.wh-nav a.wh-nav__top[aria-current="page"] { color: var(--acc); }

/* the panel: fades, does not slide */
.wh-mega {
  position: absolute; left: 0; right: 0; top: 100%;
  background: rgba(10,12,15,0.98);
  backdrop-filter: saturate(160%) blur(20px);
  -webkit-backdrop-filter: saturate(160%) blur(20px);
  border-top: 1px solid var(--line);
  border-bottom: 1px solid transparent;   /* the shine line below replaces it */
  opacity: 0; visibility: hidden;
  transition: opacity .3s var(--ease), visibility .3s var(--ease);
}
/* same shine hairline as the header base - it separates the panel from the
   page far better than the flat --line border did. No transition of its own:
   .wh-mega already fades as a whole, and this rides along with it. */
.wh-mega::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: -1px; height: 1px; z-index: 3;
  background: linear-gradient(90deg, transparent, var(--acc), transparent);
  pointer-events: none;
}
.wh-mega.is-open { opacity: 1; visibility: visible; }
/* Keep --bleed here. `padding: 46px 0` zeroed the horizontal padding it
   inherits from .wh-shell, so the panel ran wider than the page content and
   the mega cards did not line up with the work cards below. */
.wh-mega__in { display: grid; gap: 44px; padding: 46px var(--bleed) 52px; }
/* FLIPPED (David, 2026-08-03): copy + button on the LEFT, the three cards on
   the RIGHT. 1fr / 2.6fr keeps the thumbnails at roughly the width they had
   under the old 3fr, so the cards still line up with the work cards further
   down the page - the lede column simply takes the space the sector list left.
   Text is FIRST in the DOM, which is also the order it should stack in when
   this collapses to one column at 1180px. */
.wh-mega__in--work { grid-template-columns: 1fr 2.6fr; }
.wh-mega__in--build { grid-template-columns: 2fr 1fr; }
/* 1.4fr 1fr was sized for the old four-link list. With one link and a short
   paragraph the left column left a hole in the middle, so the card takes the
   extra width - which also suits it carrying the 30-years statement. */
.wh-mega__in--about { grid-template-columns: 1fr 1.25fr; }
/* two options, so both get a card - the links column carries the phone numbers */
.wh-mega__in--support { grid-template-columns: 1.1fr 1fr 1fr; }

.wh-mega__note { margin: -8px 0 20px; max-width: 34ch; font-size: 14.5px; color: var(--body); }

/* the up-right arrow that marks an external destination */
.wh-ext { font-size: .82em; color: currentColor; }

/* screen-reader only - announces "opens in a new tab" without showing it */
.wh-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0;
}

.wh-mega h4 {
  font-family: var(--font-body); font-size: 11px; font-weight: 500;
  letter-spacing: .26em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 22px;
}
.wh-mega ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 2px; }
/* David: vertical bounce is what he dislikes. A 1-2px HORIZONTAL nudge on
   sub-nav / mega links is fine and is the house default. Never translateY. */
.wh-mega ul a {
  display: block; padding: 9px 12px; margin-left: -12px; border-radius: 2px;
  color: var(--cream); font-size: 16px; font-weight: 300;
  transition: color .24s var(--ease), background-color .24s var(--ease), transform .24s var(--ease);
}
.wh-mega ul a:hover, .wh-mega ul a:focus-visible {
  color: var(--acc); background: rgba(255,255,255,0.05);
  transform: translateX(2px);
}
.wh-mega ul a span { display: block; font-size: 13.5px; color: var(--muted); }

.wh-mega__cols { display: grid; grid-template-columns: 1fr 1fr; gap: 34px; }

/* featured project tiles inside the Portfolio panel */
.wh-mega__feat { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
.wh-mega__feat a {
  display: grid; gap: 12px; padding: 0; margin: 0;
  border: 1px solid var(--line); border-radius: 3px; overflow: hidden;
  background: var(--ink-2);
  transition: border-color .28s var(--ease), background-color .28s var(--ease);
}
.wh-mega__feat a:hover, .wh-mega__feat a:focus-visible { border-color: var(--acc); background: var(--ink-3); }
/* same hover zoom-OUT as the home page cards - transform does not affect
   layout, so the grid cell holds its size and the card background shows in the
   margin the image leaves behind. Matches .wh-card__shot img exactly. */
.wh-mega__feat img {
  width: 100%; aspect-ratio: 16/11; object-fit: cover; object-position: top center;
  transform-origin: center;
  transition: transform .7s var(--ease);
}
.wh-mega__feat a:hover img,
.wh-mega__feat a:focus-visible img { transform: scale(0.92); }
.wh-mega__feat b { display: block; padding: 0 16px 5px; font-weight: 400; font-size: 16px; color: var(--cream); }
.wh-mega__feat em { display: block; padding: 0 16px 16px; font-style: normal; font-size: 11px; letter-spacing: .16em; text-transform: uppercase; color: var(--acc); }

/* Portfolio panel lede - the prose that replaced the sector list. No max-width
   in ch: the grid track is already narrow, so a character cap would only fight
   it. Note the two rules are BOTH one class deep - .wh-mega__lede p would beat
   a bare .wh-mega__cta on specificity and silently zero the button's margin,
   so the second selector has to carry the parent class too. */
.wh-mega__lede p { margin: 0; font-size: 15.5px; line-height: 1.62; color: var(--body); }
.wh-mega__lede .wh-mega__cta { margin: 26px 0 0; }

/* callout card with photo */
.wh-mega__card {
  position: relative; border: 1px solid var(--line); border-radius: 3px;
  overflow: hidden; display: grid; align-content: end; min-height: 320px;
  padding: 26px; background: var(--ink-2);
}
/* z-index MUST be explicit here. The `> *` rule below also matches this img and
   was lifting it to z-index 2, i.e. ON TOP of the ::after scrim - so the scrim
   painted behind the photo and did nothing. Layers: media 0, scrim 1, copy 2. */
.wh-mega__card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; z-index: 0; }
/* The old flat 0.55 -> 0.93 ramp PASSED contrast even over a white image, but
   the text still read badly: the problem is busy detail (sky, foliage, faces),
   not luminance. So the top third is opened right up - the photo shows far more
   than it used to - and the ramp then goes near-solid from ~34% down, where the
   copy actually sits. Card min-height went 250 -> 320 to buy that photo band.
   Worst case solved against a PURE WHITE image, since David swaps these:
   cream #f0f1f3 needs alpha 0.59, body #b6bcc4 needs 0.73. */
.wh-mega__card::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg,
    rgba(8,9,11,0.06) 0%,
    rgba(8,9,11,0.14) 22%,
    rgba(8,9,11,0.78) 34%,
    rgba(8,9,11,0.95) 46%,
    rgba(8,9,11,0.995) 100%);
}
.wh-mega__card > * { position: relative; z-index: 2; }
.wh-mega__card b { display: block; font-family: var(--font-display); font-weight: 300; font-size: 21px; color: var(--cream); }
/* same accent-a-word idiom as the h1/h2 em. Amber holds 5.06:1 here even over
   a pure-white image, because the card scrim is >=0.78 by the time it reaches
   the heading - so this is safe for any photo David swaps in. */
.wh-mega__card b em { font-style: normal; color: var(--acc); }
.wh-mega__card p { margin: 8px 0 18px; font-size: 14.5px; color: var(--body); }

/* burger */
.wh-burger {
  display: none; justify-self: end; width: 50px; height: 50px; padding: 0;
  background: transparent; border: 1px solid var(--line-ui); border-radius: 2px;
  cursor: pointer; position: relative;
  transition: background-color .28s var(--ease), border-color .28s var(--ease);
}
.wh-burger:hover { background: rgba(255,255,255,0.06); }
.wh-burger span {
  position: absolute; left: 14px; width: 22px; height: 1px; background: var(--cream);
  transition: opacity .2s ease, top .28s var(--ease), transform .28s var(--ease);
}
.wh-burger span:nth-child(1) { top: 18px; }
.wh-burger span:nth-child(2) { top: 24px; }
.wh-burger span:nth-child(3) { top: 30px; }
/* Open state is a real X: outer bars slide to the centre line and cross, middle
   bar fades. Movement is fine here - the site's no-motion rule is about HOVER,
   and this is a state change the user just asked for by tapping.
   The burger sits in .wh-header (z-index 900) and the drawer is 890, so it
   stays above the panel and remains tappable as the close control.
   aria-label is swapped to "Close menu" in main-003.js - already handled. */
body.nav-open .wh-burger span:nth-child(1) { top: 24px; transform: rotate(45deg); }
body.nav-open .wh-burger span:nth-child(2) { opacity: 0; }
body.nav-open .wh-burger span:nth-child(3) { top: 24px; transform: rotate(-45deg); }

/* mobile drawer - the PANEL may fade, the links never move.
   NOT --ink-0: the page behind is also near-black, so a full-bleed black panel
   gave no signal that anything had opened. It now wears the same plate + blur
   as the stuck header (--hdr-plate at --hdr-alpha), and stops 20px short of
   the right edge so a strip of the real canvas stays visible alongside - that
   sliver is what reads as "a panel slid over the page". */
.wh-mobile {
  position: fixed; top: 0; right: 20px; bottom: 0; left: 0; z-index: 890;
  background: rgba(var(--hdr-plate), var(--hdr-alpha));
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
  /* TWO rows since 2026-08-04, not three - the "Talk to us" footer block was
     removed and the nav list now runs to the bottom of the panel. The first row
     is the spacer that clears the fixed header. */
  display: grid; grid-template-rows: var(--nav-h) 1fr;
  opacity: 0; visibility: hidden;
  transition: opacity .34s var(--ease), visibility .34s var(--ease);
  overflow-y: auto;
}
/* the shine line, stood on its end - the header's ::after rotated 90deg, amber
   at the middle fading to nothing top and bottom. It marks the panel's open
   edge, which is the one edge the eye has to find. */
.wh-mobile::after {
  content: ""; position: absolute; top: 0; right: 0; bottom: 0; width: 1px;
  background: linear-gradient(180deg, transparent, var(--acc), transparent);
  pointer-events: none;
}
body.nav-open .wh-mobile { opacity: 1; visibility: visible; }
/* Scroll guard: the drawer is a fixed 2-row grid and this is the 1fr row, so
   without min-height:0 + overflow the list silently CLIPS on a short phone
   rather than scrolling - two expandable rows can now outgrow it easily. Rows
   still sit at the top when everything fits, so there is no visual change.
   ⚠️ The 90px BOTTOM PADDING is clearance, not styling. The accessibility
   widget (bottom left) and the Freshworks launcher (bottom right) are fixed and
   paint ABOVE this drawer. That clearance used to live on .wh-mobile__foot;
   with the footer gone the list runs to the bottom of the panel and inherits
   the job - without it the last row scrolls to a stop underneath them. */
.wh-mobile__list {
  min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  margin: 18px 0 0; padding: 0 var(--bleed) 90px; list-style: none;
  display: grid; align-content: start;
}
/* --line (alpha 0.09) was too faint to separate rows on a phone in daylight,
   and --line-2 (0.16) was still under-reading there - David asked for brighter
   again on 2026-08-04. Held as a DRAWER-LOCAL token rather than by pushing
   --line-2 up: that token also draws the chain connector, the band foot rule
   and every <hr> in editor content, none of which asked to change. One value,
   both drawer borders (top-level rows here, sub-rows below). */
.wh-mobile { --drawer-line: rgba(255,255,255,0.26); }
.wh-mobile__list > li { border-top: 1px solid var(--drawer-line); }
/* ⚠️ THE AMBER DASH WAS REMOVED FROM THESE ROWS on 2026-08-04 - the icon is the
   row's mark now, and the dash + its gap cost 34px of label width. That mattered
   because David reads the site ZOOMED on his phone: at his zoom level the
   viewport is narrow enough that "Policies & Accessibility" wrapped to two
   lines. Removing the dash is what buys the width back, so do NOT reinstate it
   while the icons are in - it is one or the other, not both. The house dash
   survives everywhere else (every .wh-eyebrow--rule).
   ⚠️ COUPLED, all three move together: the ::before rule (gone), the column
   count on .wh-mobile__top, and .wh-mobile__sub's left padding. */
.wh-mobile__list > li > a,
.wh-mobile__list > li > .wh-mobile__top {
  display: grid; grid-auto-flow: column; justify-content: start; align-items: center; gap: 14px;
  padding: 10px 0; color: var(--cream);
  /* 19px, down from 28. This rule only ever paints below the burger
     breakpoint, so it is a phone-only size - at 28 the words ate the panel and
     "Client Support" crowded its own edge. Padding came down with it, 20 -> 10:
     the rows inherit the body's 1.72 line-height, so the box is still ~53px
     and clears the 44px touch target with room to spare. */
  font-family: var(--font-display); font-size: 19px; font-weight: 200; letter-spacing: -0.02em;
  transition: color .24s var(--ease);
}
.wh-mobile__list > li > a:hover,
.wh-mobile__list > li > a:focus-visible,
.wh-mobile__list > li > .wh-mobile__top:hover,
.wh-mobile__list > li > .wh-mobile__top:focus-visible { color: var(--acc); }

/* ============ DRAWER ICONS - APPROVED AND KEPT, 2026-08-04 ============
   TO REVERT: delete this fenced block, delete the six <i class="wh-mobile__ico">
   tags in header.php, and PUT THE AMBER DASH BACK - the ::before rule above was
   removed to make room for these, so reverting the icons without restoring it
   would leave the rows with no mark at all. The column count on
   .wh-mobile__top and .wh-mobile__sub's left padding both stay as they are:
   the icon replaced the dash one-for-one at the same 20px + 14px gap.
   A FIXED 20px COLUMN, not the glyph's natural width - FontAwesome glyphs vary
   (fa-life-ring is round and wide, fa-paper-plane narrow and angled), so
   letting them size themselves would leave every label starting at a different
   x. Same job `fa-fw` does, done in CSS so it does not depend on a utility
   class surviving a future FA swap.
   Amber at .85, a step below the label's cream, so the icon supports the word
   rather than competing with it - and it leaves the lit state somewhere to go. */
.wh-mobile__ico {
  width: 20px; text-align: center; font-size: 15px;
  color: var(--acc); opacity: .85;
  transition: opacity .24s var(--ease);
}
.wh-mobile__list > li > a:hover .wh-mobile__ico,
.wh-mobile__list > li > a:focus-visible .wh-mobile__ico,
.wh-mobile__list > li > .wh-mobile__top:hover .wh-mobile__ico,
.wh-mobile__list > li > .wh-mobile__top:focus-visible .wh-mobile__ico,
.wh-mobile__top[aria-expanded="true"] .wh-mobile__ico { opacity: 1; }
/* ==================== END DRAWER ICONS ==================== */

/* THE ONE EXPANDABLE ROW (Client Support). Everything above is shared with the
   four plain links; what follows is only what makes this one differ.
   Button resets first - the shared rule sets the font outright, so only the
   chrome a <button> brings with it has to be undone. */
.wh-mobile__top {
  width: 100%; background: none; border: 0; text-align: left;
  -webkit-appearance: none; appearance: none; cursor: pointer;
}
/* Caret to the RIGHT EDGE, not tucked against the label as it is on desktop.
   A far-right chevron is the phone convention for "this opens", and it is the
   only visual difference between this row and the four that navigate - David
   expected a tap to expand, so the affordance has to be unmistakable.
   `justify-content: start` from the shared rule packs the columns left and
   leaves the slack unassigned, so margin-left:auto on the caret would do
   nothing (see mobile-nav-icon-space-between-trap - same trap, opposite
   direction). Give the caret its own 1fr track instead.
   FULL `.wh-mobile__list > li >` path required, not a bare `.wh-mobile__top`:
   justify-content is being overridden from the shared rule above, which carries
   that path and would otherwise win on specificity. */
.wh-mobile__list > li > .wh-mobile__top {
  /* THREE columns: icon, label, caret. Was four while the dash was still there
     - the dash's ::before was removed on 2026-08-04 to buy the label 34px back
     under zoom, so the icon simply took its slot. */
  grid-template-columns: auto auto 1fr; justify-content: normal;
}
.wh-mobile__top .caret {
  justify-self: end; font-size: 12px; color: var(--body);
  transition: transform .32s var(--ease), color .24s var(--ease);
}
.wh-mobile__top[aria-expanded="true"] { color: var(--acc); }
.wh-mobile__top[aria-expanded="true"] .caret { color: var(--acc); transform: rotate(180deg); }

/* The collapse itself: a 0fr -> 1fr grid row, so it animates to the sub-list's
   OWN height with no max-height guess to maintain. The wrapper is the grid, the
   <ul> is the single item and carries the overflow clip.
   visibility is doing the accessibility half - a 0fr row still leaves the links
   focusable and in the a11y tree, which would tab a keyboard user into a
   collapsed panel. It is delayed on the way out so the clip finishes first. */
.wh-mobile__subwrap {
  display: grid; grid-template-rows: 0fr;
  transition: grid-template-rows .32s var(--ease);
}
.wh-mobile__top[aria-expanded="true"] + .wh-mobile__subwrap { grid-template-rows: 1fr; }

/* The left padding is what INDENTS the sub-row dividers: a border on the <li>
   starts at the list's content edge, i.e. under the letters, which is the whole
   point of them.
   ⚠️ NO vertical padding or margin on this <ul>. It is the clipped grid item,
   and `box-sizing: border-box` means its own padding is a FLOOR it cannot
   shrink below - an 18px tail here made the collapsed row 71.7px against 53.7px
   for a plain one, which is the uneven gap David spotted under Client Support
   on 2026-08-04. The tail now sits on the last <li>, INSIDE the clipped box, so
   it goes to nothing when the row folds. */
.wh-mobile__sub {
  /* 34px = icon 20 + its 14px gap, so the sub-items line up under the parent's
     TEXT. Briefly 68px while the dash and the icon were both in the row. */
  margin: 0; padding: 0 0 0 34px; list-style: none; display: grid;
  min-height: 0; overflow: hidden;
  visibility: hidden; transition: visibility 0s linear .32s;
}
/* the 18px of air between the last sub-item and the next top-level row */
.wh-mobile__sub > li:last-child { padding-bottom: 18px; }
.wh-mobile__top[aria-expanded="true"] + .wh-mobile__subwrap .wh-mobile__sub {
  visibility: visible; transition-delay: 0s;
}
/* li + li, so there is no line directly under the parent row - that row already
   has its own full-width border above it and a second line would double up.
   The gap had to go: a border sits on the box edge, so with a gap the line
   hugs the row below it instead of sitting midway between the two. Symmetric
   padding on the <a> replaces it. */
.wh-mobile__sub > li + li { border-top: 1px solid var(--drawer-line); }
/* display:block so the padding makes the whole row tappable, not just the
   glyphs - 15.5px text + 24px padding clears the 44px touch target. */
.wh-mobile__sub a {
  display: block; padding: 12px 0;
  color: var(--body); font-size: 15.5px; transition: color .24s var(--ease);
}
/* colour only - no translateX. This site reverses the usual hover-nudge: the
   nav and buttons never move. */
.wh-mobile__sub a:hover, .wh-mobile__sub a:focus-visible { color: var(--acc); }
/* `.wh-mobile__foot` ("Talk to us" + the direct line) was REMOVED on 2026-08-04
   together with its markup - David dropped it in favour of a second expandable
   row. Nothing replaces it: the number is still in the Client Support mega, the
   site footer and on the contact page. Two things it used to own moved rather
   than disappeared - the drawer went from three grid rows to two, and its 90px
   of ADA/Freshworks widget clearance is now bottom padding on
   `.wh-mobile__list`. Both are commented at their new homes above. */

/* --------------------------------------------------------------------------
   6. VIDEO HERO
   -------------------------------------------------------------------------- */
.wh-hero { position: relative; min-height: 100vh; min-height: 100svh; display: grid; align-items: center; overflow: hidden; }

.wh-hero__bg { position: absolute; inset: 0; z-index: 0; background: #05070a; }
/* the poster is what paints first - the video only ever replaces it */
.wh-hero__bg img,
.wh-hero__bg video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}
.wh-hero__bg video { opacity: 0; transition: opacity 1.2s ease; }
.wh-hero__bg video.is-playing { opacity: 1; }

/* The copy all sits in the left 60%, so the right side only has to protect the
   nav - which the 180deg top stop already does. Opening the right up takes the
   worst-case coverage there from ~0.59 to ~0.25 and lets the video actually
   read. Solved against the poster: tightest zone is the nav caret at 6.56:1. */
.wh-hero__scrim {
  position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(90deg, rgba(8,9,11,0.95) 0%, rgba(8,9,11,0.86) 38%, rgba(8,9,11,0.26) 66%, rgba(8,9,11,0.08) 100%),
    linear-gradient(180deg, rgba(8,9,11,0.80) 0%, rgba(8,9,11,0.18) 40%, rgba(8,9,11,0.92) 100%);
}

/* NARROW DESKTOP - the one place the ramp above does not hold.
   The horizontal ramp assumes the copy sits in the left 60%, which is true
   once .wh-hero__copy hits its 820px cap and the viewport keeps growing. It is
   NOT true between ~760 and ~1060px: there the copy still fills ~90% of the
   width, so the h1 runs out into the 0.26 -> 0.08 tail. Solved against six
   frames of network.mp4 at each of 780/860/900/950/1000/1060: amber "Integration"
   was the failure, 4.24-4.46:1 at the tightest (x~70%, y~47%). Raising ONLY the
   two tail stops - the 0% and 38% stops are untouched, so nothing changes where
   the video already reads - takes the worst case to 5.14-5.63:1.
   This band FAILED BEFORE the mobile change too: 900-1000px already loaded the
   video, so this is a pre-existing fix, not a cost of enabling it on phones. */
@media screen and (min-width: 761px) and (max-width: 1060px) {
  .wh-hero__scrim {
    background:
      linear-gradient(90deg, rgba(8,9,11,0.95) 0%, rgba(8,9,11,0.86) 38%, rgba(8,9,11,0.40) 66%, rgba(8,9,11,0.24) 100%),
      linear-gradient(180deg, rgba(8,9,11,0.80) 0%, rgba(8,9,11,0.18) 40%, rgba(8,9,11,0.92) 100%);
  }
}

/* The gold-dust clip is ~16/255 mean luminance behind the copy, so it needs
   barely any scrim. The blue clip is ~126/255 there and needs the heavy one. */
/* Values solved against the actual artwork, not eyeballed: the gold particles
   are bright enough in places to fail AA under a lighter scrim. Worst-pixel
   contrast in the copy zone = 5.96:1 at these stops. */
.wh-hero--soft .wh-hero__scrim {
  background:
    linear-gradient(90deg, rgba(8,9,11,0.86) 0%, rgba(8,9,11,0.60) 46%, rgba(8,9,11,0.16) 100%),
    linear-gradient(180deg, rgba(8,9,11,0.72) 0%, rgba(8,9,11,0.20) 34%, rgba(8,9,11,0.82) 100%);
}

.wh-hero__in { position: relative; z-index: 2; width: 100%; padding-top: var(--nav-h); }
.wh-hero__copy { max-width: min(820px, 100%); min-width: 0; }
.wh-hero h1 {
  font-size: clamp(40px, 6.4vw, 86px);
  font-weight: 200;
  letter-spacing: -0.035em;
  line-height: 1.02;
}
.wh-hero h1 em { font-style: normal; color: var(--acc); }
.wh-hero__p { margin-top: 28px; max-width: 56ch; font-size: 19px; }
/* The price, lifted out of the paragraph in full cream. Colour only - no
   weight change: the hero type is deliberately thin and a bolder run would
   read as a different font rather than as emphasis. Cream over the hero scrim
   measures 11.4:1 (already solved for the h1), so this is BRIGHTER than the
   body text it sits in, never a contrast risk. */
.wh-hero__price { color: var(--cream); }

/* video control - REQUIRED by WCAG 2.2.2 for autoplaying motion over 5s.
   Sits clear of the Freshworks support tab, which is pinned bottom-right. */
.wh-hero__vc {
  position: absolute; right: calc(var(--bleed) - 18px); bottom: 94px; z-index: 3;
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 10px;
  padding: 10px 16px;
  background: rgba(8,9,11,0.62); color: var(--cream);
  border: 1px solid var(--line-ui); border-radius: 2px;
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer;
  transition: background-color .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease);
}
.wh-hero__vc:hover, .wh-hero__vc:focus-visible { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
.wh-hero__vc[hidden] { display: none; }

/* the band's own pause control - same object, different anchor. It needs its
   own because SC 2.2.2 wants a mechanism for EACH piece of autoplaying motion,
   and the hero button cannot reach this video. */
.wh-band__vc {
  /* same -18px nudge as .wh-hero__vc, so both controls sit on one right edge
     and clear the Freshworks tab pinned bottom-right */
  position: absolute; right: calc(var(--bleed) - 18px); bottom: 26px; z-index: 4;
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 10px;
  padding: 10px 16px;
  background: rgba(8,9,11,0.62); color: var(--cream);
  border: 1px solid var(--line-ui); border-radius: 2px;
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  cursor: pointer;
  transition: background-color .28s var(--ease), color .28s var(--ease), border-color .28s var(--ease);
}
.wh-band__vc:hover, .wh-band__vc:focus-visible { background: var(--acc); color: var(--acc-ink); border-color: var(--acc); }
.wh-band__vc[hidden] { display: none; }

/* --------------------------------------------------------------------------
   7. TICKER
   -------------------------------------------------------------------------- */
.wh-ticker {
  position: relative;
  background: var(--ink-1);
  /* Borders kept but TRANSPARENT, not removed: the shine hairlines below sit on
     these exact rows, so the box height never changes and nothing shifts. This
     is the same arrangement as .wh-header - and leaving them coloured would give
     the 2px double line that caught us at the CTA band / footer join. */
  border-top: 1px solid transparent; border-bottom: 1px solid transparent;
  padding: 22px 0 20px;
}
/* The house amber shine, top and bottom (David, 2026-08-03). --shine-soft, not
   full --acc: both of this band's neighbours are near-black (the hero bottom is
   scrimmed to ~0.95, the work section is --ink-0), and an isolated hairline
   reads heavier than an identical one with bright content beside it. Same call
   as .wh-band::after. */
.wh-ticker::before, .wh-ticker::after {
  content: ""; position: absolute; left: 0; right: 0; height: 1px;
  background: linear-gradient(90deg, transparent, var(--shine-soft), transparent);
}
.wh-ticker::before { top: -1px; }
.wh-ticker::after { bottom: -1px; }
/* The edge fade belongs to the RAIL, not to .wh-ticker. On the block it also
   ate the left-aligned label: the shell gutter (48px, 22px on phones) sits
   well inside the 7% ramp, so the label rendered part-transparent - and worse
   below --shell, where the gutter is 48px into a ~94px fade. */
.wh-ticker__rail {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}
/* Left-aligned to the standard container via a plain .wh-shell wrapper in the
   markup - no class stacked onto .wh-shell, so --bleed is not clobbered.
   Cream, not --muted, so it reads as the LABEL of the row rather than as one
   more name in it. */
/* ⚠️ The `.wh-ticker` prefix is LOAD-BEARING, not tidiness. The base sheet has a
   trailing-paragraph guard, `p:last-child { margin-bottom: 0 }` - specificity
   (0,1,1), which outranks a bare `.wh-ticker__label` (0,1,0). This label is the
   only child of its .wh-shell wrapper, so it matches that guard and its bottom
   margin was silently dropped; the gap you saw was pure line-height. Raising the
   selector to (0,2,0) is what makes the margin real. Any single-paragraph block
   on this site has the same trap waiting. */
.wh-ticker .wh-ticker__label {
  margin: 0 0 15px;   /* went 15 -> 35 -> back to 15: the +20 only became
                         visible once the p:last-child guard above was beaten,
                         and at that point it was too much */
  font-size: 11px; font-weight: 500; letter-spacing: .26em; text-transform: uppercase;
  color: var(--cream);
}
/* The duration is for ONE full pass of the doubled track, so pixel speed is
   track-width / duration - adding clients speeds the bar up unless this moves
   with it. 72s suited 24 names; 29 names needs ~88s to read the same. */
.wh-ticker__track { display: grid; grid-auto-flow: column; width: max-content; animation: whTick 88s linear infinite; }
.wh-ticker:hover .wh-ticker__track { animation-play-state: paused; }
.wh-ticker__track span {
  font-size: 12px; font-weight: 400; letter-spacing: .18em; text-transform: uppercase;
  color: var(--muted); padding: 0 24px; white-space: nowrap;
}
.wh-ticker__track span::before { content: "\2022"; color: var(--acc); margin-right: 24px; }
@keyframes whTick { from { transform: translateX(0); } to { transform: translateX(-50%); } }

/* --------------------------------------------------------------------------
   8. WORK CARDS - no lift, no zoom. Border + veil only.
   -------------------------------------------------------------------------- */
.wh-work { display: grid; grid-template-columns: repeat(3, 1fr); gap: 30px; }
.wh-card {
  display: grid; grid-template-rows: auto 1fr;
  background: var(--ink-2); border: 1px solid var(--line); border-radius: 3px; overflow: hidden;
  transition: border-color .32s var(--ease), background-color .32s var(--ease);
  /* anchor for the stretched title link below - without it the ::after would
     size itself against the page, not the card */
  position: relative;
}
.wh-card:hover, .wh-card:focus-within { border-color: var(--acc); background: var(--ink-3); }
/* transparent, NOT a fixed colour, so the margin revealed by the hover
   zoom-out is whatever the card is - and it inherits the card's own
   ink-2 -> ink-3 hover transition for free */
.wh-card__shot { position: relative; aspect-ratio: 16/11; overflow: hidden; background: transparent; }

/* Hover zoom-OUT. These are website screenshots, so scaling UP softens them -
   they are already at their natural size. Scaling down instead keeps them
   crisp and reads as the site settling back into its frame, with the card
   colour showing around the edge. The card itself never moves. */
.wh-card__shot img {
  width: 100%; height: 100%; object-fit: cover; object-position: top center;
  transform-origin: center;
  transition: transform .7s var(--ease);
}
.wh-card:hover .wh-card__shot img,
.wh-card:focus-within .wh-card__shot img { transform: scale(0.92); }
.wh-card__veil {
  position: absolute; inset: 0;
  background: linear-gradient(180deg, rgba(8,9,11,0.10), rgba(8,9,11,0.82));
  display: grid; align-items: end; justify-items: start; padding: 18px;
  opacity: 0; transition: opacity .32s var(--ease);
}
.wh-card:hover .wh-card__veil, .wh-card:focus-within .wh-card__veil { opacity: 1; }
.wh-card__veil span {
  display: inline-grid; grid-auto-flow: column; align-items: center; gap: 9px;
  background: var(--acc); color: var(--acc-ink);
  padding: 9px 16px; border-radius: 2px; font-size: 12.5px; font-weight: 500;
}
.wh-card__body { padding: 24px 24px 28px; display: grid; align-content: start; gap: 9px; }
/* 10.5 -> 11px, 2026-08-04. The accessibility scanner flags any computed size
   UNDER 11px as very small text; 11px and up is clean, verified against the
   11px labels on the same page which it does not flag. Applies to every
   amber micro-label on the site - see .wh-facts dt, .wh-chain__tag and
   .wh-footer h4, which were all on the same 10.5 and moved together. */
.wh-card__tag { font-size: 11px; font-weight: 500; letter-spacing: .22em; text-transform: uppercase; color: var(--acc); }
.wh-card h3 { font-size: 21px; letter-spacing: -0.018em; line-height: 1.28; }
.wh-card h3 a { color: var(--cream); transition: color .28s var(--ease); }
.wh-card:hover h3 a { color: var(--acc); }
/* STRETCHED LINK - the whole card is one anchor, 2026-08-04.
   The screenshot used to be a second <a> to the same URL; the accessibility
   scanner counted all 29 pairs on /portfolio/ as redundant links. This blank
   ::after covers the card instead, so the click target is unchanged (bigger,
   in fact - the tag and description are live now too) with half the anchors.
   No z-index needed: .wh-card__shot is positioned but comes FIRST in the DOM,
   so this positioned pseudo paints over it and over the veil.
   Known trade-off of the pattern: the card's paragraph is no longer
   selectable. Accepted - these are three-line summaries nobody copies. */
.wh-card h3 a::after { content: ""; position: absolute; inset: 0; }
.wh-card p { font-size: 15px; color: var(--muted); line-height: 1.6; }

.wh-work-foot { margin-top: 58px; display: grid; grid-template-columns: 1fr auto; gap: 26px; align-items: center; }
.wh-work-foot p { font-size: 18px; max-width: 50ch; }

/* --------------------------------------------------------------------------
   9. ORBITAL
   -------------------------------------------------------------------------- */
.wh-orbit-wrap { display: grid; grid-template-columns: 1fr 1fr; gap: 76px; align-items: center; }
.wh-orbit { position: relative; width: 100%; max-width: 500px; margin: 0 auto; aspect-ratio: 1; }
.wh-orbit__ring { position: absolute; border-radius: 50%; border: 1px solid rgba(255,255,255,0.10); }
.wh-orbit__ring--1 { inset: 6%; border-style: dashed; }
.wh-orbit__ring--2 { inset: 23%; }
.wh-orbit__spin { position: absolute; inset: 6%; animation: whSpin 52s linear infinite; }
.wh-orbit__spin::before {
  content: ""; position: absolute; inset: -1px; border-radius: 50%;
  border: 1px solid transparent; border-top-color: var(--acc); border-right-color: rgba(240,164,60,0.3);
}
@keyframes whSpin { to { transform: rotate(360deg); } }
.wh-orbit__core {
  position: absolute; inset: 31%; border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, #22190d, #0a0c0f 72%);
  border: 1px solid rgba(240,164,60,0.34);
  display: grid; align-content: center; justify-items: center; gap: 5px;
  text-align: center; padding: 14px;
  box-shadow: 0 0 60px rgba(240,164,60,0.12), inset 0 0 40px rgba(0,0,0,0.6);
}
.wh-orbit__core b { font-family: var(--font-display); font-size: clamp(15px,2vw,20px); font-weight: 300; color: var(--cream); line-height: 1.2; }
.wh-orbit__core span { font-size: 9.5px; letter-spacing: .24em; text-transform: uppercase; color: var(--acc); }
.wh-node { position: absolute; width: 116px; margin-left: -58px; margin-top: -44px; display: grid; justify-items: center; gap: 9px; text-align: center; }
.wh-node i {
  width: 56px; height: 56px; border-radius: 50%; display: grid; place-items: center;
  background: var(--ink-2); border: 1px solid rgba(240,164,60,0.38);
  color: var(--acc); font-size: 19px;
  box-shadow: 0 0 0 6px rgba(8,9,11,0.9);
}
.wh-node b { font-family: var(--font-display); font-size: 14px; font-weight: 300; color: var(--cream); }
.wh-node--n { left: 50%; top: 6%; }
.wh-node--e { left: 94%; top: 50%; }
.wh-node--s { left: 50%; top: 94%; }
.wh-node--w { left: 6%; top: 50%; }
.wh-orbit__note { margin-top: 24px; text-align: center; font-size: 13px; color: var(--muted); }

/* --------------------------------------------------------------------------
   10. MEDALLION GRID
   -------------------------------------------------------------------------- */
.wh-med-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
.wh-med {
  background: var(--ink-2); border: 1px solid var(--line); border-radius: 3px;
  padding: 34px 28px 36px; display: grid; align-content: start; gap: 15px;
  transition: border-color .32s var(--ease), background-color .32s var(--ease);
}
.wh-med:hover { border-color: var(--acc); background: var(--ink-3); }
.wh-med i {
  width: 58px; height: 58px; border-radius: 50%; display: grid; place-items: center;
  border: 1px solid rgba(240,164,60,0.38); color: var(--acc); font-size: 21px;
  transition: background-color .32s var(--ease), color .32s var(--ease);
}
.wh-med:hover i { background: var(--acc); color: var(--acc-ink); }
.wh-med h3 { font-size: 20px; }
.wh-med p { font-size: 15.5px; color: var(--muted); line-height: 1.62; }

/* --------------------------------------------------------------------------
   11. PHOTO BAND + CHAIN
   -------------------------------------------------------------------------- */
.wh-band { position: relative; padding: 124px 0; background-size: cover; background-position: center; overflow: hidden; }
.wh-band::before { content: ""; position: absolute; inset: 0; z-index: 1; background: linear-gradient(180deg, rgba(8,9,11,0.92), rgba(8,9,11,0.82) 46%, rgba(8,9,11,0.95)); }

/* Video band (the One System section). Layers MUST be explicit: the ::before
   scrim is generated ahead of the <video> in the box tree, so with z-index
   auto the video would paint OVER its own scrim. media 0, scrim 1, copy 2. */
.wh-band--video .wh-band__vid {
  position: absolute; inset: 0; z-index: 0;
  width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity 1.2s ease;
}
.wh-band--video .wh-band__vid.is-playing { opacity: 1; }
/* The band's poster. Held here rather than in an inline style on the section so
   the mobile swap below can override it without !important.
   Under 900px main-003.js never fetches gold.mp4, so on a phone this file is
   not a placeholder - it is the finished band, and the only thing downloaded.
   66KB -> 15KB. Paths are relative to THIS file in /css/, hence ../img/. */
.wh-band--video { background-image: url('../img/gold-poster.jpg'); }
@media screen and (max-width: 900px) {
  .wh-band--video { background-image: url('../img/gold-poster-sm.jpg'); }
}
/* Mid stop solved to 0.68 - worst-pixel contrast in the copy zone = 5.48:1 */
.wh-band--soft::before { background: linear-gradient(180deg, rgba(8,9,11,0.88), rgba(8,9,11,0.68) 45%, rgba(8,9,11,0.92)); }

/* Forest band (the CTA). Two jobs in one pseudo-element, stacked:

   layer A - the BLEED. Solid --ink-1 (#0d0f13) at the top edge, gone by 46%,
   so this band starts as the exact colour of .wh-section--raised above it and
   the seam between the two disappears. If that section's background ever
   changes, change the two ink-1 stops here to match or the seam comes back.

   layer B - the usual readability scrim, but it does NOT run to the bottom
   edge: it holds full strength to 164px off the bottom (the base of the button
   row, measured) then ramps to nothing, so the last 50px of the band is
   untouched photo and the undergrowth reads at full strength. Those two stops
   are px, not %, on purpose - the band changes height with the copy and a
   percentage would slide the clear window up into the buttons on a tall one.
   The bottom 124px is all padding, so no copy sits in the ramp; the ghost
   button is the only thing near it and its 0.36 border still measures 4.83:1
   against SC 1.4.11's 3:1. Do not push the 164px stop any lower.
   Nothing dark is left at the bottom edge to blend into the #050607 footer -
   the ::after hairline is what defines that seam now.

   Mid stop is only 0.60 - LIGHTER than the 0.82 the other bands carry - and it
   still measures 5.81:1 on the amber eyebrow (the site's existing CTA band is
   6.03:1, so this holds the same bar with more photo showing). That is only
   possible because the art is pre-toned: redwood-forest-band.jpg is
   redwood-forest.jpg with the highlights rolled off above 110 and an overall
   0.72 gain, which kills the blown-out sun shafts that were the worst pixels.
   Point this at the untoned original and the eyebrow drops to 2.80:1.
   Amber is what breaks first here, never the white text. */
.wh-band--forest { background-position: center bottom; }
.wh-band--forest::before {
  background:
    linear-gradient(180deg, rgba(13,15,19,1) 0%, rgba(13,15,19,0.98) 12%, rgba(13,15,19,0) 46%),
    linear-gradient(180deg, rgba(8,9,11,0.52) 0%, rgba(8,9,11,0.60) 52%, rgba(8,9,11,0.60) calc(100% - 164px), rgba(8,9,11,0) calc(100% - 50px));
}
/* Clouds band (#how). This art is uniformly bright with no quiet corner, so
   the flat band scrim just turns it to brown mud - darkening soft cloud forms
   removes the very light/dark separation that gave them shape. Pre-toning it
   the way redwood-forest-band.jpg was toned does not rescue it either, for the
   same reason: there is no hard structure to survive the darkening.
   So this scrim is DIRECTIONAL instead. An ellipse parked on the free
   upper-right quadrant - the one region no copy occupies - stays open at alpha
   0.18 so the gold reads as a break in the cloud, and falls to 0.88 outward,
   over the headline, the chain row and the band foot.
   Measured on the amber: 7.55:1 headline, 7.64:1 chain, 8.52:1 foot.
   DESKTOP ONLY, and that is not cosmetic. Below 1181px the chain reflows to
   two columns, the band grows to ~1700px tall, and background-size: cover
   zooms hard into a bright central strip - the ellipse then sits directly over
   copy and the amber collapses to 1.02:1. Under that width it deliberately
   falls back to the flat .wh-band scrim, whose 0.82 mid stop holds 5.86:1 even
   against a pure white source pixel, so no crop can break it. If you ever
   widen this ellipse, RE-MEASURE the headline - at 0.92W it bled left and took
   the amber down to 3.04:1. */
@media screen and (min-width: 1181px) {
  .wh-band--clouds::before {
    background:
      radial-gradient(ellipse 34% 32% at 82% 12%, rgba(8,9,11,0.18), rgba(8,9,11,0.88) 70%),
      linear-gradient(180deg, transparent 42%, rgba(8,9,11,0.58) 100%);
  }
}

.wh-band > .wh-shell { position: relative; z-index: 2; }
/* Same 1px hairline as the header and mega, but at --shine-soft. It is
   geometrically identical to those - the reason it LOOKED heavier is that it
   is isolated between the near-black band bottom and the near-black footer,
   whereas the header line has brighter page content directly beneath it
   diluting it. Equal pixels, unequal surround, so the alpha compensates. */
.wh-band::after { content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; z-index: 3; background: linear-gradient(90deg, transparent, var(--shine-soft), transparent); }

/* auto-fits however many steps $wh_chain holds - was hard-coded to 5 and broke
   the moment a 6th was added */
.wh-chain { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; }
.wh-chain__step { position: relative; padding: 0 20px; }
.wh-chain__step:first-child { padding-left: 0; }
.wh-chain__step:last-child { padding-right: 0; }
.wh-chain__step::before { content: ""; position: absolute; left: 0; right: 0; top: 20px; height: 1px; background: var(--line-2); }
.wh-chain__step:first-child::before { left: 20px; }
.wh-chain__step:last-child::before { right: 20px; }
.wh-chain__dot {
  position: relative; z-index: 2; width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; background: var(--ink-0); border: 1px solid var(--acc);
  color: var(--acc); font-family: var(--font-display); font-size: 13px; font-weight: 400; margin-bottom: 24px;
}
.wh-chain__step h3 { font-size: 22px; }
.wh-chain__step p { margin-top: 11px; font-size: 15.5px; line-height: 1.6; }
.wh-chain__tag {
  display: inline-block; margin-top: 16px; font-size: 11px; font-weight: 500;
  letter-spacing: .18em; text-transform: uppercase; padding: 6px 12px; border-radius: 2px;
  background: var(--acc); color: var(--acc-ink);
}
.wh-chain__tag--muted { background: rgba(255,255,255,0.13); color: var(--cream); }
/* Optional = the only item that is NOT part of the fee, so it gets the
   quietest treatment: outline, no fill. Padding is 1px tighter to absorb the
   border so it sits the same height as the filled tags beside it. */
.wh-chain__tag--optional {
  background: transparent; color: var(--cream);
  border: 1px solid var(--line-ui); padding: 5px 11px;
}
.wh-band-foot { margin-top: 70px; padding-top: 32px; border-top: 1px solid var(--line-2); display: grid; grid-template-columns: 1fr auto; gap: 28px; align-items: center; }
.wh-band-foot p { font-size: 20px; color: var(--cream); max-width: 54ch; }

/* --------------------------------------------------------------------------
   12. SPLIT
   -------------------------------------------------------------------------- */
.wh-split { display: grid; grid-template-columns: 1fr 1fr; gap: 72px; align-items: center; }
.wh-split--flip .wh-split__media { order: 2; }
.wh-split__media { position: relative; border-radius: 3px; overflow: hidden; border: 1px solid var(--line); }
.wh-split__media img { width: 100%; aspect-ratio: 4/3.2; object-fit: cover; }
/* Portrait variant. The virtual-webmaster block has a lot of copy on the
   right, so a landscape image left a short picture beside a tall column.
   4/5.2 matches the source (825x1073) so nothing is cropped away. */
.wh-split__media--tall img { aspect-ratio: 4 / 5.2; }
/* Lets the art keep its own proportions instead of being cropped to the 4/3.2
   box - for portrait pieces whose composition runs edge to edge and cannot
   lose its sides. Used by the agency section: san-francisco.jpg carries
   Salesforce Tower hard against the left edge and the Transamerica pyramid
   against the right, so ANY horizontal crop decapitates a landmark.
   That is also why this is not align-self: stretch. Stretching would match the
   text column's height exactly, but it would do it by cropping width, and the
   split is 2-col only above 1180px where the column runs 520-586px - at the
   narrow end that crop reaches ~11% a side and takes the tower with it.
   Natural leaves the image ~56px shy of the copy at 1340px, which the grid's
   align-items: center distributes top and bottom. */
.wh-split__media--natural img { aspect-ratio: auto; }
/* Because the natural image is shorter than the copy, .wh-split's
   align-items: center splits the difference into two gaps. Sitting it on the
   baseline instead lines the bottom of the art up with the bottom of the
   facts card and collects the whole gap at the top, under the eyebrow.
   Only worth doing while the two are side by side - below 1180px the split is
   a single column and there is no shared baseline to meet. */
@media screen and (min-width: 1181px) {
  .wh-split__media--natural { align-self: end; }
}

.wh-split__media::after { content: ""; position: absolute; inset: 0; background: linear-gradient(200deg, rgba(240,164,60,0.10), rgba(8,9,11,0.52)); }

.wh-list { margin: 32px 0 0; padding: 0; list-style: none; display: grid; }
.wh-list li { display: grid; grid-template-columns: 52px 1fr; gap: 20px; padding: 20px 0; border-bottom: 1px solid var(--line); }
.wh-list li:first-child { border-top: 1px solid var(--line); }
.wh-list i { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; border: 1px solid rgba(240,164,60,0.36); color: var(--acc); font-size: 16px; }
.wh-list b { display: block; color: var(--cream); font-family: var(--font-display); font-size: 17px; font-weight: 300; }
.wh-list span { display: block; margin-top: 5px; font-size: 15px; color: var(--muted); line-height: 1.58; }

/* --------------------------------------------------------------------------
   13. HOSTING
   -------------------------------------------------------------------------- */
.wh-host { display: grid; grid-template-columns: repeat(4, 1fr); border-top: 1px solid var(--line); }
.wh-host__item { padding: 38px 28px; border-bottom: 1px solid var(--line); border-right: 1px solid var(--line); }
.wh-host__item:first-child { padding-left: 0; }
.wh-host__item:last-child { border-right: 0; padding-right: 0; }
.wh-host__k { display: inline-block; margin-bottom: 16px; font-family: var(--font-display); font-size: 30px; font-weight: 200; color: var(--acc); }
.wh-host__item h3 { font-size: 17px; }
.wh-host__item p { margin-top: 10px; font-size: 15px; color: var(--muted); }

/* --------------------------------------------------------------------------
   14. FACTS
   -------------------------------------------------------------------------- */
.wh-facts { margin-top: 32px; border: 1px solid var(--line); border-radius: 3px; padding: 28px; display: grid; gap: 18px; background: var(--ink-2); }
.wh-facts dl { margin: 0; display: grid; gap: 18px; }
.wh-facts dt { font-size: 11px; font-weight: 500; letter-spacing: .22em; text-transform: uppercase; color: var(--acc); }
.wh-facts dd { margin: 6px 0 0; color: var(--cream); font-size: 16.5px; font-weight: 300; line-height: 1.55; }
.wh-facts dd a { color: var(--cream); transition: color .26s var(--ease); }
.wh-facts dd a:hover { color: var(--acc); }

/* --------------------------------------------------------------------------
   15. FOOTER
   -------------------------------------------------------------------------- */
/* NO border-top. The CTA band above ends with the amber shine hairline, and a
   grey border here stacked directly under it made a 2px bar - visibly thicker
   than the same hairline at the base of the mega. One line, not two. */
.wh-footer { background: #050607; padding: 80px 0 0; }
.wh-footer__top { display: grid; grid-template-columns: 1.4fr 1fr 1fr 1fr; gap: 42px; padding-bottom: 54px; border-bottom: 1px solid var(--line); }
.wh-footer img.wh-flogo { width: 176px; }
.wh-footer h4 { font-family: var(--font-body); font-size: 11px; font-weight: 500; letter-spacing: .24em; text-transform: uppercase; color: var(--acc); margin-bottom: 20px; }
/* Row pitch here has to equal the address column's so all four footer columns
   sit on one baseline grid. address is 17px at 1.8 = 30.6px a line, so the
   lists drop the 12px gap and take the same 1.8 instead - a 12px gap on top of
   a 1.72 line box was 41.2px, which is what made the left columns read loose.
   Change one, change the other.
   The lost gap does not cost target spacing: SC 2.5.8's offset exception wants
   24px between adjacent targets and the 30.6px pitch clears it. */
.wh-footer ul { margin: 0; padding: 0; list-style: none; display: grid; gap: 0; }
.wh-footer li { line-height: 1.8; }
.wh-footer a { color: var(--body); transition: color .26s var(--ease); }
.wh-footer a:hover, .wh-footer a:focus-visible { color: var(--acc); }
.wh-footer address { font-style: normal; line-height: 1.8; }
.wh-footer__tag { margin-top: 20px; font-size: 14.5px; color: var(--muted); max-width: 34ch; }
/* Bottom padding is 100, not the 40 it was. The Freshworks support tab is
   position: fixed bottom-right and the legal links are the right-hand column of
   this row, so at full scroll the tab sat directly on top of Terms / Privacy /
   Accessibility and swallowed the clicks. The tab occupies roughly the lowest
   68px of the viewport, so 100 clears it outright rather than by a hair.
   The padding lives HERE and not on the body: the footer is #050607 against the
   body's --ink-0, so padding the body would show as a lighter strip under the
   footer instead of extending it. */
.wh-footer__bottom { padding: 24px 0 100px; display: grid; grid-template-columns: 1fr auto; gap: 16px; align-items: center; font-size: 13.5px; color: var(--muted); }
.wh-footer__bottom p { margin: 0; }

/* --------------------------------------------------------------------------
   16. LOADERS
   -------------------------------------------------------------------------- */
#page-loader {
  position: fixed; inset: 0; z-index: 99999; background: var(--ink-0);
  display: grid; align-content: center; justify-items: center; gap: 24px;
  transition: opacity .85s ease, visibility .85s ease;
}
#page-loader.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.pl-logo { width: 204px; opacity: 0; animation: whFade .7s .15s forwards ease-out; }
/* Track widened to sit under the longer tagline, and the rail dropped to 0.08
   so the unfilled part reads as a hint rather than a bar. */
.pl-track {
  width: min(430px, 78vw); height: 1px;
  background: rgba(255,255,255,0.08); overflow: hidden;
  opacity: 0; animation: whFade .4s .45s forwards ease-out;
}
/* The fill FADES IN from the left instead of being a solid block - same idiom
   as the eyebrow rule and the section hairlines, so the loader belongs to the
   same family. The gradient scales with the width, so the leading edge stays
   bright amber while the tail always dissolves. */
.pl-fill {
  height: 100%; width: 0;
  background: linear-gradient(90deg, rgba(240,164,60,0) 0%, rgba(240,164,60,0.55) 45%, var(--acc) 100%);
  animation: whBar 1.5s .55s forwards cubic-bezier(.3,.6,.2,1);
}
@keyframes whBar { 0% { width: 0; } 62% { width: 74%; } 100% { width: 100%; } }
.pl-tag {
  font-size: 11px; letter-spacing: .24em; text-transform: uppercase;
  color: var(--muted); text-align: center; max-width: min(430px, 78vw);
  opacity: 0; animation: whFade .4s .85s forwards ease-out;
}
@keyframes whFade { to { opacity: 1; } }

#click-loader {
  position: fixed; inset: 0; z-index: 99998; background: transparent;
  opacity: 0; visibility: hidden; pointer-events: none;
  display: grid; align-content: center; justify-items: center;
  transition: opacity .3s ease, visibility .3s ease;
}
#click-loader.active { opacity: 1; visibility: visible; pointer-events: all; }
.cl-dots { display: grid; grid-auto-flow: column; gap: 9px; }
.cl-dots span { width: 8px; height: 8px; border-radius: 50%; background: var(--acc); animation: whDot 1.1s infinite ease-in-out; }
.cl-dots span:nth-child(2) { animation-delay: .18s; background: var(--acc-soft); }
.cl-dots span:nth-child(3) { animation-delay: .36s; background: rgba(255,255,255,0.34); }
@keyframes whDot { 0%,80%,100% { opacity: .3; } 40% { opacity: 1; } }

/* --------------------------------------------------------------------------
   17. SCROLL REVEAL - fade only, no travel (David: no movement)
   -------------------------------------------------------------------------- */
.wh-reveal { opacity: 0; transition: opacity .85s var(--ease); }
.wh-reveal.vis { opacity: 1; }
.wh-reveal.d1 { transition-delay: .08s; }
.wh-reveal.d2 { transition-delay: .16s; }
.wh-reveal.d3 { transition-delay: .24s; }
.wh-reveal.d4 { transition-delay: .32s; }
.wh-reveal.d5 { transition-delay: .40s; }

/* --------------------------------------------------------------------------
   18. RESPONSIVE
   -------------------------------------------------------------------------- */
@media screen and (max-width: 1180px) {
  :root { --bleed: 34px; }
  .wh-work, .wh-med-grid { grid-template-columns: repeat(2, 1fr); }
  .wh-orbit-wrap, .wh-split { grid-template-columns: 1fr; gap: 52px; }
  .wh-split--flip .wh-split__media { order: 0; }
  .wh-chain { grid-auto-flow: row; grid-template-columns: repeat(2, 1fr); gap: 42px 22px; }
  .wh-chain__step { padding: 0; }
  .wh-chain__step::before { display: none; }
  .wh-host { grid-template-columns: repeat(2, 1fr); }
  .wh-host__item:nth-child(2n) { border-right: 0; padding-right: 0; }
  .wh-host__item:nth-child(2n+1) { padding-left: 0; }
  .wh-mega__in--work, .wh-mega__in--build, .wh-mega__in--about { grid-template-columns: 1fr; }
}

@media screen and (max-width: 992px) {
  .wh-nav { display: none; }
  .wh-burger { display: block; }
  /* Bar is solid from page load on mobile, rather than fading in past 30px of
     scroll. On a phone the transparent state sits over the top of the hero for
     the whole first screen and the burger has to hold its own against whatever
     frame the art happens to be on. Same three things .is-stuck turns on -
     plate, shine line, blur - just never turned off. Deliberately tied to this
     breakpoint: it is exactly where the nav gives way to the burger. */
  .wh-header {
    backdrop-filter: saturate(160%) blur(18px);
    -webkit-backdrop-filter: saturate(160%) blur(18px);
  }
  .wh-header::before,
  .wh-header::after { opacity: 1; }
  .wh-header__in { grid-template-columns: auto 1fr; }
  .wh-section, .wh-band { padding: 88px 0; }
  .wh-footer__top { grid-template-columns: 1fr 1fr; gap: 42px 30px; }
}

@media screen and (max-width: 760px) {
  :root { --bleed: 22px; --nav-h: 74px; }
  body { font-size: 16px; }
  /* svh, matching the base rule - the old bare 90vh was throwing this away at
     exactly the width it matters most. On iOS `vh` is the LARGE viewport, i.e.
     the height with the browser chrome hidden, so a 90vh hero is taller than
     what is actually on screen and the centred copy sits lower than it does in
     desktop device emulation, where vh and the visible area are the same thing.
     That mismatch is why the pause button collided on a real iPhone and not in
     a resized desktop window. */
  .wh-hero { min-height: 90vh; min-height: 90svh; }
  /* +40 on top of --nav-h. The bar is opaque from load at this width now, so
     the eyebrow reads as sitting ON the header rather than below it. Started at
     +20; David asked for 20 more (2026-08-03) - it gives the eyebrow room to
     breathe and opens up more of the moving background above the copy. Only
     needed where the bar is solid. */
  .wh-hero__in { padding-top: calc(var(--nav-h) + 40px); }
  .wh-hero__scrim {
    background: linear-gradient(180deg, rgba(8,9,11,0.88) 0%, rgba(8,9,11,0.58) 38%, rgba(8,9,11,0.95) 100%);
  }
  .wh-hero__p { font-size: 17px; margin-top: 22px; }
  .wh-actions { grid-auto-flow: row; justify-content: stretch; }
  .wh-button-primary, .wh-button-ghost { justify-content: center; }
  /* RARELY SEEN as of 2026-08-04: main-003.js no longer starts the hero video
     under 900px, and the control is only unhidden when the video actually
     starts - so on a phone this button does not exist. What is left below is
     the fallback for one case: a DESKTOP window narrowed past 760 while the
     clip is already playing. The motion is real then, so WCAG 2.2.2 still
     wants the control and it must not simply be display:none'd.
     Layout, for that case: IN THE FLOW, sitting under the buttons - not
     floating in a corner. There is no free corner to float into: .wh-actions
     stretches to the full column, and on a small phone the copy fills the hero
     outright, so the hero is content-height and has no slack at the bottom.
     Bottom-left lands on the ghost button, bottom-right is the accessibility +
     Freshworks widgets, and top-right reads as a stray chip above the eyebrow.
     .wh-hero is already `display: grid`, so dropping position:absolute makes
     this the second grid item and it lands directly below .wh-hero__in with no
     markup change. align-content keeps the pair grouped and centred instead of
     letting the two auto rows stretch apart. */
  .wh-hero { align-content: center; }
  .wh-hero__vc {
    position: static; justify-self: start; align-self: start;
    /* 40 above (clear of "See our work") and 40 below, to the ticker's top
       border. The ticker's own 32px top padding then spaces its label off that
       border - the two are separate gaps and both were asked for. */
    margin: 40px 0 40px var(--bleed);
    /* Matches .wh-button-ghost's type and padding so it reads as a real button
       rather than a chip. Deliberately NOT stretched like .wh-actions: this is
       a utility control, and full width would give it the same weight as the
       two CTAs above it. */
    padding: 16px 30px;
    font-size: 15px; font-weight: 500; letter-spacing: normal; text-transform: none;
    gap: 12px;
  }
  /* the label was tight against the hero above it */
  .wh-ticker { padding: 32px 0 26px; }
  /* .wh-ticker prefix required here too - see the p:last-child note on the base rule */
  /* font-size dropped from this override 2026-08-04 - it reduced the base 11px
     to 10px and put the label under the scanner's small-text threshold on
     phones only. The letter-spacing cut is what actually buys the width back
     (.26em -> .22em on a wide-tracked label), so the space saving survives. */
  .wh-ticker .wh-ticker__label { letter-spacing: .22em; margin-bottom: 18px; }
  .wh-work, .wh-med-grid { grid-template-columns: 1fr; }
  .wh-chain { grid-auto-flow: row; grid-template-columns: 1fr; gap: 34px; }
  .wh-host { grid-template-columns: 1fr; }
  .wh-host__item { border-right: 0; padding: 26px 0; }
  .wh-work-foot, .wh-band-foot, .wh-footer__top, .wh-footer__bottom { grid-template-columns: 1fr; }
  .wh-orbit { max-width: 320px; }
  .wh-node { width: 90px; margin-left: -45px; }
  .wh-node i { width: 46px; height: 46px; font-size: 16px; }
  .wh-node b { font-size: 12px; }
}

@media screen and (max-width: 420px) {
  .wh-brand img { width: 142px; }
  .wh-orbit { max-width: 274px; }
}

/* --------------------------------------------------------------------------
   19. REDUCED MOTION - video never autoplays, poster stands in
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .wh-reveal { opacity: 1; }
  .wh-orbit__spin, .wh-ticker__track { animation: none !important; }
  /* The universal rule above zeroes transition-DURATION but not delay, and the
     drawer accordion hides its sub-list on a .32s visibility delay. Left alone
     the panel would snap shut while its links stayed focusable for a third of a
     second. */
  .wh-mobile__sub { transition-delay: 0s; }
}

@media print {
  #page-loader, #click-loader, .wh-header, .wh-ticker, .wh-hero__vc, .wh-band__vc { display: none !important; }
}

/* --------------------------------------------------------------------------
   20. WORDPRESS EDITOR CONTENT
   Terms / Privacy / Accessibility and the contact page body come from the WP
   editor, so the theme has to style raw editor output rather than known markup.
   -------------------------------------------------------------------------- */
.wh-prose { max-width: 100%; }   /* full content width - David prefers this, see standard-page-text-width-caps */
.wh-prose h2 { font-size: clamp(24px, 2.6vw, 34px); margin: 46px 0 16px; }
.wh-prose h3 { font-size: clamp(19px, 1.9vw, 24px); margin: 34px 0 12px; }
.wh-prose h2:first-child, .wh-prose h3:first-child { margin-top: 0; }
.wh-prose p { margin: 0 0 20px; }
.wh-prose ul, .wh-prose ol { margin: 0 0 22px; padding-left: 22px; display: grid; gap: 9px; }
.wh-prose li { padding-left: 4px; }
/* ⚠️ The :not() pair is LOAD-BEARING. A button dropped into editor content is
   still an <a>, and `.wh-prose a` (0,1,1) outranks `.wh-button-primary` (0,1,0),
   so prose wins EVERY property it declares. It took three properties in turn:
     color          -> amber label on an amber button, text invisible
     border-bottom  -> an underline struck through the button
     padding-bottom -> 1px instead of 16px, so the label sat low in a squat box
   Restating each stolen property on an override rule is whack-a-mole and I lost
   that game twice. Excluding the buttons from the link rule once is the only
   version that also covers whatever gets added to the button rules later.
   Keep any new theme button class in this :not() list. */
.wh-prose a:not(.wh-button-primary):not(.wh-button-ghost) {
  color: var(--acc); border-bottom: 1px solid rgba(240,164,60,0.4); padding-bottom: 1px;
}
.wh-prose a:not(.wh-button-primary):not(.wh-button-ghost):hover,
.wh-prose a:not(.wh-button-primary):not(.wh-button-ghost):focus-visible {
  color: var(--acc-soft); border-bottom-color: var(--acc-soft);
}
.wh-prose strong { color: var(--cream); font-weight: 500; }
.wh-prose hr { border: 0; height: 1px; background: var(--line-2); margin: 42px 0; }
.wh-prose blockquote {
  margin: 30px 0; padding: 4px 0 4px 24px;
  border-left: 1px solid var(--acc); color: var(--cream); font-size: 19px;
}
.wh-prose table { width: 100%; border-collapse: collapse; margin: 0 0 24px; }
.wh-prose th, .wh-prose td { text-align: left; padding: 11px 14px; border-bottom: 1px solid var(--line); }
.wh-prose th { color: var(--cream); font-weight: 500; }
/* editor-inserted images should never break the grid */
.wh-prose img { max-width: 100%; height: auto; border-radius: 3px; margin: 8px 0 24px; }

/* --------------------------------------------------------------------------
   CONTACT LAYOUT - form panel + aside cards

   .wh-split is align-items: center, which against a tall form pushed the short
   aside column down to the form's midpoint - that is the "too low and a bit
   lost" - so this page tops them out instead.

   Elevation ladder, darkest to lightest: page --ink-0, panel/card --ink-1,
   input --ink-2. Each surface a step lighter than the one it sits on, which is
   what makes the fields read as inset without needing a shadow.
   -------------------------------------------------------------------------- */
.wh-page-contact .wh-split { align-items: start; }

.wh-form,
.wh-aside__card {
  background: var(--ink-1);
  border: 1px solid var(--line);
  border-radius: 3px;
}
.wh-form { padding: 40px; }

.wh-aside { display: grid; gap: 18px; }
.wh-aside__card { padding: 28px 30px; }
/* Accent on the RIGHT edge, not the left: this column sits on the right of the
   split, so the rule lands on the page's outer edge and frames it. Flip to
   border-left if the column is ever moved. */
.wh-aside__card--accent { border-right: 3px solid var(--acc); }
.wh-aside__card .wh-eyebrow { margin-bottom: 16px; }
.wh-aside__card address { font-style: normal; line-height: 1.8; color: var(--body); }
.wh-aside__card p { margin: 0; font-size: 15px; color: var(--body); }
.wh-aside__card .wh-contact-list a { font-size: 24px; }

@media screen and (max-width: 1180px) {
  .wh-form { padding: 30px 24px; }
}

/* --------------------------------------------------------------------------
   GRAVITY FORM (contact)
   GF's own CSS is off site-wide (functions.php: gform_disable_css), so these
   plain class rules fully own the markup - no #gform_wrapper_ID, no !important.
   Never select on the form id: it differs between localhost and production.
   .wh-form sits on the SAME element as .wh-prose, so anything prose styles
   (ul as a grid, links with an underline) has to be undone here explicitly.
   -------------------------------------------------------------------------- */
.wh-form .gform_wrapper { margin: 0; }
.wh-form .gform_fields {
  display: grid; gap: 22px;
  margin: 0; padding: 0; list-style: none;   /* .wh-prose ul would add both */
}
.wh-form .gfield { margin: 0; padding: 0; }
/* GF wraps the Name field in a <fieldset>; the browser default border and
   padding is the stray box that appears around First/Last. */
.wh-form fieldset.gfield { border: 0; padding: 0; min-width: 0; }

.wh-form .gfield_label,
.wh-form legend.gfield_label {
  display: block; padding: 0; margin: 0 0 9px;
  font-family: var(--font-body);
  font-size: 11px; font-weight: 500; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--body);
}
/* First / Last sit under the group legend, so they stay sentence case */
.wh-form .gform-field-label--type-sub {
  display: block; margin: 7px 0 6px;
  font-size: 12.5px; font-weight: 300; letter-spacing: 0.02em;
  text-transform: none; color: var(--muted);
}

.wh-form input[type="text"],
.wh-form input[type="email"],
.wh-form input[type="tel"],
.wh-form input[type="url"],
.wh-form input[type="number"],
.wh-form select,
.wh-form textarea {
  width: 100%; box-sizing: border-box;
  padding: 13px 15px;
  background: var(--ink-2);
  border: 1px solid var(--line-2);
  border-radius: 2px;
  font-family: var(--font-body); font-size: 16px; font-weight: 300;
  color: var(--cream);
  transition: border-color .26s var(--ease), box-shadow .26s var(--ease);
  -webkit-appearance: none; appearance: none;
}
.wh-form textarea { min-height: 160px; resize: vertical; line-height: 1.6; }
.wh-form input::placeholder,
.wh-form textarea::placeholder { color: var(--muted); opacity: .8; }
/* Amber ring rather than the browser default. :focus-visible alone would miss
   click-focus on inputs, where a visible ring is genuinely wanted. */
.wh-form input:focus,
.wh-form select:focus,
.wh-form textarea:focus {
  outline: none;
  border-color: var(--acc);
  box-shadow: 0 0 0 3px rgba(240,164,60,0.20);
}

/* First/Last two-up. With GF's CSS off its own gform-grid-* classes are inert,
   so the grid has to be supplied here. */
.wh-form .ginput_container_name { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.wh-form .ginput_container_name > span { min-width: 0; }

/* Required mark - GF emits two different shapes depending on version/settings,
   so both are handled or the asterisk doubles up. */
.wh-form .gfield_required:not(.gfield_required_asterisk) .gfield_required_text { display: none; }
.wh-form .gfield_required:not(.gfield_required_asterisk)::after { content: "*"; color: var(--acc); margin-left: 4px; }
.wh-form .gfield_required.gfield_required_asterisk { color: var(--acc); margin-left: 4px; }

/* Validation. Warm red so it separates from the amber accent while still
   sitting in the palette - measures 6.4:1 on --ink-0. */
.wh-form .gfield_error input,
.wh-form .gfield_error textarea { border-color: #e5705a; }
.wh-form .validation_message { margin-top: 7px; font-size: 13.5px; color: #e5705a; }
.wh-form .gform_validation_errors {
  margin-bottom: 24px; padding: 14px 16px;
  background: rgba(229,112,90,0.10);
  border: 1px solid rgba(229,112,90,0.38);
  border-radius: 2px;
}
.wh-form .gform_validation_errors h2,
.wh-form .gform_validation_errors .gform_submission_error {
  margin: 0; font-family: var(--font-body); font-size: 15px; color: #e5705a;
}
.wh-form .gform_confirmation_message { font-size: 17px; line-height: 1.7; color: var(--cream); }

.wh-form .gform_footer { margin: 26px 0 0; padding: 0; }
/* the submit is a real .wh-button-primary (see gform_submit_button filter), so
   it needs the underline .wh-prose puts on links stripped, nothing more */
.wh-form .gform_footer .wh-button-primary { border-bottom: 0; }

/* GF's own hiding CSS is off with the rest of it, so the honeypot and the
   anti-spam machinery would otherwise RENDER. This block is load-bearing. */
.wh-form .gform_hidden,
.wh-form .gfield_visibility_hidden,
.wh-form .gform_validation_container,
.wh-form .gfield--type-honeypot { display: none; }

@media screen and (max-width: 560px) {
  .wh-form .ginput_container_name { grid-template-columns: 1fr; }
}

/* legal row in the footer bottom */
.wh-footer__legal { display: grid; grid-auto-flow: column; justify-content: start; gap: 22px; margin: 0; }
.wh-footer__legal a { color: var(--muted); }
.wh-footer__legal a:hover, .wh-footer__legal a:focus-visible { color: var(--acc); }

/* phone list on the contact page */
.wh-contact-list { margin: 0; padding: 0; list-style: none; display: grid; gap: 18px; }
.wh-contact-list a {
  display: block; color: var(--cream); font-family: var(--font-display);
  font-weight: 300; font-size: 26px; letter-spacing: -0.01em;
  transition: color .26s var(--ease);
}
.wh-contact-list a:hover, .wh-contact-list a:focus-visible { color: var(--acc); }
.wh-contact-list span { display: block; font-size: 13px; color: var(--muted); margin-top: 2px; }

@media screen and (max-width: 760px) {
  .wh-footer__legal { grid-auto-flow: row; gap: 10px; }
}

/* --------------------------------------------------------------------------
   21. PAGE HERO - thin banded header for interior pages
   Uses the gold-dust poster (a frame from David's own licensed Envato clip),
   NOT one of the unlicensed stock photos.
   -------------------------------------------------------------------------- */
/* grid + align-content so the title CENTRES in the band rather than being
   positioned by padding alone - the band can then be given a height without the
   copy drifting to the top of it. Only .wh-shell is an in-flow child here; the
   ::before scrim and ::after hairline are absolute and sit outside the grid. */
.wh-pagehero {
  position: relative; overflow: hidden;
  display: grid; align-content: center;
  padding: calc(var(--nav-h) + 92px) 0 84px;
  /* center top, not center 38%: the band is short and the viewport is wide, so
     cover throws away most of the image height on desktop. Anchoring the TOP
     makes what survives predictable - it is always the top of the frame, and
     the crop only ever eats upward from the bottom - so the art can be composed
     against a known edge instead of a moving one. */
  background-size: cover; background-position: center top;
}
/* Two layers: a horizontal ramp that keeps the left dark enough for the h1, and
   a vertical one that settles the band against the bar above and the page below.

   The vertical layer used to run 0.72 -> 0.22 -> 0.78. On a 300px band the two
   ENDS are only 300px apart, so those heavy stops dominated the whole height and
   veiled the photo right across - the 0.22 was a sliver at the exact midpoint,
   not a plateau. It now holds 0.04 from 22% to 78% and only darkens at the very
   edges, which is what actually opens the art up.
   The old 0.72 top also predates the header change: it was sized for a header
   floating TRANSPARENT over the hero. The bar is opaque now and owns its own
   edge, so the band no longer has to darken itself to meet it.

   Measured against a PURE WHITE image, the worst case this file always solves
   against because the art is swapped per page:
     h1 zone      7.10:1 (was 7.46 - unchanged in practice)
     right edge   veil 0.30 top / 0.14 mid / 0.46 bottom (was 0.78 / 0.39 / 0.82)
   The h1 sits LEFT, so the left ramp is the part that must not be lightened -
   the longest title (Accessibility) reaches ~40% of the width. */
.wh-pagehero::before {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(90deg, rgba(8,9,11,0.90) 0%, rgba(8,9,11,0.80) 32%, rgba(8,9,11,0.32) 64%, rgba(8,9,11,0.10) 100%),
    linear-gradient(180deg, rgba(8,9,11,0.22) 0%, rgba(8,9,11,0.04) 22%, rgba(8,9,11,0.04) 78%, rgba(8,9,11,0.40) 100%);
}
/* the house shine hairline closes the band, same as the header and mega */
.wh-pagehero::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px; z-index: 3;
  background: linear-gradient(90deg, transparent, var(--acc), transparent);
}
.wh-pagehero > .wh-shell { position: relative; z-index: 2; }
.wh-pagehero h1 { font-size: clamp(34px, 5vw, 62px); letter-spacing: -0.03em; }
.wh-pagehero h1 em { font-style: normal; color: var(--acc); }
.wh-pagehero .wh-lede { margin-top: 16px; }

/* interior pages start right below the band, so no extra top padding. 54, not
   the 84 it was: the hero band now carries its own height, so the old gap left
   the eyebrow floating too far off the hairline that closes the band. */
.wh-section--afterhero { padding-top: 54px; }

/* INTERIOR PAGES: the band sits BELOW the bar instead of running under it.
   Everywhere else the header floats transparent over the top of the hero and
   .wh-pagehero's calc(--nav-h + 92px) top padding is what keeps the h1 clear
   of it. Here the page is pushed down by --nav-h instead, so the bar owns a
   solid strip of its own and the photo starts under it - which means the hero
   padding drops the --nav-h it no longer has to clear.
   padding on the body, not margin on the hero: .wh-pagehero is body's first
   child, so a top margin would collapse through it and move the body instead.
   The bar is then pinned to its scrolled look. NOT by pre-setting .is-stuck -
   main-003.js toggles that against scrollY > 30 and would strip it the moment
   you scroll - so this re-declares the same three things is-stuck turns on:
   the ::before plate, the ::after shine line, and the backdrop blur.
   Contrast improves rather than degrades: the nav was previously sitting on
   bare photo here, and now it sits on the measured --hdr-plate ground. */
body.wh-page-banded { padding-top: var(--nav-h); }
/* Taller band - 300px against the ~245px the padding alone used to give - with
   the title centred in it by the grid above. box-sizing is border-box, so the
   300 includes the padding and the two act as a floor, not an addition.
   Bottom padding is top + 10. That asymmetry is deliberate and OPTICAL: the h1
   line box at line-height 1.12 carries more air above the cap height than below
   the baseline, so a mathematically centred title reads low. The extra 10px
   below lifts it by 5 and it lands where the eye expects. */
body.wh-page-banded .wh-pagehero {
  min-height: 300px;
  padding-top: 92px;
  padding-bottom: 102px;
}
body.wh-page-banded .wh-header {
  backdrop-filter: saturate(160%) blur(18px);
  -webkit-backdrop-filter: saturate(160%) blur(18px);
}
body.wh-page-banded .wh-header::before,
body.wh-page-banded .wh-header::after { opacity: 1; }

/* --------------------------------------------------------------------------
   19. PLACES - the About page "where we work" block
   -------------------------------------------------------------------------- */
/* TWO tracks, not three, with the first panel spanning both. Sonoma County
   carries more client names than the other two regions put together, so three
   equal columns left it roughly triple the height of its neighbours with two
   tall empty boxes beside it. Spanning it turns that imbalance into the point:
   the region a local visitor is here to check is the one given full width.
   Everything below reuses .wh-med's material - same ink-2 ground, same hairline
   border, same amber-on-hover - so the block reads as part of the same system
   even though the home page never shows it. */
.wh-places { display: grid; grid-template-columns: repeat(2, 1fr); gap: 24px; }
.wh-place {
  background: var(--ink-2); border: 1px solid var(--line); border-radius: 3px;
  padding: 34px 30px 36px;
  transition: border-color .32s var(--ease), background-color .32s var(--ease);
}
.wh-place:hover { border-color: var(--acc); background: var(--ink-3); }
.wh-place--lead { grid-column: 1 / -1; }

/* Same display face, weight and amber as .wh-host__k - this is the house's
   "big quiet number" treatment applied to a place name instead of a figure. */
.wh-place__k {
  display: block; margin-bottom: 6px;
  font-family: var(--font-display); font-size: 30px; font-weight: 200;
  letter-spacing: -0.01em; color: var(--acc);
}
.wh-place h3 { font-size: 17px; margin: 0 0 12px; }
.wh-place p { font-size: 15.5px; color: var(--muted); line-height: 1.62; margin: 0; }

/* The town line is the single strongest signal on the page for a local reader -
   it is the line that says we have actually been here. So it takes --cream and
   sits ABOVE the client names rather than being folded in with them at --muted. */
.wh-place__towns { margin-top: 18px; font-size: 15.5px; color: var(--cream); line-height: 1.8; }

/* inline-block, not flex. These are ragged-width chips that only need to wrap,
   grid cannot wrap ragged widths without declaring tracks, and a flex row here
   would be the only one in this entire file - not worth starting for a list.
   List semantics are kept (it IS a list of organizations) with the marker off. */
.wh-place__orgs { list-style: none; margin: 22px 0 0; padding: 0; }
.wh-place__orgs li {
  display: inline-block; margin: 0 8px 8px 0; padding: 7px 13px;
  border: 1px solid var(--line-2); border-radius: 999px;
  font-size: 13.5px; line-height: 1.3; color: var(--body);
}
/* Warmer outline on the lead panel only. --line-2 on all three read flat, and
   the amber is what ties the chips to the place name above them. */
.wh-place--lead .wh-place__orgs li { border-color: rgba(240,164,60,0.30); }

@media screen and (max-width: 760px) {
  .wh-places { grid-template-columns: 1fr; }
}

/* --------------------------------------------------------------------------
   20. HOST STRIP - optional corner icons (About page)
   -------------------------------------------------------------------------- */
/* ⚠️ .wh-host IS SHARED with the home page's hosting strip, which is LIVE.
   Every rule here is behind .wh-host--icons so the home page cannot move.

   A SECOND GRID COLUMN, not an absolutely positioned icon. .wh-host__item's
   horizontal padding changes three separate times down the breakpoints - the
   first and last child lose their outer padding at full width, :nth-child(2n)
   loses its right padding in the 2-up layout, and under 760px every item drops
   to 0 both sides. An absolute icon would need a matching right offset at each
   of those, and would silently drift the day any of them moved. A grid track
   inherits the padding for free and needs no media queries at all. */
.wh-host--icons .wh-host__item {
  display: grid; grid-template-columns: 1fr auto;
  align-items: start; column-gap: 16px;
}
.wh-host--icons .wh-host__k { grid-area: 1 / 1; }
/* Both span the full width, so the icon only ever shares a row with the big
   display token and never squeezes the body copy into a narrower measure. */
.wh-host--icons .wh-host__item h3,
.wh-host--icons .wh-host__item p { grid-column: 1 / -1; }

.wh-host__ico {
  grid-area: 1 / 2; justify-self: end;
  font-size: 22px; color: var(--acc); opacity: 0.3;
  /* Optical, not arbitrary: .wh-host__k is 30px in a line-height 1.72 box, so
     half-leading puts the numeral's em top about 11px below the top of its line
     box. Without this the icon floats above the number it belongs to. */
  margin-top: 11px;
}
