/* ==========================================================================
   Assiduous Staffing — main stylesheet
   Design-system approach (tokens, fluid type scale, component classes)
   borrowed as a TECHNIQUE from the Recruitment Labs 2.0 build. Palette,
   fonts and every visual decision below are Assiduous-original — white and
   misty-pink dominant, brand rose as accent only, warm neutral ink instead
   of navy. See /PROJECT CLUR/00_PROJECT_FOUNDATIONS.md for the reuse rules.
   ========================================================================== */

:root{
  /* ---- Colour: white + misty pink dominant, brand rose as accent ---- */
  --white:        #FFFFFF;
  --blush-25:     #FDF9F8;   /* page background */
  --blush-50:     #F8EFEE;   /* soft section band */
  --blush-100:    #F2E2E0;   /* misty pink surface / hero overlay base */
  --blush-200:    #E8CFCC;   /* card / divider tint */
  --rose-300:     #CAB3B3;   /* brand kit, secondary tint (not primary) */
  /* Primary brand palette, confirmed by Liam 25 July 2026: Pastel / Mid /
     Dark + White. These four are the ones to reach for by default;
     everything else in this file (blush-*, rose-300, grey-200) is a
     supporting tint derived around them, not a primary colour. */
  --rose-400:     #B59595;   /* Pastel -- brand primary */
  --rose-500:     #A07777;   /* Mid -- brand primary */
  --rose-600:     #865E5C;   /* Dark -- brand primary */
  --grey-200:     #E6E6E6;   /* brand kit, secondary tint (not primary) */

  --ink:          #4A4341;   /* warm dark grey -- no black text anywhere, per Liam's direction. Headings use --rose-600 instead; this is the fallback for everything else (body copy, nav states, form text). */
  --ink-soft:     #5C4E4C;
  --ink-faint:    #8C7D7A;
  --line:         #E9DEDC;
  --line-soft:    #F2E8E6;

  /* ---- Type ---- */
  --font-hero:    "Marcellus", "Times New Roman", Georgia, serif;
  /* Cormorant Garamond -> Playfair Display, 27 July 2026 (Liam picked
     Playfair Display from the alternatives offered). --font-hero
     above is a completely separate variable driving only the locked
     homepage hero wordmark, so it's untouched by this. */
  --font-display: "Playfair Display", Georgia, serif;
  --font-body:    "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  --fs-hero:    clamp(3rem, 1.7rem + 6vw, 7.5rem);
  --fs-h1:      clamp(2.25rem, 1.6rem + 2.8vw, 3.75rem);
  --fs-h2:      clamp(1.85rem, 1.45rem + 1.8vw, 2.75rem);
  --fs-h3:      clamp(1.4rem, 1.2rem + 0.9vw, 1.85rem);
  --fs-h4:      clamp(1.15rem, 1.05rem + 0.4vw, 1.3rem);
  --fs-lead:    clamp(1.1rem, 1.02rem + 0.35vw, 1.3rem);
  --fs-body:    1.0625rem;
  --fs-small:   0.9rem;
  --fs-eyebrow: 0.82rem;

  /* ---- Space / shape ---- */
  --space-section:    clamp(3.5rem, 2.6rem + 4vw, 7.5rem);
  --space-section-sm: clamp(2.25rem, 1.8rem + 1.8vw, 4rem);
  --gutter:      clamp(1.25rem, 1rem + 2vw, 3rem);
  --radius-sm:   6px;
  --radius-md:   16px;
  --radius-lg:   32px;
  --container:   1280px;
  --container-narrow: 820px;
  --header-h: 96px; /* header is position:fixed and overlays content -- pages with a solid top section must pad for this */

  --ease: cubic-bezier(.22,.68,.16,1);
  --dur:  .5s;

  --shadow-sm: 0 2px 12px rgba(134,94,92,.08);
  --shadow-md: 0 14px 34px rgba(134,94,92,.12);
  --shadow-lg: 0 28px 64px rgba(134,94,92,.16);
}

/* ---- Reset ---- */
*,*::before,*::after{ box-sizing:border-box; }
html{ scroll-behavior:smooth; }
body{
  margin:0;
  font-family:var(--font-body);
  font-size:var(--fs-body);
  line-height:1.65;
  color:var(--ink);
  background:var(--blush-25);
  -webkit-font-smoothing:antialiased;
}
img,video{ max-width:100%; height:auto; display:block; }
/* height:auto added 5 August 2026 -- regression fix. Once img_dims()
   started emitting width="" height="" attributes site-wide (4 August
   audit, for CLS/layout-shift), any <img> that relied on nothing but
   this base reset for its sizing (no more specific CSS width/height of
   its own, e.g. the About Assiduous / Why Assiduous founder photo) got
   visibly squished: HTML width/height attributes act like a very
   low-priority author style, so max-width:100% correctly capped the
   *width* down to the container, but with no height:auto to match, the
   *height* stayed pinned at the raw attribute value (e.g. 931px)
   instead of scaling with it -- stretching the image vertically. Every
   img/video that already had its own explicit width/height or
   object-fit rule elsewhere (e.g. .page-hero-media img, the icon
   components) was already fine and still is; this only restores
   correct proportional scaling for the ones relying purely on this
   base rule. */
a{ color:inherit; text-decoration:none; }
ul{ list-style:none; margin:0; padding:0; }
button{ font:inherit; }
h1,h2,h3,h4,p,figure{ margin:0; }

.skip-link{
  position:absolute; left:-999px; top:0; z-index:999;
  background:var(--ink); color:var(--white); padding:.75rem 1.25rem;
  border-radius:0 0 var(--radius-sm) 0;
}
.skip-link:focus{ left:0; }

/* ---- Typography ---- */
/* All headings default to the brand rose (--rose-600), never black/near-
   black -- per Liam's direction. Contexts sitting on a rose/photo
   background (hero, page-hero, cta-band, footer) override back to white
   with their own more specific selector, same as before. */
h1,.h1{ font-family:var(--font-display); font-weight:600; font-size:var(--fs-h1); line-height:1.08; letter-spacing:-.01em; color:var(--rose-600); }
h2,.h2{ font-family:var(--font-display); font-weight:600; font-size:var(--fs-h2); line-height:1.12; letter-spacing:-.01em; color:var(--rose-600); }
h3,.h3{ font-family:var(--font-display); font-weight:600; font-size:var(--fs-h3); line-height:1.2; color:var(--rose-600); }
h4,.h4{ font-family:var(--font-body); font-weight:700; font-size:var(--fs-h4); color:var(--rose-600); }
.lead{ font-size:var(--fs-lead); color:var(--ink-soft); line-height:1.55; }
.eyebrow{
  display:inline-flex; align-items:center; gap:.5rem;
  font-family:var(--font-body); font-weight:700; font-size:var(--fs-eyebrow);
  letter-spacing:.14em; text-transform:uppercase; color:var(--rose-500);
}
.eyebrow::before, .eyebrow::after{ content:""; width:22px; height:2px; background:var(--rose-400); display:inline-block; }
.text-soft{ color:var(--ink-soft); }
.text-faint{ color:var(--ink-faint); }
/* Inline bold + Mid brand colour, for calling out a phrase within a
   paragraph without turning the whole sentence into a heading (Liam:
   "bold, and mid colour 'people behind the success'" / "bold and Mid
   colour 'Claire Booth' and 'raise the bar'"). */
.text-accent{ font-weight:700; color:var(--rose-500); }
/* Same idea as .text-accent but Dark rather than Mid, for bold inline
   links that need to read as a real link/action rather than just an
   emphasised phrase (Liam, Upload CV page: "make contact us directly
   bold and dark... current vacancies bold and dark... privacy policy
   bold and dark"). Still never black, per the sitewide no-black-text
   rule -- rose-600 is the brand Dark tone. */
.text-accent-dark{ font-weight:700; color:var(--rose-600); }
.text-accent-dark:hover{ color:var(--rose-500); }
/* Underline draws in from both sides to meet in the middle, once the
   section has scrolled into view -- Liam: "an underline comes in from
   both sides, the left and the right, to meet and underline the
   words people behind the success." Scoped to its own modifier
   class (not baked into .text-accent generally) since only this one
   phrase should get the underline -- "Claire Booth"/"raise the bar"
   stay plain bold+colour.

   Originally hooked into the shared .reveal/.is-visible scroll
   mechanism (see main.js), which only ever fires once per element
   (observer unobserves after the first reveal). Liam later asked for
   this specific effect to replay every time it scrolls into view,
   in either direction: "the underline effect... resets when
   off-screen." That needed its own always-on observer rather than
   the shared one-shot .reveal mechanism, so it now keys off a
   dedicated `.underline-active` class that main.js adds AND removes
   every time this element crosses the viewport boundary (see the
   separate "Underline reveal" observer block in main.js). Delayed
   .5s so it reads as a deliberate follow-up beat, not simultaneous
   with the moment it enters view. */
.text-underline-reveal{ position:relative; display:inline-block; }
.text-underline-reveal::before, .text-underline-reveal::after{
  content:""; position:absolute; bottom:-3px; height:2px; width:0;
  background:var(--rose-500);
}
/* Switched from a `transition:width` to a `@keyframes` animation (25 July
   2026 fix, Liam: "it's just there static immediately"). A transition only
   plays when the browser has actually painted a "before" frame to animate
   FROM -- if .is-visible lands on the ancestor before that first frame ever
   renders (plausible for a section this high up the page, depending on
   viewport height), there is no observed change to transition and the
   underline simply appears at width:50% with no motion. A @keyframes
   animation has no such dependency: it always runs its own 0%->100%
   timeline the moment it starts applying, regardless of what was painted
   before. Two separate animations (one per side) driven off the same
   ancestor .is-visible class, each growing from its own edge toward the
   middle so they visually meet -- Liam: "two lines come in from each side...
   to meet to form the underline." */
@keyframes underlineRevealLeft{ from{ width:0; } to{ width:50%; } }
@keyframes underlineRevealRight{ from{ width:0; } to{ width:50%; } }
.text-underline-reveal::before{ left:0; }
.text-underline-reveal::after{ right:0; }
.text-underline-reveal.underline-active::before{ animation:underlineRevealLeft .7s var(--ease) .5s forwards; }
.text-underline-reveal.underline-active::after{ animation:underlineRevealRight .7s var(--ease) .5s forwards; }

/* ---- Layout ---- */
.container{ width:100%; max-width:var(--container); margin-inline:auto; padding-inline:var(--gutter); }
.container-narrow{ width:100%; max-width:var(--container-narrow); margin-inline:auto; padding-inline:var(--gutter); }
/* Wider than the sitewide 1280px container -- currently only used by
   the About Us five-card row (27 July 2026, Liam: "I'm happy to make
   the whole grid wider along the page as well, so the initial cards
   are larger and the post-transition cards are larger as well").
   Combine with .container (`class="container container-wide"`) so it
   only needs to override max-width, not repeat the width/margin/
   padding rules. */
.container-wide{ max-width:1560px; }
.section{ padding-block:var(--space-section); }
.section-sm{ padding-block:var(--space-section-sm); }
/* "How We Help" section only -- tighter top, slightly deeper bottom
   (26 July 2026, Liam: "tighten the space at the top of the how we
   help section, between the top and the title... add a little padding
   to the bottom"). Scoped to this one section (not a sitewide .section
   change, and not shared with the Values strip even though both use
   .bg-dark-test) via its own modifier class. Expressed as a fraction/
   addition of --space-section rather than a flat px value so it still
   scales sensibly across the same responsive range as every other
   section. */
.section-how-we-help{
  padding-top:calc(var(--space-section) * .5);
  padding-bottom:calc(var(--space-section) + 2rem);
}
/* Vacancies page: close the gap between the RecruitCRM widget and the
   "Don't see the right role?" CTA card below it (26 July 2026, Liam:
   "close the gap between the recruit CRM widget and the CTA card").
   Same scoped-modifier technique as .section-how-we-help above --
   tightens just these two adjacent sections rather than the shared
   .section spacing used sitewide. */
.section-vacancies-widget{ padding-bottom:calc(var(--space-section) * .3); }
.section-vacancies-cta{ padding-top:calc(var(--space-section) * .3); }
/* Candidates page: Our Process + Start Here moved onto adjacent dark
   sections and asked to "flow like one section" (30 July 2026). Same
   scoped-modifier tightening convention as the Vacancies pair above. */
.section-candidates-process{ padding-bottom:calc(var(--space-section) * .3); }
.section-candidates-start-here{ padding-top:calc(var(--space-section) * .3); }
/* Clients hub: same Our Process -> Start Here tightened pairing as
   Candidates above, added 30 July 2026 when a matching Start Here
   ghost-box strip (Contact Us / What We Recruit / Client FAQ) was
   added to the Clients hub, mirroring the Candidates one. */
.section-clients-process{ padding-bottom:calc(var(--space-section) * .3); }
.section-clients-start-here{ padding-top:calc(var(--space-section) * .3); }
/* Start Here ghost-button rows widened and pulled closer together on
   both hub pages (30 July 2026, Liam: "make the rows of ghost buttons
   a little wider and closer together, I think it will look more
   premium"). Widening the .container itself (rather than the grid) is
   what actually gives each card more width at a fixed 3-column count --
   same technique already used for Career/Hiring Advice's own wider
   cards (#candidate-services/#hiring-advice .container{max-width:1500px}),
   reused here at the same 1500px value rather than inventing a new
   number. Gap dropped from the sitewide default (--gutter, ~2.5-3rem at
   desktop) down to 1rem specifically on this grid, so the three cards
   sit noticeably tighter together -- the "premium, sleek row" look Liam
   described, similar in spirit to the Who We Partner strip's own
   tightened .gap-sm treatment. */
.section-candidates-start-here .container,
.section-clients-start-here .container{ max-width:1500px; }
/* Gap halved again, .6rem -> .3rem (30 July 2026, second follow-up,
   Liam: "the gap between the buttons still isn't closed enough, I want
   it at half of what it is now" -- said for both the Candidates and
   Clients hub, same shared grid). Narrower gaps also hand each of the
   3 columns a little extra width (fixed container, less of it eaten by
   gaps), which is what actually helps with the overflow fix below --
   Liam's own read: "that might give us a little more width in the
   buttons to bring the text up".

   ROOT CAUSE of the gap not actually changing (30 July 2026, third
   follow-up, Liam with a screenshot: "the distance between the buttons
   is the same"): `.start-here-ghost-grid{ gap:... }` and the base
   `.grid{ gap:var(--gutter); }` rule are both single-class selectors --
   identical specificity -- and `.grid` happens to be declared LATER in
   this file than this rule was, so at equal specificity the cascade's
   "last one wins" tiebreak was silently handing it back to
   var(--gutter) regardless of what value was set here. Every previous
   edit to this line was a genuine no-op. Fixed by compounding the
   selector to `.grid.start-here-ghost-grid` (two classes = higher
   specificity than `.grid` alone), which wins outright regardless of
   source order -- same defensive pattern already used elsewhere in
   this file for exactly this reason (.grid.approach-grid, .grid.
   partner-strip, etc). Verified this time by checking the compiled
   selector's specificity, not just assuming the value would apply. */
.grid.start-here-ghost-grid{ gap:.3rem; }
/* Pre-transition box brought in closer to the text, top and bottom
   (30 July 2026, follow-up). Scoped to .start-here-ghost-grid only --
   the shared .teaser-card-expand/.teaser-card-slot rules below are
   also used by Home/About's "How We Help" section, which wasn't part
   of this ask, so its own 2.25rem padding / 140px slot height stay
   untouched. Left/right padding also untouched -- only top/bottom and
   the slot's own resting height were called out.

   First pass went to 1.5rem padding / 112px slot height, but Liam
   reported the Candidates page's own "Submit Your Resume"/"View
   Vacancies" links then sitting OUTSIDE the box at rest -- those two
   titles are long enough that, combined with the first pass's very
   tight height, the resting content (h3 + margin + link) no longer
   reliably fit inside the box before hover even started (the Clients
   page's shorter titles happened to still fit, which is why only
   Candidates showed the bug). Eased back up slightly to 1.75rem /
   126px, splitting the difference between the original 2.25rem/140px
   and the too-tight 1.5rem/112px -- still meaningfully tighter than
   the original, with enough buffer that the longer Candidates titles
   shouldn't overflow. Combined with the extra column width freed up by
   the .3rem gap above. Still not confirmed against a live render --
   flag if "Submit Your Resume"/"View Vacancies" still overflow, or if
   there's still visible dead space once you see it. */
.start-here-ghost-grid .teaser-card-slot{ height:126px; }
.start-here-ghost-grid .teaser-card-expand{ padding-top:1.75rem; padding-bottom:1.75rem; }
/* Career Advice -> closing CTA gap (30 July 2026, Liam: "close the gap
   between the career advice section and the CTA at the bottom of the
   page"). Same scoped-modifier tightening convention as the pairs above,
   not a change to --space-section itself. */
.section-candidates-services{ padding-bottom:calc(var(--space-section) * .3); }
.section-candidates-cta{ padding-top:calc(var(--space-section) * .3); }
/* Widened so a hovered .process-step row has real room to grow WIDER
   than its resting siblings (30 July 2026, see the .process-step hover
   rule for the full rationale). Same value already used for
   .section-partner-context's own .container-narrow widening elsewhere
   on the site, reused here rather than inventing a new number. Resting
   rows are unaffected -- .process-step's own max-width still caps them
   at the shared --container-narrow (820px) via its base rule, so this
   extra room sits unused until a row is actually hovered.
   Generalised from a Candidates-only selector to .bg-dark-test
   .process-list (30 July 2026, Liam: "replicate it for the client
   process on the client hub page") -- this behaviour was never really
   specific to Candidates, it's "a .process-list on a dark section
   needs a wider parent for its hovered rows to grow into", which now
   also applies to Clients' own Our Process section the same way.
   Scoped to .process-list specifically (not all .container-narrow
   inside .bg-dark-test) so it can't accidentally widen an unrelated
   .container-narrow that happens to also sit on a dark section
   elsewhere (e.g. Who We Partner's own .container-narrow lives on a
   plain .bg-white section, so it was never affected either way, but
   this keeps the rule's reach explicit rather than incidental). */
.bg-dark-test .process-list.container-narrow{ max-width:1040px; }
/* Contact page: close the gap between the form/tile-cards section and
   the "Prefer to browse first?" CTA card below it (28 July 2026, Liam:
   "let's close the gap above the CTA card as well and tighten it up").
   Same scoped-modifier technique as the Vacancies page pair above. */
.section-contact-details{ padding-bottom:calc(var(--space-section) * .3); }
.section-contact-cta{ padding-top:calc(var(--space-section) * .3); }
/* Mission & Values page: close the gap between the photo hero and the
   Our Mission section below it (28 July 2026, Liam: "tighten the gap
   above our mission"). Same scoped-modifier technique as the pairs
   above -- only this section's own top padding shrinks. Bottom padding
   added in a follow-up round (Liam: "tighten the gap at the bottom of
   our mission section and the top of our values section") paired with
   .section-values' own top-padding cut below, same two-sided technique
   as the Contact/Vacancies pairs above. */
/* Bottom padding eased back up 28 July 2026 (Liam: "add a bit of the gap
   back in at the bottom of the our mission section, it's too tight...
   that was my call and it doesn't look as good") -- was tightened to
   .4x in an earlier round to close the gap to Our Values below; top
   stays at .4x (that part still reads fine), bottom eased up to .65x. */
.section-mission-intro{ padding-top:calc(var(--space-section) * .4); padding-bottom:calc(var(--space-section) * .65); }
.section-values{ padding-top:calc(var(--space-section) * .4); }
/* Sub-page pager (prev/current/next, sitewide across all 12 About/
   Candidates/Clients sub-pages that have one) -- spacing above/below is
   actually controlled by the .section:has(> .container > .subpage-pager)
   rules further down this file (search "Sub-page prev/current/next
   pager"), not here. A .section-subpage-pager{padding-top/bottom:...}
   rule was added here 28 July 2026 but turned out to be dead code: the
   :has() selector below has higher specificity (.section + :has(.class)
   beats a single .class), so it always won regardless of source order
   and silently zeroed the padding this rule tried to set. Removed to
   avoid two competing, contradictory sources of truth -- see the :has()
   block below for the real, effective rule and its history. */
/* Why Assiduous: close the gap between the merged Meet the Founder /
   Our Story card-glass panel and Our Approach below it (27 July 2026,
   Liam: "tighten the gap above Our Approach as well"). Same scoped-
   modifier technique as the two rules above -- only this section's
   own top padding shrinks, the card-glass panel above keeps its normal
   bottom padding. Tightened further in round 2 (still 27 July 2026,
   Liam: "lets tighten the gap above approach"), .35 -> .2. Tightened
   again in round 3 (still 27 July 2026, Liam: "tighten the gap...
   between our story and our approach") alongside the card-glass
   panel's own bottom padding above finally being reduced too (see that
   section's inline style) -- previously only this section's top padding
   had ever been touched, so the gap still read loose even after two
   rounds of tightening it alone. */
.section-approach-tight{ padding-top:calc(var(--space-section) * .12); }
/* Faint "As" monogram texture, replicating the fixed-viewport "molecule"
   technique from the Recruitment Labs 2.0 build (technique only, not
   their asset/content -- see 00_PROJECT_FOUNDATIONS.md Section 2).
   background-attachment:fixed pins the image to the VIEWPORT rather than
   the section, so every qualifying section acts as a window onto what
   reads as one continuous canvas rather than each section having its
   own independent copy. Colour swaps with the section's own background
   so the motif reads as a consistent two-tone signature: pink monogram
   on white (.bg-white), white monogram on misty pink (.bg-mist) --
   Liam's call, 25 July 2026, once it became clear the site doesn't have
   a true dark/navy-style section the way Recruitment Labs did; using
   .bg-white/.bg-mist as the two "colours" achieves the same alternating
   light/dark-relative-to-itself effect their molecule had. Footer, home
   hero and page-hero banners are explicitly excluded (separate classes,
   never in scope here). .bg-ink also carries the white monogram, ready
   for if a genuinely dark full-bleed section is introduced later --
   currently unused sitewide, forward-prep only.

   Switched from a single non-repeating canvas to a REPEATING tile
   (fixed size, not 100% auto) after Liam reported a lot of blank space
   near the top of the viewport as he scrolled. Root cause: with
   background-attachment:fixed, "top" positions the image against the
   VIEWPORT, not the page, so the same slice of the image sits at the
   same screen position forever regardless of scroll -- if that
   particular slice happened to land on a sparse patch of the source
   art, every section showed that same gap, permanently, at the same
   spot on screen. A moderate repeating tile (1100px) means several
   copies of the pattern always exist within any single viewport height,
   so there's no longer a single large blank margin that can dominate
   the visible area, while staying loose enough not to look busy. */
/* Linen weave (Texture 6) added as a second, lower texture layer behind
   the "As" monogram (14 August 2026, Liam: "put the linen texture on
   the other pages that still have the As texture so I can see how it
   looks with linen, and As... then I'll make the call about which way
   to go (as or logo)"). This is the comparison-only rollout: the Home
   page's own trial (see body.body-brand-logo-trial rules further down)
   replaces the As monogram entirely with the big logo + linen: no As
   there at all. Everywhere else keeps the As monogram exactly as
   before, just with the same faint linen grain now sitting behind it
   -- same texture-weave-*.webp assets and alpha ceiling as the Home
   trial (11/255 pink here; see .bg-ink/.bg-dark-test below for the
   white/dark-section variant), same layer-order logic (monogram on
   top, linen underneath, base colour last) as the logo trial uses.
   Once Liam picks a direction this either becomes permanent (folded
   into these base rules for good) or reverts (drop the second
   background-image/-position/-size/-repeat/-attachment value from
   each list below, back to a single monogram layer). */
.bg-white{
  background-color:var(--white);
  background-image:url('/assets/img/photos/texture-monogram-pink.webp'), url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top, center top;
  background-size:1100px 1100px, 700px 700px;
  background-repeat:repeat, repeat;
  background-attachment:fixed, fixed;
}
/* .bg-mist now carries the same PINK monogram as .bg-white, not white --
   Liam's call: "let's give the misty pink sections the dark texture
   background instead of white." Pink reads as the "dark" mark against
   any light surface (white or blush-50 alike); white is reserved for
   genuinely dark sections (.bg-ink/.bg-dark-test) where it actually
   pops, per the earlier dark-section test. */
.bg-mist{
  background-color:var(--blush-50);
  background-image:url('/assets/img/photos/texture-monogram-pink.webp'), url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top, center top;
  background-size:1100px 1100px, 700px 700px;
  background-repeat:repeat, repeat;
  background-attachment:fixed, fixed;
}
.bg-mist-100{ background:var(--blush-100); }
.bg-ink{
  background-color:var(--ink); color:var(--white);
  background-image:url('/assets/img/photos/texture-monogram-white.webp'), url('/assets/img/photos/texture-weave-white.webp');
  background-position:center top, center top;
  background-size:1100px 1100px, 700px 700px;
  background-repeat:repeat, repeat;
  background-attachment:fixed, fixed;
}
@media (max-width:640px){
  .bg-white, .bg-mist, .bg-ink{
    background-attachment:scroll, scroll;
    background-repeat:repeat, repeat;
    background-size:640px 640px, 400px 400px;
  }
}

/* TRIAL: genuinely dark section, testing whether the white monogram
   reads more clearly against real dark than it does against the pale
   .bg-mist (blush-50) tint. Liam's call, 25 July 2026: "make the how
   we help section dark with a gradient, then we will see if the white
   As pops" -- confirmed it does. Also now used behind the CTA band at
   the bottom of the home page: applied to the outer .section wrapper,
   NOT the .cta-band card itself, so the card keeps its own existing
   rose-500->rose-600 gradient untouched and just floats on top of the
   dark section backdrop. Softened from rose-600->ink to rose-500->ink
   (same 135deg angle as .cta-band) per Liam's "make both, white and
   dark more subtle again" -- rose-500 is the lighter Mid brand tone,
   so the gradient reads softer without introducing any off-brand
   colour. Monogram alpha also reduced again this round (see the webp
   regeneration note below). Text-colour overrides follow the same
   established pattern as .page-hero/.cta-band/.footer (see comment
   above h1-h4 colour rules). .card/.teaser-card are left untouched --
   the white cards float fine on any background.

   Colour stops adjusted (26 July 2026, Liam: "our dark gradient
   sections are drifting more into a brown on the right-hand side...
   I love the colour on the left... the right looks like it ends up
   as brown... we might need to shift it over a bit"). Root cause: a
   135deg gradient's 0%->100% travel is measured along the box's actual
   diagonal, and these sections are far wider than they are tall -- so
   at any given height, the right edge is proportionally much further
   along that diagonal than the left edge, sliding much closer to
   --ink (which reads brownish once blended with the rose) well before
   the visible right edge of a wide section. Giving rose-500 a hard
   stop partway along the line (rather than starting the blend
   immediately at 0%) keeps the preferred rose tone solid across most
   of the section's width and pushes the ink transition toward the
   corner instead, which is the "shift it over" Liam asked for.

   Second round (28 July 2026, Liam, re: the Who We Partner page's
   alternating dark sections: "having that same issue with the gradient
   where the bottom right corner looks more of a brown, dark gray, can
   we shift the palette or the gradient over a little bit"). Both
   background-image layers use background-attachment:fixed, which per
   spec sizes/positions them against the VIEWPORT, not each section's own
   box -- so this single gradient is really one fixed backdrop shared by
   every .bg-dark-test section site-wide, and which slice of it a given
   section shows depends on where that section happens to sit on screen
   at the time, not on the section's own dimensions alone. Who We
   Partner's partner-feature sections are shorter than Home's (a single
   icon+text row vs a fuller section), so their bottom-right corner is
   more likely to fall further along that shared diagonal, into the
   65%->100% rose-to-ink blend zone, than a taller section's corner
   would. Rather than only nudging the existing hard stop (which still
   jumps straight from rose-500 to true --ink, and pure ink is what reads
   grey/brown once blended), inserted a rose-600 step in between and
   pushed both stops further out: rose-500 now holds solid to 70% (was
   65%), eases into rose-600 -- a deeper on-brand rose, not a neutral --
   by 88%, and only reaches true --ink in the final 12% sliver right at
   the corner. Keeps the section genuinely dark at the extreme corner
   (the effect Liam originally wanted from real --ink) while shrinking
   the muddy-looking rose-to-ink blend band to a much smaller area, so
   far less of any given section's visible crop of the shared backdrop
   can land inside it. */
/* Darkened a shade or two sitewide (14 August 2026, Liam: trialled as a
   one-page-only modifier on the Candidates hub -- "what happens if we
   darken our dark background... go darker a shade or 2" -- then
   approved and rolled out everywhere: "let's do that site-wide,
   slightly darker dark sections roll out that we trialled earlier").
   Folded the trial's color-mix() stops straight into this base rule
   (rather than keeping the separate .bg-dark-test-darker modifier
   class, per the "or just fold it in and drop the modifier" option
   noted at the time) so every .bg-dark-test section sitewide picks it
   up automatically with no per-template class changes needed. Each
   stop still mixed with 12% black via color-mix() rather than
   inventing new one-off hex values -- keeps the exact same rose->ink
   hue relationship and diagonal, just each stop a shade or two darker.
   Revert: change each color-mix(in srgb, X 88%, black) back to plain
   var(X). */
.bg-dark-test{
  background-color:color-mix(in srgb, var(--ink) 88%, black); color:var(--white);
  background-image:url('/assets/img/photos/texture-monogram-white.webp'), url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg,
    color-mix(in srgb, var(--rose-500) 88%, black) 0%,
    color-mix(in srgb, var(--rose-500) 88%, black) 70%,
    color-mix(in srgb, var(--rose-600) 88%, black) 88%,
    color-mix(in srgb, var(--ink) 88%, black) 100%);
  background-position:center top, center top, center top;
  background-size:1100px 1100px, 700px 700px, cover;
  background-repeat:repeat, repeat, no-repeat;
  background-attachment:fixed, fixed, fixed;
}
.bg-dark-test .eyebrow{ color:rgba(255,255,255,.85); }
.bg-dark-test .eyebrow::before, .bg-dark-test .eyebrow::after{ background:rgba(255,255,255,.5); }
.bg-dark-test h2{ color:var(--white); }
.bg-dark-test .lead{ color:rgba(255,255,255,.88); }
@media (max-width:640px){
  .bg-dark-test{
    background-attachment:scroll, scroll, scroll;
    background-repeat:repeat, repeat, no-repeat;
    background-size:640px 640px, 400px 400px, cover;
  }
}

/* Bottom CTA section background, site-wide (every page ends on a
   .cta-band -- 22 pages have one). Correction: "you have put it on
   the section again and not the pink card. Keep the section
   background white. I am only talking about the Let's find great
   people, properly card." Same plain pink-monogram treatment as
   .bg-white -- the photo now lives entirely on .cta-band itself, see
   below. */
.bg-cta{
  background-color:var(--white);
  background-image:url('/assets/img/photos/texture-monogram-pink.webp'), url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top, center top;
  background-size:1100px 1100px, 700px 700px;
  background-repeat:repeat, repeat;
  background-attachment:fixed, fixed;
}
@media (max-width:640px){
  .bg-cta{
    background-attachment:scroll, scroll;
    background-repeat:repeat, repeat;
    background-size:640px 640px, 400px 400px;
  }
}

/* TRIAL: big fixed wordmark logo, replacing the tiled "As" monogram
   (14 August 2026, Liam: "what if we replaced the As texture with a
   big static Assiduous Staffing logo... as we scroll through colour
   sections it changes from pink to white... test it on one page with
   different sections and see what we think"). Scoped entirely under
   body.body-brand-logo-trial (see build.py, home page entry) so this
   is a one-page preview -- every other page keeps the existing tiled
   monogram untouched, and reverting is just deleting this block +
   the body_class line in build.py.

   Second attempt, after first-pass feedback on four specific points:
   a) "can't appear over the video section" -- solved for free: .hero
      is a distinct class, never one of the four selectors below, so
      it's never touched by this rule regardless of what else changes.
   b) "remove the other As texture so it's not too busy" -- these
      rules fully REPLACE background-image on .bg-white/.bg-mist/
      .bg-cta/.bg-dark-test (not layered alongside), so the tiled
      monogram is gone wherever the logo swap applies.
   c) "make it much larger" -- 2200px-wide render (vs the texture's
      1100px tile), and non-repeating/single-instance rather than a
      tiled pattern, so it reads as one big mark, not a busy tile.
   d) "make sure it sits behind the relevant things... I can see it in
      the picture of Claire, probably don't want it there" -- this was
      the fatal flaw of the first attempt (a position:fixed DOM overlay,
      which paints in its own stacking context ON TOP of normal in-flow
      content like photos/cards, no matter what z-index it's given).
      Baking the logo into background-image instead sidesteps the
      whole problem: backgrounds always paint behind a box's own
      content by definition, so it's automatically behind every photo,
      card and button in every section, with zero z-index logic
      needed -- exactly how the "As" texture it's replacing already
      worked.

   background-attachment:fixed (same mechanism the "As" texture already
   uses) is what makes the logo appear to sit still at one spot on the
   screen while different coloured sections scroll past/over it --
   position:center center rather than the texture's center top, since
   this is a single centred mark, not a tiled pattern anchored to the
   top. Pink logo-colour.svg on light sections, white logo-white.svg
   on the dark section (layered before .bg-dark-test's own gradient,
   same two-layer pattern already used there).

   Faded copies (14 August 2026, Liam: "size is good, both colours
   need to be way more subtle though"): at large size + full brand-
   colour opacity the mark read as too strong/loud, so rather than
   fighting it with CSS opacity (which isn't available per-background-
   layer -- it would also fade the gradient/colour sitting underneath
   it) two trial-only SVG copies were made with fill-opacity baked
   directly into the paths: logo-colour-faded.svg (8%) and
   logo-white-faded.svg (12%, a touch stronger since it has to read
   against the busier rose->ink gradient rather than a flat tint).
   The real logo-colour.svg/logo-white.svg files used everywhere else
   on the site (header, footer, hero) are untouched.

   Not extended to .bg-ink here since it isn't used anywhere on the
   Home page yet -- add a matching rule if/when it is.

   Fluid sizing (14 August 2026, Liam: "is the logo supposed to auto
   size with the screen"): it wasn't -- background-size was two flat
   pixel values (2200px above 640px, 900px below), so the mark rendered
   at the same absolute size on a 1366px laptop as a 2560px ultrawide
   (proportionally huge on the former, small on the latter), with a
   hard jump at the 640px breakpoint rather than a smooth scale.
   Switched to clamp(min, vw-based-preferred, max) so the width tracks
   viewport width between the two caps instead of jumping between two
   fixed states -- min/max values chosen so it still looks like "one
   big mark", never shrinking small enough to look like a mistake or
   growing past a sensible ceiling on huge monitors.

   Coefficient corrected 110vw -> 78vw (same day, Liam: "on my laptop
   the ends are cut off a little, looks fine on my ultra wide with
   padding either side"). Root cause: 110vw means the rendered image
   is intentionally 10% WIDER than the viewport at every size in that
   range, so centered background-position always pushed 5% of the
   image off-screen on each side -- that's the cut-off edges on a
   laptop-width screen. It only "looked fine" on the ultrawide because
   that screen's width exceeds the 2200px cap, so the clamp maxed out
   there and the (now narrower-than-viewport) image left padding
   either side instead of overflowing. 78vw keeps the image narrower
   than the viewport at every width below the cap, so there's always
   padding on both sides, not just once the cap kicks in -- matching
   the ultrawide look at every screen size instead of only the widest
   ones. Floor also nudged down (1400px -> 1000px) so that floor value
   itself doesn't start overflowing again on the smaller end of the
   fluid range.

   Texture layer added (14 August 2026, Liam supplied three candidate
   background textures in a "Texture Test Backgrounds" folder and asked
   to trial Texture 3 -- "very, very subtle and faint... then Assiduous
   logo, then page content", "same concept, colour shift between light
   and dark sections"). Sits as a THIRD background layer, between the
   logo (top) and the base colour/gradient (bottom) -- backgrounds
   paint in listed order, so this ordering alone satisfies "texture,
   then logo, then content" with no z-index needed, same reasoning as
   the logo layer itself.

   Texture 3 turned out NOT to be a uniform tileable speckle like the
   old "As" monogram -- sampling coverage at the centre vs the four
   corners of the source file showed roughly a 10x density difference
   (heavy in the middle, sparse at the edges), confirming it's one
   large radial/diamond composition. Tiling that at a small size the
   way the old monogram tiled would have repeated the same bright
   central blob across the page with visible seams between copies. So
   this uses the same mechanism as the logo layer instead: a single
   full image, background-size:cover (not a repeating tile size),
   background-attachment:fixed, so it reads as one continuous texture
   behind every section rather than a repeated motif.

   Source PNG has no alpha (rose-coloured dots baked onto solid white)
   so it was converted to two transparent trial-only assets --
   texture-speckle-pink.webp / texture-speckle-white.webp -- using the
   green channel's deviation from white as a coverage/alpha mask (dot
   colour in the source is rgb(160,119,119), i.e. --rose-500, on pure
   white, so 255-G tracks "how much dot" cleanly through the
   antialiased edges), then recolouring to a flat fill (rose-500 for
   light sections, white for the dark one) at that alpha.

   Alpha ceiling picked, then picked again: first pass matched the old
   monogram's own values exactly (40/255 pink, 25/255 white) since
   those were an already-accepted "faint" baseline on this site -- but
   previewed far louder than the monogram ever did, because this is a
   dense full-bleed speckle covering nearly the whole frame rather than
   sparse letterforms, and because white dots read with more contrast
   against the dark gradient than pink dots do against near-white at
   the same alpha value. Dropped well below the monogram's own ceiling
   instead -- 22/255 pink, 12/255 white -- verified by compositing each
   over a flat swatch matching its target section colour before wiring
   in, rather than eyeballing it live.

   Halved again (same day, Liam: "keep assiduous like it is, can we
   make the texture background even fainter"). Logo layer untouched --
   only these two texture assets regenerated, at 11/255 pink and
   6/255 white.

   Swapped Texture 3 -> Texture 6 (same day, Liam supplied a fourth
   candidate: "can we try texture 6, again faint, assiduous over it").
   Texture 6 is the opposite case from Texture 3: a genuinely uniform
   woven-linen/crosshatch grain -- density-checked centre vs all four
   corners this time came back within a couple of points of each other
   (~17-20 throughout, no radial falloff), confirming it tiles cleanly.
   So this one uses background-repeat instead of the cover/no-repeat
   trick Texture 3 needed: a small 700px tile (texture-weave-pink.webp /
   texture-weave-white.webp), same green/luminosity-deviation alpha
   mask technique as before, matching the same already-dialled-in
   faintness ceiling (11/255 pink, 6/255 white) rather than
   re-litigating brightness from scratch -- "again faint" was explicit.

   White/dark-section variant bumped back up (same day, Liam: "make it
   a little more visible on the dark, looks great on the lighter
   colours") -- 6 -> 12/255. Pink/light-section variant untouched, so
   the two are no longer symmetric on purpose.

   Easy revert to Texture 3: swap the URLs and size/repeat values on
   this second layer back to texture-speckle-*.webp / cover / no-repeat
   per the block above; the logo layer (first) and gradient/colour
   layer (last, dark section only) are untouched either way. */
body.body-brand-logo-trial .bg-white,
body.body-brand-logo-trial .bg-mist,
body.body-brand-logo-trial .bg-cta{
  background-image:url('/assets/img/logos/logo-colour-faded.svg'), url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center center, center top;
  background-size:clamp(1000px, 78vw, 2200px) auto, 700px 700px;
  background-repeat:no-repeat, repeat;
  background-attachment:fixed, fixed;
}
body.body-brand-logo-trial .bg-dark-test{
  background-image:url('/assets/img/logos/logo-white-faded.svg'), url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500) 0%, var(--rose-500) 70%, var(--rose-600) 88%, var(--ink) 100%);
  background-position:center center, center top, center top;
  background-size:clamp(1000px, 78vw, 2200px) auto, 700px 700px, cover;
  background-repeat:no-repeat, repeat, no-repeat;
  background-attachment:fixed, fixed, fixed;
}
@media (max-width:640px){
  body.body-brand-logo-trial .bg-white,
  body.body-brand-logo-trial .bg-mist,
  body.body-brand-logo-trial .bg-cta{
    background-attachment:scroll, scroll;
    background-size:clamp(500px, 140vw, 900px) auto, 400px 400px;
  }
  body.body-brand-logo-trial .bg-dark-test{
    background-attachment:scroll, scroll, scroll;
    background-size:clamp(500px, 140vw, 900px) auto, 400px 400px, cover;
  }
}

.section-head{ max-width:640px; margin-bottom:2.75rem; }
.section-head.center{ margin-inline:auto; text-align:center; }
/* Our Values intro line, widened so it wraps to two lines not three on
   desktop (28 July 2026, Liam: "can we make sure this sits on two
   lines on desktop, not three"). Scoped to just this section's own
   .section-head rather than raising the shared 640px default, which is
   used site-wide -- same scoped-override pattern as the Our Mission
   card's own width bump earlier this session. */
.section-values .section-head{ max-width:820px; }

.grid{ display:grid; gap:var(--gutter); }
.grid-2{ grid-template-columns:repeat(2,1fr); }
.grid-3{ grid-template-columns:repeat(3,1fr); }
.grid-4{ grid-template-columns:repeat(4,1fr); }
.grid-5{ grid-template-columns:repeat(5,1fr); }
@media (max-width:900px){ .grid-3,.grid-4,.grid-5{ grid-template-columns:repeat(2,1fr); } }
@media (max-width:640px){ .grid-2,.grid-3,.grid-4,.grid-5{ grid-template-columns:1fr; } }
/* Mobile min-height for the flip cards lives with the rest of the
   .roles-card rules further down (the flip layers are absolutely
   positioned, so unlike a plain card, they NEED a real height to
   render at all -- can't drop to 0 the way the old flat cards
   could). */

/* ---- Icons (inline SVG via templates/partials/icons.html) ---- */
.icon{ width:20px; height:20px; display:block; flex-shrink:0; }

/* ---- Buttons ---- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:.5rem;
  padding:.9rem 1.75rem; border-radius:999px; font-weight:700; font-size:.95rem;
  border:1.5px solid transparent; cursor:pointer; transition:transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background var(--dur) var(--ease), color var(--dur) var(--ease);
  white-space:nowrap;
}
.btn-primary{ background:var(--rose-500); color:var(--white); box-shadow:var(--shadow-sm); }
.btn-primary:hover{ background:var(--rose-600); transform:translateY(-2px); box-shadow:var(--shadow-md); }
.btn-outline{ background:transparent; border-color:var(--rose-400); color:var(--rose-600); }
.btn-outline:hover{ background:var(--blush-100); transform:translateY(-2px); }
.btn-ghost-white{ background:rgba(255,255,255,.14); border-color:rgba(255,255,255,.6); color:var(--white); backdrop-filter:blur(6px); }
.btn-ghost-white:hover{ background:rgba(255,255,255,.28); transform:translateY(-2px); }
.btn-lg{ padding:1.1rem 2.25rem; font-size:1.02rem; }

/* ==========================================================================
   Header / nav
   ========================================================================== */
.site-header{
  position:fixed; top:0; left:0; right:0; z-index:200;
  display:flex; align-items:center; justify-content:space-between;
  padding:1.1rem var(--gutter);
  background:transparent; backdrop-filter:none;
  border-bottom:1px solid transparent;
  transition:background var(--dur) var(--ease), box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease);
}
/* Solid state once scrolled (or immediately on non-hero pages, which set body.no-hero below) */
.site-header.is-solid{
  background:rgba(253,249,248,.9); backdrop-filter:blur(10px);
  border-bottom-color:var(--line-soft); box-shadow:var(--shadow-sm);
}
body.no-hero .site-header{
  background:rgba(253,249,248,.9); backdrop-filter:blur(10px);
  border-bottom-color:var(--line-soft);
}
.site-header .logo{ position:relative; display:inline-block; }
.site-header .logo img{ height:56px; width:auto; display:block; }
/* Home-page white->colour logo crossfade (see header.html comment) */
.site-header .logo .logo-img-white{ transition:opacity var(--dur) var(--ease); }
.site-header .logo .logo-img-colour{
  position:absolute; inset:0; opacity:0;
  transition:opacity var(--dur) var(--ease);
}
.site-header.is-solid .logo .logo-img-white{ opacity:0; }
.site-header.is-solid .logo .logo-img-colour{ opacity:1; }
/* Soft shadow for legibility while the logo sits over the photo hero,
   home page only -- dropped once the header goes solid, where a shadow
   meant for a photo backdrop would look like a smudge on a flat bar. */
body:not(.no-hero) .site-header:not(.is-solid) .logo img{
  filter:drop-shadow(0 2px 10px rgba(43,33,32,.35));
}
.nav-primary{ display:flex; align-items:center; gap:2.25rem; }
.nav-links{ display:flex; align-items:center; gap:1.85rem; }
/* Mid -> Dark on hover, plus a grow, per Liam's direction (first pass
   used Pastel as the base with a smaller 1.08 scale; adjusted to Mid
   base + a bigger 1.16 scale on this round). aria-current (the active
   page) gets the same Dark colour as hover for consistency but NOT the
   scale-up, since that's a hover-only interaction, not a permanent
   state. */
.nav-links a{
  font-weight:600; font-size:.96rem; color:var(--rose-500);
  position:relative; padding-block:.25rem; display:inline-block;
  transition:color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.nav-links a::after{
  content:""; position:absolute; left:0; right:100%; bottom:-2px; height:2px;
  background:var(--rose-500); transition:right var(--dur) var(--ease);
}
.nav-links a:hover, .nav-links a[aria-current="page"]{ color:var(--rose-600); }
/* `>` direct-child scoping (26 July 2026 -- root cause of "the pink
   highlight is still exceeding the white submenu background"): this
   scale-up pop was meant for the six top-level bar items only, but the
   plain `.nav-links a:hover` selector also matched every nested
   `.dropdown-menu a` and flyout link, since they're technically
   descendants of `.nav-links` too. That silently grew every submenu
   item 16% on hover -- well past the panel's own inner width -- and
   the panel's overflow:hidden (which exists specifically to keep the
   pink hover fill inside the rounded box) then clipped the oversized,
   still-rounded pill flush at its straight edge, which is exactly what
   reads as a squared-off, edge-to-edge block. The panel corner-radius
   and padding fix from earlier today was correct on its own terms --
   this transform was the actual, separate bug making it look broken
   regardless. Scoping to `> li > a` matches only the direct top-level
   links (Home, About Us, Candidates, Clients, Insights, Vacancies,
   Contact); every `.dropdown-menu a` sits two levels deeper and is
   unaffected. */
.nav-links > li > a:hover{ transform:scale(1.16); }
.nav-links a:hover::after, .nav-links a[aria-current="page"]::after{ right:0; }
.nav-caret{ font-size:.65em; opacity:.6; margin-left:.15em; display:inline-block; transition:transform var(--dur) var(--ease); }
.nav-links li.has-dropdown:hover .nav-caret,
.nav-links li.has-dropdown:focus-within .nav-caret{ transform:rotate(180deg); }
.nav-cta{ display:flex; align-items:center; gap:1rem; }
/* Tappable area bumped to the 44x44px recommended minimum (4 August 2026
   audit -- flagged as a known-but-unfixed minor item across several prior
   audits, actually fixed this round). Was ~40x32px (24px glyph + .5rem
   padding), under the WCAG 2.5.5 / mobile touch-target guideline. The
   visible glyph (3 short spans) is unchanged -- min-width/min-height plus
   centering just grows the invisible tappable box around it. */
.hamburger{
  display:none; flex-direction:column; align-items:center; justify-content:center;
  gap:5px; background:none; border:0; cursor:pointer; padding:.5rem;
  min-width:44px; min-height:44px;
}
.hamburger span{ width:24px; height:2px; background:var(--rose-600); border-radius:2px; transition:background var(--dur) var(--ease); }

/* Home-page-only header nav white->pink crossfade, mirroring the logo:
   white (with the caret/hamburger following via currentColor/explicit
   rules) while the header sits transparent over the hero video, fading
   to the same rose tone used everywhere else once the header goes solid
   on scroll. Scoped to top-level `.nav-links > li > a` only (not `>` the
   nested `.dropdown-menu a`) so the dropdown panel's own links -- which
   sit on an opaque white card, not the video -- never turn white and
   disappear against it. Inner pages never hit this rule (their header
   is always solid), so their nav stays the same rose tone as the logo
   there. */
body:not(.no-hero) .site-header:not(.is-solid) .nav-links > li > a{ color:var(--white); }
body:not(.no-hero) .site-header:not(.is-solid) .nav-links > li > a:hover,
body:not(.no-hero) .site-header:not(.is-solid) .nav-links > li > a[aria-current="page"]{ color:var(--white); }
body:not(.no-hero) .site-header:not(.is-solid) .hamburger span{ background:var(--white); }

/* Desktop hover-dropdowns for About/Candidates/Clients/Insights -- each
   reveals its sub-pages so hub pages can stay as overviews while the real
   depth lives on dedicated pages (per Liam's "hub + full subpages, subpages
   in a hover menu" direction).

   Two things made these fiddly to actually use (Liam: "hard to get from
   the buttons... they seem to disappear quickly"), both fixed below:

   1. `margin-top` on the panel used to leave a real gap of dead space
      between the bottom of the trigger link and the top of the panel.
      That gap belonged to no element, so as soon as the mouse crossed it
      on the way down, `:hover` was lost on the trigger -- and because
      `.dropdown-menu` had `pointer-events:none` in its closed state with
      no transition-delay, that switched to `none` *instantly*, which
      also stops the panel from ever registering the mouse re-entering it
      (an element can't regain :hover via a pointer that pointer-events
      is telling the browser to ignore). Practically: any path from the
      link to the panel broke the hover chain and the menu became
      unclickable before you could reach it. Fix: `top:100%` now sits
      flush against the trigger (no margin) so the hoverable area is
      continuous all the way from the link into the panel -- there's no
      gap left to lose the mouse in.
   2. Even with the gap gone, a brief overshoot or a slightly wobbly
      mouse path would immediately start closing the menu, since opacity/
      pointer-events had no grace period on hover-out. Fix: closing now
      waits ~150ms before it starts fading (and pointer-events stays
      `auto` for that same window), so a momentary flick off the trigger
      doesn't kill the menu -- opening remains instant either way. */
.nav-links li.has-dropdown{ position:relative; }
.dropdown-menu{
  position:absolute; top:100%; left:50%;
  transform:translateX(-50%) translateY(8px);
  min-width:230px; background:var(--white); border-radius:var(--radius-md); box-shadow:var(--shadow-lg);
  padding:.75rem; display:flex; flex-direction:column; gap:.2rem;
  opacity:0; visibility:hidden; pointer-events:none;
  transition:opacity .28s var(--ease) .15s, transform .28s var(--ease) .15s, visibility 0s linear .43s, pointer-events 0s linear .15s;
  z-index:250;
  /* Clip contents to the panel's own rounded rect so a link's pink hover
     fill can never visually poke past the white box's edge/corners (Liam,
     25 July 2026: "make sure the pink selector background doesn't exceed
     the white pop out box"). overflow:hidden only clips children/content --
     it doesn't affect this element's own box-shadow, which is painted
     outside the box regardless.

     Padding raised .6rem -> .75rem (26 July 2026, Liam: "curved corners,
     but sit within the white sub-menu box... it now gets cut off and
     just looks like a rectangle"). At .6rem padding, the first/last item
     in the list sat inside the panel's own 16px corner-curve zone, so
     its much smaller item-level radius (see .dropdown-menu a below)
     visually clashed with the panel's curve right at the corner instead
     of reading as a cleanly nested rounded highlight. The extra padding
     clears the item fully outside that curve zone. */
  overflow:hidden;
}
/* `>` (direct child), not a plain descendant selector, specifically so
   this rule reveals ONLY the top-level panel belonging to the hovered
   trigger and never reaches into a nested flyout further down the DOM
   (see .dropdown-menu--flyout below) -- a flyout panel is also a
   `.dropdown-menu` and a descendant of this same `li.has-dropdown`, so
   a plain descendant selector here would have popped it open the
   moment you hovered e.g. "About Us", long before you ever reached
   "What We Recruit" inside it. */
.nav-links > li.has-dropdown:hover > .dropdown-menu,
.nav-links > li.has-dropdown:focus-within > .dropdown-menu{
  opacity:1; visibility:visible; pointer-events:auto; transform:translateX(-50%) translateY(0);
  transition:opacity .2s var(--ease), transform .2s var(--ease), visibility 0s, pointer-events 0s;
}
.dropdown-menu a{
  display:block; padding:.65rem .9rem; border-radius:10px; font-size:.92rem; font-weight:600;
  color:var(--ink-soft); white-space:nowrap;
}
/* Item radius bumped var(--radius-sm) (6px) -> 10px (26 July 2026, Liam:
   "make the highlight pink background... have curved corners... sized
   properly to look premium"). 6px read as barely-there on a ~200px-wide
   row -- technically a mathematically "correct" concentric nest against
   the panel's own radius, but too subtle to actually look curved at
   this scale. 10px plus the extra panel padding above is deliberately a
   bit more generous than strict concentric nesting to make the curve
   actually legible as a soft highlight "pill" sitting inside the box. */
.dropdown-menu a:hover, .dropdown-menu a[aria-current="page"]{ background:var(--blush-100); color:var(--ink); }
.dropdown-menu a::after{ content:none; }

.mobile-nav{
  position:fixed; inset:0; z-index:300; background:var(--blush-25);
  display:flex; flex-direction:column; padding:1.5rem var(--gutter);
  transform:translateY(-100%); transition:transform .4s var(--ease);
  overflow-y:auto;
}
.mobile-nav.is-open{ transform:translateY(0); }
.mobile-nav-close{ align-self:flex-end; background:none; border:0; font-size:1.75rem; cursor:pointer; color:var(--ink); }
.mobile-nav-links{ display:flex; flex-direction:column; gap:1.25rem; margin-top:2rem; padding-bottom:2rem; }
.mobile-nav-links > a{ font-family:var(--font-display); font-size:1.6rem; font-weight:600; color:var(--rose-600); }
.mobile-nav-group summary{
  font-family:var(--font-display); font-size:1.6rem; font-weight:600; color:var(--rose-600);
  cursor:pointer; list-style:revert; padding-block:.1rem;
}
.mobile-nav-group[open] summary{ margin-bottom:.75rem; }
.mobile-nav-group a{
  display:block; font-family:var(--font-body); font-size:1.02rem; font-weight:600;
  color:var(--ink-soft); padding:.55rem 0 .55rem 1.1rem;
}

/* Nested "Browse all specialties" flyout, one level deeper than a
   regular .mobile-nav-group entry (26 July 2026) -- deliberately its
   own smaller, indented style rather than reusing .mobile-nav-group's
   large display-font summary, so it reads as a sub-menu rather than a
   sibling top-level section. */
.mobile-nav-subgroup{ margin-left:1.1rem; border-left:1px solid var(--line-soft); }
.mobile-nav-subgroup summary{
  font-family:var(--font-body); font-size:.95rem; font-weight:700; color:var(--rose-600);
  cursor:pointer; list-style:revert; padding:.5rem 0 .5rem 1rem;
}
.mobile-nav-subgroup a{ font-size:.92rem; padding-left:2.1rem; }

@media (max-width:920px){
  .nav-links{ display:none; }
  .hamburger{ display:flex; }
}

/* ==========================================================================
   Hero (home page) — intro sequence
   Stage 1 (~0-7s): full-bleed HARD opaque pink -- video barely/not visible --
                    wordmark grows in, holds, crossfades to the definition,
                    which grows in massive and holds. No logo here; the
                    header's own logo (top-left) is the only logo, permanently.
   Stage 2 (~7s+):  text fades, mist opens up and the video reveals properly,
                    hero content (h1/lead/buttons) fades up.
   ========================================================================== */
.hero{
  position:relative; min-height:100svh; display:flex; align-items:center; justify-content:center;
  overflow:hidden; background:var(--blush-100);
}
.hero-media{
  position:absolute; inset:0; z-index:1;
}
.hero-media video, .hero-media img{
  position:absolute; inset:0; width:100%; height:100%; object-fit:cover;
  opacity:0; transition:opacity 1.8s var(--ease);
}
.hero.is-media-ready .hero-media video,
.hero.no-js .hero-media video,
.hero.no-js .hero-media img{ opacity:1; }
.hero-mist{
  position:absolute; inset:0; z-index:2;
  /* Hard, near-opaque, light blush pink -- Liam confirmed white text reads
     fine on this lighter tone, preferred it over the deeper rose tried
     briefly above. Video should barely show through this at all. */
  background:linear-gradient(180deg, rgba(242,226,224,.97) 0%, rgba(242,226,224,.92) 45%, rgba(181,149,149,.88) 100%);
  transition:background 1.6s var(--ease), opacity 1.6s var(--ease);
}
.hero.is-media-ready .hero-mist{
  /* Reveal state -- only reached once the intro text is done (see main.js) */
  background:linear-gradient(180deg, rgba(242,226,224,.72) 0%, rgba(242,226,224,.42) 45%, rgba(134,94,92,.38) 100%);
}
/* BUG FIX (found after Liam reported the intro text was invisible): .hero
   is display:flex, and .hero-intro / .hero-content were both plain
   position:relative flex ITEMS -- meaning flexbox laid them out side by
   side in a row, squeezing/misplacing the intro text, rather than the two
   simply overlapping and swapping visibility. Both are now position:absolute
   full-bleed overlays with their own internal centering, so each is always
   correctly centred full-screen regardless of the other's opacity state. */
.hero-intro{
  position:absolute; inset:0; z-index:3;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; padding-inline:1.5rem;
  opacity:1; transition:opacity .9s var(--ease);
}
.hero.is-intro-done .hero-intro{ opacity:0; pointer-events:none; }

/* Definition line still types left-to-right via plain JS textContent
   build-up (see main.js) -- not a CSS clip-path/keyframe trick, so the
   worst case if JS is slow/blocked is the full text just sits there
   statically instead of silently staying invisible.

   The wordmark above it is different: it's real vector letterforms
   pulled from the actual logo (see partials/hero-wordmark-svg.html),
   not text, so it can't be "typed" character by character. Instead
   main.js reveals its .wm-unit letter/dot groups one at a time on the
   same ~100ms rhythm the old typing effect used. Each unit defaults to
   visible (opacity:1) so a no-JS visitor or a failed script just sees
   the finished wordmark sitting there, per the "never silently
   invisible" rule -- JS hides units synchronously before staggering
   them back in, it never relies on a hidden-by-default state. */
.hero-wordmark-line{ position:relative; display:inline-block; }
.hero-wordmark-svg{
  width:clamp(300px, 68vw, 1500px); height:auto; display:block;
  color:var(--white);
  filter:drop-shadow(0 4px 28px rgba(43,33,32,.4)) drop-shadow(0 1px 4px rgba(43,33,32,.55));
}
.wm-unit{ opacity:1; transition:opacity .18s var(--ease); }
.wm-unit.is-hidden{ opacity:0; }
.hero-definition.is-typing{ border-right:.045em solid rgba(255,255,255,.85); padding-right:.1em; animation:caretBlink .6s step-end infinite; }
.hero-definition{
  font-family:var(--font-display); font-weight:600; font-style:italic; color:var(--white);
  text-shadow:0 4px 28px rgba(43,33,32,.4), 0 1px 4px rgba(43,33,32,.55);
  font-size:clamp(1.35rem, .7rem + 4.4vw, 3.75rem); /* aggressive vw scaling so it still fits one line on narrow screens */
  line-height:1.2; white-space:nowrap; max-width:100%;
  margin-top:1.75rem; display:inline-block;
}

@keyframes caretBlink{ 50%{ border-color:transparent; } }

.hero-content{
  position:absolute; inset:0; z-index:3;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; max-width:46rem; margin-inline:auto; padding-inline:1.5rem;
  opacity:0; transform:translateY(18px);
  transition:opacity 1s var(--ease), transform 1s var(--ease);
}
.hero.is-intro-done .hero-content{ opacity:1; transform:translateY(0); transition-delay:.6s; }
/* Logo above the hero H1, inside .hero-content so it fades/rises in with
   the rest of the post-transition content (no separate animation needed --
   it inherits .hero-content's own opacity/transform transition). Liam:
   "have an assiduous staffing logo appear above [the H1]... on the homepage
   post the transition... I can help you dial in the size." Width left as a
   starting value pending his sizing pass. */
.hero-content-logo{ width:325px; height:auto; margin-bottom:1.25rem; filter:drop-shadow(0 2px 16px rgba(43,33,32,.35)); }
.hero-content h1{ color:var(--white); text-shadow:0 2px 24px rgba(43,33,32,.35); }
.hero-content .lead{ margin-top:1rem; color:rgba(255,255,255,.94); text-shadow:0 1px 16px rgba(43,33,32,.3); }
.hero-actions{ display:flex; gap:1rem; justify-content:center; flex-wrap:wrap; margin-top:2rem; }

.hero-scroll-cue{
  position:absolute; bottom:1.75rem; left:50%; transform:translateX(-50%); z-index:3;
  width:26px; height:42px; border:2px solid var(--white); border-radius:14px;
  opacity:0; transition:opacity .8s var(--ease) 1.6s;
}
.hero.is-intro-done .hero-scroll-cue{ opacity:.8; }
.hero-scroll-cue::before{
  content:""; position:absolute; top:8px; left:50%; width:4px; height:8px; border-radius:2px;
  background:var(--white); transform:translateX(-50%); animation:scrollCue 1.8s ease-in-out infinite;
}
@keyframes scrollCue{ 0%{ opacity:1; top:8px; } 70%{ opacity:0; top:20px; } 100%{ opacity:0; top:8px; } }

@media (prefers-reduced-motion: reduce){
  .hero-wordmark-svg,.wm-unit,.hero-definition,.hero-content,.hero-mist,.hero-media video,.hero-media img,.hero-scroll-cue,.hero-intro{
    animation:none !important; transition:none !important;
  }
  .wm-unit.is-hidden{ opacity:1; }
  .hero .hero-intro{ opacity:0; }
  .hero .hero-content{ opacity:1; transform:none; }
  .hero-media video, .hero-media img{ opacity:1; }
}

/* Breadcrumbs (sits inside .page-header, above the h1) */
.breadcrumbs{ display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:.4rem; font-size:.85rem; font-weight:600; margin-bottom:1rem; }
.breadcrumbs a{ color:var(--ink-faint); transition:color var(--dur) var(--ease); }
.breadcrumbs a:hover{ color:var(--rose-600); }
.breadcrumbs [aria-current="page"]{ color:var(--rose-600); }
.crumb-sep{ color:var(--ink-faint); opacity:.6; }

/* Non-hero page header -- legacy flat-colour variant, kept for any page
   that hasn't been migrated to the photo .page-hero below. */
.page-header{
  padding-block:calc(var(--header-h) + var(--space-section-sm)) var(--space-section-sm);
  background:var(--blush-100); text-align:center;
}
.page-header .eyebrow{ justify-content:center; }
.page-header h1{ margin-top:.75rem; }
.page-header .lead{ max-width:40rem; margin:1rem auto 0; }

/* Photo page-hero -- shared across every inner page (see partials/page-hero.html).
   Header is position:fixed and out of normal flow, so top padding accounts
   for it explicitly. Uses the exact same pink/rose gradient overlay as
   the footer (see .site-footer below) -- Liam's explicit call, so the top
   and bottom of every page read as one consistent treatment rather than
   two different overlay styles. Text sits in the lower half of this band,
   which lands in the gradient's darker rose-brown end, so contrast holds
   the same way it already does on the footer's white text.

   min-height + flex (rather than letting padding-block and the heading/
   lead text alone decide the height) is the same technique the
   Recruitment Labs 2.0 build uses for its own photo page-hero
   (`.page-hero.photo{min-height:clamp(460px,58vh,620px);display:flex;
   align-items:center;}` in their main.css) -- Liam flagged that our
   version was visibly inconsistent page-to-page and screen-to-screen,
   and the root cause was exactly this: with no min-height, the hero
   band's height (and so its aspect ratio, and so where object-fit:cover
   crops) was purely a function of how much heading/lead text a given
   page had and how wide the viewport was, both of which vary constantly.
   Locking the band to a narrow, mostly-viewport-height-driven range
   fixes that -- every page now gets materially the same band, so the
   same object-position crop reads the same way everywhere. min-height
   (not height) means a page with an unusually long heading still just
   grows the band a little rather than clipping anything. */
.page-hero{
  position:relative; overflow:hidden; text-align:center; color:var(--white);
  padding-block:calc(var(--header-h) + var(--space-section-sm)) var(--space-section-sm);
  min-height:clamp(400px, 48vh, 560px);
  display:flex; align-items:center;
}
.page-hero-media{ position:absolute; inset:0; z-index:0; }
/* page-hero-bridge-opera.webp: Liam's own hand-picked crop (source
   "Hero 7.png"), a wide panoramic sunset shot with the Opera House AND
   the Harbour Bridge both in frame at once (2400x739). object-position
   Y nudged 42% -> 30% -> 18% (Liam: "lower the image", then "one more
   nudge" in the same direction) -- a lower Y value shifts the crop
   window up within the image, trading a bit more sky in for a bit
   less of the bottom-edge waterline/foreground each step. Open to
   further adjustment. */
.page-hero-media img{ width:100%; height:100%; object-fit:cover; object-position:50% 18%; }
.page-hero::before{
  content:""; position:absolute; inset:0; z-index:1;
  background:linear-gradient(160deg, rgba(202,179,179,.5) 0%, rgba(160,119,119,.55) 45%, rgba(110,75,73,.68) 100%);
}
.page-hero .container{ position:relative; z-index:2; }
.page-hero .eyebrow{ justify-content:center; color:var(--white); }
.page-hero .eyebrow::before, .page-hero .eyebrow::after{ background:var(--white); }
.page-hero h1{ color:var(--white); margin-top:.75rem; text-shadow:0 2px 20px rgba(0,0,0,.3); }
/* Mission & Values hero: white Assiduous wordmark inset under the
   heading line (28 July 2026, Liam: "between these two lines in the
   hero banner, can we inset the white Assiduous logo... size the logo
   appropriately for the text"). em-based height ties it to the h1's own
   font-size, so it scales with the responsive --fs-h1 clamp exactly the
   way the surrounding text does, rather than needing its own breakpoints.
   Same drop-shadow treatment as the heading's text-shadow, so it holds
   the same contrast against the photo.
   Round 2 (live check): wasn't centring -- an inline <img> should center
   via the inherited text-align:center on .page-hero, but a wide,
   width:auto SVG with no explicit intrinsic-size fallback can end up
   sized/positioned inconsistently as an inline replaced element across
   browsers. display:block + margin-inline:auto centers it against the
   h1's own box directly instead of relying on inline layout, which is
   unambiguous regardless of the image's rendered width. Also sized up
   (.85em -> 1.4em) per "bump the size up a bit". */
.hero-heading-logo{ display:block; height:1.4em; width:auto; margin:.4em auto 0; filter:drop-shadow(0 2px 14px rgba(0,0,0,.3)); }
/* Widened 40rem -> 50rem sitewide (15 August 2026, Liam: "on every page
   on the hero banners... make the text section under the byline just
   on two lines on the desktop version"). Was 40rem, which pushed
   several of the longer standfirsts (Contact, About, Who We Partner
   hub, Candidates Roles We Recruit, several role-page taglines) onto 3
   lines. 50rem comfortably fits everything up to ~180 characters onto
   2 lines at the desktop --fs-lead size; the one real outlier is
   Candidates > Upload CV at 238 characters, nearly double the next
   longest standfirst on the site, which will most likely still wrap to
   3 lines at any width that doesn't look disproportionately wide
   against the hero photo -- flagged to Liam rather than trimmed
   silently, since that's his copy to shorten, not a CSS fix. Briefly
   had a one-off .page-hero-contact override at this same 50rem value
   before this sitewide change made it redundant; removed along with
   its extra_class argument on Contact's page_hero() call. */
.page-hero .lead{ max-width:50rem; margin:1rem auto 0; color:rgba(255,255,255,.92); }
/* Widened just for Personal Assistants (15 August 2026, Liam: "widen
   the section so all of the text... wraps on 2 lines") -- its tagline
   ("Trusted support for individual fee earners and leaders... close
   attention to detail.", 167 characters, the longest role-page tagline
   on the site) was landing as 2 full lines plus "detail." alone on a
   3rd. Only ~1 word short of fitting at 50rem, so a modest bump to
   56rem is enough, rather than the much larger jump other genuinely-
   longer standfirsts have needed elsewhere. Scoped via page_hero()'s
   extra_class param (role.slug == 'personal-assistants' check in
   what-we-recruit-role.html), same technique as the Contact page's
   now-removed one-off override -- the other 8 role pages and every
   other page's hero are unaffected, still governed by the shared
   50rem rule above. */
.page-hero-pa-wide .lead{ max-width:56rem; }
/* Widened just for Candidates hub (15 August 2026, Liam: "spread over
   two lines") -- its standfirst ("In a competitive market, your next
   move deserves more... connect with the right opportunities.", 158
   characters) at 3 lines at the shared 50rem. Same technique and same
   56rem value as Personal Assistants above (its tagline is only 9
   characters longer), rather than inventing a new number for a
   near-identical-length problem. */
.page-hero-candidates-wide .lead{ max-width:56rem; }
/* Widened just for Candidates > Roles We Recruit (15 August 2026, Liam:
   "widen the field... currently on three lines, I would like it across
   two") -- its standfirst ("Browse the legal support and business
   operations roles... we'll be in touch.", 176 characters, longer than
   both Personal Assistants' and the Candidates hub's standfirsts
   above) was 3 lines at the shared 50rem. Same 56rem value reused --
   estimated capacity at that width comfortably covers 176 characters,
   same logic as the other two scoped overrides above rather than
   inventing a new number without a reason to. */
.page-hero-candidates-rwr-wide .lead{ max-width:56rem; }
/* Widened just for About > Roles We Recruit (15 August 2026, Liam:
   "widen the text so... fits on one line") -- its standfirst had a
   hardcoded <br> forcing a 2-line break; removed that (about-roles-we-
   recruit.html) so it wraps naturally, and widened the container here
   so the full 85-character line actually fits on one line at desktop
   widths rather than wrapping unintentionally. Same 56rem value reused
   a third time now for a "just needs a bit more room" case. */
.page-hero-rwr-wide .lead{ max-width:56rem; }
/* Narrowed just for Legal Secretaries (15 August 2026, Liam: "less
   words on the top line, and more on the second") -- opposite problem
   to the other scoped overrides above. Its tagline ("The people who
   keep legal work moving, matters on track and teams performing at
   their best.", 91 characters) was wrapping unevenly at the shared
   50rem, with most of the sentence crammed onto line 1. First attempt
   at 28rem overshot -- came back 3 lines with "best." alone as an
   orphan on line 3, so this width's real per-line capacity is a bit
   less than the character-count estimate assumed. Bumped to 31rem, a
   modest increase (same "just short by a word" fix as the other
   orphan cases above, not a full re-estimate) -- flagged to Liam to
   check in preview since this is still estimated, not rendered. */
.page-hero-ls-narrow .lead{ max-width:31rem; }
.page-hero .breadcrumbs{ justify-content:center; }
.page-hero .breadcrumbs a{ color:rgba(255,255,255,.75); }
.page-hero .breadcrumbs a:hover{ color:var(--white); }
.page-hero .breadcrumbs [aria-current="page"]{ color:var(--white); }
.page-hero .crumb-sep{ color:rgba(255,255,255,.5); }

/* ==========================================================================
   Cards / teasers / values / process
   ========================================================================== */
/* Frosted-glass panel -- technique borrowed from Recruitment Labs 2.0's
   .card-glass (contact/request-form card sitting on a photo/texture
   backdrop; see 00_PROJECT_FOUNDATIONS.md Section 2, technique only,
   values below are Assiduous's own tokens).

   UNIFIED 8 August 2026 -- Liam: "for all of the white cards across the
   website, can we mirror whatever the translucency/opaqueness of the
   white cards in the firm size in practice section of the what we
   recruit legal secretaries page? I find that with those white cards
   the AS is exactly as visible as I want... so can we use that same
   sort of setting [everywhere]." Those cards are .tier-card (What We
   Recruit role pages, "Firm size in practice"), set earlier today to
   rgba(255,255,255,.82) + blur(3px). This rule now adopts those exact
   figures as the single shared .card-glass value, replacing what had
   become five different per-section opacities (.55 base, .85 bg-mist,
   .62 wwr-faq-glass, .32 mission intro, .4 who-we-partner) accumulated
   across several earlier, narrower requests -- all of which are removed
   below rather than left as dead/conflicting overrides. If a future
   request wants one specific section different again, scope it the
   same way those did, but the shared default is now the tier-card
   figure everywhere until told otherwise.

   Made more see-through again, twice (14 August 2026): Liam first
   asked for just the Home "About Assiduous" card to be more
   see-through (.82 -> .68 -> .56, via a scoped .section-home-about
   .card-glass override at the time), then: "can we do the same for
   all other white cards across the site." Rather than re-scoping that
   .56 figure card-by-card, folded it straight into this shared base
   rule -- same "roll it out" pattern already used elsewhere this
   session (e.g. .bg-dark-test-darker) -- so every .card-glass usage
   site-wide picks it up with no per-page edits. The scoped
   .section-home-about override and its class in home.html were
   removed as redundant (would just be re-setting the same value the
   base rule already carries). */
/* Linen weave added to the shared base rule (14 August 2026, Liam:
   "give all of the white cards without a linen treatment, a linen
   texture -- so like the seek testimonials, the about assiduous on the
   home page etc"). .card-glass is what wraps both of those examples
   (About Assiduous AND the Sourcr/SEEK widget embed -- see
   .testimonial-embed-wrap's own card-glass wrapper in home.html/
   clients.html/candidates.html) plus every other .card-glass usage
   site-wide (Mission intro, WWR hero card, contact form, etc), so one
   edit here covers all of them at once -- same "fold it into the
   shared rule" pattern already used for the .56 opacity change above.
   Pink weave -- .card-glass is only ever used on light sections
   (.card-glass-dark is the separate class for dark ones, untouched
   here, "white cards" doesn't describe it anyway). NOTE: this styles
   the .card-glass WRAPPER around the Sourcr/SEEK widget, not the
   individual review cards the widget renders inside itself -- that's
   third-party embedded content (external script, not our markup), so
   it can't be styled from this stylesheet at all. */
.card-glass{
  background-color:rgba(255,255,255,.56);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  border-radius:var(--radius-lg);
  padding:clamp(2rem,4vw,3.5rem);
  box-shadow:var(--shadow-lg);
}
/* Intro card widened towards (not all the way to) the partner sections'
   own card width (28 July 2026, Liam: "make the white card for The Right
   Fit Starts With Context a little wider... closer but does not match
   the other section white cards... because of their width the top one
   looks a bit thin, I think we should meet in the middle distance
   wise"). This section sits in .container-narrow (820px); the partner
   sections' own card-glass sits in the full .container (1280px, so up
   to ~1184px of usable content width once the gutter is subtracted at
   large viewports). Same scoped-override technique as the Mission
   intro's own .container-narrow widening -- 1040px lands roughly
   halfway between the two rather than matching either. */
.section-partner-context .container-narrow{ max-width:1040px; }
/* Widened just for this section (28 July 2026, Liam: "make the mission
   card wider so... 'It reflects how we work...' fits nearly onto two
   lines, not three") -- the shared .container-narrow (820px) plus the
   card-glass panel's own padding was squeezing that sentence down to
   three lines. Scoped override rather than raising the shared
   .container-narrow value, which is reused for several other genuinely
   narrow single-column text sections across the site. */
.section-mission-intro .container-narrow{ max-width:980px; }
/* Widened just for this section (15 August 2026, Liam: "I want the text
   exactly the same... but only 4 lines, not 5") -- the shared
   .container-narrow (820px) was wrapping this 3-sentence intro onto 5
   lines. Same scoped-override technique and reused 1040px value as
   .section-partner-context above, rather than inventing a new number. */
.section-wwr-intro .container-narrow{ max-width:1040px; }
.card{
  background:var(--white); border:1px solid var(--line-soft); border-radius:var(--radius-lg);
  padding:2.25rem; box-shadow:var(--shadow-sm); transition:transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.card:hover{ transform:translateY(-4px); box-shadow:var(--shadow-md); }
/* Linen weave, Contact page form card only (14 August 2026, sitewide
   "every white card" sweep). Scoped to .contact-form-card rather than
   the base .card rule -- .card is reused for several things that
   shouldn't get texture (e.g. the plain-white hover-recede state on
   .approach-grid/.card-pop siblings), so this stays a deliberate,
   named addition rather than a blanket change. Pink weave, light
   .bg-white section. */
.contact-form-card{
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
}
.card-icon{
  width:56px; height:56px; border-radius:50%; background:var(--blush-100);
  display:flex; align-items:center; justify-content:center; margin-bottom:1.25rem;
  color:var(--rose-600);
}
/* Flex column + margin-top:auto on the link pins "Explore..." to the
   bottom of every teaser-card, so it lines up across a grid row even
   when one card's paragraph runs longer than its neighbour's (grid
   items already stretch to equal height by default -- this just makes
   the link inside each card sit at that shared bottom edge instead of
   wherever its own text happens to end). Liam's call: "make sure the
   explore icons align between both the candidate and client card". */
.teaser-card{ display:flex; flex-direction:column; }
.teaser-card h3{ margin-top:0; }
.teaser-card p{ color:var(--ink-soft); margin-top:.6rem; }
.teaser-card .card-link{ display:inline-flex; align-items:center; gap:.4rem; margin-top:auto; padding-top:1.25rem; font-weight:700; color:var(--rose-600); }

/* Premium sweep, 26 July 2026 (Liam: "restructure, change backgrounds...
   are there icons we used which we can recolour Assiduous, boxes where
   we can apply the same transition effects, change the process
   sections to similar colour change/grow/icon flip effects... go from
   a Mazda to a Rolls-Royce"). Reference: Recruitment Labs 2.0's
   .card-invert (homepage "What We Recruit" tiles) uses a sibling-recede
   grid hover, a solid brand-colour pop with glow on the hovered card,
   and a 3D icon flip (topic icon front, brand mark back) -- technique
   only, no navy/cyan colours or copy carried over. `.card-pop` is the
   Assiduous-flavoured equivalent: a generic upgrade for any icon-led
   `.card`/`.card-icon` feature grid (About/Candidates/Clients "why work
   with us" rows, Candidate Services, Who We Partner's client/candidate
   perspective cards) that previously had no icon and no hover language
   at all, layered on top of `.card`/`.card-icon` without touching
   either base rule so plain `.card` usage elsewhere is unaffected. */
.card-pop{ position:relative; overflow:hidden; transition:transform .5s var(--ease), box-shadow .5s var(--ease), background .45s var(--ease), border-color .45s var(--ease); }
.card-pop::before{
  content:""; position:absolute; top:0; left:0; right:0; height:3px;
  background:linear-gradient(90deg, var(--rose-400), var(--rose-600));
  transform:scaleX(0); transform-origin:left; transition:transform .5s var(--ease);
}
.card-pop:hover::before{ transform:scaleX(1); }
.card-pop .card-icon{ transition:transform .5s var(--ease), background .4s var(--ease), color .4s var(--ease); }
.card-pop h2, .card-pop h3, .card-pop h4{ transition:color .4s var(--ease); }
.card-pop p{ transition:color .4s var(--ease); }
/* Explicit resting colour for any .card-pop paragraph that isn't
   already using .text-soft (e.g. Who We Partner's client/candidate
   perspective copy) -- needed because plain <p> has no site-wide
   colour rule to inherit, so without this the hover-state white text
   rule below would have nothing correct to transition FROM. */
.card-pop-body{ color:var(--ink-soft); }
/* Candidate Services cards moved to two paragraphs each (30 July 2026,
   Liam's rewrite) -- h3/p both reset to margin:0 sitewide (see the base
   reset near the top of this file), so with no rule added here the
   heading and both paragraphs would run flush together with no visual
   separation at all. Same "explicit margin-top on every .card-pop p"
   fix already used for multi-paragraph prose elsewhere (.article-body p,
   .policy-content p), scoped to this one section specifically rather
   than changing .card-pop p everywhere, since every OTHER .card-pop
   usage on the site is still single-paragraph and works fine relying on
   its own icon/heading spacing alone. */
#candidate-services .card-pop p{ margin-top:.75rem; }
/* Career Advice cards, follow-up round (30 July 2026, Liam: "centre the
   titles in the card... same dark background transition we use in the
   our approach section... titles on two lines"). Centring is independent
   of the hover mechanism below -- applies at rest, not just on hover.
   Widened to the whole card (30 July 2026, third follow-up, Liam:
   "centre all the text in the cards") -- was h3-only, now covers the
   paragraphs too. */
#candidate-services .card-pop{ text-align:center; }
/* Bottom-align the resting icons across all 6 cards (5 August 2026,
   Liam: "the interview preparation icon is a little higher than the
   resume advice icon, because there's less text above it... push it
   down so the icons across all six cards are aligned at the bottom of
   the cards"). Root cause: .advice-icon-rest is a normal-flow block
   straight after .advice-text-rest (see that element's own comment
   further down), and grid rows stretch every card in the same row to
   the tallest card's height -- so a card with shorter copy has less
   content above the icon, the icon starts higher, and the row's
   equalised extra height just becomes dead space below the icon
   instead of above it. Fixed properly rather than padding individual
   cards by hand (which would break again the next time any card's copy
   changes): .card-pop is now a flex column, with a new invisible
   .advice-spacer (flex:1 1 auto) inserted in the markup between
   .advice-text-hover and the icon spans -- it absorbs whatever leftover
   height the row's equalisation creates, so .advice-icon-rest always
   lands flush with the card's bottom padding regardless of how much
   text sits above it. Deliberately NOT done by switching
   .advice-icon-rest's own margin-top to auto -- that element already
   has a carefully-tuned transition of its own (margin-top 1.5rem->0 on
   hover, see its comment below), and a margin-top of auto can't be
   smoothly interpolated in a CSS transition (browsers just snap it
   instantly), which would have reintroduced the exact "herky jerky"
   hover feel Liam already asked to have smoothed out once before. The
   spacer is invisible either way, at rest or collapsed, so it can't
   interfere with that existing animation. */
#candidate-services .card-pop{ display:flex; flex-direction:column; }
#candidate-services .advice-spacer{ flex:1 1 auto; }
/* Resting card made translucent (30 July 2026, seventh follow-up, Liam:
   "the pre-transition white card can be more transparent"). This section
   is .bg-white, which already carries a sitewide pink monogram texture
   of its own (see .bg-white above, texture-monogram-pink.webp at
   1100px) -- the cards' own fully opaque white background was hiding
   that texture completely. Same rgba-white-plus-blur technique already
   used for the homepage's .teaser-card, so the section's own pattern
   reads through the cards instead of stopping dead at their edges. Only
   the RESTING background -- the hover state further down still forces
   solid var(--white) explicitly, which is unaffected by this. */
#candidate-services .card-pop{ background:rgba(255,255,255,.82); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px); }
/* Linen weave (Texture 6) added behind the icon/title on the 6 Career
   Advice cards (14 August 2026, Liam: "same with the six career advice
   and hiring advice cards... linen > icon > text, back to front"). Pink
   variant (this card is a light/white translucent fill). background
   shorthand above already set background-color to the rgba figure and
   reset -image/-position/-size/-repeat to their initial values, so
   those need restating here alongside -image rather than relying on
   the shorthand rule to carry them. Automatically sits behind both
   .advice-icon-rest and .advice-text-rest -- they're normal child
   content, backgrounds always paint behind content, no z-index. */
#candidate-services .card-pop{
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top;
  background-size:400px 400px;
  background-repeat:repeat;
}
/* Section-head .card-glass previously had its own scoped override here
   to match the advice cards' translucency (5 August 2026). Removed 8
   August 2026 -- the shared base .card-glass rule now uses these exact
   figures (rgba(255,255,255,.82), blur(3px)) sitewide, so this section
   already gets them from the base rule with no override needed. */
/* Text now split into two layers, mirroring the icon's own
   advice-icon-rest/advice-icon-hover pattern (30 July 2026, third
   attempt at the growth mechanic -- see the long history above and
   below for the two approaches that didn't work):
     Round A (font-size on the single h3/p): grew correctly (proper
       reflow, no overlap) but was a genuine layout-affecting property
       change, so the hovered card's real height changed, which grew
       its grid row (align-items:stretch sizes every row to its tallest
       member), which grew the whole section, shoving the NEXT section
       down and back on hover/unhover -- Liam's "herky jerky... section
       divider jolting" complaint.
     Round B (transform:scale on the same single h3/p): fixed the jolt
       (transform is paint-only, never affects layout) but broke
       spacing outright -- transform doesn't reserve any extra room for
       neighbours to move into, so three stacked, multi-line blocks all
       scaling up at once just overlapped each other. Liam's screenshot:
       "the text post-transition is fucked up".
   Round C (this one) resolves both at once by duplicating the content
   into two real elements instead of resizing one:
     .advice-text-rest -- plain, normal-flow, ALWAYS at resting size.
       Never changes size on hover, so it can never affect this card's
       real height, full stop -- this is what actually stops the jolt,
       not a property choice.
     .advice-text-hover -- a second copy of the same h3/p, at the
       larger size PERMANENTLY (not conditionally), taken out of flow
       entirely via position:absolute so it can't influence the grid row
       either. Invisible at rest, crossfades in on hover (same opacity
       transition idea as .advice-icon-rest/-hover already use) --
       because it's a genuinely separate, normally-laid-out block, its
       own title/paragraphs still push each other apart with real
       spacing, so no overlap.
   aria-hidden on the hover copy in the markup keeps assistive tech from
   ever reading the same paragraph twice -- .advice-text-rest is the one
   real, accessible copy. */
#candidate-services .advice-text-rest{ position:relative; z-index:2; }
#candidate-services .advice-text-hover{
  position:absolute; inset:0; z-index:2; pointer-events:none;
  display:flex; flex-direction:column; justify-content:center;
  padding:0 1.5rem;
  opacity:0;
  transition:opacity .35s var(--ease);
}
#candidate-services .advice-text-hover h3{ font-size:calc(var(--fs-h3) * 1.4); }
#candidate-services .advice-text-hover p{ font-size:calc(var(--fs-body) * 1.24); }
@media (hover:hover){
  #candidate-services .card-pop:hover .advice-text-rest{ opacity:0; transition:opacity .35s var(--ease); }
  #candidate-services .card-pop:hover .advice-text-hover{ opacity:1; }
}
@media (prefers-reduced-motion:reduce){
  #candidate-services .advice-text-rest,
  #candidate-services .advice-text-hover{ transition:none !important; }
}
/* Career Advice cards widened (30 July 2026, Liam: "I am now also
   thinking let's go 2 rows of wider cards"). This grid is already 2 rows
   of 3 at desktop -- .grid-3 only collapses to 2-then-1 column under
   900px (see the sitewide .grid-3 breakpoints above) -- so "wider" is
   the real ask: widen this section's own .container past the sitewide
   1280px so each of the 3 columns gets meaningfully more room for the
   two-line title + two paragraphs each card now carries. Same principle
   as widening .process-list's own .container-narrow for Our Process,
   just scoped to this section's plain .container instead -- doesn't
   touch the shared --container var used everywhere else on the site. */
#candidate-services .container{ max-width:1500px; }
/* Ports the Our Approach spotlight-reveal recipe (see the full history
   above .approach-grid .card-pop::after -- clip-path circle grown from a
   fixed point, texture-layer-in-front-of-gradient order, un-blurred/
   un-oversized inset:0 box, explicit border-color on the seam fix -- all
   of that hard-won detail applies unchanged here, only the colour and
   anchor point differ). Candidate Services previously relied on the
   generic sitewide .card-pop hover fill (instant flat var(--rose-600),
   "Dark") -- swapped for the same growing-circle motion Approach uses,
   repainted in Dark instead of Approach's Mid, since Liam asked for
   "dark" specifically and Dark was already this section's resting hover
   colour before this change, just delivered as an instant swap rather
   than a reveal.
   This grid's cards use a plain <span class="card-icon"> in normal flow
   (not Approach's absolutely-positioned crossfade image), so there's no
   measured icon-centre coordinate to anchor to the way Approach's
   --fill-x/--fill-y were (getBoundingClientRect against a fixed overlay
   position) -- anchored instead to the icon's approximate resting corner
   (top-left, where .card-icon always sits relative to the card's own
   padding box). No live render available to confirm the exact motion;
   flag to Liam to check against a real hover before treating this as
   final. */
#candidate-services .card-pop::before{ content:none; }
#candidate-services .card-pop::after{
  content:""; position:absolute; z-index:0; inset:0;
  background-color:var(--rose-600);
  /* "As" monogram texture layer removed (5 August 2026, Liam: "remove
     the As texture from the cards, keep the colour gradient and the
     icons effects") -- gradient fill and the icon crossfade/clip-path
     reveal choreography below are both untouched, this only drops the
     url(texture-monogram-white-card.webp) layer that used to sit under
     the gradient. background-size/position/repeat below trimmed from
     two comma-separated values (texture, gradient) to one, matching the
     single remaining layer -- leftover per-texture values would just be
     ignored, but keeping them would misleadingly suggest two layers
     still exist here.

     Linen weave (Texture 6) added back in as the post-transition
     texture (14 August 2026, Liam: "the who we partner strips and the
     advice cards can we layer in the texture pre (already done) and
     post transition (need to do)"). This isn't the old As monogram
     coming back -- it's the same texture-weave-white.webp used on the
     dark sections/roles-grid tiles, white variant since this fill is
     the dark rose-500->rose-600 family. Back to two background-image
     layers again, texture over gradient. */
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:400px 400px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
  clip-path:circle(0% at var(--fill-x, 16%) var(--fill-y, 20%));
  /* .6s -> .5s (30 July 2026, Liam: "smooth the transition, it's a
     little herky jerky"). The dark fill was the one outlier duration in
     this card's whole hover choreography -- everything else clusters
     between .28s and .45s (icon fade, text size/colour, card lift/
     shadow), so the fill alone finishing 100-300ms after the rest of
     the card had already settled read as a late "second arrival"
     rather than one cohesive motion. .5s matches the card's own base
     lift/shadow transition (--dur), giving the fill and the card's
     physical movement one shared finish line. No other timing changed
     -- kept deliberately minimal per Liam's "I do like it, no dramatic
     change" ask. */
  transition:clip-path .5s var(--ease);
  pointer-events:none;
}
#candidate-services .card-pop > .advice-icon-rest,
#candidate-services .card-pop > .advice-icon-hover{ z-index:1; }
/* Was `.card-pop > h3, > p{ position:relative; z-index:2; }` -- no
   longer valid once h3/p moved one level deeper, inside the new
   .advice-text-rest/.advice-text-hover wrappers (see that rule above,
   which sets position/z-index on the WRAPPERS instead now, achieving
   the same "stay above the dark fill and the icon" stacking for
   whatever text content sits inside them). */
/* h3 was missing from this rule in the previous round (30 July 2026,
   Liam: "the title disappears on transition"). Root cause: a
   non-positioned, z-index:auto element (plain h3) paints BELOW any
   positioned element in the same stacking context regardless of DOM
   order -- .card-pop::after is position:absolute with z-index:0, so once
   its clip-path opens up on hover it painted over the title outright,
   even though h3 comes later in the DOM. p and .advice-icon were already
   correctly elevated; h3 just got left out when .advice-head (which
   used to wrap it) was removed last round.
   Text bumped to z-index:2, above the icon's z-index:1, in the fifth
   follow-up round (30 July 2026, Liam: the grown white icon reads as too
   opaque "above the As background" and needs to stay readable behind the
   text). Text was always meant to win regardless of DOM order or
   overflow once the icon grows past its own 150px box on hover -- see
   the icon's own opacity fix further down for the other half of this. */
/* Icon relocated to bottom-centre, in-place crossfade (30 July 2026,
   fourth follow-up, Liam: "new plan -- icon at the bottom centre of each
   card, transition in place, no swapping sides, make them much larger").
   Reverses the side-swap layout from the previous round (see
   candidates.html's own comment for that history) -- .advice-head and
   .advice-icon-ghost are gone. Cards are back to a plain stacked column
   (title, both paragraphs, icon last), and .advice-icon now holds BOTH
   the mid and white images in the same spot, straight-ported from Our
   Approach's own crossfade recipe (.approach-icon img / img.icon-white
   above -- opacity + scaleX(0)->scaleX(1) "unfurl", not a literal
   rotateY flip, per the project's standing anti-3D-flip call) -- that
   recipe already transitions in place with no side-swapping, it just
   needed relocating and resizing here.
   Sized much larger than Approach's 72px box per Liam's ask -- 150px
   tall. No live render available to confirm every icon clears the
   card's own overflow:hidden cleanly at every viewport -- flag to Liam
   if any icon reads as clipped or crowding the card edges live. */
/* Sixth follow-up (30 July 2026, Liam: "make sure the colour icons along
   the bottom are all aligned and a similar size... post-transition have
   the faint icon appear in the centre of the card, not the bottom...
   with the icon removed from the bottom, let's use that space for larger
   text"). Three changes bundled into one restructure:
   (1) Alignment -- .advice-icon used to sit in NORMAL FLOW after the two
   paragraphs (margin-top:1.5rem, height:150px), so its vertical position
   depended on how much text came before it. Cards in the same grid row
   stretch to equal height, so a card with shorter copy left extra
   whitespace below its icon while a card with longer copy pushed its
   icon closer to the true bottom edge -- exactly the misalignment in
   Liam's screenshots. Fixed by taking the icon out of flow entirely:
   .advice-icon is now position:absolute; inset:0 (the full card), so
   img.icon-mid can anchor to a fixed `bottom` distance from the card's
   own bottom edge on every card, regardless of text length.
   (2) This also directly answers "use that space for larger text" --
   with the icon no longer reserving 150px + margin in the flow, the
   card's own height now comes from the text alone, and h3/p font-sizes
   are bumped below to make use of the reclaimed room.
   (3) Positioning split -- previously both icon-mid and icon-white sat
   in the exact same spot ("transition in place"). Liam now wants the
   opposite for the white icon specifically: faint and centred on the
   whole card post-hover, not anchored to the old bottom spot. Since both
   images are independently absolutely-positioned within the same
   full-card .advice-icon box, this is just two different resting
   transforms per image (icon-mid: bottom-anchored; icon-white:
   centre-anchored) rather than one shared position -- each still
   "transitions in place" at ITS OWN spot, they just don't share a spot
   with each other any more. */
/* Eighth follow-up (30 July 2026, Liam: "pre-transition still has the
   icon behind the text, it should be below the text... pre = small text
   up top, colour icon beneath. Post = icon large and fainted in the
   background (as it is), text larger to pop out more"). The previous
   round's single .advice-icon wrapper (holding both mid + white images,
   position:absolute inset:0 over the whole card) worked fine for the
   white icon's "big faint centred overlay" job, but it meant the RESTING
   colour icon was ALSO an absolute overlay rather than a normal in-flow
   element -- so it sat at a fixed spot regardless of the text, and once
   text ran long enough it could paint behind/through the text rather
   than genuinely being laid out below it.
   Split into two separate elements in the markup (advice-icon-rest,
   advice-icon-hover) so each can use the right layout model for its own
   job:
   - .advice-icon-rest is back in NORMAL FLOW, after the paragraphs, so
     the colour icon is genuinely laid out beneath the text -- it can
     never overlap, because normal flow guarantees it comes after.
   - .advice-icon-hover stays a position:absolute; inset:0 overlay
     (unchanged behaviour, per Liam's explicit "as it is" for the white
     icon) so it can still centre a large icon across the whole card
     independent of the text's own flow. */
#candidate-services .advice-icon-rest{
  display:block; position:relative; height:140px; margin-top:1.5rem;
  /* height/margin-top brought down from .35s to match this element's
     own opacity/transform at .28s (30 July 2026, same smoothing pass)
     -- the icon's image was fading out over 280ms while the space it
     reserves kept collapsing for another 70ms after, a small internal
     desync on the same element that added to the overall jerkiness.
     Now both halves of this icon's own exit finish together. */
  transition:opacity .28s var(--ease), transform .28s var(--ease), height .28s var(--ease), margin-top .28s var(--ease);
}
#candidate-services .advice-icon-rest img{
  position:absolute; top:50%; left:50%; height:100%; width:auto; max-width:none;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(1);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
#candidate-services .advice-icon-hover{
  position:absolute; inset:0; pointer-events:none;
}
#candidate-services .advice-icon-hover img{
  position:absolute; top:50%; left:50%;
  /* 340px base height, carried over unchanged from the previous round
     (Liam: "the faint icons need to be much larger", and this round's
     "as it is" for the post-transition state). */
  height:340px; width:auto; max-width:none;
  opacity:0;
  transform:translate(-50%,-50%) scale(0) scaleX(0);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
@media (hover:hover){
  #candidate-services .card-pop:hover .advice-icon-rest img{
    opacity:0;
    transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
  }
  /* Collapse the now-invisible icon's own reserved flow space (30 July
     2026, ninth follow-up, Liam: "the card is way too tall, still a lot
     of padding at the bottom post-transition"). Fading the image to
     opacity:0 above never freed up the 140px + 1.5rem margin the
     CONTAINER itself still reserved in flow -- that dead reserved block
     is exactly the leftover bottom padding Liam kept seeing, regardless
     of how much bigger the text got. Collapsing the container's own
     height/margin to 0 on hover removes that reserved space outright,
     so the card's hover height comes from the (now much larger) text
     alone, no invisible icon-shaped gap tacked on underneath it. */
  #candidate-services .card-pop:hover .advice-icon-rest{
    height:0; margin-top:0;
  }
  #candidate-services .card-pop:hover .advice-icon-hover img{
    opacity:.16;
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * 1.15)) scaleX(1);
    transition:opacity .32s var(--ease) .16s, transform .38s var(--ease) .16s;
  }
  /* Sibling recede/grow, re-pointed at the renamed .advice-icon-rest. */
  #candidate-services .grid:has(.card-pop):hover .card-pop .advice-icon-rest img{
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * .65));
    opacity:.45;
  }
  #candidate-services .grid:has(.card-pop):hover .card-pop:hover .advice-icon-rest img{
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * 1.2));
    opacity:0;
  }
}
@media (prefers-reduced-motion:reduce){
  #candidate-services .advice-icon-rest,
  #candidate-services .advice-icon-rest img,
  #candidate-services .advice-icon-hover img{ transition:none !important; }
}
@media (prefers-reduced-motion:reduce){
  #candidate-services .card-pop::after{ transition:none !important; }
}
@media (hover:hover){
  #candidate-services .grid:has(.card-pop):hover .card-pop:hover{
    background:var(--white); border-color:var(--rose-600);
  }
  #candidate-services .grid:has(.card-pop):hover .card-pop:hover::after{
    clip-path:circle(150% at var(--fill-x, 16%) var(--fill-y, 20%));
  }
  /* One card pops, its siblings step back -- same "grid learns which
     card you mean" language as the roles-grid hover reveal, applied
     here to plain feature-card rows for the first time. */
  .grid:has(.card-pop):hover .card-pop{ transform:scale(.97); opacity:.75; }
  .grid:has(.card-pop):hover .card-pop:hover{
    transform:translateY(-6px) scale(1.04); opacity:1;
    background:var(--rose-600); border-color:transparent;
    box-shadow:var(--shadow-lg), 0 0 40px rgba(160,119,119,.35);
  }
  .card-pop:hover .card-icon{ background:var(--white); color:var(--rose-600); transform:scale(1.12); }
  .card-pop:hover h2, .card-pop:hover h3, .card-pop:hover h4{ color:var(--white); }
  .card-pop:hover p{ color:rgba(255,255,255,.9); }
}
@media (max-width:640px){
  /* No hover on touch -- keep the resting card-icon/heading colours,
     just drop the sibling-recede scale so nothing looks "stuck" half
     dimmed on a phone that can't hover. */
  .grid:has(.card-pop):hover .card-pop{ transform:none; opacity:1; }
}

/* Hiring Advice (Clients hub) -- straight port of #candidate-services'
   entire mechanic, just re-scoped to #hiring-advice (30 July 2026,
   Liam: "use all of the same mechanics/effects as career advice
   section, this is the client mirror"). Every rule below is a 1:1 copy
   of that section's own final-state CSS with the ID swapped -- see
   #candidate-services above for the full round-by-round history of why
   each piece of this exists (translucent card bg over .bg-white's
   monogram texture, hover-only font-size bump, Dark radial spotlight
   reveal, the advice-icon-rest/advice-icon-hover split, sibling recede
   already generic). Kept as a genuine duplicate rather than merging the
   two ID selectors into one shared class, matching this project's
   standing convention of duplicating one-off section content/CSS
   instead of extracting shared partials (see Our Approach's own
   history for the same call). */
#hiring-advice .card-pop p{ margin-top:.75rem; }
#hiring-advice .card-pop{ text-align:center; }
#hiring-advice .card-pop{ background:rgba(255,255,255,.82); backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px); }
/* Linen weave, straight port of the #candidate-services rule above --
   same request, same reasoning, this is the client mirror. */
#hiring-advice .card-pop{
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top;
  background-size:400px 400px;
  background-repeat:repeat;
}
/* Bottom-align resting icons across all 6 cards (5 August 2026, same
   request and same fix as #candidate-services' own icon-alignment fix
   this same day -- see that section's comment for the full root-cause
   explanation and why this is a flex spacer rather than margin-top:auto
   on .advice-icon-rest itself). */
#hiring-advice .card-pop{ display:flex; flex-direction:column; }
#hiring-advice .advice-spacer{ flex:1 1 auto; }
/* Text split into two layers (advice-text-rest/advice-text-hover), same
   fix and full reasoning as #candidate-services above -- resting copy
   never resizes (can't affect this card's real height, so it can't
   grow the grid row/section), hover copy is a genuinely separate,
   normally-laid-out block at the larger size permanently, taken out of
   flow via position:absolute so it can't affect the row either, and
   crossfades in on hover. Fixes both the section-jolt (font-size
   attempt) and the text overlap (transform:scale attempt) that were
   each tried and rejected in turn -- see that section's comment for
   the full round-by-round history. */
#hiring-advice .advice-text-rest{ position:relative; z-index:2; }
#hiring-advice .advice-text-hover{
  position:absolute; inset:0; z-index:2; pointer-events:none;
  display:flex; flex-direction:column; justify-content:center;
  padding:0 1.5rem;
  opacity:0;
  transition:opacity .35s var(--ease);
}
#hiring-advice .advice-text-hover h3{ font-size:calc(var(--fs-h3) * 1.4); }
#hiring-advice .advice-text-hover p{ font-size:calc(var(--fs-body) * 1.24); }
@media (hover:hover){
  #hiring-advice .card-pop:hover .advice-text-rest{ opacity:0; }
  #hiring-advice .card-pop:hover .advice-text-hover{ opacity:1; }
}
@media (prefers-reduced-motion:reduce){
  #hiring-advice .advice-text-rest,
  #hiring-advice .advice-text-hover{ transition:none !important; }
}
#hiring-advice .container{ max-width:1500px; }
#hiring-advice .card-pop::before{ content:none; }
#hiring-advice .card-pop::after{
  content:""; position:absolute; z-index:0; inset:0;
  background-color:var(--rose-600);
  /* "As" monogram texture layer removed (5 August 2026, Liam: "drop the
     As texture from the hiring advice cards" -- same request already
     applied to #candidate-services' matching cards this same day, see
     that block's own comment for the full reasoning). Gradient fill and
     icon choreography below untouched.

     Linen weave added back as the post-transition texture, straight
     port of the #candidate-services fix above (14 August 2026, same
     request, client mirror). */
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:400px 400px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
  clip-path:circle(0% at var(--fill-x, 16%) var(--fill-y, 20%));
  /* .6s -> .5s, same smoothing fix as #candidate-services (30 July
     2026, Liam: "smooth the transition on hiring advice, it's a little
     herky jerky... if we learn anything, apply it to candidate advice
     too") -- see that section's own comment for the full reasoning. */
  transition:clip-path .5s var(--ease);
  pointer-events:none;
}
#hiring-advice .card-pop > .advice-icon-rest,
#hiring-advice .card-pop > .advice-icon-hover{ z-index:1; }
/* Was `.card-pop > h3, > p{...}` -- no longer valid once h3/p moved a
   level deeper into .advice-text-rest/.advice-text-hover (see that
   rule further up, which now handles this stacking on the wrappers). */
#hiring-advice .advice-icon-rest{
  display:block; position:relative; height:140px; margin-top:1.5rem;
  /* height/margin-top brought down to .28s to match opacity/transform,
     same fix as #candidate-services -- see that section's comment. */
  transition:opacity .28s var(--ease), transform .28s var(--ease), height .28s var(--ease), margin-top .28s var(--ease);
}
#hiring-advice .advice-icon-rest img{
  position:absolute; top:50%; left:50%; height:100%; width:auto; max-width:none;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(1);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
#hiring-advice .advice-icon-hover{
  position:absolute; inset:0; pointer-events:none;
}
#hiring-advice .advice-icon-hover img{
  position:absolute; top:50%; left:50%;
  height:340px; width:auto; max-width:none;
  opacity:0;
  transform:translate(-50%,-50%) scale(0) scaleX(0);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
@media (hover:hover){
  #hiring-advice .card-pop:hover .advice-icon-rest img{
    opacity:0;
    transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
  }
  #hiring-advice .card-pop:hover .advice-icon-rest{
    height:0; margin-top:0;
  }
  #hiring-advice .card-pop:hover .advice-icon-hover img{
    opacity:.16;
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * 1.15)) scaleX(1);
    transition:opacity .32s var(--ease) .16s, transform .38s var(--ease) .16s;
  }
  #hiring-advice .grid:has(.card-pop):hover .card-pop .advice-icon-rest img{
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * .65));
    opacity:.45;
  }
  #hiring-advice .grid:has(.card-pop):hover .card-pop:hover .advice-icon-rest img{
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * 1.2));
    opacity:0;
  }
}
@media (prefers-reduced-motion:reduce){
  #hiring-advice .advice-icon-rest,
  #hiring-advice .advice-icon-rest img,
  #hiring-advice .advice-icon-hover img{ transition:none !important; }
}
@media (prefers-reduced-motion:reduce){
  #hiring-advice .card-pop::after{ transition:none !important; }
}
@media (hover:hover){
  #hiring-advice .grid:has(.card-pop):hover .card-pop:hover{
    background:var(--white); border-color:var(--rose-600);
  }
  #hiring-advice .grid:has(.card-pop):hover .card-pop:hover::after{
    clip-path:circle(150% at var(--fill-x, 16%) var(--fill-y, 20%));
  }
}

/* Our Approach icon+title row (27 July 2026, Liam: "swap the icons out to
   the set I have provided... put the icon and title together on one
   line, it will help us tighten the whole grid and make it sharper...
   can we have an icon flip as well"). Replaces the old single-tone SVG
   .card-icon (stacked above the heading, via {{ icon(...) }}) with a
   small inline badge that sits beside the heading instead -- a new class
   rather than touching .card-icon, since that shared rule still drives
   the stacked layout everywhere else (About/Candidates/Clients "why work
   with us" rows etc.) and isn't being changed.

   Liam's set ships every concept as a genuine two-tone pair (mid/white --
   same naming precedent as about-icon-*-white.webp), so "flip" here is a
   plain opacity cross-fade between the two images, not a literal 3D
   transform -- matching the project's standing call (see the
   .roles-card-surface rebuild note above) to avoid rotateX/rotateY/
   preserve-3d after that approach proved janky earlier in the build.
   Same two-layer fade idea, just on a much smaller element.

   --icon-scale is the same per-icon correction factor used by
   .roles-card-icon/.about-card-icon: the six concept icons have
   different internal padding and aspect ratios (measured via ImageMagick
   trim), so an identical bounding-box height alone doesn't read as an
   identical visual size -- e.g. the two-person "Meeting People Matters"
   glyph is a thin outline with a lot of internal negative space next to
   the solid-filled "Focused on Fit" target, so it needs scaling up to
   match rather than down.

   Round 2 (still 27 July 2026, Liam reviewing the round-1 build live):
   "drop the circular backgrounds and just have the icons... make the
   icons larger... make the colours of the titles mid pre transition."
   The blush-100 circle swatch is gone entirely -- .approach-icon is now
   just an invisible positioning box (no fill, no radius) so the raster
   glyph itself is the only thing visible, at rest AND on hover; the
   white variant already reads cleanly against the solid rose-600 hover
   fill without needing a halo behind it. Box + inner sizing both bumped
   up for "larger" (52px -> 72px box, 56%/76% -> 74%/90% of it). Title
   colour was inheriting the sitewide h3 default (Dark/rose-600, same as
   every other heading) -- overridden to Mid/rose-500 at rest only; the
   existing .card-pop:hover h3 rule (higher specificity, :hover counts
   as a class) still wins on hover and turns it white as before. */
/* Trims the shared .card's horizontal padding a little (2.25rem -> 1.5rem
   left/right, vertical untouched) purely to reclaim extra text width for
   the "2 lines not 3" request above -- on top of the wider container and
   tighter grid gap, this is what actually tips the two longest
   paragraphs ("Honest Partnerships", "A Decade of Context") from a short
   3rd line down to 2. */
.approach-grid .card{ padding-left:1.5rem; padding-right:1.5rem; }
/* Round 5 (still 27 July 2026, Liam: "on the meeting people matters card,
   we need the white icon and the title to move to the right a bit, the
   icon is very close to the edge post transition and too close to the
   title"). "Meeting People Matters" has the largest --icon-scale (1.18)
   of the six, and the hover state multiplies that by a further 1.4x (see
   the icon-grow note below) -- 1.652x total is the biggest the icon gets
   across the whole grid, so it's the one that visibly runs closest to
   the card's left edge and into the title's gap at full hover size,
   even though the underlying box/gap values are shared by all six cards.
   margin-left on .approach-icon shifts the icon (and, since it's the
   first flex item in the row, the title following it) right as one unit
   away from the card edge; gap widened 1rem -> 1.5rem for more breathing
   room specifically between icon and title. Both apply to all six cards
   for consistency, not just this one -- harmless at the smaller scales,
   necessary at this one. */
/* Round 6 (still 27 July 2026, Liam: "make the white images even larger
   post-transition, being conscious of the spacing to the box edges and
   the space between the larger icon and titles"). Bumping the hover
   scale alone would have made the exact crowding problem round 5 fixed
   ("Meeting People Matters") come straight back, only worse -- so gap
   and margin-left both grow here too, proportionally more than the icon
   itself, to bank extra clearance rather than merely keep pace with it. */
.approach-head{ display:flex; align-items:center; gap:1.75rem; margin-bottom:1rem; }
.approach-head h3{ margin:0; color:var(--rose-500); }
.approach-icon{ position:relative; flex:0 0 auto; width:72px; height:72px; margin-left:1.25rem; }
/* Round 3 (still 27 July 2026, Liam: "the icons don't seem to flip, just
   straight transition to white... can we also have the white icons grow
   and be noticeably larger"). A plain opacity cross-fade read as a fade,
   not a flip, so each image now also scales its own width to 0 as it
   leaves and grows back in from 0 as it arrives (scaleX only -- still no
   rotateY/perspective/preserve-3d, staying inside the project's standing
   anti-3D-flip decision noted above) -- the mid icon visually collapses
   edge-on and the white icon unfurls in its place, which reads as a
   genuine flip rather than a dissolve. A short transition-delay on the
   incoming white icon lets the outgoing icon collapse first before the
   new one unfurls, instead of both happening on top of each other. The
   white icon's own scale is boosted 1.4x over its resting --icon-scale
   so it arrives noticeably larger, not just a same-size recolour --
   .approach-icon has no overflow:hidden so it's free to grow past the
   72px positioning box without being clipped. */
.approach-icon img{
  position:absolute; top:50%; left:50%;
  height:74%; width:auto; max-width:90%;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(1);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
.approach-icon img.icon-white{
  opacity:0;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
}
@media (hover:hover){
  .card-pop:hover .approach-icon img.icon-mid{
    opacity:0;
    transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
  }
  .card-pop:hover .approach-icon img.icon-white{
    opacity:1;
    /* 1.4 -> 1.55 (27 July 2026, Liam: "make the white images even larger
       post-transition") -- see the margin-left/gap increase on
       .approach-head/.approach-icon just above, added in the same round
       specifically to absorb this extra size without recreating the
       "Meeting People Matters" edge-crowding round 5 already fixed once. */
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale, 1) * 1.55)) scaleX(1);
    transition:opacity .32s var(--ease) .16s, transform .38s var(--ease) .16s;
  }
}
@media (prefers-reduced-motion:reduce){
  .approach-icon img{ transition:none !important; }
}
/* Scoped override: Our Approach cards use Mid instead of Dark for the
   hover fill (27 July 2026, Liam: "can we make the fill colour mid
   instead of dark as well"). The shared .card-pop hover rule above
   (background:var(--rose-600)) still drives every other .card-pop grid
   sitewide (About/Candidates/Clients "why work with us" rows) -- adding
   .approach-grid into the same selector chain only raises this one
   compound selector's specificity, so just this grid's hover fill
   changes.

   ROLLBACK NOTE (round 4, still 27 July 2026): this flat instant-swap
   fill was the entire hover treatment through round 3. Liam then asked
   for design suggestions to make the transition "sexier"; the radial
   reveal below was chosen and replaces it. To go back to the plain flat
   fill, delete the .approach-grid .card-pop::after / --fill-x/--fill-y
   block below and un-comment this one line:
   .grid.approach-grid:has(.card-pop):hover .card-pop:hover{ background:var(--rose-500); } */
.grid.approach-grid:has(.card-pop):hover .card-pop:hover{ background:var(--white); border-color:var(--rose-600); }

/* Round 4: radial "spotlight" reveal replacing the flat fill above
   (Liam: "lets try the radial reveal idea"). Instead of the whole card
   instantly swapping to a solid colour, a circle of Mid grows outward
   from the icon's own position until it covers the card -- ties the
   colour fill directly to the icon that's already flipping there,
   rather than two unrelated things happening on hover at once.

   .card-pop already has its own ::before (the top gradient bar), so
   this uses ::after instead.

   That ::before was never actually removed once the radial ::after fill
   took over as this grid's real hover effect, so it kept quietly
   drawing its own thin pink line across the top edge of each card on
   hover -- a leftover from the flat-fill era above, not an intentional
   part of the radial treatment (29 July 2026, Liam: "old effect
   artifact of a pink line that draws across the top of the card... we
   can remove that given the fill effects we have in place"). Switched
   off here, scoped to .approach-grid only, same technique as the other
   scoped overrides on this page -- .card-pop's own ::before stays
   intact for every other grid that still uses it (About/Candidates/
   Clients "why work with us" rows, etc). It's absolutely positioned, which in CSS
   paint order sits ABOVE static in-flow content regardless of source
   order -- so .approach-head and the paragraph both need an explicit
   position:relative + z-index:1 to stay on top of it (see below); the
   card's actual background stays plain white throughout (the override
   directly above), since this ::after is the only thing providing the
   colour now.

   Round 5 (still 27 July 2026, Liam: "give the radial effect a soft
   edge so it looks premium... centre the point more behind each icon,
   it seems slightly off"). Two fixes:

   1. Soft edge: swapped clip-path circle() (a hard geometric cut) for
      an actual circular element (fixed size, border-radius:50%) that's
      blurred with filter:blur(24px) and animated via transform:scale()
      instead of a clip-path percentage. A blur is a real feathered
      pixel falloff at the shape's edge, which clip-path can't produce
      on its own -- scaling the whole (already blurred) shape via
      transform keeps that soft edge at every step of the growth rather
      than only at the very end. width is set in %, sized generously
      (260%) so at full scale it's certain to overshoot every corner of
      the card regardless of how far off-centre the icon origin sits;
      .card-pop's existing overflow:hidden clips it to the card, so the
      soft edge itself is only ever visible mid-transition, resolving to
      a clean, fully solid fill at rest, matching the old flat-fill end
      state exactly. height uses aspect-ratio:1 rather than a percentage
      height, because percentage heights on an absolutely positioned
      element don't resolve against a parent with auto/content height
      (.card-pop's actual height) -- aspect-ratio sidesteps that
      entirely and guarantees a true circle off the width alone.

   2. Off-centre origin: --fill-x/--fill-y were an eyeballed guess
      (13%/34%). Measured the icon's actual rendered centre via a
      getBoundingClientRect check in the browser instead of guessing
      again -- 10.6%/33.4% of the card's own box, consistent across all
      six cards since they're all the same size in this grid -- and
      updated the defaults to match.

   Round 7 (28 July 2026, Liam: "lets add in the idea of a very faint
   white A pattern to the background, but be very careful this rolls in
   and out with the radial effect properly... lets give it a gradient
   then as well"). Both additions are painted straight onto this same
   ::after -- the one element that's already doing the scale(0->1) hover
   transition -- rather than as a separate layer, which is what makes
   them "roll in and out with the radial" for free: there's only one
   thing animating, so the gradient and the monogram are necessarily
   revealed at exactly the same rate as the fill itself, with no extra
   timing to keep in sync.
   - Gradient: flat var(--rose-500) replaced with the same 135deg
     rose-500->rose-600 gradient already used site-wide for .cta-band,
     reused rather than invented so the "premium" fill reads as part of
     the same brand gradient language instead of a one-off.
   - Monogram: layered in as a second background-image, the same
     texture-monogram-white.webp asset already used for .bg-ink, at a
     smaller 260px tile (vs the sitewide 1100px) so several "A"s
     actually show inside a single card's fill circle rather than one
     oversized mark. background-blend-mode:soft-light blends the white
     glyph into the gradient underneath rather than sitting on top of it
     at full opacity -- soft-light lightens where the glyph is opaque
     and does nothing where it's transparent, which is what keeps this
     "very faint" without a separate opacity layer. Left on default
     (scroll) attachment rather than the sitewide fixed-to-viewport
     technique: this element is itself being transform:scale()'d for
     the reveal, and fixed-attachment backgrounds on a transformed
     element are a known cross-browser rendering trap -- scroll
     attachment paints the texture as part of the same layer that's
     scaling, so it grows with the fill exactly like the gradient does.

     Fix, same day, after live-checking it: background-size:cover on the
     gradient layer was sizing it to cover this element's own box --
     which is 260% of the card's width (oversized on purpose, so the
     blurred circle always overshoots every corner regardless of origin
     -- see the round-5 note above). .card-pop's overflow:hidden then
     only ever shows a small clipped slice of that huge box, so "cover"
     was stretching the full rose-500->rose-600 sweep across an area
     ~2.6x wider than what's ever visible, making it read as flat.
     Switched the gradient to a fixed size/position computed from the
     known, constant geometry (box width is always 260% of the card,
     origin always --fill-x/--fill-y) so it's sized and placed to match
     the CARD's own box exactly, however big the box around it is -- the
     monogram layer didn't have this problem since it's a small repeating
     tile, not a single sweep, so it was left as-is.

     Second fix, same day: soft-light against this texture was rendering
     as good as invisible. Checked the source asset directly (ImageMagick
     alpha histogram) -- texture-monogram-white.webp is baked at a max
     ~10% alpha, by design, since site-wide it's a full-page, fixed-
     attachment watermark meant to be a bare hint against a whole section
     (see .bg-white/.bg-mist/.bg-ink above). At that native strength,
     inside a small card-sized fill it doesn't register at all. First
     attempt stacked the same texture three times with
     background-blend-mode:screen on each (compounding passes, roughly
     ~27% effective alpha) -- confirmed via a rendered ImageMagick
     comparison sheet (not just guessed) that this was still too faint to
     read reliably. Rather than keep stacking layers, generated one
     dedicated stronger copy instead: texture-monogram-white-approach.webp,
     same source, alpha boosted -- a new file scoped to this one use, so
     the original stays untouched for its site-wide full-page watermark
     job elsewhere.

     Third fix, next session (Liam: "I can't see the As background image
     at all... confirm we are using the same background image as on the
     dark sections of the site, use the homepage as reference. If we are,
     we might just have to boost the image so it's less faint"). Confirmed
     first: yes, same source file as .bg-dark-test on the homepage (How We
     Help / Values strip), which is where Liam's "it actually pops" mental
     reference comes from. But .bg-dark-test layers it with plain
     background-image (no blend mode) over --ink, a genuinely dark
     backdrop -- even the texture's native ~10% alpha reads clearly there
     because near-black + a little white is a big contrast jump. This
     fill sits on a much lighter rose-500/600 gradient, so the same low
     alpha (even boosted 8x, even with screen blend) had far less
     contrast to work with and was genuinely invisible, not just hard to
     screenshot -- confirmed by Liam directly on his own screen, not a
     capture artefact. Fixed by (1) boosting the dedicated copy much
     harder (8x -> 30x, max alpha ~10% -> 100%) and (2) switching from
     background-blend-mode:screen to plain background-image layering
     (blend-mode:normal), matching .bg-dark-test's own technique exactly
     instead of a different blend trick.

     Fourth fix, next session (Liam: "It's completely invisible... are we
     sitting it behind another layer of fill?"). It was never a fill-order
     or stacking problem -- the card's own background stays white and the
     ::after (z-index:0) paints above it exactly as intended, confirmed by
     inspecting paint order directly. The actual bug was hiding in plain
     sight: filter:blur(24px) is applied to this ::after unconditionally,
     at rest as well as mid-transition -- CSS filter blurs everything the
     element paints, both the soft edge of the growing circle AND its own
     interior contents, with no way to blur just one and not the other.
     24px of gaussian blur on a 420px tile of thin monogram strokes
     smears the letterform into a near-uniform haze -- confirmed by
     rendering the exact same recipe (gradient + boosted texture) through
     both an unblurred and a blur(24px) canvas pass and comparing: the
     unblurred version keeps clearly readable stroke edges (matches the
     genuine brightness swing measured earlier), the blurred version
     flattens to a barely-there smear even at 30x alpha and normal blend.
     No amount of alpha boosting on the source asset was ever going to
     fix this, because the blur was erasing the boosted signal just as
     fast as it was added. First attempted fix: swap filter:blur() for
     mask-image:radial-gradient() (fades opacity toward the shape's own
     edge instead of blurring pixels) on the SAME oversized element --
     removed the blur correctly (confirmed via computed style: filter:none),
     but the monogram was still completely invisible live, which meant a
     second, deeper bug was stacked on top of the blur.

     Fifth fix, same round: the element itself is sized to 260% of the
     card's width (1883px on this grid) purely so a CIRCLE positioned
     off-centre is guaranteed to overshoot every corner of a RECTANGULAR
     card -- that oversizing was only ever needed for the shape's own
     geometry, not for the gradient/monogram content painted inside it,
     but the old rule bolted them together onto one huge element. Chromium
     rasterises very large masked/animated layers at reduced fidelity for
     performance (a real, documented compositor behaviour, not a one-off
     guess) -- painting a 420px repeating tile onto a ~1883x1883 layer and
     only ever displaying a small clipped slice of it is exactly the kind
     of oversized-layer case that triggers it, which is consistent with
     the monogram surviving in an isolated canvas simulation (drawn at its
     true, small, unscaled size) but disappearing on the actual huge
     element.

     Rebuilt the whole mechanism so the shape's geometry and the fill's
     content are no longer the same oversized box:
     - The ::after is now sized to the CARD itself (inset:0 -- exactly
       100%, not 260%), so background-size:cover/420px tiling paints
       against the real, small card dimensions with no oversized-layer
       rasterisation risk, and no more hand-computed percentage sizing
       hacks to match an artificially huge box (the round-7 "cover sizes
       against the wrong box" bug class is gone outright, not patched).
     - The "always reach every corner from an off-centre point" job that
       the 260% width used to do is now handled by mask-image's own
       radial-gradient default sizing: a bare `circle at X Y` with no
       explicit size keyword defaults to `farthest-corner` per spec --
       the 100% colour-stop automatically resolves to the exact distance
       from the fill point to the card's farthest corner, for any card
       size or origin, with no guessing or overshoot margin needed.
     - The "grows on hover" animation is still a plain transform, now
       transform-origin:var(--fill-x) var(--fill-y) + scale(0)->scale(1)
       on the correctly-sized box -- scaling a full-card-sized layer from
       the icon's point outward reproduces the same growing-reveal motion
       as before, just without ever instantiating a huge intermediate
       layer at any point in the animation, at rest or mid-transition.

     Sixth fix, same round: even after all of the above, measured the
     actual on-screen stroke width the 420px tile setting was producing
     and it was thinner than it should be. The source texture's "A"
     glyphs are ~18px wide strokes in the native 2200px canvas (checked
     directly via a pixel-run measurement, not eyeballed) -- at a 420px
     tile, background-size shrinks the whole 2200px canvas down by 5.2x,
     so those strokes render at only ~3.4px on screen, thinner than a
     hairline and an easy loss even before accounting for WebP/JPEG
     softening on top. The sitewide reference Liam pointed to
     (.bg-dark-test on the homepage, where the same texture "actually
     pops") uses a 1100px tile -- less than half the shrink factor, ~9px
     strokes -- which is a big part of why that version reads clearly and
     this one didn't, independent of the backdrop-contrast difference
     already fixed above. Bumped this tile to 700px (confirmed by
     rendering the exact same recipe -- source texture, this card's real
     pixel dimensions, the boosted alpha -- through Python/PIL and looking
     at the actual output image rather than a live screenshot, since the
     screenshot/zoom tool was separately confirmed this round to lose
     fine repeating detail under its own JPEG compression even for an
     unrelated, much bolder test pattern -- not pushed all the way to
     1100px since the card is much shorter than a full page section and
     needs at least one or two clean repeats to read as a pattern rather
     than one oversized, mostly-cropped letter).

     Seventh fix, next session (Liam, after the mask-image + transform
     version above: "It hasn't worked... I'm looking at a rectangular
     expansion, a very blurred edge on the right hand side, and no
     patterns... we've lost the radial effect"). Real regression, not a
     visibility problem this time -- combining an animated
     transform:scale() with a percentage-based mask-image on the same
     element is asking the browser to keep a shape defined in the box's
     own local coordinate system in sync with that same box being scaled
     frame by frame, and evidently that doesn't track cleanly everywhere
     -- consistent with what Liam described (a rectangular fill growing
     underneath a mask shape that isn't scaling in step with it, which
     would show mostly as a plain rectangular block with a soft/blurred
     remainder at one edge, exactly as reported).

     Replaced the transform+mask pairing entirely with clip-path, driven
     by a single animatable property instead of two that need to agree.
     `clip-path:circle(R at X Y)` directly defines the visible boundary
     as a circle at the fill point -- no separate transform, no scale
     animation to keep in sync, and R transitions smoothly on its own via
     a plain CSS transition (clip-path is natively interpolatable,
     supported everywhere modern without vendor prefixes). Grows from 0%
     to 150% -- percentage values for circle() radius resolve against
     sqrt((width^2 + height^2)/2) of the box, so 150% comfortably clears
     the farthest corner from any off-centre origin on a card this shape
     without needing to guess or oversize the box itself, the same
     "farthest-corner" job the mask/oversized-box approaches were both
     trying to solve differently. The gradient/monogram content sits on
     the same card-sized (inset:0), unmasked, unblurred, un-transformed
     box as the "sixth fix" rebuild above -- only the reveal mechanism
     changed, not the fill layer's own sizing/content, which was already
     confirmed correct in isolation.

     Eighth fix, next session (Liam: "the radial effect is back... the As
     texture image is still not visible on the post-transition card").
     Found the actual root cause, and it's been there since round 7, day
     one -- every alpha/blur/sizing fix since was real and necessary, but
     none of them could ever have worked, because the two background
     layers were listed in the wrong order. In a CSS background-image
     list, the FIRST layer is the TOPMOST/frontmost one and later layers
     sit behind it -- this rule had the opaque gradient listed first and
     the monogram texture second, meaning the fully-opaque gradient was
     always painted in front of the texture, completely hiding it,
     regardless of the texture's own alpha, blend mode, tile size, or
     blur state. This is exactly why every Python/PIL simulation used to
     "prove" the recipe worked kept showing a clear pattern while the
     live page never did: the simulations correctly compositied the
     texture ON TOP of the gradient colour (the right order), which is a
     different recipe to what the live CSS was actually doing. Fixed by
     swapping the list order -- texture first (front), gradient second
     (back) -- so the texture's own transparency now correctly reveals
     the gradient behind it instead of being buried under it. No other
     values changed.

     Ninth fix, same session (Liam, once the pattern was finally visible:
     "it works now, we just need to make the As image much much fainter
     like on the homepage, very subtle here"). The whole reason a
     dedicated 30x-alpha-boosted copy (texture-monogram-white-approach.webp)
     existed was to compensate for the layer-order bug seeming to make the
     texture invisible -- boosting alpha was chased for three separate
     rounds trying to fix a problem that was never really about alpha.
     Now that the real bug is fixed and the texture actually renders,
     that 30x boost is the wrong direction entirely -- it's showing as
     bold, overlapping white letters instead of a faint watermark.
     Switched back to the original shared texture-monogram-white.webp
     (max ~9.8% alpha, mean ~0.25%) -- the exact same asset and same
     native strength already used for the homepage's .bg-dark-test
     sections that Liam is using as the subtlety reference, so this
     should land very close to that register by construction rather than
     needing another round of guessing a multiplier.

     Tenth fix, next session (28 July 2026, Liam: "post transition the
     boxes leave a subtle white border around the edges like they are not
     completely filled"). Root cause: the general .card-pop hover rule
     (see the sibling-recede block above .approach-head) sets
     border-color:transparent on hover. That's harmless everywhere else
     .card-pop is used, because those cards' hover colour comes from the
     CARD'S OWN background-color, which by default paints under the
     border all the way to the border-box's outer edge -- so a transparent
     border there is genuinely seamless, nothing is left uncovered. This
     grid is different: its hover colour comes entirely from this ::after,
     a separate element sized to inset:0, which is the padding box -- the
     INSIDE edge of the border, not the outside. So once the general
     rule's border-color:transparent applies, the 1px border ring between
     the ::after's edge and the card's true outer edge has nothing
     painted there at all, and the white section background shows through
     -- read live as an unfilled gap around an otherwise solid card. Fixed
     by giving this grid's own hover override (below) an explicit
     border-color instead of inheriting the general rule's transparent
     one, so the border reads as part of the fill rather than a seam
     around it. */
/* Pre-transition (resting) card made translucent + given the linen weave
   (14 August 2026, Liam: "can we give our Approach sections the linen
   treatment too? Pre- and post-transitions... make sure the pre
   transition cards are that .52 see through like the about us on the
   homepage"). .52 is a deliberately different figure from the shared
   .card-glass .56 -- Liam gave this section its own explicit number
   rather than asking to reuse the shared class, so it's a scoped rule
   here rather than a .card-glass edit. Pink weave, matching this
   section's light/white resting fill (same convention as every other
   light-vs-dark texture pairing on the site). Automatically sits
   behind the icon/text -- normal child content, no z-index needed. */
.approach-grid .card-pop{
  background-color:rgba(255,255,255,.52);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
}
.approach-grid .card-pop::before{ content:none; }
.approach-grid .card-pop::after{
  content:""; position:absolute; z-index:0; inset:0;
  background-color:var(--rose-500);
  /* "As" monogram texture swapped for the linen weave, post-transition
     (14 August 2026, same request as above -- "let's strip out the
     current As texture post transition"). Same texture-weave-white.webp
     used for every other dark rose-family fill on the site. */
  background-image:
    url('/assets/img/photos/texture-weave-white.webp'),
    linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:400px 400px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
  background-blend-mode:normal, normal;
  clip-path:circle(0% at var(--fill-x, 10.6%) var(--fill-y, 33.4%));
  transition:clip-path .6s var(--ease);
  pointer-events:none;
}
.approach-head, .approach-grid .card-pop > p{ position:relative; z-index:1; }
@media (hover:hover){
  .grid.approach-grid:has(.card-pop):hover .card-pop:hover::after{
    clip-path:circle(150% at var(--fill-x, 10.6%) var(--fill-y, 33.4%));
  }
}
@media (prefers-reduced-motion:reduce){
  .approach-grid .card-pop::after{ transition:none !important; }
}

/* Clients hub "Who We Partner" summary strip (30 July 2026, Liam: "5
   cards in a row with the who we partner icon set... a learn more type
   of button at the bottom"). A fresh, compact icon crossfade -- NOT a
   reuse of .approach-icon (that one's sized/margined to sit beside a
   heading in a 2-up grid) or .partner-feature-icon-card (scroll-
   triggered, built for the big alternating layout on the source page).
   Same opacity+scaleX crossfade technique as every other icon set on
   the site, own sizing for a 5-up strip. Card padding tightened from
   the 2.25rem default (same reasoning as .approach-grid .card) so five
   columns have room to breathe; text centred since there's no body
   copy here, just an icon and a short title. */
/* Sleek strip tightening (30 July 2026, Liam: "tighten the padding at
   the top and bottom... so it's a sleek strip across the page"). Same
   scoped-modifier padding convention as every other section-specific
   tightening rule on the site (e.g. .section-candidates-services),
   just shrinking BOTH sides here rather than one edge flowing into a
   neighbour -- this section isn't being merged visually with Our
   Approach or Our Process above/below it, it's meant to read as its
   own thin band. */
.section-partner-strip{ padding-block:calc(var(--space-section) * .35); }
/* Subhead forced back onto one line (30 July 2026, same round, Liam:
   "make this on one line"). The sitewide .section-head max-width:640px
   was wrapping this specific sentence at its length/font-size --
   widened just for this section and nowrap only from 641px up, so a
   genuinely narrow phone can still wrap it rather than overflow. */
.section-partner-strip .section-head{ max-width:900px; }
@media (min-width:641px){
  .section-partner-strip .section-head h2{ white-space:nowrap; }
}
.partner-strip-card{ text-align:center; padding:1.75rem 1.25rem; }
/* Linen weave (Texture 6) added behind the icon/title on the 5 firm-size
   cards (14 August 2026, Liam: "can we give the five who we partner
   cards on the candidate hub and client hub the linen texture as well
   ... linen > icon > text, back to front"). .partner-strip-card is only
   ever used in candidates.html/clients.html (verified), so no extra
   page-scoping needed. Pink variant, matching this card's light/white
   resting fill (same convention as every other light-vs-dark texture
   pairing on the site). Stacking is automatic, no z-index needed: a
   background-image always paints behind an element's own content, and
   both the icon (.partner-strip-icon) and title (h3) are normal child
   content of this card, so "linen > icon > text" falls out for free.
   Only the RESTING background -- the post-hover fill was explicitly
   stripped of texture earlier this project ("not from the strip... from
   the 5 individual cards") and stays that way; this doesn't touch that
   rule. .card's own plain background:var(--white) shorthand resets
   background-image to none, but this rule's background-image comes
   later in the cascade at equal specificity, so it wins per-property
   without needing to repeat the shorthand. */
.partner-strip-card{
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top;
  background-size:400px 400px;
  background-repeat:repeat;
}
/* Titles now two lines via an explicit <br> in the markup (30 July
   2026, follow-up, Liam supplied the exact split per card -- "Boutique
   / Firms" etc). line-height inherited from the sitewide h3 rule
   (1.2) already reads fine at two lines with no extra rule needed
   here. */
.partner-strip-card h3{ margin:0; color:var(--rose-500); font-size:var(--fs-h4); }
.partner-strip-icon{ position:relative; display:block; width:100%; height:64px; margin:0 auto 1.1rem; }
.partner-strip-icon img{
  position:absolute; top:50%; left:50%;
  height:80%; width:auto; max-width:88%;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(1);
  transition:opacity .28s var(--ease), transform .28s var(--ease);
}
.partner-strip-icon img.icon-white{
  opacity:0;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
}
@media (hover:hover){
  .card-pop:hover .partner-strip-icon img.icon-mid{
    opacity:0;
    transform:translate(-50%,-50%) scale(var(--icon-scale, 1)) scaleX(0);
  }
  .card-pop:hover .partner-strip-icon img.icon-white{
    opacity:1;
    /* icon_scale_grown was used unmultiplied at first, but Liam asked
       to make the white icons "a bit larger as well on the transition"
       (30 July 2026, second follow-up) -- rather than editing
       icon_scale_grown itself (shared with the About page's own
       alternating-layout component, a different use of the same data),
       a local *1.2 multiplier is applied here only, same approach as
       .approach-icon's own local hover multiplier. */
    transform:translate(-50%,-50%) scale(calc(var(--icon-scale-grown, 1) * 1.2)) scaleX(1);
    transition:opacity .32s var(--ease) .16s, transform .38s var(--ease) .16s;
  }
  .card-pop:hover .partner-strip-card h3{ color:var(--white); }
}
@media (prefers-reduced-motion:reduce){
  .partner-strip-icon img{ transition:none !important; }
}
/* Hovered card pops above its row, siblings recede (30 July 2026,
   follow-up, Liam: "lift the card we are hovering on so it sits higher
   than the others, maybe it pops out, the others shrink back"). The
   sitewide sibling-recede rule (.grid:has(.card-pop):hover .card-pop /
   :hover, see above) already does the shrink-back/lift-up transform
   for every .card-pop grid on the site, including this one -- but at
   this strip's tight 5-up spacing with no z-index bump, a lifted card
   visually painted BEHIND its next DOM sibling (later elements paint
   over earlier ones at equal stacking level, regardless of transform),
   so the "pop" read as clipped rather than genuinely on top. Fixed with
   an explicit z-index bump on the hovered card only, plus a slightly
   bigger lift/scale than the sitewide default so it reads clearly at
   this card's smaller size.
   Pushed further still (30 July 2026, second follow-up, Liam: "make it
   more pronounced") -- lift and scale both increased again, and the
   receding siblings shrink/dim a little more too so the contrast
   between the popped card and the rest of the row reads unmistakably,
   plus a stronger drop shadow under the popped card to sell the height
   change rather than relying on scale alone. */
.grid.partner-strip:has(.card-pop):hover .card-pop{ z-index:1; transform:scale(.94); opacity:.65; }
.grid.partner-strip:has(.card-pop):hover .card-pop:hover{
  z-index:3;
  transform:translateY(-18px) scale(1.1);
  opacity:1;
  box-shadow:var(--shadow-lg), 0 22px 40px rgba(160,119,119,.4);
}
/* Top accent bar removed (30 July 2026, same follow-up, Liam: "remove
   the pink line going along the top of the card on the transition, it
   gets lost in the fill anyway"). .card-pop::before is the sitewide
   gradient bar that reveals on hover -- pointless once the whole card
   fills with colour/texture behind it, so switched off here the same
   way Approach and Career/Hiring Advice already do for their own
   textured-fill treatments. */
.partner-strip-card::before{ content:none; }
/* Post-transition fill given the same Dark "As" texture as every other
   textured hover fill on the site, instead of a flat colour (30 July
   2026, same follow-up, Liam: "give the post transition the dark As
   background texture, we don't want just solid colour fills anywhere").
   Overrides the sitewide flat .grid:has(.card-pop):hover .card-pop:hover
   background with the same texture-monogram-white-card.webp asset
   Career Advice/Hiring Advice use for their own hover fill, applied as
   a straight instant fill here rather than a radial clip-path reveal,
   since these cards are much smaller and don't have a measured
   icon-centre point to anchor a spotlight to. Extra .partner-strip
   class in the selector raises this rule's specificity above the
   generic sitewide one so just this grid's fill changes.

   Tile size dropped from that shared 220px to 110px (30 July 2026,
   follow-up, Liam: "zoom the As texture out a little, it reads like
   only one big As at the moment" -- screenshot showed a single
   oversized "As" filling almost the whole card). Root cause: 220px was
   tuned for Career/Hiring Advice's own much wider cards (~450px, so
   roughly 2 tiles across); this strip's cards are only ~230px wide, so
   the same 220px tile was ~1.05 repeats across the card -- essentially
   one uncropped "As" pair rather than a repeating pattern. Confirmed
   with a Python/PIL tiling simulation at several sizes against an
   approximate 230x260 card box before picking 110px, which gives ~2
   clean repeats across the width and reads as a genuine texture rather
   than one dominant letterform. */
/* "As" monogram texture dropped (14 August 2026, Liam: "strip the As
   texture from... the 5 individual cards" in this strip -- clarifying
   an earlier, wrongly-scoped request that removed it from the whole
   strip's background instead, since reverted). Same call already made
   on several other components this project (tier-cards, advice cards,
   roles-grid tiles) -- just the gradient fill remains, tile size/
   position/repeat properties dropped along with the texture layer since
   there's now only one background image instead of two. */
/* Linen weave added as the post-transition texture (14 August 2026,
   Liam: "the who we partner strips and the advice cards can we layer
   in the texture pre (already done) and post transition (need to
   do)"). Not the old As monogram -- same texture-weave-white.webp
   used everywhere else this fill's dark rose family appears. */
.grid.partner-strip:has(.card-pop):hover .card-pop:hover{
  background-color:var(--rose-600);
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:400px 400px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
}
/* 5 August 2026 -- Candidates/Clients hub strip cards now link straight
   to the matching section on the dedicated Who We Partner page
   (id="{{ p.slug }}", see that template). .site-header is
   position:fixed (~92px tall: 1.1rem padding top+bottom + the 56px
   logo), so without this the browser's anchor jump lands the section
   heading right underneath it. */
.partner-type-section{ scroll-margin-top:100px; }
/* .partner-strip-cta / .partner-strip-learn-more ("Learn more about who
   we partner" link below the strip) removed 5 August 2026 -- Liam: "we
   can remove this link now because we've linked all of the individual
   firm sizes to the page." Each card itself now deep-links to its own
   section on /about/who-we-partner/ (see .partner-type-section just
   above), so the one generic link to the top of that same page was
   redundant. Confirmed nothing else on the site used either class or
   referenced this specific rule before removing it -- the shared
   rolesLearnMoreThrob keyframe these rules used is still very much
   alive elsewhere (.roles-card-learn-more), only the two classes
   specific to this now-removed link are gone. */
@media (max-width:900px){
  .partner-strip-card{ padding:1.5rem 1rem; }
}

/* Hover polish for the "How we help" teaser cards (25 July 2026, first
   pass: title/link grow + a pastel shimmer sweep; second pass, Liam:
   "lets loose the shimmer, make the grow effect bigger and maybe make
   those two white cards a little more transparent vs solid white" --
   shimmer removed entirely, grow scale increased 1.08 -> 1.2, and the
   card background is now a translucent white (rgba, not var(--white))
   with a light backdrop blur, so a touch of the dark gradient section
   behind it bleeds through instead of sitting as a flat solid card. */
/* Linen weave added (14 August 2026, sitewide "every white card" sweep).
   Covers every plain .teaser-card usage -- About Us "Why Assiduous/Our
   Values/Who We Work With" teaser row, Who We Partner index/role pages,
   the Insights article CTA links -- all confirmed on light .bg-white/
   .bg-mist sections, so pink weave throughout. .article-card's own
   scoped override (above) still wins for Insights article cards
   specifically, same equal-specificity/source-order reasoning already
   used elsewhere -- no conflict. */
.teaser-card{
  background-color:rgba(255,255,255,.88);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px);
}
.teaser-card h3{ display:inline-block; transform-origin:left center; transition:transform .3s var(--ease); }
.teaser-card .card-link{ transform-origin:left center; transition:transform .3s var(--ease), color var(--dur) var(--ease); }
@media (hover:hover){
  .teaser-card:hover h3{ transform:scale(1.2); }
  .teaser-card:hover .card-link{ transform:scale(1.2); }
}
/* Insights article cards specifically opt back OUT of the title/link
   grow above (3 August 2026, Liam: "the effect grows outside of the
   box... I think here we can just have a simple card lift effect") --
   root cause was exactly that: scale(1.2) is paint-only and doesn't
   reserve extra space, so on a longer article title sitting close to
   the card's own padding edge, the enlarged text visibly overflowed
   past the card boundary. Scoped to the dedicated .article-card class
   (added in partials/article-card.html) rather than lowering the scale
   or removing the rule from the shared .teaser-card itself, since that
   class is also used sitewide for Home's How We Help cards and the
   Clients/Candidates hub 3-card rows, which weren't part of this
   complaint and still want the existing grow effect. With the grow
   cancelled here, the plain .card:hover lift (translateY + shadow,
   defined at the base .card level) is the only hover effect left on
   Insights cards -- exactly the "simple card lift" Liam asked for. */
@media (hover:hover){
  .article-card:hover h3, .article-card:hover .card-link{ transform:none; }
  /* Noticeable lift (14 August 2026, Liam: "can we give the article
     cards on the insights page a noticeable lift on hover") -- the
     plain base .card:hover (translateY(-4px)/shadow-md) read as too
     subtle once the title/link grow was switched off above. Compound
     selector (.card.article-card:hover, not just .article-card:hover)
     for guaranteed specificity over the base .card:hover rule
     regardless of source order -- same footgun already root-caused
     several times elsewhere in this file for single-class hover
     overrides tying on specificity. */
  .card.article-card:hover{
    transform:translateY(-10px) scale(1.02);
    box-shadow:var(--shadow-lg);
  }
}
/* Linen weave (Texture 6) added behind the article cards' icon/title/
   excerpt (14 August 2026, Liam: "can we give the article cards on the
   insights page the linen treatment?"). Scoped to .article-card, not
   the shared .teaser-card, so Home's How We Help cards and the
   Clients/Candidates hub 3-card rows are untouched -- same reasoning
   already used for this class's other Insights-only override just
   above. Pink weave, matching this card's light/white .88 fill and
   the .bg-white section it sits on. */
.article-card{
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
}

/* "For Candidates" / "For Clients" compact expand-on-hover cards (How We
   Help section, Home page only -- the plain always-open .teaser-card
   above is still used everywhere else, e.g. the Clients hub's 3-card
   row). Liam: "Create a compact interactive card... Default state...
   display only [title] and [CTA]... the heading should sit toward the
   top and the CTA toward the bottom... Hover: the white card should
   smoothly expand vertically, extending slightly upward and downward...
   should not push or shift the surrounding page layout... reveal the
   description between the heading and CTA... avoid creating the effect
   solely by changing the card's normal document height... use a
   layout-stable approach such as an absolutely positioned expanded card
   layer."

   Mechanism: `.teaser-card-slot` is the real grid item -- it carries a
   FIXED, never-animated height, so it alone determines the grid row's
   height (same for both cards) and nothing about the hover interaction
   ever touches it. `.teaser-card-expand` inside it is `position:absolute;
   inset:0`, so at rest it exactly fills the slot (reads as one normal
   compact card) but is already OUT of the document flow -- growing its
   `top`/`bottom` offsets on hover only changes its own rendered box, and
   since position:absolute boxes never feed into a parent's layout size,
   the slot (and therefore the grid row, and everything below it) never
   moves. This is the "expanding layer" approach from Liam's brief,
   rather than animating a real height.

   Deliberately NOT reusing the shared .card/.teaser-card classes here
   (kept fully standalone instead) so this component's hover mechanics
   never interact with the older .teaser-card:hover title/link-grow
   rules just above.

   Card made fully transparent (26 July 2026, Liam: "make the cards
   transparent, so the section background is what shows through" --
   following on from a discussion about the "For Candidates"/"For
   Clients" CTAs looking better as white-outline ghost buttons, which
   couldn't read against the old near-white frosted fill). Dropped the
   `rgba(255,255,255,.88)` background and the backdrop-filter blur
   entirely -- the dark gradient section (`.bg-dark-test`) and its
   monogram texture now show straight through, same idea as
   Recruitment Labs' `.card-invert` tiles sitting directly on their
   navy section. Border switched from `--line-soft` (a light-mode
   hairline, invisible on a dark backdrop) to a translucent white line;
   box-shadow removed too since a drop shadow doesn't read the same
   way against a textured dark backdrop as it did against a solid
   white card -- the scale-up + brightening border on hover/focus now
   carry the "lift" on their own. Title, description and the CTA
   button below all switch from the old dark-on-light palette
   (rose-600 heading, ink-soft body, rose-600 link) to white/near-white,
   matching every other component already living on `.bg-dark-test`
   (the Values strip words, etc). */
.teaser-card-slot{ position:relative; height:140px; }
.teaser-card-expand{
  position:absolute; inset:0; z-index:1;
  display:flex; flex-direction:column; justify-content:flex-start;
  background:transparent;
  border:1px solid rgba(255,255,255,.25); border-radius:var(--radius-lg);
  padding:2.25rem; cursor:pointer;
  transition:top .4s cubic-bezier(.22,1,.36,1), bottom .4s cubic-bezier(.22,1,.36,1),
             transform .4s cubic-bezier(.22,1,.36,1), border-color .4s cubic-bezier(.22,1,.36,1);
}
/* Liam's correction (26 July 2026), quoting his own screenshot: "we
   need to completely close the gap between For Candidates and Explore
   candidate support. They should literally just sit on top of each
   other, which will allow the overall white card to be smaller and
   then grow out accordingly." The first version used
   justify-content:space-between across a 172px box, which stretched
   the gap between the heading and CTA to fill all the leftover space
   in the resting (description-collapsed) state -- switched to
   justify-content:flex-start plus a small fixed margin under the
   heading instead, so the heading and CTA sit right against each
   other with only a small deliberate gap, and the slot's own height
   could shrink from 172px to 140px to match. */
/* Title and CTA both grow in step with the same expand transition
   (26 July 2026, Liam: "I still want the growth of the For Candidate
   and For Clients titles to happen in with the transition, and for the
   explore links" -- i.e. brought back, on this component too, the same
   idea as the plain .teaser-card's title/link hover-grow elsewhere on
   the site, but eased in sync with this card's own .4s expand motion
   rather than snapping instantly). transform-origin:left center so
   both grow outward from their natural left-aligned reading position,
   matching the same convention used for .teaser-card/.roles-card
   title-grow elsewhere. */
.teaser-card-expand h3{
  margin:0 0 .5rem 0; display:inline-block; transform-origin:left center;
  color:var(--white);
  transition:transform .4s cubic-bezier(.22,1,.36,1) 0s;
}
/* Description reveal: switched from animating `max-height` to the
   `grid-template-rows: 0fr -> 1fr` technique (26 July 2026, Liam: "I
   want it to smoothly go line by line. There's herky-jerky, the whole
   middle text just appears happening now"). The bug: max-height had to
   be set to an arbitrary ceiling (200px) comfortably larger than the
   real text -- but the real content only ever reaches ~85-90px tall,
   so the eased transition's early progress (0 to ~90px, under half of
   the declared 0-200 range) is where ALL of the visible growth
   actually happens; the remaining transition time animates through
   empty, invisible overshoot. The practical effect is that the text
   looks like it snaps/pops in well before the transition "finishes",
   instead of growing in step with it.

   `grid-template-rows` with `fr` units doesn't have this problem: 1fr
   always resolves to the child's own real content height, so the
   eased curve maps onto the ACTUAL 0-to-real-height range with no
   wasted portion, and the paragraph's own `overflow:hidden` clips it
   in sync -- so as the row grows, the text is revealed progressively
   from the top down (line by line), which is the effect Liam asked
   for. The wrapper div is required because `grid-template-rows` is a
   property of the grid CONTAINER, not the item -- can't apply it to
   the <p> directly. `min-height:0` on the paragraph overrides grid
   items' default min-height:auto, which would otherwise refuse to let
   the row shrink below the text's natural height. */
.teaser-card-expand-desc-wrap{
  display:grid; grid-template-rows:0fr; margin:0;
  transition:grid-template-rows .4s cubic-bezier(.22,1,.36,1) 0s, margin .4s cubic-bezier(.22,1,.36,1) 0s;
}
.teaser-card-expand-desc{
  color:var(--white); margin:0; overflow:hidden; min-height:0;
  opacity:0; transform:translateY(6px);
  transition:opacity .4s cubic-bezier(.22,1,.36,1) 0s, transform .4s cubic-bezier(.22,1,.36,1) 0s;
}
/* The visible CTA link's own ::before is stretched to cover the WHOLE
   card (its containing block is `.teaser-card-expand`, since that's the
   nearest positioned ancestor and `.card-link` itself stays position:
   static, which is also just the default) -- Liam: "ensure the entire
   card can be clicked." Only one real link exists in the DOM, so
   :focus-within below fires from that single, unambiguous tab stop
   rather than juggling a second invisible duplicate link. */
/* CTA stays a plain link, not a boxed button (26 July 2026 follow-up,
   Liam: "don't put any extra border around the Explore buttons or
   fill; just leave them as links in the broader transparent card" --
   a first pass had wrapped this in a bordered/filling ghost-button pill
   modelled on Recruitment Labs' `.btn-outline-light`, but Liam decided
   the plain-link look reads better once the whole card itself is
   transparent). Same shape as the original (inline arrow link, no
   background/border/padding/radius), just recoloured white instead of
   rose-600 now that it sits directly on the dark section. */
.teaser-card-expand .card-link{
  display:inline-flex; align-items:center; gap:.4rem; font-weight:700; color:var(--white);
  transform-origin:left center; transition:transform .4s cubic-bezier(.22,1,.36,1) 0s;
}
.teaser-card-expand .card-link::before{ content:""; position:absolute; inset:0; }
/* Growth is deliberately asymmetric, not an even 50/50 top and bottom
   split -- Liam's screenshot showed the expanded card cutting into the
   "Two dedicated paths..." lead text sitting just above the grid.
   Measured live (getBoundingClientRect, not just the CSS margin
   value): the real rendered clearance there is only ~20px, not the
   ~44px `.section-head{margin-bottom}` alone would suggest, so -52px
   upward was always going to overlap it. Growth is now biased almost
   entirely downward, where the section has plenty of room to spare
   before the next section starts, with only a small 10px lift upward
   that stays comfortably inside that measured 20px ceiling. */
/* About Us's own copy of this section (see .about-how-we-help below)
   needs bottom clearance matching this exact -110px hover overflow --
   kept right here so the two rules stay obviously linked if either
   value ever changes. */
@media (hover:hover){
  .about-how-we-help{ padding-bottom:130px; }
}
/* .card-glass-dark bottom clearance for the two -110px/-85px
   teaser-card-expand overflows (14 August 2026, part of the sitewide
   glass-panel rollout -- see .card-glass-dark's own definition further
   down for the effect itself; the Start Here figure was originally
   -60px, raised to -85px the same day once Liam flagged the ghost
   cards' own links spilling past their box -- see that rule's own
   comment for the full story). The old .about-how-we-help fix just
   above only ever needed to keep the overflow inside the SECTION's own
   dark background, so the next (white) section wasn't cut into -- now
   that this content sits inside a visually distinct glass TILE, Liam
   was explicit the tile itself has to be "deep enough... to cover the
   expansion of the buttons", not just the section behind it. Scoped by
   descendant selector off each section's own existing class (no new
   markup needed) rather than baking extra padding into the shared
   .card-glass-dark rule itself, which would needlessly over-pad the
   sections that don't use teaser-card-expand at all (Client Experience,
   What Strong Candidates Bring, Our Values, Who We Partner). Hover-only,
   matching the .about-how-we-help fix above and the teaser-card-expand
   rules themselves -- touch devices drop the absolute-position overflow
   entirely (`@media (hover:none)` further up converts the card to
   normal flow), so nothing needs extra room there. Home's own How We
   Help section never had a dedicated fix before today (only About's
   did, because that's the one Liam actually saw the white-band overlap
   on) -- .section-how-we-help covers both, so Home gets the same
   correct clearance now too. */
@media (hover:hover){
  .section-how-we-help .card-glass-dark{ padding-bottom:calc(clamp(2rem,4vw,3.25rem) + 110px); }
  .section-candidates-start-here .card-glass-dark,
  .section-clients-start-here .card-glass-dark{ padding-bottom:calc(clamp(2rem,4vw,3.25rem) + 85px); }
}
@media (hover:hover){
  .teaser-card-expand:hover, .teaser-card-expand:focus-within{
    top:-10px; bottom:-110px; transform:scale(1.015); border-color:rgba(255,255,255,.5); z-index:5;
  }
  .teaser-card-expand:hover h3, .teaser-card-expand:focus-within h3{ transform:scale(1.15); }
  .teaser-card-expand:hover .card-link, .teaser-card-expand:focus-within .card-link{ transform:scale(1.1); }
  /* The wrapper's row (grid-template-rows) starts opening almost
     immediately (.05s) so the card visibly begins "unrolling" right
     away; the text's own fade/rise starts a beat later (.15s), once
     there's already some room open, rather than both firing at the
     exact same instant -- reads as a staged, line-by-line reveal
     instead of the whole paragraph appearing in one go. Top margin
     halved from the original .6rem to .3rem per Liam's follow-up:
     "when the text in the middle appears, we need to halve the space
     between 'For Candidates' and the 'specialist guidance...'" --
     bottom margin (between the description and the CTA) is untouched,
     only the gap under the heading was called out. */
  .teaser-card-expand:hover .teaser-card-expand-desc-wrap,
  .teaser-card-expand:focus-within .teaser-card-expand-desc-wrap{
    grid-template-rows:1fr; margin:.3rem 0 .6rem 0; transition-delay:.05s;
  }
  .teaser-card-expand:hover .teaser-card-expand-desc,
  .teaser-card-expand:focus-within .teaser-card-expand-desc{
    opacity:1; transform:translateY(0); transition-delay:.15s;
  }
}
/* Keyboard focus must expand the card on every input type, not just
   mice with hover -- kept outside the (hover:hover) query above. */
.teaser-card-expand:focus-within{
  top:-10px; bottom:-110px; transform:scale(1.015); border-color:rgba(255,255,255,.5); z-index:5;
}
/* Start Here (Candidates page) ghost cards: shorter grown state (30
   July 2026, Liam, with a screenshot: "make the boxes on start here
   shorter post-transition... there's a lot of dead space"). Same
   .teaser-card-expand component as How We Help, but these two
   descriptions ("Register your details..." / "Browse current legal
   support...") are roughly half the length of How We Help's own copy,
   which was written expecting -110px of extra bottom room to fit a
   longer paragraph. Scoped override on just `bottom` (top, transform,
   border-color, z-index all still come from the shared rules above) so
   Home/About's own How We Help section, which still needs the full
   -110px for its longer text, is untouched. Estimated from relative
   copy length, not a live render -- flag if it still leaves noticeable
   dead space or, the other way, feels cramped. */
/* -60px raised to -85px (14 August 2026, Liam, with a screenshot: "the
   Submit your resume and view vacancies links drop outside the box on
   expansion"). Root cause: -60px was estimated purely from relative
   copy length against How We Help's own -110px figure (see above), but
   never checked against a live render -- "Submit Your Resume"'s and
   "View Vacancies"'s own descriptions run long enough to wrap to two
   lines at this card's width, and the resulting title+description+link
   stack needed more than 126px+60px of box height to actually fit;
   .teaser-card-expand has no overflow:hidden of its own (deliberately,
   so real content is never clipped/cut off rather than just visually
   spilling), so the excess simply rendered past the card's own bottom
   edge instead of being contained by it. "Candidate FAQ"'s own shorter
   description already fit inside -60px, which is presumably why only
   the other two cards were flagged. -85px sits roughly halfway to How
   We Help's -110px -- comfortably covers both longer descriptions with
   some spare room, short of reopening the original "too much dead
   space" complaint on FAQ's own shorter card. Still an estimate, not a
   live render -- flag again if either edge (cramped or too spacious)
   still shows up. */
@media (hover:hover){
  .start-here-ghost-grid .teaser-card-expand:hover,
  .start-here-ghost-grid .teaser-card-expand:focus-within{ bottom:-85px; }
}
.start-here-ghost-grid .teaser-card-expand:focus-within{ bottom:-85px; }
/* Start Here cards: grow taller only, never wider (30 July 2026, follow-
   up, Liam: "the buttons get slightly wider on hover, they should only
   get taller, the top should remain fixed and the bottom drops down").
   The shared How We Help rules above set `top:-10px` (a small upward
   lift) and `transform:scale(1.015)` (a uniform, BOTH-axis zoom) on
   hover/focus -- that scale is what was widening the box sideways, not
   just growing it downward, and the -10px top offset was moving the top
   edge too, not keeping it fixed. Overridden here to top:0 (pins the
   top edge in place) and transform:none (removes the width-affecting
   zoom entirely) -- the box still visibly grows, but purely through the
   bottom:-60px offset above increasing its own height, not through any
   scale. Text-level zooms (h3/.card-link scale on hover) are untouched
   -- those only affect the type inside the box, not the box's own
   width, so they're not part of this bug. */
@media (hover:hover){
  .start-here-ghost-grid .teaser-card-expand:hover,
  .start-here-ghost-grid .teaser-card-expand:focus-within{ top:0; transform:none; }
}
.start-here-ghost-grid .teaser-card-expand:focus-within{ top:0; transform:none; }
.teaser-card-expand:focus-within h3{ transform:scale(1.15); }
.teaser-card-expand:focus-within .card-link{ transform:scale(1.1); }
.teaser-card-expand:focus-within .teaser-card-expand-desc-wrap{
  grid-template-rows:1fr; margin:.3rem 0 .6rem 0; transition-delay:.05s;
}
.teaser-card-expand:focus-within .teaser-card-expand-desc{
  opacity:1; transform:translateY(0); transition-delay:.15s;
}
/* Touch devices: no hover to reveal the description on, and a fake
   tap-then-tap-again interaction adds real complexity for a marginal
   gain -- Liam's own suggested fallback was to "show the description by
   default," which is what this does: skip the collapse machinery
   entirely and just show the full, already-expanded card at rest. */
@media (hover:none){
  .teaser-card-slot{ height:auto; }
  .teaser-card-expand{ position:relative; inset:auto; }
  .teaser-card-expand-desc-wrap{ grid-template-rows:1fr; margin:.3rem 0 .6rem 0; }
  .teaser-card-expand-desc{ opacity:1; transform:none; }
}
@media (prefers-reduced-motion:reduce){
  .teaser-card-expand, .teaser-card-expand-desc-wrap, .teaser-card-expand-desc,
  .teaser-card-expand h3, .teaser-card-expand .card-link{ transition:none; }
}

/* Roles We Recruit card: layered fade/reveal (26 July 2026, replacing
   the 3D rotating-prism flip built across several earlier rounds this
   session -- Liam: "replace the current 3D prism-style card flip with
   a smoother, layered transition while preserving the existing
   expanding and reflowing grid effect... avoid rotateX, rotateY,
   perspective, preserve-3d and separate card faces... build the card
   using layered elements within the same flat card").

   Four flat, absolutely-positioned layers share the same card area
   instead of three faces mounted around a rotating 3D axis: the
   original brand-coloured text, a central transitional icon, the
   final white text, and a colour fill that spreads outward from the
   centre via clip-path. Only opacity, translateY, scale and clip-path
   ever animate here -- no 3D transform of any kind -- so none of this
   can ever conflict with .roles-card's own size/position inside the
   bento grid reflow below, which is a completely separate mechanism
   (grid-track sizing on the OUTER .roles-card) untouched by any of
   this. perspective is dropped from .roles-card entirely since
   nothing here needs a 3D camera anymore.

   The four layers live inside .roles-card-surface, a persistent white
   "box" (background/border/radius/shadow) that never itself moves or
   changes shape -- only its CONTENTS cross-fade over it. Stacking
   order bottom-to-top: colour fill, original text, icon, final text --
   each stage paints over the previous one as it takes over, which is
   what makes the requested overlap (icon fading in over the still-
   fading original text, final text fading in over the still-fading
   icon) read as one continuous hand-off instead of three separate
   flickers.

   Enter timing (hover/focus-within in, ~680ms total, inside the
   requested 550-750ms window): original text fades/lifts out over
   200ms; the colour fill spreads over 550ms; the icon fades in with a
   translateY+scale rise over its first ~300ms, holds briefly, then
   fades back out over its last ~250ms (a single non-monotonic
   keyframe -- a plain transition can only move between two end-states
   and can't express "appear, then disappear again" on its own); final
   white text starts at 460ms and finishes at 680ms, deliberately
   overlapping the icon's own fade-out tail so the hand-off feels
   continuous rather than staged, and only really becoming legible
   once the colour fill has essentially finished spreading.

   Leave mirrors the same idea in reverse using the same asymmetric-
   timing technique already used elsewhere on this site (e.g. the
   compact How We Help cards): the "off" transition values live on
   each layer's own BASE rule (governing the transition back to rest),
   while the "on" values live on the :hover/:focus-within rule
   (governing the transition into the hovered state) -- entering and
   leaving can have completely different pacing with no JS at all.
   The one exception is the icon's brief reappearance on leave: since
   that's ALSO a non-monotonic "invisible -> visible -> invisible"
   journey, and CSS can't auto-reverse-play a keyframe once :hover is
   simply removed, a small main.js addition toggles a temporary
   .is-leaving class for exactly as long as the leave transition takes,
   driving a second, mirrored keyframe (roleIconLeave) just for that
   one moment. Every other layer here is pure CSS.

   Keyboard support: the card itself gets tabindex="0" (roles-grid.html)
   so :focus-within can actually be triggered by keyboard users tabbing
   through the grid, not just mouse hover -- previously there was
   nothing focusable inside these cards at all. Only the ORIGINAL text
   layer stays exposed to assistive tech (the icon and final-text
   layers are aria-hidden duplicates/decoration in roles-grid.html) --
   an improvement over the old 3D version, which had two literal,
   un-hidden copies of the same text both permanently in the
   accessibility tree regardless of which face was visually rotated
   into view. */
.roles-card{ position:relative; background:none; border:none; padding:0; box-shadow:none; }
/* Compound selector (.roles-card.reveal, not just .roles-card) so this
   beats the site-wide .reveal{transition:opacity .8s, transform .8s}
   rule on specificity rather than losing a same-specificity source-
   order tie -- .reveal is defined later in this file and would
   otherwise win, silently re-adding an .8s eased transform transition
   that smoothed out the hover-out "snap back to grid" this section
   relies on. */
.roles-card.reveal{ transition:none; }
.roles-card:hover{ box-shadow:none; }
/* Resting fill trial: white/Blush -> dark gradient + white AS monogram,
   28 July 2026 (Liam: "what if we tried out other little trick and make
   [them a] dark gradient with the white AS texture background baked in
   and white text to start?"). Reuses the exact same dark-gradient +
   monogram recipe as .bg-dark-test elsewhere on the site (same colour
   stops, same texture file) rather than inventing a new dark treatment,
   just re-scaled for a small card instead of a full section: the
   texture tile is shrunk from .bg-dark-test's 1100px (sized for a
   full-width section) down to 260px, matching the tile-card/values-card
   baked-in-monogram convention already used elsewhere for small
   components, so the marks actually show at card scale instead of
   mostly falling outside such a small box. background-attachment:fixed
   is deliberately dropped too -- that's a section-level parallax trick
   tied to the viewport, and would look broken pinned to a small card
   that scrolls independently of it. Border switched from --line-soft
   (a near-white line meant for light cards, effectively invisible here)
   to a translucent white, matching how .value-accordion and the
   partner-feature placeholders already border themselves on
   .bg-dark-test. See below for the accompanying white-text overrides on
   the resting title layer this trial also requires. */
/* Corner colour fixed same day (Liam: "shift the gradient so the
   corner doesn't look as dark brown? The colour palette... is supposed
   to be like pastel pinks and that corner just looks dark grey/brown").
   This originally copied .bg-dark-test's own stops verbatim (rose-500
   0%/65%, --ink 100%), but that recipe was tuned for a wide, short,
   full-width SECTION, where a 135deg diagonal is long relative to the
   box and the ink corner stays tucked out of the way (same "brown
   drift" issue root-caused earlier this project, see the comment above
   .bg-dark-test). A small, roughly square CARD has a much shorter
   diagonal, so the same 65% stop puts a proportionally much bigger,
   much more visible wedge of --ink (a warm dark grey-brown, not a
   pink) right in the corner. Real fix is two-part: pushed the hard
   stop from 65% to 82% so far less of the card's area falls in the
   transition zone at all, AND swapped the end colour from --ink to
   --rose-600 (Dark) instead -- same "stay inside the rose family,
   never reach the grey-brown ink" approach already used by .cta-band's
   own rose-500->rose-600 gradient elsewhere in this file. */
/* Real-photo hint tried and rolled back same day (Liam: "I love the
   functionality... but looking at the initial pre-transition grid it
   just looks a bit basic. A bit cheap. Do you have any suggestions?" ->
   given free rein -> tried blending a low-opacity version of each
   card's hover photo into the resting surface -> "no, i don't like
   that hint of the photo now that i can see it. Apply something else
   or roll back"). Back to the flat gradient + monogram from the corner
   fix above; see the icon-based fix below instead for the "basic/cheap"
   problem -- a quiet resting icon rather than a hinted photo. */
/* "As" monogram texture dropped entirely (14 August 2026, Liam: "can we
   actually remove the As, I think it looks too busy" -- same call
   already made on the tier-cards and the advice cards earlier this
   project, now extended to this grid). Just the plain rose-500->rose-600
   gradient remains -- one background layer instead of two, so
   background-position/size/repeat below are now single values rather
   than the old texture/gradient comma-separated pairs. The resting
   icon (below) is sized up in the same change to fill the visual
   interest this texture used to add, without the busyness.

   Linen weave (Texture 6) added back in behind the icon/title (same
   day, later, Liam: "can we add the linen texture to the what we
   recruit grids at the back behind the icon and titles... I'm guessing
   the same texture we use on the dark sections" -- correct guess:
   texture-weave-white.webp, the same white/dark-fill variant used on
   .bg-dark-test/.bg-ink, since this card's resting fill is the same
   dark rose-500->rose-600 family). This isn't the As monogram coming
   back -- it's the new fine linen grain from the site-wide As-vs-linen
   trial, landing here as a second background layer ABOVE the gradient
   but (since .roles-card-surface itself carries no z-index, unlike its
   .roles-card-fill/-icon/-text-final siblings which all do) still
   automatically behind every one of those, satisfying "at the back
   behind the icon and titles" for free -- no z-index or extra element
   needed, same reasoning used for the texture layers on the trial
   sections. No background-attachment:fixed here -- that trick is for
   full-viewport sections; this is a small bounded card, so the texture
   just scrolls with the card like everything else on it. */
.roles-card-surface{
  position:absolute; inset:0; overflow:hidden;
  background-color:var(--rose-600);
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500) 0%, var(--rose-500) 82%, var(--rose-600) 100%);
  background-position:center top, center;
  background-size:700px 700px, cover;
  background-repeat:repeat, no-repeat;
  border:1px solid rgba(255,255,255,.3);
  border-radius:var(--radius-lg); box-shadow:var(--shadow-sm);
}
.roles-card-fill{
  position:absolute; inset:0; z-index:1;
  background-color:var(--rose-500);
  background-size:cover, cover;
  background-position:center, center;
  background-repeat:no-repeat, no-repeat;
  clip-path:circle(0% at 50% 50%);
  transition:clip-path .5s var(--ease) .15s;
}
.roles-card-text-original, .roles-card-text-final{
  position:absolute; inset:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center;
  padding:1.6rem;
}
.roles-card-text-original{
  z-index:3;
  opacity:1; transform:translateY(0);
  transition:opacity .3s var(--ease) .35s, transform .3s var(--ease) .35s;
}
/* White text on the resting card, 28 July 2026, same round as the
   dark-gradient surface trial above -- the title otherwise inherits the
   sitewide h3 default (--rose-600, meant for light backgrounds) which
   would be close to unreadable against this new dark fill. */
.roles-card-text-original h2{ color:var(--white); }
/* Quiet resting visibility added 28 July 2026, second attempt at the
   "basic/cheap" resting-grid fix after the real-photo hint (see the
   roles-card-surface comment above) was tried and rolled back for
   looking muddy rather than premium once actually live. An icon reads
   differently -- crisp vector-style linework stays legible even very
   faint, where a softly blurred photo peeking through a wash read as
   an accident rather than an intentional treatment. Resting opacity
   raised from 0 to .14 (a quiet watermark-style crest behind the
   title, not a fully hidden layer) and a transition added so any
   opacity change while NOT mid-animation (i.e. once roleIconLeave
   finishes and JS removes .is-leaving) eases back to .14 instead of
   snapping. The .14 value is intentionally echoed in both keyframes
   below rather than 0, so entering/leaving hover starts and ends
   exactly at the new resting state with no visible jump. */
.roles-card-icon{
  position:absolute; inset:0; z-index:2;
  display:flex; align-items:center; justify-content:center;
  opacity:.14; transform:translateY(6px) scale(.85);
  transition:opacity .3s var(--ease);
  pointer-events:none;
}
/* Sized by HEIGHT, not width (26 July 2026, Liam: "3 additional white
   icons... let's use these 3 with the existing scales to alternate
   what the logo is that appears behind each grid card... be
   incredibly careful to make sure the absolute icon size is the same
   -- some are wider than taller and I think height is always a good
   thing to match"). The 4 icon files are processed to a shared 360px
   tall content crop but differing widths (court 360x360, gable
   398x360, law book 481x360, scales 360x360 already) -- if this rule
   sized by width like the old single-icon version did, the wider
   icons would render visibly SHORTER than the square ones once scaled
   to the same width. Sizing by height with width:auto guarantees every
   icon renders at the identical visual height regardless of its own
   aspect ratio, which is what actually reads as "the same size" when
   several different icon shapes share one component. max-width caps
   the widest icon (law book, ~1.34:1) so it can never crowd a
   squeezed tile's padding even in the tightest corner case. */
/* Sized up ~50% (26 July 2026, Liam: "make the white icons in the what
   we recruit larger, maybe 50% so they nearly fill the boxes/cards") --
   46% -> 70% of the card's own height, `max-width` raised in step so
   the widest icon (law book, ~1.34:1) still has room to grow without
   crowding a squeezed tile's padding. */
/* Sized up again, 70% -> 88% (14 August 2026, part of the "remove the
   As" change above -- Liam: "have the pre transitions icons... fill up
   more of the initial tile, have them larger"). Note the resting
   .roles-card-icon container ALSO carries its own scale(.85) at rest
   (see that rule above) -- the two multiply together, so this doesn't
   read as a full 88% at rest, only once roleIconEnter's mid-hover hold
   brings the container to scale(1) does the icon actually reach this
   full 88% height. That resting scale(.85) is left alone here (changing
   it would mean re-deriving the 0%/15%/100% keyframe values that echo
   it, for a resting-only size tweak that doesn't need it) -- bumping
   the img's own height is enough on its own to noticeably fill more of
   the tile at every stage, rest included. max-width raised to match so
   the widest icon (law book) isn't clipped at the new size. The title
   text (.roles-card-text-original) needs no change -- it was already
   centred over this same icon via matching inset:0 + flex-centre rules,
   independent of the icon's own size. */
.roles-card-icon img{ height:88%; width:auto; max-width:96%; filter:drop-shadow(0 12px 28px rgba(74,67,65,.28)); }
/* Back/final colour: Mid (rose-500), carried over from the previous
   flip version's back-face colour (Liam had already settled on Mid
   over Dark for this in an earlier round). */
.roles-card-text-final{
  z-index:4;
  opacity:0; transform:translateY(6px);
  color:var(--white);
  transition:opacity .22s var(--ease) 0s, transform .22s var(--ease) 0s;
}
.roles-card-text-final h2{ color:var(--white); }
.roles-card-text-final p{ color:rgba(255,255,255,.9); }
.roles-card h2{ margin-top:0; }
.roles-card p{ margin-top:.5rem; }
/* Whole card ~15% smaller, resting-state font included (26 July 2026,
   Liam: "can we make the initial grid, card, font size (whole thing)
   maybe 15% smaller"). Scoped to the final text layer here (not the
   icon layer, which has no text) and kept separate from the
   @container squeeze-state override further down (1.4rem) -- that
   rule only ever fires on a genuinely hover-squeezed tile, and this
   85%-of-normal resting size stays comfortably above it at every
   viewport, so the two never conflict. */
.roles-card-text-final h2{ font-size:calc(var(--fs-h3) * .85); }
.roles-card-text-final p{ font-size:calc(var(--fs-body) * .85); }
/* Resting white card: title only, much larger (26 July 2026, Liam:
   "what if we get rid of the description lines on the initial cards
   and only have them appear on the dark tiles? That would allow us to
   make the titles much larger and more prominent"). The description
   itself moved to a .sr-only paragraph in roles-grid.html -- still in
   the DOM and still announced to assistive tech, just not rendered
   visually here -- so this rule only needs to size the title. 1.15x
   the base heading size: bigger than the old 0.85x used when a
   paragraph still had to share the space, but still comfortably below
   the 1.3x used on the enlarged hover tile, so the hover state still
   reads as the visual peak. */
.roles-card-text-original h2{ font-size:calc(var(--fs-h3) * 1.15); }
/* Hover-affordance hint (26 July 2026, Liam: "under the titles on the
   white cards, should we add subtle flashing elements that would
   encourage you to hover... using the scroll icon on the hero video
   as a reference"). A small chevron sits below the title, slowly
   fading and dipping in a loop -- same quiet, one-shape-plus-motion
   language as .hero-scroll-cue's dot (a shape that gently repeats a
   small movement to say "there's more here, do something"), just
   adapted to "hover" instead of "scroll". Lives inside
   .roles-card-text-original, so it automatically fades away with the
   rest of that layer the instant a card is actually hovered/focused --
   no separate hide rule needed, it just rides the existing transition. */
/* Colour switched Pastel -> translucent white, 28 July 2026, same round
   as the dark-gradient surface trial -- rose-400 was chosen for a white
   card and reads too close to the new dark rose/ink gradient's own
   colour family to work as a clear hint anymore. */
.roles-card-hint{
  display:block; margin-top:.7rem; color:rgba(255,255,255,.6);
  animation:rolesHintPulse 2.4s ease-in-out infinite;
}
@keyframes rolesHintPulse{
  0%,100%{ opacity:.3; transform:translateY(0); }
  50%{ opacity:.9; transform:translateY(4px); }
}
@media (prefers-reduced-motion:reduce){
  .roles-card-hint{ animation:none; opacity:.5; }
}
/* 0%/15% opacity raised 0 -> .14, 28 July 2026, to match the icon's new
   resting opacity (see .roles-card-icon comment) -- so entering hover
   starts exactly where the resting state left off, instead of jumping
   from a faintly-visible watermark down to fully invisible for an
   instant before rising back up. 100% stays 0 -- that's the "fully
   revealed" state where the coloured final-text layer has taken over,
   correctly fully hidden. */
@keyframes roleIconEnter{
  0%   { opacity:.14; transform:translateY(6px) scale(.85); }
  15%  { opacity:.14; transform:translateY(6px) scale(.85); }
  44%  { opacity:1; transform:translateY(0) scale(1); }
  63%  { opacity:1; transform:translateY(0) scale(1); }
  100% { opacity:0; transform:translateY(-4px) scale(1); }
}
/* 100% opacity raised 0 -> .14 to match, same day/same reason -- leaving
   hover now settles back on the new resting watermark opacity instead
   of landing on fully-invisible and needing the base rule to snap it
   back up. 0%/20% stay 0 -- that's the true starting point when a leave
   begins, since the icon was already fully hidden behind the coloured
   final-text layer at that moment. */
@keyframes roleIconLeave{
  0%   { opacity:0; transform:translateY(-4px) scale(1); }
  20%  { opacity:0; transform:translateY(-4px) scale(1); }
  50%  { opacity:1; transform:translateY(0) scale(1); }
  72%  { opacity:1; transform:translateY(0) scale(1); }
  100% { opacity:.14; transform:translateY(6px) scale(.85); }
}
@media (hover:hover){
  .roles-card:hover .roles-card-fill{ clip-path:circle(100% at 50% 50%); transition:clip-path .55s var(--ease) 0s; }
  .roles-card:hover .roles-card-text-original{ opacity:0; transform:translateY(-6px); transition:opacity .2s var(--ease) 0s, transform .2s var(--ease) 0s; }
  .roles-card:hover .roles-card-icon{ animation:roleIconEnter .68s var(--ease) forwards; }
  .roles-card:hover .roles-card-text-final{ opacity:1; transform:translateY(0); transition:opacity .22s var(--ease) .46s, transform .22s var(--ease) .46s; }
}
/* Touch fallback (15 August 2026, mobile/touch completeness pass, Liam:
   "we want the effects on touch... as close to the desktop experience
   as feasibly possible"). On a real hover-capable pointer, the resting
   card shows only the title -- the description lives in a .sr-only
   paragraph (accessible but not visually rendered) and the "Learn more"
   link only exists inside .roles-card-text-final, which starts at
   opacity:0/pointer-events:none. A touch device has no hover to reveal
   any of that, so without this block the description and the Learn
   More link would be genuinely unreachable by sight or by tap on every
   phone/tablet visit -- not a decorative miss, a real content/nav gap.
   Same precedent already established for .teaser-card-expand just above:
   skip the reveal choreography entirely and show the "hovered" layer by
   default. Text-final already carries its own title, description and
   link, so nothing is lost by hiding text-original outright. */
@media (hover:none){
  .roles-card-text-original{ opacity:0; }
  .roles-card-text-final{ opacity:1; transform:none; pointer-events:auto; }
}
.roles-card:focus-within .roles-card-fill{ clip-path:circle(100% at 50% 50%); transition:clip-path .55s var(--ease) 0s; }
.roles-card:focus-within .roles-card-text-original{ opacity:0; transform:translateY(-6px); transition:opacity .2s var(--ease) 0s, transform .2s var(--ease) 0s; }
.roles-card:focus-within .roles-card-icon{ animation:roleIconEnter .68s var(--ease) forwards; }
.roles-card:focus-within .roles-card-text-final{ opacity:1; transform:translateY(0); transition:opacity .22s var(--ease) .46s, transform .22s var(--ease) .46s; }
/* Icon's brief reappearance on hover-out -- driven by main.js toggling
   this class for exactly as long as the leave transition takes (see
   the JS comment in main.js for why this one piece can't be pure CSS). */
.roles-card.is-leaving .roles-card-icon{ animation:roleIconLeave .6s var(--ease) forwards; }
@media (prefers-reduced-motion:reduce){
  .roles-card-fill, .roles-card-text-original, .roles-card-text-final, .roles-card-icon, .roles-card-learn-more{ transition:none !important; animation:none !important; }
}
/* True bento/mosaic grid reflow (26 July 2026 rebuild -- Liam supplied a
   much more detailed brief this round, written with ChatGPT's help:
   "the hovered tile should enlarge and become the clear focal point.
   The other eight tiles should shrink... the entire grid should
   smoothly reflow and compress around the enlarged tile... the enlarged
   tile should push the surrounding tiles away naturally rather than
   overlapping them... please implement this as a true layout
   transition, not simply by applying transform: scale() to tiles while
   leaving their original grid cells unchanged." This replaces the
   previous "shrink/fade in place" version below (kept as design
   history in earlier log entries) -- that version used transform:scale
   on the CARDS themselves while their grid cells stayed fixed size,
   which is exactly the approach Liam explicitly asked to move away
   from this time. The 3D flip-per-card rotation (roleSpin etc. above)
   is untouched -- Liam: "I still want the rotation effect."

   Mechanism: `.roles-grid` gets its own explicit `grid-template-columns`
   AND `grid-template-rows` (previously only columns were set; rows were
   implicit/auto, sized off each card's own `min-height`). At rest all
   six tracks are equal (1fr each), splitting a FIXED, never-changing
   grid height across 3 equal rows -- fixed height is what keeps "the
   overall grid... approximately the same outer dimensions" true
   regardless of what's hovered, since redistributing fr proportions
   within a constant total never changes the container's own size.

   On hovering any one of the 9 cards, both the column and the row that
   card sits in switch from 1fr to a LARGER share (2fr) while the other
   two columns and two rows drop to a SMALLER share (.75fr). Because
   track sizing is genuinely reallocated (not a per-element transform),
   every tile sharing the hovered tile's row or column grows in that one
   dimension, the hovered tile itself grows in both (row AND column,
   landing at the intersection of the two enlarged tracks) becoming the
   clear focal point, and the four tiles that share neither its row nor
   its column shrink in both dimensions -- exactly the "compress around
   the enlarged tile" mosaic/bento behaviour asked for, with real grid
   reflow doing the work instead of a fake scale. All 9 hover/self
   combinations are generated below (hovered tile N -> its row =
   ceil(N/3), column = ((N-1) mod 3)+1).

   `min-height:250px` moved off `.roles-card` itself and onto the grid's
   own fixed height instead (see `.roles-grid` below) -- a per-card
   floor would have fought the row track trying to shrink it on hover,
   silently overriding the whole effect. Scoped to desktop only
   (min-width:901px) since `.grid-3` itself collapses to 2 then 1 column
   below that, at which point "row vs column" bento reflow no longer
   makes sense -- tablet/mobile fall back to the simpler pre-existing
   card-level min-height sizing untouched. Transition uses the site's
   standard --ease curve (matching every other motion in this section)
   over .5s, a touch slower than most hover effects on this site since
   a whole grid reflowing reads as a heavier, more deliberate motion
   than a single element -- still land well inside the "smooth,
   premium... fluid rather than abrupt" territory Liam asked for. */
/* Tighter tile spacing (26 July 2026, Liam: "tighten the grid so the
   space between boxes is 50% of what it is now"). Scoped to
   .roles-grid via its own custom property rather than touching the
   shared --gutter (used everywhere else on the site for section/
   container padding and every other .grid's own gap) -- only this one
   grid gets a tighter gap, at every viewport width.

   Halved again (29 July 2026, Liam: "bring the what we recruit grids
   closer together, maybe half the size of the current gaps") -- .5 ->
   .25 of --gutter, i.e. a quarter of the ORIGINAL sitewide gap now.
   Same custom-property mechanism, still scoped to just this grid; the
   height calc() and grid-template transitions below reference
   --roles-gap directly so they pick up the new value automatically,
   no separate edit needed there. This is the shared roles_grid()
   partial, so the change applies everywhere it's used at once: What We
   Recruit hub, and the Roles We Recruit pages under About, Candidates
   and Clients. */
.roles-grid{ --roles-gap:calc(var(--gutter) * .25); gap:var(--roles-gap); }
@media (min-width:901px){
  .roles-grid{
    height:calc(213px * 3 + var(--roles-gap) * 2);
    grid-template-columns:1fr 1fr 1fr; grid-template-rows:1fr 1fr 1fr;
    transition:grid-template-columns .5s var(--ease), grid-template-rows .5s var(--ease);
  }
  /* container-type:size turns each card into its own query context (see
     the @container rules below) -- scoped to desktop only, alongside
     the rest of this mechanism, since below 901px the card reverts to
     plain content-based min-height sizing, which size containment would
     otherwise fight. */
  .roles-card{ height:100%; container-type:size; container-name:roles-tile; }
}
/* Liam, after seeing the first version live: "the hover card is going
   too large, which is making the other cards go smaller than their
   text, so the text is spilling over... tweak it further to make it
   tidy." Two changes:

   1. Dialled back the large:small ratio from 2fr/.75fr to 1.5fr/.9fr --
      a noticeably less extreme swing (roughly 494x341px hovered vs
      622x429px before, and a 297x297px-ish worst-case corner tile
      instead of 233x161px), so "the hover card going too large" is
      toned down directly, which in turn keeps the shrunk tiles roomier
      to begin with.

   2. Added a container-query safety net (@container roles-tile) so
      shrunk tiles simplify their own content instead of relying purely
      on there being enough room: once a card's own rendered size drops
      below 230px in either dimension, its face padding shrinks, the
      title's font-size drops, and the description paragraph hides
      entirely, leaving just the (already line-broken) title -- which
      comfortably fits even the tightest corner tile regardless of
      exactly how the fr ratio gets tuned in future. 230px sits safely
      below the resting 250px card height, so normal/resting cards are
      never affected -- only genuinely squeezed tiles are. */
@media (min-width:901px) and (hover:hover){
  .roles-grid:has(.roles-card:nth-child(1):hover){ grid-template-columns:1.5fr .9fr .9fr; grid-template-rows:1.5fr .9fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(2):hover){ grid-template-columns:.9fr 1.5fr .9fr; grid-template-rows:1.5fr .9fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(3):hover){ grid-template-columns:.9fr .9fr 1.5fr; grid-template-rows:1.5fr .9fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(4):hover){ grid-template-columns:1.5fr .9fr .9fr; grid-template-rows:.9fr 1.5fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(5):hover){ grid-template-columns:.9fr 1.5fr .9fr; grid-template-rows:.9fr 1.5fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(6):hover){ grid-template-columns:.9fr .9fr 1.5fr; grid-template-rows:.9fr 1.5fr .9fr; }
  .roles-grid:has(.roles-card:nth-child(7):hover){ grid-template-columns:1.5fr .9fr .9fr; grid-template-rows:.9fr .9fr 1.5fr; }
  .roles-grid:has(.roles-card:nth-child(8):hover){ grid-template-columns:.9fr 1.5fr .9fr; grid-template-rows:.9fr .9fr 1.5fr; }
  .roles-grid:has(.roles-card:nth-child(9):hover){ grid-template-columns:.9fr .9fr 1.5fr; grid-template-rows:.9fr .9fr 1.5fr; }
}
/* Bigger, more prominent title + text on the enlarged/hovered tile
   (26 July 2026, Liam: "given the size of the hover squares, let's
   make the titles much bigger and more prominent, and the text also
   larger"). Scoped to the FINAL text layer specifically -- that's the
   layer actually visible for virtually the whole hover duration, once
   the card has grown and the colour fill has settled -- and to
   desktop only (901px+), since that's the only place a tile actually
   grows on hover at all. Sized well above the 85%-of-normal resting
   size used everywhere else on this card. */
@media (min-width:901px) and (hover:hover){
  .roles-card:hover .roles-card-text-final h2{ font-size:calc(var(--fs-h3) * 1.3); }
  .roles-card:hover .roles-card-text-final p{ font-size:calc(var(--fs-body) * 1.15); }
}
@media (min-width:901px){
  .roles-card:focus-within .roles-card-text-final h2{ font-size:calc(var(--fs-h3) * 1.3); }
  .roles-card:focus-within .roles-card-text-final p{ font-size:calc(var(--fs-body) * 1.15); }
}
/* Threshold recalculated (26 July 2026, same round as the 15% overall
   size reduction below): resting card height dropped from 250px to
   213px as part of that shrink, which put the OLD 230px squeeze
   threshold above the new resting size -- meaning every tile, hovered
   or not, would have permanently tripped the safety net and hidden its
   description. New threshold picked the same way as before (a buffer
   below resting, comfortably above the worst-case squeezed corner
   tile, which now measures ~297x174): 195px sits ~18px below the new
   213px resting height and ~21px above the 174px squeeze low. */
/* Title size bumped up on squeezed tiles (26 July 2026, Liam: "when
   minimised boxes just have the titles, let's make those titles
   larger, just when they're in minimised form, as we have the real
   estate" -- since the paragraph is already hidden below this
   threshold, there's spare room to let the remaining title breathe
   rather than keeping it small just because the box is small).
   1.05rem -> 1.4rem. */
@container roles-tile (max-height:195px) or (max-width:195px){
  .roles-card-text-original, .roles-card-text-final{ padding:.9rem; }
  .roles-card-text-original h2, .roles-card-text-final h2{ font-size:1.4rem; }
  .roles-card-text-final p{ display:none; }
  .roles-card-hint{ display:none; }
}
/* Below the bento breakpoint: revert to the simple, original per-card
   sizing (min-height, auto grid rows) -- the fixed-height/fr-track
   mechanism above is only meaningful for a true 3-column layout. */
@media (max-width:900px){
  .roles-card{ min-height:213px; }
}
@media (max-width:640px){ .roles-card{ min-height:179px; } }
.value-card{ position:relative; }
.value-card h3{ margin-top:0; }
.value-card .subline{ color:var(--rose-600); font-weight:700; font-size:.9rem; margin-top:.35rem; }
.value-card p{ color:var(--ink-soft); margin-top:.5rem; }
.value-card p.subline + p{ margin-top:.6rem; }

/* Home page "Our Values" strip -- a vertical stack of just the four
   value words, sitting on .bg-dark-test, with a push-away hover effect.
   Liam: "add a values strip under testimonials section. dark gradient
   background, same vertical stack effect as Recruitment Labs with the
   lines and other values pushing away. Only the Four Values, just the
   words." Technique adapted from Recruitment Labs 2.0's vertical-stack
   hover pattern (technique only, own class names/values/copy -- see
   00_PROJECT_FOUNDATIONS.md Section 2, RL folder is reference-only).

   Each word is its own flex item with its own border-bottom acting as
   the divider line between words (no separate element needed). Siblings
   get pushed away from the hovered word via :has()+:nth-child, generated
   for all 4x3 hover/sibling pairs, with push distance decaying the
   further a word sits from the hovered one (46px adjacent, 26px one
   further, 14px furthest -- scaled down from RL's 5/6-item taper since
   this stack only has 4 words).

   Re-centred per Liam's follow-up: "I want the words all centred with
   each other, and I want the lines underneath the words to only be
   slightly longer than the longest word (Understanding)." Fixed
   width:fit-content (rather than a fixed max-width) makes the container
   shrink-wrap to its widest child -- Understanding -- so every divider
   line and every word sits at that same width regardless of the actual
   word length; text-align:center plus the small horizontal padding then
   centres each shorter word within that shared width, with the padding
   being exactly the "slightly longer than the word" margin either side.
   transform-origin moved from left center to center center to match --
   the hovered word now grows symmetrically outward instead of from its
   old left edge, consistent with everything else now being centred. */
.value-stack{ display:flex; flex-direction:column; width:fit-content; margin-inline:auto; }
.value-stack-item{
  position:relative; padding:1.1rem 1.1rem; margin:0;
  border-bottom:1px solid rgba(255,255,255,.25);
  transform-origin:center center; text-align:center;
  transition:transform .5s var(--ease), border-color .4s ease;
}
.value-stack-item .word{
  font-family:var(--font-display); font-weight:600; color:var(--white);
  font-size:clamp(1.6rem, 1.3rem + 1.2vw, 2.15rem);
}
/* Letter-wave hover (Liam: "animate its letters individually from left
   to right... each letter should briefly grow and rise slightly, one
   after another, creating the impression of a wave travelling across
   the word... after the wave has travelled across the entire word, all
   letters should settle into a consistently larger, solid state").
   Each letter is its own inline-block span (see value_word() macro in
   home.html) carrying a --i custom property for its position, so one
   calc() rule staggers every letter's animation-delay instead of
   hand-writing a rule per letter per word. z-index bumps on hover so
   the enlarged letters render above neighbouring words, not behind them.
   The sibling push-away rules below are unaffected -- they key off
   :has(.value-stack-item:hover) existing at all, not off what transform
   the hovered item itself carries.

   Mechanism: animation only declared inside the :hover-active rule
   (fires the staggered wave-in via @keyframes letterWave, ending on a
   "solid, enlarged" resting frame per letter via forwards fill-mode);
   the base .letter rule keeps a plain `transition:transform`, so the
   moment :hover stops matching, the animation stops applying and the
   letter smoothly transitions back to rest via that transition instead
   -- same asymmetric in/out technique used elsewhere on this site
   (teaser-card grow, roles-card snap-back). ~55ms stagger sits in
   Liam's requested 40-70ms window.

   The whole item ALSO scales up on hover (Liam: "can we make them still
   grow as well on hover?", after an earlier pass had dropped this in
   favour of the letters growing individually) -- amplitudes on both the
   item-level scale and the per-letter settle/peak were trimmed down
   together so the two effects read as one coherent grow-plus-ripple
   rather than compounding into an oversized jump. */
.value-stack-item .word .letter{
  display:inline-block; transform-origin:center bottom;
  transition:transform .35s var(--ease);
}
@keyframes letterWave{
  0%{ transform:translateY(0) scale(1); }
  45%{ transform:translateY(-6px) scale(1.28); }
  100%{ transform:translateY(-2px) scale(1.12); }
}
@media (hover:hover){
  .value-stack-item:hover .word .letter{
    animation:letterWave .5s var(--ease) forwards;
    animation-delay:calc(var(--i) * 55ms);
  }
  .value-stack:has(.value-stack-item:hover) .value-stack-item{ border-color:transparent; }
  /* Hover grow 15% larger (26 July 2026, Liam: "make the hover values
     15% larger as well"): 1.3 -> 1.5 (1.3 * 1.15 = 1.495, rounded to a
     clean 1.5). */
  .value-stack-item:hover{ transform:scale(1.5); z-index:2; }

  /* Non-hovered/pushed words bunch closer together (26 July 2026,
     Liam: "can we make the non-hover values push closer together, e.g.
     if I'm on loyalty, understanding is pushed down, but the gap
     between understanding and respect is half what it is now").

     CORRECTED (26 July 2026, same day, Liam's follow-up: "the gap
     between non-hovered values still looks the same, we want to make
     any group of values either side of the hovered ones closer
     together"). The first pass at this got the arithmetic backwards --
     it moved the decay table from 46/26/14 to 46/36/30, which actually
     REDUCED how much the gap closed (measured live: natural resting
     gap between adjacent words is 92.75px; the 46/26/14 table closed
     it to 72.75px, but the "fix" only closed it to 82.75px, barely
     different from resting, hence Liam's "still looks the same").

     The real lever: gap between two non-hovered words on the same side
     = natural_gap - (closer_word_offset - farther_word_offset). To
     make farther words hug the closer one tightly as a group, the
     farther offsets need to be MUCH smaller than the immediate
     neighbour's, not closer to it. New table: 46 / 14 / 10 for distance
     1/2/3 -- distance-1 keeps its original prominent push (the
     hovered word's immediate neighbour still reads as clearly pushed
     away), but distance-2 and distance-3 sit close to each other
     (14 vs 10, only 4px apart) so the farther word barely adds any
     extra separation beyond the first, reading as one tight trailing
     group instead of a spread-out decay. Verified live: hovering
     Loyalty now closes the Understanding/Respect gap to ~60.75px
     (down from 92.75px resting and 82.75px in the previous, broken
     attempt). */
  .value-stack:has(.value-stack-item:nth-child(1):hover) .value-stack-item:nth-child(2){ transform:translateY(46px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(1):hover) .value-stack-item:nth-child(3){ transform:translateY(14px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(1):hover) .value-stack-item:nth-child(4){ transform:translateY(10px) scale(.9); }

  .value-stack:has(.value-stack-item:nth-child(2):hover) .value-stack-item:nth-child(1){ transform:translateY(-46px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(2):hover) .value-stack-item:nth-child(3){ transform:translateY(46px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(2):hover) .value-stack-item:nth-child(4){ transform:translateY(14px) scale(.9); }

  .value-stack:has(.value-stack-item:nth-child(3):hover) .value-stack-item:nth-child(1){ transform:translateY(-14px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(3):hover) .value-stack-item:nth-child(2){ transform:translateY(-46px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(3):hover) .value-stack-item:nth-child(4){ transform:translateY(46px) scale(.9); }

  .value-stack:has(.value-stack-item:nth-child(4):hover) .value-stack-item:nth-child(1){ transform:translateY(-10px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(4):hover) .value-stack-item:nth-child(2){ transform:translateY(-14px) scale(.9); }
  .value-stack:has(.value-stack-item:nth-child(4):hover) .value-stack-item:nth-child(3){ transform:translateY(-46px) scale(.9); }
}
/* Touch devices never match (hover:hover), so the whole block above --
   letter wave, border-fade, sibling push-away -- simply never engages
   on tap; touch users get the plain resting stack with normal link/tap
   navigation, no effect ever gets stuck "on" after a tap the way a
   :hover-only build can on some touch browsers. This is the same
   hover-gating convention used everywhere else on this site (roles-card
   flip, teaser-card grow, etc.), which is what makes it "sensible" here
   too -- no separate touch-specific JS needed. */
@media (prefers-reduced-motion:reduce){
  .value-stack-item:hover .word .letter{ animation:none; transform:none; }
  .value-stack-item .word .letter{ transition:none; }
}

.process-list{ counter-reset:step; display:flex; flex-direction:column; gap:0; }
/* Premium sweep, 26 July 2026 (Liam: "change the process sections to
   similar colour change/grow/icon flip effects"). Previously a plain
   counter(step) number on a bare ::before with no interaction at all.
   Reworked into a real circular badge that grows and fills solid
   rose-600 on hover, cross-fading its number into a checkmark -- a 2D
   opacity swap rather than a literal 3D rotateY flip. Deliberate call:
   roles-card already tried a true 3D transform-style:preserve-3d flip
   (task history: "Rebuild roles-card flip as true single-rotation 3D
   prism") and it caused a genuine velocity-discontinuity/"herky-jerky"
   bug that took a dedicated fix, before the team replaced it outright
   with a simpler fade/reveal. A cross-fade gets the same "front face
   swaps to a different face" feeling the flip effects elsewhere on the
   site go for, without reopening that exact class of bug on a step
   list that runs on every Candidates/Clients hub page. Left accent
   bar + heading colour shift on hover for the "grow" language; nothing
   that would reflow layout (only border-colour/background/transform/
   opacity animate) so hovering quickly between steps stays smooth. */
.process-step{
  counter-increment:step; display:grid; grid-template-columns:64px 1fr; gap:1.5rem;
  align-items:center;
  padding:1.75rem 1.5rem 1.75rem 1.35rem; border-top:1px solid var(--line-soft);
  border-left:3px solid transparent; border-radius:0 var(--radius-md) var(--radius-md) 0;
  transition:border-color .4s var(--ease), background .4s var(--ease);
  /* max-width/margin-inline added 30 July 2026 for the Candidates dark-
     mode variant below, which needs each row to be able to grow WIDER
     than its resting siblings on hover (Liam: "I want it to grow wider
     then the non-hover cards... centre the icons to the text block").
     At rest this exactly matches the row's own .container-narrow
     parent width on every page that uses .process-list (both Candidates
     and Clients), so it's a no-op everywhere until a page specifically
     widens that parent and overrides this on hover -- see the
     .bg-dark-test-scoped rules below and the .section-candidates-
     process container override. align-items:center (grid, not the old
     implicit stretch) centres the badge column against the text
     column's real height instead of pinning it near the top -- matters
     more now that the badge can grow taller than the text on hover, but
     is a small, reasonable improvement in light mode too. */
  max-width:var(--container-narrow); margin-inline:auto;
}
.process-step:last-child{ border-bottom:1px solid var(--line-soft); }
.step-badge{
  position:relative; width:52px; height:52px; border-radius:50%; background:var(--blush-100);
  display:flex; align-items:center; justify-content:center; flex-shrink:0; margin-top:.1rem;
  transition:transform .45s var(--ease), background .4s var(--ease);
}
.step-badge-num{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-display); font-size:1.3rem; font-weight:600; color:var(--rose-600);
  transition:opacity .3s var(--ease);
}
.step-badge-num::before{ content:counter(step,decimal-leading-zero); }
.step-badge-check{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  color:var(--white); opacity:0; transition:opacity .3s var(--ease);
}
.step-badge-check .icon{ width:20px; height:20px; }
/* Selector retargeted h4 -> h3 (15 August 2026, SEO heading-hierarchy
   pass, Liam: "do a full SEO review... heading hierarchy") -- the
   markup itself changed from <h4> to <h3 class="h4"> (see candidates.html
   /clients.html) so these step titles no longer skip a level under the
   section's own <h2>; the "h4" CLASS keeps the exact same visual size/
   weight (font-body, bold, --fs-h4) via the shared h4,.h4{} rule in the
   base type styles above, so nothing changes visually, only the real
   semantic tag. */
.process-step h3{ margin-bottom:.35rem; transition:color .35s var(--ease); }
.process-step p{ color:var(--ink-soft); margin:0; }
@media (hover:hover){
  .process-step:hover{ border-left-color:var(--rose-500); background:var(--blush-100); }
  .process-step:hover .step-badge{ background:var(--rose-600); transform:scale(1.15); }
  .process-step:hover .step-badge-num{ opacity:0; }
  .process-step:hover .step-badge-check{ opacity:1; }
  .process-step:hover h3{ color:var(--rose-600); }
}
/* Dark-section variant (30 July 2026, Candidates page's "Our process"
   moved onto .bg-dark-test). This same .process-list/.process-step
   markup is also used on Clients' own light-background page, so the
   light-mode rules above are left completely untouched -- these are
   scoped overrides layered on top, only active where .process-step
   actually sits inside .bg-dark-test.

   RESTING state: translucent-white borders/badge/text so the row reads
   cleanly against the dark gradient (same "translucent white instead
   of a light-mode token" approach as every other dark-section
   component -- teaser-card-expand's border, the Who We Partner circle,
   etc).

   HOVER: built same day in two passes. Pass 1, Liam: "can we use the
   same transition and grow and colour transition as we use for the Our
   Values section on the values page? ... just borrowing the card
   growth and colour background" -- reuses the Our Values
   (.value-accordion, Mission page) hover recipe verbatim: background-
   color easing to --blush-100 underneath the same two-layer
   background-image (translucent white wash + the pink monogram
   texture, texture listed AFTER the wash, same layer order
   .value-accordion uses), plus a matching soft blush glow. Text/badge
   flip to the same palette .process-step already uses in plain
   light-mode (rose-600 heading, ink-soft body) once the card itself
   turns light, same "text recolours once its background goes light"
   logic value-accordion's own word/description colours already
   follow.

   Pass 2, Liam: "we need the growth to be much bigger... really pop
   out. Let's have the numbers flip into a new icon set... have the
   post transition icon be much larger." Padding growth roughly
   doubled from pass 1, plus a bigger lift and a stronger/wider glow so
   the size jump reads as a real "pop" rather than a nudge. The
   checkmark cross-fade (still used as-is on Clients' identical
   component, see .step-badge-check above) is replaced here with
   .step-badge-icon -- a real per-step brand icon, growing via actual
   height/width (not transform:scale, which wouldn't affect layout) so
   the row genuinely gets taller in real document flow as the icon
   grows, exactly like .value-accordion's own real-flow growth
   (deliberately NOT the Who We Partner layout-stable absolute-overlay
   technique, which was built specifically to AVOID pushing layout --
   here we want the opposite). `gap` grows alongside it (also a real,
   animatable grid property) so the icon, which is visually wider than
   the 52px badge box on several of these 6 icons, has clear horizontal
   room before the text column starts, without needing to touch
   grid-template-columns (deliberately avoided sitewide after the About
   Us hub's grid-template-columns retrigger jank -- gap is a much
   simpler, safer property to animate for the same "make more room"
   effect). See the candidates.html template comment above the process
   list for the --icon-scale values and how they were computed.
   `transition` is fully redeclared here (not appended) since CSS
   transitions can't merge across cascade layers -- this scoped rule's
   transition list has to name every property being eased, including
   the ones already in the base .process-step rule above. */
/* Full curved-corner card, not a flush-left list row (30 July 2026,
   Liam: "change the card to not have the straight edge and just make
   it a filled curved-corner rectangle"). The light-mode base rule above
   is built as a continuous list -- flush border-left accent, square
   left corners, only a top hairline shared between rows (bottom only
   on :last-child) -- which reads fine for Clients' own copy of this
   component, but no longer suits Candidates now that each row is a
   big, independently-growing, discrete "card". border and border-
   radius are both redeclared in full (all 4 sides / all 4 corners)
   rather than patched, and .process-list itself gets a real gap so
   each row reads as its own separated card instead of a joined strip. */
.bg-dark-test .process-list{ gap:1rem; }
.bg-dark-test .process-step{
  border:1px solid rgba(255,255,255,.18); border-radius:var(--radius-lg);
  padding:1.75rem 1.5rem;
  background-color:transparent; background-image:none;
  transition:border-color .4s var(--ease), background-color .4s var(--ease),
             padding .5s var(--ease), gap .5s var(--ease), max-width .5s var(--ease),
             box-shadow .5s var(--ease), transform .5s var(--ease);
}
.bg-dark-test .step-badge{
  background:rgba(255,255,255,.15);
  transition:background-color .4s var(--ease), height .6s var(--ease);
}
.bg-dark-test .step-badge-num{ color:var(--white); }
.bg-dark-test .process-step h3{ color:var(--white); }
.bg-dark-test .process-step p{ color:rgba(255,255,255,.75); }
/* New brand icon, fades/grows in to replace the number on hover (see
   the pass-2 comment above). Sized 0 at rest -- fully invisible until
   triggered, exactly like Who We Partner's own mid/white icon pairs
   start from a collapsed state. height (not transform:scale) drives
   the size change so .step-badge's own real height, and therefore the
   grid row, genuinely grows to contain it.

   Squish, round 3 -- the ACTUAL root cause (30 July 2026, Liam's
   second screenshot: still squished after both the flex-shrink:0 fix
   and the switch to position:absolute). Both of those were reasonable
   but were treating the wrong culprit. The real cause is the sitewide
   reset at the very top of this file: `img,video{ max-width:100%; }`
   (line 83) -- it applies to every <img> on the site with no
   exceptions, including this one. For a position:absolute element,
   percentage widths (including max-width) resolve against its
   CONTAINING BLOCK, which here is .step-badge itself (the nearest
   positioned ancestor) -- a box that is deliberately only 52px wide,
   because the whole point of this component is that the icon is meant
   to grow past that small badge. So max-width:100% was silently
   capping the icon at 52px this entire time regardless of which
   layout technique positioned it, then leaving the explicit height:
   130px alone -- which is exactly what a squished, too-narrow-for-its-
   height render looks like. Every OTHER icon component on the site
   that grows past its own small anchor (Who We Partner's mid/white
   icons, .approach-icon img, .about-card-icon img, .roles-card-icon
   img) never hit this because each of THEIR containing blocks happens
   to already be generously larger than the icon could ever render at
   (300px, or an explicit 90%/92% max-width tuned to a bigger box) --
   this component is the first one anchored to something as small as a
   52px badge, so it's the first one that ever exposed this. Fix:
   max-width:none overrides the sitewide reset specifically here, so
   width is free to resolve purely from height x the image's own
   intrinsic ratio with nothing clamping it. */
.step-badge-icon{
  position:absolute; top:50%; left:50%; height:0; width:auto; max-width:none; opacity:0;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1));
  filter:drop-shadow(0 8px 18px rgba(74,67,65,.2));
  transition:opacity .5s var(--ease), height .6s var(--ease);
}
@media (hover:hover){
  .bg-dark-test .process-step:hover{
    border-color:var(--line-soft);
    /* THIRD pass on this fill, same day (Liam: "still more of a solid
       pinkish colour... I want them to look like the advice cards are
       pre-transition -- more visibly white and visible linen"). Root
       cause finally isolated: background-color was var(--blush-100), a
       genuinely opaque cream/pink swatch (#F2E2E0) -- a straight legacy
       carry-over from .value-accordion's own "make the white fill-in a
       little more of a cream colour" recipe on the Mission page, ported
       here wholesale during the earlier As-texture-removal pass without
       questioning whether blush was ever the right base for THIS card.
       That's structurally different from how the advice cards
       (#candidate-services/#hiring-advice .card-pop) get their "visibly
       white" look: those use a translucent WHITE (rgba(255,255,255,.82))
       with the linen texture painted directly on top, no wash layer in
       between. Ported that exact recipe here: solid var(--white) (not
       blush, and not translucent -- this card sits on top of a busy dark
       rose/ink gradient section, so a translucent white would pick up a
       muddy rose tint from the section behind it rather than reading
       clean the way it does for the advice cards' already-light section
       backdrop) plus the linen texture as the ONLY image layer, no wash
       gradient at all -- the intermediate .55/.22 wash tried earlier
       today was always going to dilute the texture to some degree
       because it existed for a blush retint this card never actually
       needed. */
    background-color:var(--white);
    background-image:url('/assets/img/photos/texture-weave-pink.webp');
    background-size:400px 400px;
    background-position:center;
    background-repeat:repeat;
    box-shadow:0 0 90px 10px rgba(242,226,224,.5);
    /* Card now actually grows WIDER than its resting siblings (30 July
       2026, Liam: "I want it to grow wider then the non-hover cards...
       you are keeping them the same width"). max-width:100% fills
       whatever .section-candidates-process's widened .container-narrow
       provides (see main.css's earlier scoped override, and the base
       .process-step rule above for why max-width/margin-inline exist
       at all) -- same "grow to fill a deliberately wider parent, stay
       centred via margin-inline:auto" technique .value-accordion uses
       on the Mission page, just reusing the project's existing
       .container-narrow-widening convention instead of a one-off
       number. Padding and gap increased alongside it so the bigger box
       actually has proportionally more room, not just a wider outer
       edge -- this is also what fixes the icon looking squeezed:
       previously padding grew while the row's own width didn't, which
       shrank the real content area instead of the row simply reading
       "bigger" the way it does now.
       Left padding and gap nudged further still (30 July 2026, Liam:
       "move the icon off the side of the box a little, so there is
       more padding... moving the text over a little as well so
       there's padding between the icon and text") -- left padding
       bumped past the other 3 sides specifically (the icon sits
       nearest that edge), and gap increased to match so the text
       column shifts over in step and doesn't just end up closer to a
       now-further-out icon. */
    max-width:100%;
    padding:3.5rem 3.5rem 3.5rem 4.75rem;
    gap:4.25rem;
    transform:translateY(-5px);
  }
  .bg-dark-test .process-step:hover .step-badge{
    background:transparent; height:130px;
  }
  .bg-dark-test .process-step:hover .step-badge-num{ opacity:0; }
  .bg-dark-test .process-step:hover .step-badge-icon{ height:130px; opacity:1; }
  .bg-dark-test .process-step:hover h3{ color:var(--rose-600); }
  .bg-dark-test .process-step:hover p{ color:var(--ink-soft); }
}
@media (prefers-reduced-motion:reduce){
  .bg-dark-test .process-step, .step-badge-icon{ transition:none !important; }
}

/* Stat bar */
.stat-bar{
  display:grid; grid-template-columns:repeat(4,1fr); gap:1.5rem; text-align:center;
}
@media (max-width:720px){ .stat-bar{ grid-template-columns:repeat(2,1fr); } }
.stat{ padding:1.5rem 1rem; }
.stat-number{ font-family:var(--font-display); font-weight:700; font-size:var(--fs-h2); color:var(--rose-600); }
.stat-label{ color:var(--ink-soft); font-size:var(--fs-small); margin-top:.35rem; }

/* Testimonials */
.testimonial-embed-wrap{ max-width:960px; margin-inline:auto; }
.testimonial-cta{ text-align:center; margin-top:2rem; }

/* Featured review "speech bubbles" (5 August 2026) -- Liam wanted an
   angular bubble, not a rounded chat-bubble, so corners are chamfered
   with clip-path instead of border-radius, plus a small triangular tail
   (::after) pointing down toward the attribution for the speech-bubble
   read. box-shadow doesn't survive clip-path (the shadow bleeds outside
   the box, which clip-path then crops away, so it just vanishes) --
   filter:drop-shadow() is used instead, since it follows the clipped
   alpha shape rather than the original rectangular box.

   Reworked to a single alternating column (14 August 2026, Liam: "change
   the layout of the testimonials, so it's one column, not two; it spans
   the full width, then the first is centred left, next centred right,
   then left, right, etc. Give them a pre and post transition and linen
   treatment"). Was .grid.grid-3 (collapsing to 2-then-1 column at
   narrower widths, per the sitewide .grid-3 breakpoints -- that
   in-between 2-column state is what Liam was seeing). Overridden to a
   flat single column here rather than editing the shared .grid-3 rule
   itself, since that class is reused elsewhere for genuine 3-up grids
   (Approach cards, etc) that aren't part of this request. Each card
   then gets its own max-width narrower than the now-full-width row,
   alternating which side it hugs via nth-child odd/even margins --
   "spans the full width" describes the ROW, not each individual card,
   matching the "centred left / centred right" zig-zag Liam described. */
.testimonial-bubbles-grid{ grid-template-columns:1fr; margin-top:2.75rem; gap:2rem; }
.testimonial-bubble{
  position:relative;
  max-width:860px; /* widened from 680px, 14 August 2026, Liam: "make the testimonials wider again" */
  padding:2.25rem 2.25rem 2.75rem;
  margin:0 0 1rem;
  display:flex; flex-direction:column; gap:1.15rem;
  clip-path:polygon(22px 0, 100% 0, 100% calc(100% - 22px), calc(100% - 22px) 100%, 0 100%, 0 22px);
  filter:drop-shadow(0 10px 22px rgba(134,94,92,.14));
  /* Pre-transition (resting): translucent + linen weave, same "every
     white card gets this" treatment as everywhere else this round --
     .56 to match the shared .card-glass figure, pink weave since this
     is a light surface. Post-transition (hover): dark rose+linen fill,
     see :hover rule below. background-image can't itself crossfade
     smoothly on :hover (browsers don't animate between two url()s), so
     only background-color/text-colour/transform are in the transition
     list -- the texture swap reads as an instant part of the same
     motion rather than a separate flicker, same approach already used
     for other backgroundImage-swapping hover fills on this site. */
  background-color:rgba(255,255,255,.56);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
  transition:transform .4s var(--ease), background-color .35s var(--ease);
}
.testimonial-bubble p{ color:var(--ink); line-height:1.7; transition:color .35s var(--ease); }
.testimonial-bubble footer{
  margin-top:auto; font-family:var(--font-display); font-weight:600;
  color:var(--rose-600); font-size:1.02rem; font-style:normal;
  transition:color .35s var(--ease);
}
.testimonial-bubble footer span{
  display:block; font-family:var(--font-body); font-weight:500;
  font-size:.85rem; color:var(--ink-faint); margin-top:.15rem;
  transition:color .35s var(--ease);
}
/* Alternating side: now handled by .testimonial-row's justify-content
   further down (each card is wrapped in a row alongside its icon), not
   by margin-inline on the bubble itself -- see that block's comment
   for the full mechanism. */
@media (hover:hover){
  .testimonial-bubble:hover{ transform:translateY(-4px); }
}
/* Post-transition fill: same dark rose gradient + linen-white weave
   recipe used on every other hover-fill card this round, not the old
   flat white resting colour. */
.testimonial-bubble:hover{
  background-color:var(--rose-600);
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:400px 400px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
}
.testimonial-bubble:hover p{ color:var(--white); }
.testimonial-bubble:hover footer{ color:var(--white); }
.testimonial-bubble:hover footer span{ color:rgba(255,255,255,.8); }
@media (prefers-reduced-motion:reduce){
  .testimonial-bubble{ transition:none !important; }
  .testimonial-bubble p, .testimonial-bubble footer, .testimonial-bubble footer span{ transition:none !important; }
}
/* Tail removed entirely (14 August 2026, Liam: "there's a dark spot at
   the bottom of each card that transitions, we can remove that") --
   was a small triangular ::after pointer sitting just below the card's
   bottom-left corner; against the now-translucent/linen resting fill
   and the dark hover fill it read as a stray dark fragment rather than
   a clean speech-bubble tail, so dropped rather than re-tuned. */

/* Icon "besides each card" (14 August 2026, Liam: "can we also put the
   attached icon besides each card, so alternating sides in the space
   besides the feedback card"). Each card is now wrapped in a
   .testimonial-row flex container alongside a .testimonial-row-icon --
   the row, not the bubble itself, is the actual grid item now, which is
   what replaces the old margin-inline nth-child alternation below (that
   positioned the bubble directly as a grid child; now the ROW gets
   positioned via justify-content, and icon/bubble order flips inside
   it). Odd rows: bubble hugs the row's left edge (justify-content:
   flex-start), icon sits in the leftover space to its right (order:2).
   Even rows: mirrored -- bubble hugs the right edge, icon sits in the
   leftover space to its left. */
.testimonial-row{ display:flex; align-items:center; gap:1.75rem; width:100%; }
/* Sized up from 72px wide (14 August 2026, Liam: "make the icon larger,
   align with the height of the testimonial cards themselves, which all
   look consistent"). A true height:100% stretch-to-card was tried first
   but rejected -- the six cards' real rendered height (~220-260px at
   this width) times the icon's own ~1.4:1 aspect ratio would put its
   width close to, or past, the leftover space next to an 860px-wide
   card inside the 1280px container, risking the flex-shrink algorithm
   squeezing the card narrower than its siblings on some rows (working
   against the "look consistent" ask, not for it). A fixed 220px height
   -- matching that same real card-height range without depending on
   it -- lands as visually "the same height as the card" in practice
   while staying identical on every row regardless of minor text-length
   differences between testimonials. width:auto + object-fit:contain
   keep the icon's own proportions rather than distorting it. */
.testimonial-row-icon{
  flex-shrink:0; width:auto; height:220px;
  object-fit:contain; opacity:.85;
}
.testimonial-row:nth-child(odd){ justify-content:flex-start; }
.testimonial-row:nth-child(odd) .testimonial-bubble{ order:1; }
.testimonial-row:nth-child(odd) .testimonial-row-icon{ order:2; }
.testimonial-row:nth-child(even){ justify-content:flex-end; }
.testimonial-row:nth-child(even) .testimonial-bubble{ order:2; }
.testimonial-row:nth-child(even) .testimonial-row-icon{ order:1; }
/* Tablet step-down (15 August 2026, mobile/touch pass) -- between full
   desktop width and the 640px stack breakpoint below, the row's
   unwrapped content (bubble up to 860px + ~310px-wide icon + gap) can
   exceed the available container width, forcing the bubble to
   flex-shrink narrower than intended rather than actually laying out
   comfortably. Shrinking the icon here buys the bubble more room before
   the layout switches to a full stack. */
@media (max-width:900px){
  .testimonial-row-icon{ height:130px; }
  .testimonial-row{ gap:1.1rem; }
}
@media (max-width:640px){
  .testimonial-bubble{ margin-bottom:1.5rem; max-width:none; }
  /* Icon stacked above the card rather than dropped outright (15 August
     2026, mobile/touch pass, Liam: "we want the mobile site to be as
     functional and close to the desktop experience as feasibly
     possible") -- side-by-side with a full-width card leaves no real
     "space beside" it, so the row switches to a centred column instead:
     icon on top, card below, same as every other row on the page. Order
     reset here overrides the alternating left/right .order values above
     (those exist purely to swap which SIDE the icon sits on at desktop
     widths -- meaningless once the row is a single centred column, and
     without this reset every other row would render bubble-then-icon
     instead of matching the icon-first DOM order/visual rhythm). */
  .testimonial-row{
    flex-direction:column; align-items:center; justify-content:center;
    gap:.85rem;
  }
  .testimonial-row:nth-child(odd) .testimonial-row-icon,
  .testimonial-row:nth-child(even) .testimonial-row-icon{ order:0; }
  .testimonial-row:nth-child(odd) .testimonial-bubble,
  .testimonial-row:nth-child(even) .testimonial-bubble{ order:1; }
  .testimonial-row-icon{ height:64px; opacity:.8; }
}

/* CTA band -- "CTA Background 2" (Lady Justice statuette + blurred
   bookshelf) embedded directly IN the card, sized to fill it exactly
   (background-size:cover on both layers + background-origin:border-box
   so it's measured against the card's real edges, not its padding
   box -- same fix as the earlier full-height bug). The rose-500/600
   gradient is now a semi-opaque tint OVER the photo rather than a
   solid fill, same layering technique as .site-footer, so the photo
   reads clearly as a moody rose-toned backdrop while the white
   heading/lead/buttons on top stay legible. Correction, after the
   photo was tried on the wider section instead: "I am only talking
   about the Let's find great people, properly card... I want the
   image sized to fit the card." */
.cta-band{
  background-color:var(--rose-600);
  background-image:linear-gradient(135deg, rgba(160,119,119,.88), rgba(134,94,92,.92)), url('/assets/img/photos/cta-background-2.webp');
  background-origin:border-box, border-box;
  background-position:center, center;
  background-size:cover, cover;
  background-repeat:no-repeat, no-repeat;
  color:var(--white); border-radius:var(--radius-lg); padding:3.5rem var(--gutter);
  text-align:center;
}
.cta-band h2{ color:var(--white); }
/* margin-top added 5 August 2026 (Liam: "we need a little more padding
   between the title and the CTA and the text; they look right on top
   of each other... do a sweep and make this consistent across all CTA
   cards everywhere"). Root cause: the sitewide .lead class (main.css,
   base rule) has no margin-top of its own -- other .lead usages
   sitewide get their spacing from a per-instance inline
   style="margin-top:...", which .cta-band's h2+.lead pair never had.
   Scoped here rather than raising the shared base .lead rule (which
   would double up spacing anywhere a page already adds its own inline
   margin-top on a .lead) -- .cta-band is the one shared component every
   closing CTA card on the site already uses, so this single rule fixes
   every instance at once, which is exactly the "everywhere" part of the
   ask. */
.cta-band .lead{ color:rgba(255,255,255,.88); margin-top:1rem; }
.cta-band .hero-actions{ margin-top:2rem; }
/* Equal-width button pair, properly this time (5 August 2026, Liam:
   "make sure the width of the buttons is equal regardless of how many
   words are in the button... both buttons match the width of the
   longest"). The old fix (.cta-band-btn{ min-width:230px }, 26 July)
   only set a shared FLOOR -- if either label's own natural content
   width exceeded 230px, that one button grew past it alone while its
   sibling stayed at 230px, so a long/short pair could still end up
   visibly mismatched (exactly what's happening on pages like What We
   Recruit's "Hire an X" / "Explore Current Vacancies", where the two
   labels are rarely the same length). Real fix: .hero-actions inside a
   .cta-band becomes a 2-column CSS Grid with two equal 1fr tracks. With
   the grid container itself sized to its own content (not stretched to
   the full .container width), equal `fr` tracks resolve to the width
   the LARGER cell actually needs, and both columns come out equal to
   that -- a standard, well-supported CSS trick for "match the widest
   sibling" with no JS and no guessed pixel value. Checked every
   .cta-band-btn pair sitewide first (grep across all templates): every
   single one is exactly a 2-button pair, never 1 or 3, so a fixed
   2-column grid is safe everywhere this selector reaches. Scoped to
   .cta-band specifically (not the shared, much more broadly-used
   .hero-actions class on its own) so the 404 page's 3-button row and
   every hero/page-hero CTA row elsewhere are completely unaffected. */
/* width:max-content + centred margins added (8 August 2026, Liam: "the
   buttons in the CTA cards... are still way to large, this looks
   ridiculous... I just want them the same size so they're even, not
   different sizes, that doesn't mean giant wide buttons"). The grid
   above was never actually shrinking to its own content -- .hero-
   actions is a block-level box with no width set, so as a block box it
   filled the full width of .cta-band (the whole card, ~1100px+), and
   1fr 1fr then split THAT full width in half, stretching each button
   into a giant half-card pill. width:max-content makes the grid box
   shrink to only what its two columns actually need (the two buttons
   at their natural content width, still equal to each other via 1fr on
   an intrinsically-sized grid), and margin-inline:auto re-centres that
   now-narrower box within the card, since .cta-band's own
   text-align:center only centres inline/text content, not a block-
   level grid. */
/* grid-template-columns:1fr 1fr -> grid-auto-flow:column + grid-auto-
   columns:1fr (8 August 2026, Liam: "the buttons in the cta at the
   bottom of the contact page should be in one line") -- the Contact
   page's "Prefer to browse first?" card has THREE buttons (Candidates
   / View Vacancies / Clients), which the hardcoded 2-column grid above
   never accounted for (the 5 August comment even says "every single
   one is exactly a 2-button pair, never 1 or 3" -- that grep evidently
   missed this instance). A fixed 2-column grid puts a 3rd item on its
   own second row instead of one line. auto-flow:column with auto-
   columns:1fr generates exactly as many equal-width columns as there
   are buttons and keeps them all in a single row, for 2, 3 or any other
   count -- same "equal fr tracks on an intrinsically-sized grid" match-
   the-widest-sibling behaviour as before, just no longer hardcoded to
   two. */
.cta-band .hero-actions{ display:grid; grid-auto-flow:column; grid-auto-columns:1fr; justify-items:stretch; width:max-content; margin-inline:auto; }
@media (max-width:640px){
  /* Sitewide .hero-actions already collapses to a single column under
     640px via flex-wrap; mirror that here so full-width stacked
     buttons don't get forced into an artificially narrow single-row
     grid on a phone-width screen. max-content dropped back to auto and
     auto-flow reset to row (the default) so each stacked button reverts
     to its own natural width down its own row, rather than all being
     squeezed into one row or forced to match the widest one. */
  .cta-band .hero-actions{ grid-template-columns:1fr; grid-auto-flow:row; width:auto; }
}
/* .cta-band-btn{ min-width:230px } REMOVED (8 August 2026, Liam: "with
   regard to my earlier change on the button size for the CTA cards, I
   do mean to make it consistent in each individual card, not across
   every card. So the buttons on one card are the same width, not every
   button across every card matches the longest button"). This 230px
   floor (26 July 2026) was the ORIGINAL fix for matching a card's own
   button pair, but it's a single shared value applied to every
   .cta-band-btn sitewide, so any button under 230px on ANY card got
   floored to the same 230px -- creating exactly the cross-card
   matching Liam doesn't want. It was already superseded on 5 August
   2026 by .cta-band .hero-actions{ display:grid; grid-template-
   columns:1fr 1fr } just above, which makes each card's own two
   buttons match EACH OTHER by letting the grid's equal 1fr tracks
   resolve to that card's own wider label -- a per-card mechanism with
   no shared value, so nothing else needs to replace this rule now that
   it's gone. */

/* "Premium" CTA-card button treatment (8 August 2026) -- Liam asked
   what would make the .cta-band-btn pairs feel more premium; agreed
   plan was: (1) tighter radius than the sitewide pill, (2) both
   buttons in a card keep the IDENTICAL treatment -- no primary/
   secondary fill split, (3) label styling borrowed from .eyebrow's
   uppercase+tracked look, (4) a calmer hover (brightness lift + arrow
   nudge, not a hard fill flip), (5) a more transparent/ghost fill than
   the sitewide .btn-ghost-white default, short of fully transparent.
   Every rule below is scoped to .cta-band-btn specifically (compounded
   with .btn-ghost-white for guaranteed specificity over the shared
   rules at line ~561, rather than relying on source order -- see the
   file's specificity-discipline convention) so ONLY the bottom-of-page
   CTA cards change; the handful of other .btn-ghost-white usages
   sitewide that AREN'T .cta-band-btn (home hero, About Story/Values
   "For clients/For candidates", Testimonials "Leave a review" etc.)
   are completely untouched.

   Ends rounded back to a full pill (14 August 2026, Liam: "make them a
   little bit shorter, and more rounded ends") -- the tight 8px radius
   set in this same round on 8 August is reversed back to the sitewide
   .btn default (999px, see that rule at line ~551). Every other part of
   the 8 August "premium" treatment (ghost fill, uppercase+tracked
   label, calmer hover, arrow nudge) is untouched -- purely a shape
   change back toward the softer, fully-rounded look. */
.cta-band-btn.btn-ghost-white{
  border-radius:999px;
  text-transform:uppercase;
  /* .08em, not .eyebrow's own .14em -- button labels ("Explore Current
     Vacancies", "Submit a vacancy") run much longer than a short
     eyebrow tag, and .14em on a full phrase gets uncomfortably wide.
     .08em is enough to read as the same tracked-uppercase family
     without the label ballooning. */
  letter-spacing:.08em;
  /* Fill dropped from the shared .14 to .07 (more ghost-like, per
     Liam: "more transparent, more ghost like fill as well, but not
     full ghost" -- .07 still leaves a faint tint against the busy
     monogram/photo backdrop, doesn't disappear into it). Border eased
     back slightly (.6 -> .45) to match the lighter, more transparent
     overall feel. */
  background:rgba(255,255,255,.07);
  border-color:rgba(255,255,255,.45);
  /* Shell shortened slightly (8 August 2026, Liam: "make the height of
     the CTA buttons shorter... sleeker will look more premium, without
     changing the text size; just make the shell a little, just a
     little, shorter"). .btn-lg's vertical padding only (1.1rem ->
     .9rem) -- horizontal padding and font-size both left untouched, so
     the label size and the pill's width-per-character are unaffected,
     only its height. padding-block so this can't accidentally touch
     the inherited horizontal padding.
     Shortened again, .9rem -> .75rem (14 August 2026, same request as
     the rounded-ends change above), same padding-block-only technique. */
  padding-block:.75rem;
}
/* Arrow nudge on hover, CSS-only (no per-template SVG edit needed --
   .cta-band-btn appears in 25+ templates, so a ::after keeps this a
   single-file change). Easy to remove if it doesn't read well once
   Liam sees it live against real button labels. */
.cta-band-btn::after{
  content:"\2192";
  display:inline-block;
  margin-left:.6rem;
  font-weight:600;
  transition:transform var(--dur) var(--ease);
}
.cta-band-btn:hover::after{ transform:translateX(4px); }
/* Hover restrained to a brightness lift (.07->.15, roughly the same
   proportional step the OLD .14->.28 default had, just off a much
   lower base) instead of the sitewide default's near-doubling fill
   flip, plus a smaller lift (-1px vs the default -2px) -- calmer, more
   considered motion instead of a hard on/off switch. */
.cta-band-btn.btn-ghost-white:hover{
  background:rgba(255,255,255,.15);
  border-color:rgba(255,255,255,.7);
  transform:translateY(-1px);
}

/* Who We Partner With closing CTA (26 July 2026): separate client and
   candidate actions side by side within the same dark .cta-band,
   rather than one merged headline/button pair -- per the content
   pack's own instruction to "finish with clearly separated client and
   candidate actions." h3/p need explicit white overrides here since
   .cta-band only pre-colours its own direct h2/.lead children (see
   above), and the base h3/p defaults are dark ink. */
.partner-closing-grid{ margin-top:2rem; text-align:left; }
.partner-cta-col h3{ color:var(--white); margin-top:0; }
.partner-cta-col p{ color:rgba(255,255,255,.88); margin-top:.6rem; }
.partner-cta-col .btn{ margin-top:1.5rem; }

/* Insights article prose (distinct from .policy-content -- normal ink
   headings, not rose, since these are editorial articles not legal docs) */
/* Insights article .card-glass wrapper narrowed (8 August 2026, Liam:
   "the white card looks much, much wider than the text... it just
   needs to be broader than the text but not that large... maybe half
   that buffer"). The card had no max-width of its own, so it filled
   the full .container (1280px) while .article-body inside it stayed at
   --container-narrow (820px) -- a ~230px buffer per side. Halved to
   ~115px per side: 820 + (230/2 * 2) = 1050px. Scoped to this one
   wrapper (not a change to .card-glass itself, which stays full-width
   everywhere else -- About Assiduous, Who We Partner, etc. were not
   part of this ask). */
.insights-article-glass{ max-width:1050px; margin-inline:auto; }
.article-body{ max-width:var(--container-narrow); margin-inline:auto; }
.article-meta{ text-align:center; color:var(--ink-faint); font-size:var(--fs-small); margin-bottom:.5rem; }
.article-body h2{ font-size:var(--fs-h3); margin-top:2.75rem; margin-bottom:.85rem; }
.article-body h2:first-child{ margin-top:0; }
.article-body h3{ font-size:var(--fs-h4); font-family:var(--font-body); font-weight:700; margin-top:1.75rem; margin-bottom:.6rem; }
.article-body p{ color:var(--ink-soft); margin-top:1rem; }
.article-body p:first-child{ margin-top:0; }
.article-body ul, .article-body ol{ padding-left:1.25rem; margin-top:1rem; display:flex; flex-direction:column; gap:.5rem; }
.article-body ul{ list-style:disc; }
.article-body ol{ list-style:decimal; }
.article-body li{ color:var(--ink-soft); }
.article-body a{ color:var(--rose-600); text-decoration:underline; text-underline-offset:.15em; }
.article-body strong{ color:var(--ink); }
.article-cta{
  margin-top:3rem; padding:2rem; border-radius:var(--radius-lg);
  background:var(--blush-50); border:1px solid var(--line-soft);
}

/* FAQ accordions (Candidate FAQ / Client FAQ pages) -- grouped Q&A, same
   structural idea as the Recruitment Labs 2.0 FAQ pages (nested accordion:
   a top-level group per category, individual questions inside), but built
   with plain native <details>/<summary> rather than RL's custom JS/icon-
   flip accordion -- no JS needed, works even if the script fails to load,
   consistent with how every other disclosure widget on this site already
   works (mobile nav groups use the same technique). The "+" icon rotates
   45deg into an "x" on [open] via a CSS transform, no separate open/close
   icon assets required. */
.faq-groups{ display:flex; flex-direction:column; gap:1rem; }
.faq-group{
  border:1px solid var(--line-soft); border-radius:var(--radius-lg);
  overflow:hidden; background:var(--white);
}
/* Category titles now match the questions' font (--font-body, i.e. Manrope)
   instead of the display serif, and sit much larger -- Recruitment Labs'
   own FAQ accordion was the reference point here: their category header
   (.faq-group-trigger h3) uses font-family:var(--font-body), font-weight:
   700, font-size:clamp(1.4rem,1.2rem + 1vw,1.9rem), clearly bigger than
   their question trigger at a flat 1.25rem. Same idea here: same font
   family top-to-bottom, category size scales well above question size.
   Icons/hover effects to come later (Liam's call, not built yet). */
/* Category header given a dark textured background so it reads as a clear
   divider within the accordion, with the individual question rows staying
   explicit white beneath it for contrast (31 July 2026, Liam: "have a dark
   background with the As texture in the background, then the drop-down
   questions are white"). Same proven recipe as .roles-card-surface's resting
   fill (background-color:var(--rose-600), texture-monogram-white.webp over a
   rose-500->rose-600 135deg gradient, no ink stop) rather than the full-
   section .bg-dark-test recipe -- this is a short, wide bar, not a tall
   section, and .bg-dark-test's gradient is tuned for a much longer diagonal;
   reusing its full rose->ink range here would barely progress past rose-500
   given the bar's short height, and staying inside the rose family avoids the
   "muddy brown corner" issue already root-caused elsewhere in this file for
   short boxes. .faq-group's own overflow:hidden + border-radius already clips
   this to the card's rounded top corners, no separate radius needed here.
   Text/icon/count switch to white to stay readable on the dark fill, matching
   the same treatment already used on this site's other dark surfaces
   (.bg-dark-test .eyebrow, .teaser-card-expand, etc). */
.faq-group > summary{
  list-style:none; cursor:pointer; padding:1.25rem 1.5rem;
  display:flex; align-items:center; gap:1rem;
  font-family:var(--font-body); font-size:clamp(1.4rem, 1.15rem + 1.2vw, 1.85rem);
  font-weight:800; color:var(--white);
  background-color:var(--rose-600);
  /* "As" monogram swapped for the linen weave (14 August 2026, Liam:
     "on the candidate and client FAQs, let's drop the As texture and
     just go with linen texture there"). Same texture-weave-white.webp
     used on every other dark rose-family fill on the site. */
  background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500) 0%, var(--rose-500) 82%, var(--rose-600) 100%);
  background-position:center top, center;
  background-size:400px 400px, cover;
  background-repeat:repeat, no-repeat;
}
.faq-group > summary::-webkit-details-marker{ display:none; }
.faq-group-count{
  font-family:var(--font-body); font-weight:600; font-size:.85rem;
  color:rgba(255,255,255,.75); margin-right:auto;
}
.faq-group > summary .icon{ transition:transform .25s var(--ease); flex-shrink:0; color:var(--white); }
.faq-group[open] > summary .icon{ transform:rotate(45deg); }
.faq-group-body{ padding:0 1.5rem 1.25rem; display:flex; flex-direction:column; }
/* Question list explicit white (not just inherited from .faq-group's own
   background) so the contrast against the new dark category header above is
   intentional, not incidental. Scoped to .faq-group's own direct child only
   (Candidate FAQ / Client FAQ pages, the only templates with the categorised
   .faq-group wrapper) -- NOT the bare .faq-group-body reused as a plain
   layout wrapper on the individual What We Recruit / Who We Partner role
   pages (no category header there, just .faq-item questions straight on the
   section's own bg-mist/bg-white background, per Liam's explicit "not the
   specific role FAQs" scope confirmation, 31 July 2026). */
/* .52 see-through + linen weave (14 August 2026, Liam: "in the FAQs can
   we give the white drop downs the .52 see-through as well" -- same
   figure as the Approach cards, and consistent with the sitewide "give
   every white card the linen treatment" sweep this same request also
   covers). Pink weave -- this is a light surface, same convention as
   every other light-card texture pairing. Needs backdrop-filter here
   (unlike the plain opaque var(--white) it's replacing) so the .bg-white
   section's own texture layers underneath don't read as muddy through
   the now-translucent panel. */
.faq-group > .faq-group-body{
  background-color:rgba(255,255,255,.52);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
}
.faq-item{ border-top:1px solid var(--line-soft); }
.faq-item summary{
  list-style:none; cursor:pointer; padding:1rem 0;
  display:flex; align-items:center; justify-content:space-between; gap:1rem;
  font-family:var(--font-body); font-weight:600; color:var(--rose-600);
}
.faq-item summary::-webkit-details-marker{ display:none; }
.faq-item summary .icon{ transition:transform .25s var(--ease); flex-shrink:0; color:var(--rose-500); width:14px; height:14px; }
.faq-item[open] summary .icon{ transform:rotate(45deg); }
.faq-item-answer{ padding-bottom:1.1rem; color:var(--ink-soft); max-width:65ch; }
/* Inline links inside an FAQ answer (5 August 2026, first use: linking
   "Claire Booth" to /about/why-assiduous/ from the Candidate FAQ's
   "What actually makes Assiduous different?" answer) -- same treatment
   as .article-body a / .policy-content a, so a link buried in prose
   text actually reads as clickable instead of inheriting the sitewide
   `a{ color:inherit; text-decoration:none; }` reset and looking
   identical to the surrounding plain text. env.autoescape is off
   sitewide (see build.py), so the raw <a> tag in candidate_faq_data.py
   renders as a real link; the JSON-LD FAQPage schema block still gets
   the same string via |tojson, which is fine -- Google's FAQPage rich
   result docs explicitly allow a small set of HTML tags, including
   <a>, inside Answer.text. */
.faq-item-answer a{ color:var(--rose-600); text-decoration:underline; text-underline-offset:.15em; }

/* Policy / legal prose pages (Privacy, Terms, AI Use, Sustainability & Inclusion)
   Titles and subheadings in the brand's dark rose/pink, body copy in a dark
   warm grey (never pure black) -- per Liam's request. */
.policy-meta{ color:var(--ink-faint); font-size:var(--fs-small); margin-bottom:2.5rem; }
.policy-content h2{ color:var(--rose-600); font-size:var(--fs-h3); margin-top:2.75rem; margin-bottom:.85rem; }
.policy-content h2:first-child{ margin-top:0; }
.policy-content h3{ color:var(--rose-600); font-size:var(--fs-h4); font-family:var(--font-body); font-weight:700; margin-top:1.75rem; margin-bottom:.6rem; }
.policy-content p{ color:var(--ink-soft); margin-top:1rem; }
.policy-content p:first-child{ margin-top:0; }
.policy-content ul{ list-style:disc; padding-left:1.25rem; margin-top:1rem; display:flex; flex-direction:column; gap:.5rem; }
.policy-content ul li{ color:var(--ink-soft); }
.policy-content a{ color:var(--rose-600); text-decoration:underline; text-underline-offset:.15em; }
.policy-content strong{ color:var(--ink-soft); }

/* Contact form (Formspree-backed, plain HTML POST -- works with no JS) */
.contact-form{ display:flex; flex-direction:column; gap:1.25rem; }
.form-grid{ display:grid; grid-template-columns:1fr 1fr; gap:1.25rem; }
@media (max-width:520px){ .form-grid{ grid-template-columns:1fr; } }
.form-row{ display:flex; flex-direction:column; gap:.4rem; }
.form-row label{ font-weight:700; font-size:.9rem; color:var(--rose-600); }
.form-row input, .form-row textarea, .form-row select{
  font:inherit; font-size:1rem; color:var(--ink);
  border:1.5px solid var(--line); border-radius:var(--radius-sm);
  padding:.75rem .9rem; background:var(--white);
  transition:border-color var(--dur) var(--ease);
  resize:vertical;
}
.form-row input:focus, .form-row textarea:focus, .form-row select:focus{ outline:none; border-color:var(--rose-400); }
.form-row input[type="file"]{ border-style:dashed; padding:.65rem .9rem; font-size:.9rem; }
/* "I am..." select -- native <select> given the same custom-chevron
   treatment as the rest of this form's inputs, rather than the raw
   browser dropdown arrow: appearance:none strips the native arrow,
   replaced with an inline-SVG chevron as a background-image so it still
   works with zero JS and matches the form's rose colour palette. */
.form-row select{
  appearance:none; -webkit-appearance:none;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 9' fill='none'%3E%3Cpath d='M1 1.5L7 7.5L13 1.5' stroke='%23865E5C' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right .9rem center; background-size:12px 8px;
  padding-right:2.4rem; cursor:pointer;
}
/* Branded "Choose File" button -- ::file-selector-button styles the native
   file-picker button directly (well-supported everywhere now: Chrome/Edge
   89+, Firefox 82+, Safari 15.4+) rather than hiding the real input behind
   a fake styled label, so it stays a genuine, fully-accessible file input
   the whole way through -- just dressed in brand colours instead of each
   browser's grey system-button default. */
.form-row input[type="file"]::file-selector-button{
  font:inherit; font-weight:700; font-size:.85rem; color:var(--white);
  background:var(--rose-500); border:none; border-radius:var(--radius-sm);
  padding:.55rem 1rem; margin-right:.85rem; cursor:pointer;
  transition:background var(--dur) var(--ease);
}
.form-row input[type="file"]::file-selector-button:hover{ background:var(--rose-600); }
.form-hint{ font-size:.82rem; color:var(--ink-faint); }
.form-honeypot{ position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden; }
.form-note{ font-size:.85rem; color:var(--ink-faint); }
.form-note a{ color:var(--rose-600); }
.contact-form .btn{ align-self:flex-start; }
/* Liam: "can we centre the Send Message button?" -- .contact-form is a
   flex column, so align-self on just this one button centres it within
   the form without affecting every other .btn on the site. */
.contact-form .btn.btn-centered{ align-self:center; }

/* Contact page tile cards (address / phone / email / hours) + map embed.
   Dark (--rose-600) card with white text, per Liam's direction -- icon
   circle flips to a translucent white tint so it still reads as a
   distinct chip against the solid dark card rather than disappearing
   into it.
   28 July 2026: Liam, having seen the Our Approach monogram finally
   working, wanted the same faint "AS" watermark baked into these cards
   too ("it will help those cards just pop a little"). Same recipe as
   .bg-dark-test/the approach-card fill: the white monogram asset at its
   native alpha (no boosting -- boosted assets read as bold/overlapping,
   confirmed the hard way on the Our Approach card), layered straight
   onto the card's own background since (unlike the animated Our
   Approach reveal) this is a static resting card with no crossfade or
   transition to manage.
   Round 2, same day -- Liam: "tighten the background... it looks quite
   sparse in there. Make them a gradient as well." Two changes: (1) the
   initial 700px tile (matched to the Our Approach card, a much bigger
   card) was oversized for this card's actual footprint -- a tile card
   is roughly 380x95px, so 700px never got past a single mostly-empty
   corner of the pattern. Dropped to 260px so several repeats actually
   land inside the card. (2) added a rose-500->rose-600 gradient behind
   the texture. Texture is listed FIRST in background-image and the
   gradient SECOND -- this is the exact opposite order to what looks
   intuitive, but it's the hard-won fix from the Our Approach saga: the
   first-listed background-image layer paints on TOP, so if the opaque
   gradient were listed first it would completely hide the texture
   again regardless of alpha, exactly as happened there. */
/* "As" monogram swapped for the linen weave (14 August 2026, Liam:
   "contact page, let's drop the As texture in office, phone, email,
   hours and go dark + linen"). Already a dark rose-500->rose-600 fill
   (added specifically for this card back on 28 July), so "go dark"
   here just confirms keeping that fill -- the actual change is the
   texture swap. Kept the 260px tile size tuned specifically for this
   card's small ~380x95px footprint (see the sizing history above) --
   the general 400px tile used elsewhere is sized for bigger cards and
   would under-repeat here the same way the original 700px As tile did. */
.tile-card{
  display:flex; gap:1rem; align-items:flex-start;
  background-color:var(--rose-500);
  background-image:
    url('/assets/img/photos/texture-weave-white.webp'),
    linear-gradient(135deg, var(--rose-500), var(--rose-600));
  background-size:260px 260px, cover;
  background-position:center top, center;
  background-repeat:repeat, no-repeat;
  border:1px solid var(--rose-500); border-radius:var(--radius-md);
  padding:1.25rem 1.5rem; box-shadow:var(--shadow-sm);
}
.tile-icon{
  width:44px; height:44px; border-radius:50%; background:rgba(255,255,255,.16); color:var(--white);
  display:flex; align-items:center; justify-content:center; flex-shrink:0;
}
/* Selector retargeted h4 -> h3 (15 August 2026, SEO heading-hierarchy
   pass) -- Contact page's h1 (via page_hero) had nothing at h2/h3
   before these tiles, an h1->h4 skip; markup is now <h3 class="h4">
   (see contact.html) so it reads h1->h3, visual size unchanged. */
.tile-card h2{ margin-bottom:.25rem; color:var(--white); }
.tile-card p{ color:rgba(255,255,255,.92); margin:0; }
.tile-card a{ color:var(--white); font-weight:600; }

/* Plain icon + line contact details (27 July 2026, Why Assiduous founder
   bio, Liam: "change the contact details from buttons to lines with
   icons besides them"). Same icon-swatch idea as .card-icon (a small
   blush-100 circle, rose-600 icon) rather than .tile-card/.tile-icon's
   white-on-rose treatment above -- that one assumes a solid rose-500
   card background, but this sits directly on a light card-glass panel,
   so it needed the light-swatch version instead. */
.contact-line-list{ display:flex; flex-direction:column; gap:.85rem; }
/* 27 July 2026: "make this text mid instead of grey and grow on hover" --
   resting colour moved from plain --ink to the brand Mid (--rose-500),
   deepening to Dark (--rose-600) plus a slight scale-up on hover. The
   phone row is a plain <span>, not a link (see template), but keeps the
   exact same class/hover treatment for visual consistency with the two
   real links either side of it. */
.contact-line{
  display:inline-flex; align-items:center; gap:.85rem; width:fit-content;
  font-weight:600; color:var(--rose-500);
  transition:color var(--dur) var(--ease), transform var(--dur) var(--ease);
  transform-origin:left center;
}
.contact-line:hover{ color:var(--rose-600); transform:scale(1.05); }
.contact-line-icon{
  flex-shrink:0; width:40px; height:40px; border-radius:50%;
  background:var(--blush-100); color:var(--rose-600);
  display:flex; align-items:center; justify-content:center;
  transition:background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.contact-line-icon svg{ width:18px; height:18px; }
.contact-line:hover .contact-line-icon{ background:var(--rose-500); color:var(--white); }

/* Real Sourcr "Best Recruiter" award badges, Meet the Founder column.
   Originally 3 dashed placeholder circles (27 July 2026, Liam: "add 3
   circle placeholders that say seek" -- a reminder note, not a final
   design) sized 72px. Swapped for the real badge artwork (28 July 2026,
   Liam supplied it) -- row wrapper unchanged (still centred, same gap),
   only the child rule changes since these are now real square-ish badge
   images, not text-filled circles. Sized 20% larger than the old
   placeholder (72px -> 86px, Liam's explicit ask), then a further 10%
   (86px -> 95px, same session, next round). Same row position throughout.

   Spacing fix, same round (Liam: "make sure they are perfectly spaced
   from each other"): the three source PNGs had noticeably different
   trimmed circle sizes (the 2025 badge's ring is drawn ~10% larger
   within its own canvas than 2022/2023, measured directly) and
   inconsistent padding around each circle, which meant even though
   .award-placeholder-row's flex `gap` is a mathematically exact 1rem
   between each image's bounding box, the VISIBLE gap between the actual
   printed circles wasn't equal, because the circles themselves sat at
   different distances from their own image edges. Reprocessed all three
   from the source PNGs with a shared pipeline (fuzz-trim to content,
   resize to an identical 480px diameter, then centre onto an identical
   480x480 transparent canvas) so every badge image now has the same
   circle size and the same padding on every side -- flex gap now
   translates directly into equal circle-to-circle spacing rather than
   equal bounding-box spacing that looked different in practice. */
.award-placeholder-row{ display:flex; justify-content:center; align-items:center; gap:1rem; margin-top:1.25rem; }
.award-badge{ width:95px; height:auto; flex-shrink:0; }

/* Embeds (Vacancies / Submit Resume / Sourcr) */
.embed-frame-wrap{
  display:flex; justify-content:center; align-items:center;
  background:var(--white); border-radius:var(--radius-lg); border:1px solid var(--line-soft);
  padding:1rem; box-shadow:var(--shadow-sm); overflow:hidden;
}
.embed-frame-wrap iframe{ border:none; width:100%; }
/* Submit Your Resume page: the RecruitCRM form itself renders much
   narrower than the shared .container (1280px), so the plain
   .embed-frame-wrap left a lot of dead white margin either side of the
   actual fields. Scoped modifier (not touching the shared class, since
   Vacancies' and Contact's embeds use the full-width version) narrows
   the card to hug the form content instead. */
.embed-frame-wrap--narrow{ max-width:900px; margin-inline:auto; }

/* ==========================================================================
   Footer
   v6: background-size:cover, matching Recruitment Labs 2.0's own
   .site-footer technique exactly (checked their main.css: their footer
   photo is `url(...) center bottom / cover no-repeat`, and every one of
   their other photo-backed sections -- .section-photo-bg, .cta-banner,
   etc -- follows the same "gradient + url(...) <position> / cover"
   pattern site-wide, never `100% auto`).

   v5 used background-size:100% auto specifically to solve an earlier
   side-letterboxing bug (see CHANGE_LOG-equivalent history in
   logs/2026-07-25_session-log.md): a `contain` attempt before that had
   left pink bars down the sides. But 100% auto has its own problem --
   the footer's height isn't fixed, it's driven by however much column
   content there is, which varies by breakpoint (columns reflow/stack)
   and even by page. Since 100% auto locks SCALE to width and lets height
   follow the image, however much of the image is visible/cropped ends up
   being an accident of whatever the footer's content height happens to
   be at that moment -- which is exactly the "displays differently on
   different screens" inconsistency Liam flagged.
   `cover` doesn't have that problem: it always scales to fully fill
   whatever box it's given, in both dimensions, by definition -- same
   guarantee `object-fit:cover` gives the page-hero's <img> above. No
   gaps (unlike `contain`), no content-height-dependent crop window
   (unlike `100% auto`). Position is tuned (20% horizontal, top vertical)
   to keep the Opera House -- which sits left-of-centre in the source
   photo -- in frame even on narrow/tall mobile footers where cover
   crops in tight. */
.site-footer{
  position:relative;
  background-color:var(--rose-600);
  /* footer-banner-7.webp: Liam's own hand-picked crop (source "Footer
     Banner 7.png" in the working folder) -- Stockport Viaduct at night,
     a deliberate personal touch (Claire is originally from Stockport).
     Symmetric composition, so centred horizontally rather than the old
     20% offset that was tuned for the Opera House sitting left-of-centre.
     Overlay pushed a second time (Liam: "make the pink overlay more
     prominent, use our dark colour... just be subtly there") -- switched
     to a single colour, the official brand Dark (--rose-600, #865E5C /
     rgb(134,94,92)) at every stop rather than three different invented
     rose shades, with opacity raised to .90/.94/.97 so the Dark tone is
     what actually reads first and the photo comes through as a faint
     lit-arch texture behind it, not a featured image. Checked with a
     Python composite preview against the real crop before landing here --
     lower opacities (~.8) still let the lit brickwork's warm highlights
     compete with the overlay too much. */
  background-image:
    linear-gradient(160deg, rgba(134,94,92,.90) 0%, rgba(134,94,92,.94) 50%, rgba(134,94,92,.97) 100%),
    url('/assets/img/photos/footer-banner-7.webp');
  background-size:cover, cover;
  background-position:center, center 30%;
  background-repeat:no-repeat, no-repeat;
  color:var(--white);
  padding-block:3rem 1.5rem;
}
/* Dropped from 4 columns to 3 (8 August 2026, Liam: remove the
   Candidates/Clients/Vacancies/Insights link column entirely -- see
   footer.html's own comment for the full quote). First attempt at the
   3-column layout used CSS Grid with minmax(200px,260px) tracks, but
   the enlarged logo (see .footer-logo img below) renders at roughly
   362px wide at its fixed 87px height -- wider than the 260px track
   cap -- so the grid was forcing/clipping that column against its own
   content, throwing the gaps off (Liam: "the distance between [the
   three columns] is not equal"). Switched to flexbox instead: each
   column sizes to its own natural content width (no track cap to
   fight with), and gap between flex items is a single fixed value
   applied identically between every pair of items regardless of each
   item's own width, which is what actually guarantees equal visual
   spacing here. justify-content:center centers the row as a block
   (Liam: "all three centered in the middle of the page"). */
/* gap 3.5rem -> 7rem (8 August 2026, Liam: "can we double the gap
   between the three columns in the footer") -- doubled the desktop row
   gap only; the 640px media query below is the STACKED single-column
   layout, where gap becomes vertical spacing between centred blocks
   rather than distance "between columns", so it's left as-is unless
   Liam flags it separately once he's seen this live. */
.footer-grid{ display:flex; flex-wrap:wrap; justify-content:center; align-items:flex-start; gap:7rem; }
@media (max-width:640px){ .footer-grid{ flex-direction:column; align-items:center; text-align:center; gap:2rem; } }
/* Logo enlarged 50% (8 August 2026, Liam: "make that assiduous staffing
   logo fifty percent larger" -- 58px base * 1.5 = 87px), then another
   10% the same day (Liam: "can we make the assiduous logo in the
   footer 10% larger" -- 87px * 1.1 = 95.7px, rounded to 96px). Width is
   left to auto/aspect-ratio (no width set here), matching how this rule
   already worked before both size changes. */
.footer-logo img{ height:96px; margin-bottom:.6rem; }
/* "Sydney legal support recruitment." kept to one line (8 August 2026,
   Liam: "I think we can also fit [it] on one line"; copy itself
   shortened same day from "Sydney-based legal support recruitment." to
   "Sydney legal support recruitment.", see footer.html) -- now that
   the column is no longer squeezed into a 260px grid track (see
   .footer-grid comment above), the natural flex width is already wide
   enough to fit this on one line at desktop widths; nowrap just makes
   that explicit/guaranteed rather than relying on the column happening
   to be wide enough. Reset back to normal wrapping under 640px, where
   .footer-grid stacks to a single centered column and a fixed nowrap
   width could otherwise force horizontal overflow on narrow phones. */
.footer-logo p{ white-space:nowrap; }
@media (max-width:640px){ .footer-logo p{ white-space:normal; } }
.site-footer p{ color:var(--white); }
.footer-col h3{ margin-bottom:.6rem; font-family:var(--font-body); font-size:.92rem; font-weight:700; color:var(--white); }
.footer-col h3 a{ color:var(--white); }
.footer-col ul{ display:flex; flex-direction:column; gap:.4rem; }
.footer-col a{ color:var(--white); transition:opacity var(--dur) var(--ease); }
.footer-col a:hover{ opacity:.75; }
.footer-col li, .footer-col p{ font-size:.9rem; }
.footer-contact li{ display:flex; align-items:flex-start; gap:.55rem; }
.footer-contact .icon{ width:16px; height:16px; margin-top:.15rem; opacity:.85; flex-shrink:0; }
.footer-social{ display:flex; gap:.6rem; margin-top:.75rem; }
.footer-social a{
  width:32px; height:32px; border-radius:50%; border:1px solid rgba(255,255,255,.55); color:var(--white);
  display:flex; align-items:center; justify-content:center;
}
.footer-social a .icon{ width:16px; height:16px; }
.footer-social a:hover{ background:rgba(255,255,255,.16); }
/* footer-bottom now stacks two rows: the Acknowledgement of Country on
   its own full-width line, then the existing copyright/legal-links row
   underneath (.footer-bottom-row) -- Liam's call: "run it under the
   line that's there, just above the copyright and policies". */
.footer-bottom{
  margin-top:1.25rem; padding-top:.9rem; border-top:1px solid rgba(255,255,255,.3);
  display:flex; flex-direction:column; gap:.75rem;
  font-size:.82rem; color:var(--white);
}
.footer-bottom-row{ display:flex; justify-content:space-between; flex-wrap:wrap; gap:.5rem; }
.footer-legal{ display:flex; gap:1.25rem; flex-wrap:wrap; }
.footer-legal a{ transition:opacity var(--dur) var(--ease); }
.footer-legal a:hover{ opacity:.75; }
/* No max-width here on purpose -- the old 640px cap forced this onto two
   lines on most screens. Left unconstrained (just wraps naturally if a
   viewport is ever too narrow to fit it on one line) per Liam: "change
   this to one line". */
.acknowledgement{ font-size:.82rem; color:var(--white); text-align:center; }

/* ==========================================================================
   Reveal-on-scroll
   ========================================================================== */
.reveal{ opacity:0; transform:translateY(24px); transition:opacity .8s var(--ease), transform .8s var(--ease); }
.reveal.is-visible{ opacity:1; transform:translateY(0); }
@media (prefers-reduced-motion: reduce){
  .reveal{ opacity:1; transform:none; transition:none; }
}

/* ---- Misc utility ---- */
.sr-only{ position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; }
.flex{ display:flex; }
.flex-center{ display:flex; align-items:center; justify-content:center; }
.gap-sm{ gap:.75rem; }
.mt-lg{ margin-top:2.5rem; }

/* ==========================================================================
   What We Recruit -- 9 specialty pages + hub (26 July 2026)

   Liam: "Let's now create pages for each of the what we recruit roles...
   On the What We recruit cards/boxes, after they pop out we can add a
   link that says 'Learn More' with an arrow." Content adapted from the
   client-supplied blueprint doc; see what_we_recruit_data.py.
   ========================================================================== */

/* Roles-grid card: the new "Learn More" link on the hover/focus face.
   pointer-events:none by default on the whole final layer (it sits at a
   higher z-index than the resting card, see roles-card-text-final rule
   above) so the invisible link can never be silently clicked before the
   card is actually hovered/focused -- same class of bug already solved
   once for .dropdown-menu, see the comment above that rule. */
.roles-card-text-final{ pointer-events:none; }
.roles-card:hover .roles-card-text-final, .roles-card:focus-within .roles-card-text-final{ pointer-events:auto; }
.roles-card-learn-more{
  display:inline-flex; align-items:center; gap:.4rem; margin-top:.9rem;
  font-weight:700; font-size:.85rem; color:var(--white); white-space:nowrap;
  transform-origin:left center;
}
.roles-card-learn-more svg{ transition:transform var(--dur) var(--ease); }
.roles-card-learn-more:hover svg{ transform:translateX(3px); }
/* "Throb" hint once the card is open (27 July 2026, Liam: "have an effect
   where they throb, like they're begging you to click") -- a gentle scale
   pulse, starting after the .46s reveal delay so it only ever throbs once
   the link is actually visible, not during the reveal transition itself. */
@media (hover:hover){
  .roles-card:hover .roles-card-learn-more{ animation:rolesLearnMoreThrob 1.1s ease-in-out .46s infinite; }
}
.roles-card:focus-within .roles-card-learn-more{ animation:rolesLearnMoreThrob 1.1s ease-in-out .46s infinite; }
@keyframes rolesLearnMoreThrob{
  0%,100%{ transform:scale(1); }
  50%{ transform:scale(1.08); }
}

/* Role-page hero copy block, directly under the shared page-hero */
.wwr-hero-copy h2{ max-width:32ch; }
.wwr-hero-actions{ margin-top:1.75rem; justify-content:flex-start; }
/* Intro paragraphs sized to match the opening lead paragraph above them
   (31 July 2026, Liam: "can we also make this text as large as the
   paragraph above it") -- .text-soft alone just inherits the sitewide
   base body size (--fs-body, 1.0625rem fixed); .lead uses the larger,
   viewport-responsive --fs-lead (clamp 1.1rem-1.3rem). Matching the size
   AND line-height here makes the whole intro block, hero_body plus every
   role.intro paragraph, read as one uniform block of text rather than a
   bigger opening line followed by smaller body copy. */
.wwr-intro-p{ max-width:70ch; margin-top:1.5rem; font-size:var(--fs-lead); line-height:1.55; }
.wwr-disclaimer{
  margin-top:1.5rem; padding:1rem 1.25rem; border-radius:var(--radius-md);
  background:var(--blush-100); color:var(--ink-soft); font-size:.9rem; max-width:70ch;
}
/* Role-page intro CTAs: bold arrow links instead of boxed buttons, moved
   to the end of the copy block instead of splitting it (31 July 2026,
   Liam, rebuilding the What We Recruit role pages starting with Legal
   Secretaries: "Let's also have that as an uninterrupted block of text
   with the buttons at the bottom. Make the buttons link bold links with
   arrows, no background/pill shape around them"). Replaces the old
   .wwr-hero-actions .btn .btn-primary/.btn-outline pair (left in place
   above, unused, harmless) with the same plain bold-link-plus-arrow
   pattern already established sitewide for .card-link (teaser cards),
   just its own class since .card-link assumes a flex-card context
   (margin-top:auto) this isn't. Shared template change -- applies to all
   9 role pages' intro CTAs uniformly, not just this one. */
/* Forced onto two stacked, centred lines rather than a wrapping row (14
   August 2026, Liam: "between pages, the links in the intro section are
   sometimes side by side, sometimes above on another due to length...
   lets just put them on two lines and centre them to the tile"). The
   old flex-wrap:wrap row let the two links sit side by side whenever
   both fit on one line and wrap to a second line when they didn't --
   role.primary_cta's wording ("Hire a legal secretary" vs "Hire a
   licensed conveyancer" etc) varies enough in length across the 9 role
   pages that some pages wrapped and some didn't, an inconsistency
   that's now fixed by always stacking regardless of text length.
   Centred against the .card-glass "tile" this sits inside even though
   the copy above it (wwr-hero-copy, the intro paragraphs) stays
   left-aligned -- a deliberate centred-CTA-under-left-body pattern, not
   an oversight. */
.wwr-hero-links{ display:flex; flex-direction:column; align-items:center; gap:1rem; margin-top:2rem; text-align:center; }
.wwr-hero-links .link-arrow{
  display:inline-flex; align-items:center; gap:.45rem;
  font-weight:700; color:var(--rose-600);
}
.wwr-hero-links .link-arrow span{ display:inline-block; transition:transform var(--dur) var(--ease); }
@media (hover:hover){
  .wwr-hero-links .link-arrow:hover{ color:var(--rose-500); }
  .wwr-hero-links .link-arrow:hover span{ transform:translateX(4px); }
}

/* "Roles we recruit" chip list */
.role-chip-list{ display:flex; flex-wrap:wrap; gap:.65rem; justify-content:center; max-width:960px; margin-inline:auto; }
.role-chip{
  display:inline-block; padding:.55rem 1.1rem; border-radius:999px;
  background:var(--white); border:1px solid var(--line-soft); color:var(--ink);
  font-size:.88rem; font-weight:600; box-shadow:var(--shadow-sm);
}

/* "What strong candidates bring" section: dark "As"-textured background,
   all text set white directly (31 July 2026, Liam: "Can we make the
   background of this section the dark As the background" then, having
   seen the white-card version live, "remove the white card from this
   section and just make all the text white"). Reuses the existing
   .bg-dark-test (sitewide dark rose-to-ink gradient + monogram texture,
   already used for Our Process etc) -- no card/panel now, text sits
   straight on the texture like every other .bg-dark-test section on the
   site. The eyebrow/h2/lead are already covered by .bg-dark-test's own
   sitewide white-text rules further up this file; only the checklist
   itself needed new colour rules, since .checklist-item p/:hover were
   authored for a light card surface (ink-soft body, darkening further on
   hover) and would be low-contrast to invisible on a dark background. */
.section-candidates-bring .checklist-item p{ color:rgba(255,255,255,.85); }
@media (hover:hover){
  .section-candidates-bring .checklist-item:hover p{ color:var(--white); }
}
/* "What strong candidates bring" checklist */
/* Section-head widened to match the checklist grid's own width below it
   (31 July 2026, Liam: "can we make these lines as wide as the two
   columns underneath"). The shared .section-head{max-width:640px} is
   what was capping the eyebrow/h2/lead here to a much narrower column
   than the grid-2 checklist sitting right below it in the same
   .container -- max-width:none lets it fill exactly the same available
   width as the grid, since both are direct children of one .container
   with no other width constraint between them. Text stays centred
   (unchanged from the sitewide .section-head.center default) even
   though the line length is now much longer -- flag if that reads
   awkward once seen live; left-aligning instead is a one-line change if so. */
.section-candidates-bring .section-head{ max-width:none; }
.checklist-grid{ margin-top:2rem; gap:1.25rem 2rem; }
/* align-items:flex-start -> center (8 August 2026, Liam: "the ticks
   [are] currently aligned to the top if there are two lines [of text],
   can we make them centred"). Tick now sits centred against the full
   height of its paragraph, whether that's one line or two, instead of
   pinned to the top of it. */
/* gap .85rem -> 1.25rem (8 August 2026, second same-day correction --
   see the transform-origin comment below). Matches .checklist-grid's
   own row-gap value already, so it reads as an intentional spacing
   choice rather than an arbitrary number, and gives the hover-grown
   circle a genuinely bigger real margin to grow into before it can
   reach the text -- see below for why "genuinely bigger" beat trying
   to calculate an exact value again. */
.checklist-item{ display:flex; align-items:center; gap:1.25rem; }
/* transform-origin history (8 August 2026, same day, two corrections):
   started at "left center" (all hover growth pushed rightward, reasoned
   as "intentional bold overlap" -- wrong, Liam reported real overlap).
   Changed to plain centre, hand-calculated off assumed font metrics to
   leave only a ~1px graze against the old .85rem gap -- STILL showed
   real overlap once Liam looked at the live page (screenshot). This
   file has hit that same wall more than once this session (see the
   tier-card min-height saga): desk arithmetic off assumed line-height/
   font metrics keeps landing close but not exact once real rendering
   is involved, and this component doesn't have a Subgrid-style
   self-correcting layout escape hatch the way that one did, because
   `transform:scale` is deliberately paint-only/layout-blind (that's
   what makes it herky-jerky-free in the first place -- see the hover
   rule below). So instead of a third precise guess: two changes that
   both bias toward MORE margin than the minimum math says is needed,
   rather than the exact number. Origin moved to 70% (was 50% centre),
   biasing most of the growth leftward into genuinely open space (the
   section gutter for column-1 items, the grid's 2rem column gap for
   column-2 items) and shrinking the rightward share the gap has to
   absorb. Combined with the gap increase above, there's now real
   margin on the right side rather than a hand-calculated number sitting
   right at the edge of overlapping. Circle still reaches the same
   final size Liam asked for either way -- only where the growth goes
   changed, again. */
.checklist-tick{
  position:relative; flex-shrink:0; width:26px; height:26px; border-radius:50%; margin-top:.15rem;
  background:var(--blush-100); color:var(--rose-600);
  display:flex; align-items:center; justify-content:center;
  transform-origin:70% center;
  transition:transform var(--dur) var(--ease);
}
/* text-wrap:balance REMOVED (8 August 2026, reversing the 8 August
   addition below within the same day) -- Liam: "I think we have been a
   bit aggressive with putting the points on two lines... surely we can
   safely fit more words on the first line?" balance never creates MORE
   total lines than normal wrapping would need (it only redistributes
   words across however many lines normal wrapping already required),
   but for an item that already needs 2 lines, balance deliberately
   shortens line 1 to even it out with line 2 -- which is precisely the
   "line 1 has room to spare" look Liam's flagging. Back to the browser
   default (greedy: pack line 1 as full as possible, wrap only what's
   left onto line 2), same component the earlier "management&nbsp;around"
   nbsp fix (what_we_recruit_data.py, Personal Assistants) already
   applies to -- that fix is unaffected, a non-breaking space blocks a
   line-break at that exact word pair regardless of which wrapping
   algorithm is active. transform-origin stays pinned top-left (not the
   default centre) so the scale-on-hover below still grows away from
   the tick to its left rather than also expanding upward/leftward into
   it -- unrelated to the balance/greedy choice, still needed either way. */
.checklist-item p{ margin:0; color:var(--ink-soft); transform-origin:left top; transition:color var(--dur) var(--ease), transform var(--dur) var(--ease); }
/* Hover: text goes bold, tick crossfades to an arrow (31 July 2026, Liam:
   "As you hover on each point, let's have it go bold and the tick
   transition to an arrow"). Same rest/hover icon-crossfade convention
   used elsewhere on the site (.advice-icon-rest/-hover etc) -- both SVGs
   sit absolutely centred on top of each other inside the circle, only
   opacity changes, so there's no layout shift or icon "jump" on hover.
   Shared template/CSS change -- applies to every role page's checklist,
   not just Legal Secretaries.

   font-weight deliberately dropped from the transition list (3 August
   2026, Liam: "the bold effect... seems a bit herky jerky") -- root
   cause: Manrope is loaded here as discrete static weight instances
   (400/500/600/700/800, see the Google Fonts link in base.html), not a
   true variable font, so a browser can't actually interpolate smoothly
   between 400 and 700 the way it can with colour. What LOOKED like an
   animated transition was really a stepped/synthesized-then-swapped
   jump partway through the colour fade's own duration -- the stutter
   Liam felt. Colour still transitions smoothly on its own; font-weight
   now flips instantly, in sync with the icon crossfade (which was
   already an instant-trigger opacity fade, not a tweened property, and
   never had this problem).

   Text growth on hover (8 August 2026, Liam: "make the text a little
   larger on hover... post transition the growth doesn't cause words to
   move to other lines") -- deliberately NOT done via font-size. Growing
   font-size changes the text's actual layout box, which can shift where
   each word wraps to a different line the instant it grows -- exactly
   the "herky jerky" reflow Liam's describing, and the same category of
   bug as the font-weight stutter above, just from a different property.
   transform:scale doesn't participate in layout at all -- the browser
   keeps the exact same line breaks it already computed (helped along by
   text-wrap:balance above) and just paints the result bigger, so growth
   is purely visual with no reflow. Unlike font-weight, scale IS smoothly
   interpolable by the browser, so it's safe to add to the transition
   list rather than needing the instant-flip workaround.

   Circle + arrow growth (8 August 2026 follow-up, Liam: "can we make
   both the background circle and the arrow grow and then shrink back
   down to the tick... larger with the text"). Scaling lives on
   .checklist-tick (the circle) alone, not on .checklist-icon-arrow --
   the arrow is a DOM child of the circle, so a transform on the parent
   visually carries every child painted inside it along for free
   (position:absolute doesn't opt a child out of an ancestor's paint-
   time transform, only out of normal-flow layout), meaning scaling the
   circle already grows the arrow proportionally with it in one step,
   with no separate scale needed on the arrow itself and no risk of the
   two compounding into something bigger than intended. "Shrink back
   down to the tick" is just this same :hover rule reversing on
   mouse-out, same as the icon crossfade already did -- nothing extra
   needed for that half. */
.checklist-icon-tick, .checklist-icon-arrow{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  transition:opacity var(--dur) var(--ease);
}
.checklist-icon-arrow{ opacity:0; }
@media (hover:hover){
  /* scale 1.25 -> 2.15 (8 August 2026, Liam: "on hover can we make the
     background circle and tick grow to be the same height as two lines
     worth of text, so a noticeable difference"). Body copy is
     --fs-body:1.0625rem at line-height 1.65 -> one line is ~28px, two
     lines ~56px; the tick starts at a 26px circle, so 56/26 ≈ 2.15
     reaches that target. See .checklist-tick above for transform-origin
     and gap -- both were tuned (twice, same day) to give this growth
     genuine real margin against the text rather than a hand-calculated
     value sitting right at the edge of overlapping. */
  .checklist-item:hover .checklist-tick{ transform:scale(2.15); }
  .checklist-item:hover .checklist-icon-tick{ opacity:0; }
  .checklist-item:hover .checklist-icon-arrow{ opacity:1; }
  .checklist-item:hover p{ font-weight:700; color:var(--ink); transform:scale(1.08); }
}
@media (prefers-reduced-motion:reduce){
  .checklist-tick, .checklist-icon-tick, .checklist-icon-arrow, .checklist-item p{ transition:none !important; }
}

/* Firm-size comparison cards (Boutique / Mid-tier / Top-tier or global) */
/* EXPERIMENT, 8 August 2026 (Liam: "check what it looks like if we were
   to make the three firm size in practice cards wider and closer
   together... happy if they expand out and take up more space... we
   might immediately roll this back"). Two changes, both easy to undo
   independently:
   1) Container widened 1280px -> 1500px for just this section, same
      precedent value already used elsewhere for widened card grids
      (#candidate-services/#hiring-advice .container). Since grid-3's
      columns are 1fr each, a wider container is the whole mechanism for
      "cards expand out" -- no need to touch card padding/font-size,
      each column just gets more available width automatically.
   2) Gap dropped from the shared .grid default (var(--gutter), clamps
      up to 3rem/48px) to a flat 1.5rem/24px for "closer together".
      Deliberately a 2-class compound selector (.grid.tier-compare, not
      just .tier-compare) -- .grid already sets its own gap, so a
      single-class .tier-compare override would tie on specificity and
      become source-order-dependent (the exact bug category this file's
      comments flag repeatedly elsewhere); the compound selector is
      unambiguously more specific regardless of source order.
   Widening the columns only ever REDUCES how many lines each tier
   card's text wraps to (more width per line), so the existing
   .tier-card-block-changes min-height values (232px shared / 430px
   Personal-Assistants-only) stay safely oversized rather than becoming
   too short -- nothing else needed to touch for this to be a clean,
   non-breaking experiment. Revert: remove .section-tier-compare from
   what-we-recruit-role.html's tier-compare section and delete these two
   rules. */
.section-tier-compare .container{ max-width:1500px; }
/* column-gap/row-gap split out from the old `gap:1.5rem` shorthand (8
   August 2026, needed for the subgrid switch just below) -- gap on a
   grid applies to EVERY track pair on that axis, and .tier-compare now
   has 3 explicit row tracks (see grid-template-rows below) purely so
   .tier-card's own head/changes/matters can subgrid onto them. A plain
   `gap:1.5rem` shorthand would silently add 1.5rem of EXTRA vertical
   space between those 3 internal sections on top of their existing
   margin-based spacing (.tier-card-head's margin-bottom, .tier-card-
   block-matters' margin-top) -- the opposite of what Liam's been asking
   for all day. row-gap:0 keeps the only vertical spacing mechanism the
   one that was already there and already tuned. */
.grid.tier-compare{ column-gap:1.5rem; row-gap:0; }
/* Explicit row tracks + subgrid (8 August 2026) -- REPLACES the old
   fixed-pixel-min-height alignment system (see git history/this file's
   own past comments here for the full saga). That system required
   manually predicting how many lines each role's longest "changes"
   paragraph would wrap to, in a stand-in font, and setting a matching
   px value per role -- Liam caught it visibly wrong more than once (a
   prediction of 7 lines for Legal Secretaries' Top-Tier & Global text
   that was actually 5 in his real screenshot), because no amount of
   manual estimation reliably matches real browser text-wrapping across
   9 different roles' copy, especially copy that keeps changing.

   Subgrid removes the guessing entirely: .tier-compare defines 3 row
   tracks (head / changes / matters), and each .tier-card spans all 3
   and subgrids onto them (see .tier-card, grid-template-rows:subgrid,
   below) -- meaning the BROWSER measures every card's real
   rendered content in each row and sizes that row to the tallest one,
   automatically, correctly, forever, with zero px values to maintain
   and no risk of ever going stale as copy changes again. This is
   exactly Liam's own stated requirement ("the gap still determined by
   the largest text block above it so What matters most remain in
   line") -- subgrid just makes the browser guarantee it instead of an
   estimate trying to. Fallback below for any browser without subgrid
   support (very old browsers only -- Chrome 117+/Safari 16+/Firefox
   71+ all support it, safe baseline for 2026): reverts .tier-card to
   its pre-subgrid flex-column behaviour, same as before this change,
   so nothing breaks, it just loses the perfect alignment there. */
.tier-compare{ margin-top:1rem; align-items:stretch; grid-template-rows:auto auto auto; }
.tier-card{
  position:relative; overflow:hidden;
  display:flex; flex-direction:column;
  transition:transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease), background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
@supports (grid-template-rows: subgrid){
  .tier-card{ display:grid; grid-row:span 3; grid-template-rows:subgrid; }
}
/* Resting card made translucent (5 August 2026, Liam: "make the pre
   transition white cards a little bit transparent so the pink As is
   showing through slightly... but still allowing the text to pop") --
   this section is .bg-white, which carries the sitewide pink monogram
   texture (texture-monogram-pink.webp) behind it; .tier-card's own
   fully opaque white background (inherited from the base .card class)
   was hiding it completely. Same rgba-white-plus-blur technique already
   used for Career/Hiring Advice's resting cards on this exact same
   background. Only the RESTING background -- the hover state further
   down still sets its own explicit solid rose-600 fill with the "As"
   texture reintroduced at hover-card scale, which is unaffected by
   this. */
/* Linen weave added to the resting fill (14 August 2026, part of the
   sitewide "give every white card a linen treatment" sweep). Opacity
   (.82) left untouched -- only asked for texture here, this card's
   translucency was never part of this round's request. Hover state
   deliberately NOT given texture -- that's an earlier, explicit Liam
   call ("on the transition we also want to drop the As texture, just
   have the faint large icon", see comment above the hover block below)
   that predates and is unrelated to this pass, so it stays as-is. */
.tier-card{
  background-color:rgba(255,255,255,.82);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center top; background-size:400px 400px; background-repeat:repeat;
  backdrop-filter:blur(3px); -webkit-backdrop-filter:blur(3px);
}
/* Icon moved back beside the title, 13 August 2026 follow-up (Liam:
   "the cards are too tall now... put the initial pre-hover icon back up
   beside the title at the top, keep that scale effect... keep the same
   transition effect that it fills the colour transition. This will
   allow us to make the cards shorter") -- reverts the same-day
   bottom-icon-rest/icon-bg experiment (a port of
   #candidate-services/#hiring-advice's advice-icon-rest/advice-icon-hover
   mechanic; see git history for that experiment's own long round-by-
   round reasoning, including the overlap bug it had to work around)
   back to this component's original beside-the-title layout -- the
   bottom icon's own reserved row was exactly what was adding the extra
   card height, so removing it directly answers "make the cards
   shorter". Kept from that experiment: the per-tier --icon-scale custom
   property (1 / 1.3 / 1.65 across boutique/mid/top, driven by
   loop.index0 in the template against firm_comparison's fixed
   ordering) -- Liam explicitly wants to keep "smaller icon next to
   boutique, medium next to mid-tier, larger icon next to top tier/
   global", now applied to the restored beside-title icon's height
   instead of the bottom one's. */
/* align-items:flex-end, not center (14 August 2026, Liam: "the bottom
   of each icon is aligned... it might accentuate the different sizes")
   -- .tier-card-head is subgrid row 1, so its box height is already
   stretched to match the tallest of the 3 sibling heads (top-tier's
   1.65x icon, the biggest of the three). Centering meant the smaller
   boutique/mid icons floated in the middle of that taller box with
   empty space above AND below, which read as misaligned rather than
   "small/medium/large" -- flex-end pins every icon's (and the title's)
   bottom edge to the same line across all 3 cards, so only the TOPS
   vary, reading cleanly as three icons growing up from one baseline. */
.tier-card-head{ display:flex; align-items:flex-end; justify-content:center; gap:1.25rem; margin-bottom:1.25rem; }
/* Icon sized off the title's original 72px figure (5 August 2026, "same
   height as the two lines of text for the title"), now multiplied by
   --icon-scale so boutique stays at that original 72px, mid-tier grows
   to ~94px and top-tier to ~119px. max-width bumped from the old fixed
   160px to 220px to give the widest source image (mid-tier,
   law-firm-mid-tier-*.webp, 512x372 -- far more landscape than the
   other two) enough headroom at its own larger 1.3x size without
   clipping against the safety cap. */
.tier-card-icon-wrap{ position:relative; display:inline-block; height:calc(72px * var(--icon-scale, 1)); flex:none; }
.tier-card-icon{ position:relative; height:100%; width:auto; max-width:220px; display:block; }
/* Rest/hover icon crossfade, same convention used across this site
   (.advice-icon-rest/-hover etc): -mid.webp (brand Mid colour) at rest,
   -white.webp layered exactly on top and crossfaded in via opacity when
   .tier-card itself is hovered (see the dark-fill hover block below) so
   the icon stays legible once the card's own background goes dark. This
   is the original mechanic restored unchanged -- Liam: "keep the same
   transition effect that it fills the colour transition". */
.tier-card-icon-hover{ position:absolute; top:0; left:0; opacity:0; transition:opacity var(--dur) var(--ease); }
/* Title enlarged (13 August 2026, Liam: "put the titles in the centre
   and make them larger as well") -- centring is now handled by
   .tier-card-head's own justify-content:center above (the icon sits
   beside the title again, so the whole row is centred as a group rather
   than the text alone), the 15% font-size bump is unchanged from that
   same request. */
.tier-card h3{ margin-top:0; font-size:calc(var(--fs-h3) * 1.15); transition:color var(--dur) var(--ease); }
.tier-card-label{
  margin-top:1rem; font-size:.72rem; font-weight:700; letter-spacing:.06em; text-transform:uppercase;
  color:var(--rose-600); transition:color var(--dur) var(--ease);
}
.tier-card-body{ margin-top:.3rem; color:var(--ink-soft); transition:color var(--dur) var(--ease); }
/* Hover: bigger lift/scale than the base .card:hover (31 July 2026,
   Liam: "can we make the card grow more on hover") plus a dark gradient
   fill (Liam, same message: "can we do the colour transition to a Dark
   As Background") -- same recipe already proven elsewhere on this site
   for a hover fill on a medium card (.roles-card-surface: rose-600 base,
   rose-500->rose-600 135deg gradient, no ink stop to avoid the "muddy
   corner" issue on a card this shape). Text and the icon crossfade to
   white to stay legible on the dark fill; border-color:transparent
   avoids the light hairline border showing as a seam against the dark
   background (same fix already used on every other dark hover-fill card
   on this site). "As" monogram texture layer stays removed (13 August
   2026, Liam: "on the transition we also want to drop the As texture,
   just have the faint large icon") -- that ask was about the fill
   itself, independent of where the icon lives, so it stands even though
   the icon has since moved back beside the title. */
@media (hover:hover){
  /* .card.tier-card:hover (compound selector, not just .tier-card:hover)
     -- both this rule and the sitewide base .card:hover are two-class-
     equivalent selectors (.card:hover / .tier-card:hover), which are
     EQUAL specificity; without the compound form this would be a repeat
     of the exact gap/specificity bug already root-caused twice elsewhere
     in this file today, silently losing to source order instead of
     reliably overriding the base translateY(-4px)/shadow-md lift. */
  /* Linen weave added to the post-transition fill (14 August 2026,
     Liam: "on the Boutique to global sections of each specialisation
     page can we give the post-transition the linen treatment as
     well"). Reverses the earlier, explicit "no texture on hover" call
     (see the comment above the .tier-card-icon-hover rules) -- that
     was specifically about the As monogram reading as too busy behind
     the icon; the much fainter linen grain doesn't carry the same risk. */
  .card.tier-card:hover{
    transform:translateY(-8px) scale(1.03);
    box-shadow:var(--shadow-lg);
    border-color:transparent;
    background-color:var(--rose-600);
    background-image:url('/assets/img/photos/texture-weave-white.webp'), linear-gradient(135deg, var(--rose-500) 0%, var(--rose-500) 82%, var(--rose-600) 100%);
    background-position:center top, center;
    background-size:400px 400px, cover;
    background-repeat:repeat, no-repeat;
  }
  .card.tier-card:hover .tier-card-icon-hover{ opacity:1; }
  .card.tier-card:hover h3,
  .card.tier-card:hover .tier-card-label,
  .card.tier-card:hover .tier-card-body{ color:var(--white); }
}
@media (prefers-reduced-motion:reduce){
  .tier-card, .tier-card-icon-hover, .tier-card h3, .tier-card-label, .tier-card-body{ transition:none !important; }
}
/* "What matters most" aligned across all three cards -- see the
   .tier-compare / .tier-card comments above (Subgrid, 8 August 2026)
   for the current mechanism and the full history of what this used to
   be (a hand-maintained per-role min-height, repeatedly wrong because
   it was a font-metric guess, not a measurement). Nothing below this
   point participates in the alignment any more -- .tier-card-block-
   changes has no min-height/height rule at all now, its row is sized
   entirely by the subgrid. */
.tier-card-block-matters{ margin-top:2rem; }
.tier-card-block-matters .tier-card-label{ margin-top:0; }
/* "Tiny bit of padding" between the "What matters most" label and its
   own body text (5 August 2026, Liam's exact words) -- .tier-card-body
   is a shared class between both blocks, so this nudges only the
   matters block's copy of it rather than raising the shared .3rem
   sitewide (which would also affect the now-label-less changes block
   above, not requested and not needed there). */
.tier-card-block-matters .tier-card-body{ margin-top:.55rem; }

/* Assiduous-perspective callout, reused from the blueprint's own
   "ASSIDUOUS PERSPECTIVE" boxed-copy convention */
.perspective-callout{
  margin-top:2rem; padding:1.75rem 2rem; border-radius:var(--radius-lg);
  background:var(--blush-100); border-left:4px solid var(--rose-500);
}
.perspective-callout p:last-child{ margin-top:.4rem; color:var(--ink); font-size:1.05rem; }

/* "Where the work differs by practice area" -- standalone grid, replacing
   the old paired "Where the work differs" / "Getting the brief right"
   2-column split (31 July 2026, Liam, rebuilding the What We Recruit role
   pages starting with Legal Secretaries -- his new copy treats practice-
   area differences as its own full section with no paired mistakes list,
   and the "Roles we recruit" chip list and "Getting the brief right"
   content were confirmed droppable via a quick clarifying question rather
   than guessed, since this page establishes the template pattern for the
   other 8 role pages). workflow_differences is still authored sitewide as
   "Title: description" strings (unchanged data shape); the template splits
   on the first ": " to get a title/description pair, falling back to a
   plain paragraph if a role's entry has no colon (Reception and Client
   Services' items are full sentences with no title prefix).

   Widened from a 3-column to a 2-column grid (3 August 2026, Liam: "make
   the cards wider so they look visually better, just give them some
   breathing room" -- fewer, wider columns plus a bigger gap, rather than
   adding a solid card background/border, which would fight the dark
   section's white-text treatment below). Row/column gap bumped up from
   the shared .grid default to match. */
/* Frosted-glass panel (14 August 2026, Liam: "a very glass, completely
   see-through card... might give this section a little pop"). Revisits
   the "solid card background... would fight the dark section's
   white-text treatment" call made on 3 August above -- that was about a
   plain opaque card; a translucent glass panel is a different effect
   and doesn't carry the same risk (the dark gradient + monogram texture
   still shows through, it just gets a soft lift rather than being
   covered).
   Deliberately NOT the sitewide .card-glass value (rgba(255,255,255,.82)
   -- see that rule's own comment) -- that figure was tuned for a mostly-
   opaque white card on LIGHT backgrounds and would just read as a flat
   white box here. Named .card-glass-dark (not .practice-area-glass, its
   original name) once Liam asked for the same treatment on "What strong
   candidates bring" too -- now a shared, reusable dark-section
   counterpart to .card-glass, not a one-section experiment.

   Round 1 (same day) used blur(18px) + an inset top highlight + heavy
   box-shadow -- Liam's reaction: "it looks like a solid fill colour...
   seeing through essentially the dark AS texture background, but a
   slightly visible tile it was sitting on". Root cause: an 18px blur
   doesn't just soften the texture behind the panel, it AVERAGES it --
   the whole "AS" monogram + gradient behind the panel smears into one
   flat, roughly uniform colour, which combined with the box-shadow and
   inset highlight (both "solid card" cues) is exactly why it read as a
   filled card rather than glass. Fixed by dropping the blur to almost
   nothing (2px) and removing the inset highlight/heavy shadow entirely,
   so what you're looking at is genuinely the section's own dark
   texture, still sharp, just faintly tinted. The border became the only
   thing doing the "this is a tile sitting here" job.

   Round 2 (14 August 2026, Liam: "I like that... dial it back a tiny
   bit more") -- tint and border both nudged down again and the blur
   trimmed further, so the panel reads as even less "there" while the
   border still keeps the pane's edge readable against the dark
   texture. */
.card-glass-dark{
  background:rgba(255,255,255,.025);
  border:1px solid rgba(255,255,255,.16);
  border-radius:var(--radius-lg);
  backdrop-filter:blur(1.5px);
  -webkit-backdrop-filter:blur(1.5px);
  padding:clamp(2rem,4vw,3.25rem);
}
.practice-area-grid{ margin-top:1rem; gap:2.5rem calc(var(--gutter) * 1.15); }
/* Selectors retargeted h4 -> h3 (15 August 2026, SEO heading-hierarchy
   pass) -- markup changed to <h3 class="h4"> (see what-we-recruit-role.html)
   so these titles no longer skip a level under the section's own <h2>;
   the "h4" class preserves the exact visual size/weight unchanged. */
.practice-area-item h3{ margin:0 0 .35rem; color:var(--rose-600); }
.practice-area-item p{ margin:0; color:var(--ink-soft); }
/* Area-of-law icons (31 July 2026, Liam supplied a matching icon set --
   see PRACTICE_AREA_ICONS in what_we_recruit_data.py for the title-match
   logic).

   Restructured + moved onto a dark background (3 August 2026, Liam:
   "put this on a dark background, make the text white... have the icons
   larger, same size as the text beside them, i.e. besides the whole
   text, not just the title"). Icon used to sit in its own row beside
   just the h4, with the paragraph full-width underneath -- now the icon
   sits in ONE flex row beside a text column holding both the title AND
   paragraph, vertically centred against that whole block. The vertical
   left-accent rule that used to separate each item was dropped in the
   same round (Liam: "get rid of the vertical lines beside each area") --
   the wider 2-column grid and bigger gap above now do that separation
   job instead, and removing it also freed up the icon to grow further:
   from 22px (label-marker era) to 46-60px (full-height-beside-text era,
   previous round) to 52-72px here, now that there's genuinely more
   column width for it to sit in without crowding the text. Reuses the
   sitewide .bg-dark-test gradient+monogram recipe (same as Our Process /
   What strong candidates bring) rather than a bespoke dark treatment. */
.practice-area-item-row{
  display:flex; align-items:center; gap:1.1rem;
}
.practice-area-item-text{ min-width:0; }
/* max-width raised 68px -> 84px (5 August 2026 -- Liam reported
   Executive Assistants' "Business projects" icon looking cut off on the
   sides, full width not showing). Root cause: this height is a clamp
   that reaches 72px at wide viewports, but the old max-width (68px) was
   LOWER than that ceiling. Most of this set's 43 icons are perfect
   512x512 squares (aspect ratio 1.0), so their natural auto-width at
   height:72px is also 72px -- 4px past the old 68px cap. With no
   object-fit set, the box still renders at the explicit fixed height
   while width gets clamped to max-width, so the image stretches to fit
   a box narrower than its own true aspect ratio -- exactly the
   "squished/sides not showing" look Liam described. Not unique to this
   one icon: checked every file WORKFLOW_ICONS actually references
   (43 unique icons across all 9 role pages) and none exceed a 1:1
   ratio, so 72px was always the real worst case -- "Business projects"
   just happened to be the one Liam noticed first. 84px clears that
   worst case with real headroom, not just enough to fix this one
   report. */
/* height ceiling 72px -> 80px, max-width 84px -> 100px (8 August 2026,
   Liam: "widen the display area slightly" for the Workflow-and-capacity
   / Operations-and-risk icons reading as cut off). Turned out NOT to be
   this same bug repeating -- see the correction dated 8 August in
   workflow-capacity-delivery(-white).webp / workflow-operations-
   risk(-white).webp's own history (session log): those two files were
   never actually clipped, that was a bad reading of raw pixel-scan
   data on my part, and Liam's reuploaded originals confirmed it. This
   box-widening is left in place anyway since it's genuine extra
   headroom and Liam asked for it directly, independent of the file
   investigation's outcome. */
.practice-area-icon{ height:clamp(52px, 4.5vw, 80px); width:auto; max-width:100px; flex-shrink:0; }
.bg-dark-test .practice-area-item h3{ color:var(--white); }
.bg-dark-test .practice-area-item p{ color:rgba(255,255,255,.8); }
/* Icon flip + title grow on hover (3 August 2026, Liam: "let's do an
   icon flip and title grow on hover"). Icon "flip" is a scaleX squish-
   and-restore, NOT a rotateY/perspective 3D flip -- this project's
   standing rule is flat 2D hover techniques only (see the roles-card/
   about-card history), and a symmetric scaleX(0) midpoint reads as a
   clean coin-flip without needing a second icon face. Title grow uses
   transform:scale() rather than font-size, deliberately -- scale() is
   paint-only so it can never affect the grid row's own track height
   (no jolt, the exact bug already root-caused once this project on the
   advice-cards), and unlike that multi-line-stacked-text case, only a
   single short title line is growing here (the paragraph below it
   doesn't also grow), so a modest scale factor has no real risk of
   overlapping into the paragraph's existing margin-top gap either.
   transform-origin:left keeps growth reading as "the title expanding
   rightward from where it already sits" rather than drifting sideways.
   Gated to @media(hover:hover) only, no separate :focus-within block --
   these items have no focusable child (no link, no tabindex), so
   there's nothing for keyboard focus to land on that would need an
   equivalent trigger. */
@keyframes practiceIconFlip{
  0%, 100%{ transform:scaleX(1); }
  50%{ transform:scaleX(0); }
}
.practice-area-item-text h3{
  transform-origin:left center;
  transition:transform var(--dur) var(--ease), color var(--dur) var(--ease);
}
@media (hover:hover){
  .practice-area-item:hover .practice-area-icon{ animation:practiceIconFlip .6s var(--ease); }
  .practice-area-item:hover .practice-area-item-text h3{ transform:scale(1.12); }
}
@media (prefers-reduced-motion:reduce){
  .practice-area-icon, .practice-area-item-text h3{ transition:none !important; animation:none !important; }
}

/* "Where the work differs" / "Getting the brief right" split lists --
   .wwr-split/.tick-list themselves stay defined (still valid CSS, no risk
   leaving them) even though no page currently renders a .wwr-split block
   after the above change; kept in case a future role page still wants a
   2-column split for some other pairing. */
.wwr-split h3{ margin-bottom:1rem; }
.tick-list{ display:flex; flex-direction:column; gap:.85rem; padding:0; margin:0; list-style:none; }
.tick-list li{
  position:relative; padding-left:1.4rem; color:var(--ink-soft);
}
.tick-list li::before{
  content:""; position:absolute; left:0; top:.55em; width:7px; height:7px; border-radius:50%;
  background:var(--rose-400);
}

/* Standalone FAQ items on role pages (no .faq-group wrapper needed for
   just 5 questions -- reuses .faq-item itself unchanged) */
.wwr-faq-body{ padding:0; }

/* ==========================================================================
   Premium icon-based header dropdowns (26 July 2026)

   Liam: "I would like to move to more of a premium-looking menu design,
   the pop outs, that incorporates icons, across the header menu." Every
   dropdown link now carries a small rose-tinted icon swatch (reusing the
   same inline-SVG technique as partials/icons.html) instead of plain
   text, and the panel itself picks up a touch more breathing room.
   ========================================================================== */
.dropdown-menu a{ display:flex; align-items:center; gap:.75rem; padding:.6rem .75rem; }
.dropdown-menu-icon{
  flex-shrink:0; width:30px; height:30px; border-radius:8px;
  background:var(--blush-100); color:var(--rose-600);
  display:flex; align-items:center; justify-content:center;
  transition:background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.dropdown-menu-icon svg{ width:16px; height:16px; }
.dropdown-menu a:hover .dropdown-menu-icon, .dropdown-menu a[aria-current="page"] .dropdown-menu-icon{
  background:var(--rose-500); color:var(--white);
}
/* About Us dropdown's 5 items (27 July 2026, Liam: "change the icons in
   the header menu for all of the about sub pages to what we are using
   as the corresponding icons on the about us hub page") now point at
   the exact same source images as the About Us hub cards
   (about-icon-<slug>-white.webp) instead of the generic inline-SVG set
   every other dropdown still uses. Those source files are plain white
   line art on transparent backgrounds, with no built-in way to recolor
   them the way an inline SVG's currentColor does -- so rather than
   exporting a second rose-600-tinted copy of each icon (more assets to
   keep in sync every time Liam swaps one on the hub page), this uses a
   CSS mask: the white webp becomes a stencil, and background-color:
   currentColor fills the stencil with whatever the swatch's `color` is
   at that moment. Since `.dropdown-menu-icon` already flips its own
   `color` between --rose-600 (rest) and --white (hover/active) via the
   rule just above, the masked icon inherits that exact same transition
   automatically, with zero extra hover rules and zero extra image
   exports -- one canonical white icon file per slug, same one the hub
   page uses, always in sync. */
.dropdown-menu-icon-mask{
  display:block; width:16px; height:16px; background-color:currentColor;
  -webkit-mask-image:var(--icon-url); mask-image:var(--icon-url);
  -webkit-mask-size:contain; mask-size:contain;
  -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat;
  -webkit-mask-position:center; mask-position:center;
}

/* "What We Recruit" mega-menu variant: 9 role links plus a footer link
   to the hub, wide enough for two columns so the panel doesn't turn
   into one very long list. */
.dropdown-menu--wide{ min-width:520px; padding:.85rem; }
.dropdown-menu--wide .dropdown-menu-grid{
  display:grid; grid-template-columns:1fr 1fr; gap:.2rem .5rem;
}
.dropdown-menu--wide .dropdown-menu-icon{ width:26px; height:26px; padding:4px; border-radius:7px; }
.dropdown-menu--wide .dropdown-menu-icon img{ width:100%; height:100%; object-fit:contain; }
.dropdown-menu--wide a{ font-size:.87rem; padding:.6rem .7rem; border-radius:9px; }
.dropdown-menu-viewall{
  grid-column:1 / -1; margin-top:.4rem; padding-top:.75rem !important;
  border-top:1px solid var(--line-soft); font-weight:700 !important; color:var(--rose-600) !important;
  justify-content:center;
}
.dropdown-menu-viewall .dropdown-menu-icon{ display:none; }
@media (max-width:1180px){
  .dropdown-menu--wide{ min-width:460px; }
}

/* Nested flyout for the 9 What We Recruit pages (26 July 2026, revised
   same day -- Liam: "I don't want What We Recruit to have it's own
   header button, but to be a secondary pop-out from where What We
   Recruit is referenced in other submenus"). Hangs off the existing
   "What We Recruit" (About) / "Roles We Recruit" (Candidates, Clients)
   links one level deeper than the standard dropdowns, rather than
   living as its own top-level nav item. */
.has-flyout{ position:relative; }
.has-flyout > a{ position:relative; }
.flyout-caret{ margin-left:auto; padding-left:.5rem; font-size:.7em; opacity:.55; flex-shrink:0; }
.has-flyout:hover > a .flyout-caret, .has-flyout:focus-within > a .flyout-caret{ opacity:1; }

/* Base .dropdown-menu positions itself centred *below* its trigger
   (left:50%, translateX(-50%)) -- wrong geometry for a flyout, which
   needs to sit beside its (much narrower) trigger li instead. These
   overrides win on source order (declared after the base rule, equal
   specificity) rather than !important. */
.dropdown-menu--flyout{
  top:-.6rem; left:100%; margin-left:.5rem;
  transform:translateX(4px);
}
.dropdown-menu--flyout-left{ left:auto; right:100%; margin-left:0; margin-right:.5rem; transform:translateX(-4px); }
.has-flyout:hover > .dropdown-menu--flyout,
.has-flyout:focus-within > .dropdown-menu--flyout{
  opacity:1; visibility:visible; pointer-events:auto; transform:translateX(0);
  transition:opacity .2s var(--ease), transform .2s var(--ease), visibility 0s, pointer-events 0s;
}
/* The parent panel's own overflow:hidden (kept for every ordinary
   dropdown so a link's pink hover fill never pokes past its rounded
   corners) would also clip a flyout trying to escape sideways past
   that same edge -- lift it only where a flyout is actually present. */
.dropdown-menu:has(.has-flyout){ overflow:visible; }
/* Flyout hover dead-zone fix (4 August 2026, Liam: "hard to jump on the
   What We Recruit submenus from the header button drop-downs. It
   disappears quickly before I can scroll over onto it"). Root cause: the
   flyout sits `.5rem` away from its trigger (margin-left/-right above),
   and that visual gap is genuinely empty space -- nothing hoverable lives
   in it. The instant the cursor leaves the trigger <li> and hasn't yet
   reached the panel, :hover breaks and the base .dropdown-menu's ~150ms
   close-grace window isn't enough to survive a real dead zone on a
   diagonal mouse path, especially one level deep where the user has
   already travelled once just to open the parent dropdown. Fix: invisible
   bridge pseudo-elements on the flyout panel itself, spanning its full
   height and reaching back across the gap on whichever side it opens
   from, so the panel's own hoverable footprint has zero gap against the
   trigger -- :hover on .has-flyout never actually breaks mid-transit.
   Needs its own overflow:visible (the base .dropdown-menu sets
   overflow:hidden purely so a link's pink hover fill can't poke past its
   rounded corners -- same reasoning that already forced the rule above
   one level up; here it's needed one level deeper so the bridge itself
   isn't clipped away). Width (.75rem) is deliberately a bit more than the
   .5rem gap it's bridging, for a comfortable margin. */
.dropdown-menu--flyout{ overflow:visible; }
.dropdown-menu--flyout::before,
.dropdown-menu--flyout::after{
  content:""; position:absolute; top:0; bottom:0; width:.75rem;
}
.dropdown-menu--flyout::before{ left:-.75rem; }
.dropdown-menu--flyout::after{ right:-.75rem; }
@media (max-width:1300px){
  /* Not enough room for a right-opening flyout this close to the
     viewport edge on a laptop-width screen -- flip it left instead. */
  .dropdown-menu--flyout:not(.dropdown-menu--flyout-left){ left:auto; right:100%; margin-left:0; margin-right:.5rem; transform:translateX(-4px); }
  .has-flyout:hover > .dropdown-menu--flyout:not(.dropdown-menu--flyout-left),
  .has-flyout:focus-within > .dropdown-menu--flyout:not(.dropdown-menu--flyout-left){ transform:translateX(0); }
}

/* ==========================================================================
   Sub-page prev/current/next pager (27 July 2026, Liam: "above the CTA at
   the bottom, can we have links to navigate between the sub pages within
   the parent page... (arrow left) Previous Page, Current Page, Next Page
   (arrow right)"). See partials/subpage-pager.html for the macro and the
   three content-order lists (About / Candidates / Clients) this styles.
   ========================================================================== */
/* The pager itself was always a tight 1.5rem-padded strip -- the "way
   too much space" Liam flagged (27 July 2026: "it's got way too much
   space above it to the grid and below it to the call to action
   card") was coming from the plain .section wrapper every page puts
   around it (`<section class="section bg-white"><div class="container
   container-narrow">{{ subpage_pager(...) }}</div></section>`), whose
   shared padding-block is the same large clamp(3.5rem...7.5rem) every
   other section on the site uses. Rather than edit that markup on each
   of the 11 subpages that call this macro, :has() targets exactly the
   section wrapping a .subpage-pager (all of them use the identical
   nesting) and collapses its padding down to something that reads as
   a thin strip sitting right on the CTA band below, everywhere at
   once. Round 2 (28 July 2026, screenshot: "I want a little more space
   above it and less space below it") -- this is the rule that actually
   controls the pager section's own spacing (a same-specificity-lower
   .section-subpage-pager{padding-top/bottom} rule earlier in this file
   was tried first and did nothing live, because this :has() selector
   outranks a plain class on specificity and always wins; that dead
   rule has been removed). Now sets an explicit top padding instead of
   zero, so there's real breathing room under Our Values, and keeps the
   bottom at zero so the pager still sits right on the CTA band. */
.section:has(> .container > .subpage-pager){
  padding-top:calc(var(--space-section) * .5);
  padding-bottom:0;
}
/* 27 July 2026, Liam: "halve the gap between the page turner and the
   CTA (across all pages)". Round 2 (28 July 2026, screenshot): "less
   space below it" -- cut further, from half the default down to a
   quarter, so the CTA band sits noticeably closer under the pager.
   The pager section itself carries zero bottom padding (rule above),
   so the visible gap below it is entirely this following section's own
   top padding. Targeting "the section right after a pager section"
   with the adjacent-sibling combinator applies this site-wide, on
   every subpage where a CTA band immediately follows the pager, without
   editing 10 separate templates. (candidates/upload-cv/ has no CTA
   section after its pager, so this rule simply has no target there and
   does nothing.) */
.section:has(> .container > .subpage-pager) + .section{ padding-top:calc(var(--space-section) * .25); }
.subpage-pager{
  display:flex; align-items:center; justify-content:space-between; gap:1.5rem;
  flex-wrap:wrap; padding:1.25rem 0;
  border-top:1px solid var(--line-soft); border-bottom:1px solid var(--line-soft);
}
.subpage-pager-link{
  display:inline-flex; align-items:center; gap:.5rem;
  /* Not bold (28 July 2026, Liam: "in the page turner, can we make the
     two pages we are not the current page, not bold") -- previously
     matched the current page's weight (700), which worked against the
     current-page underline/size treatment above as the actual "you are
     here" signal; now only the current page is bold, prev/next read as
     clearly secondary. */
  font-weight:400; color:var(--rose-600); white-space:nowrap;
  transition:color var(--dur) var(--ease);
}
.subpage-pager-link:hover{ color:var(--rose-500); }
.subpage-pager-next{ margin-left:auto; }
/* 27 July 2026, Liam: didn't like the current-page label being "bold
   and black" -- switched it to the same rose-600 used by the prev/next
   links. Revised same day: "make it slightly larger again and put a
   pink hue around it" -- added a blush-100 pill background on a nested
   .subpage-pager-current-pill span. Revised again same day: "drop the
   misty pink background, reduce the current page text size back down
   but still a bit larger than the other page options, and just put a
   small underline underneath it, not the full width of the word, just
   a subtle small underline" -- pill background/padding/radius removed;
   font-size brought down from 1.35em to 1.05em (prev/next links are
   1em, via base font-weight/size, so this stays just a touch bigger);
   and a short centred ::after bar replaces the fill as the "you are
   here" marker instead of a badge shape. */
.subpage-pager-current{ display:flex; justify-content:center; text-align:center; flex:1; }
.subpage-pager-current-pill{
  position:relative; display:inline-block;
  font-weight:700; color:var(--rose-600); font-size:1.05em;
  padding-bottom:.4rem;
}
.subpage-pager-current-pill::after{
  content:""; position:absolute; left:50%; bottom:0; transform:translateX(-50%);
  width:22px; height:2px; border-radius:2px; background:var(--rose-600);
}
.subpage-pager-arrow{ display:inline-block; transition:transform var(--dur) var(--ease); }
.subpage-pager-prev:hover .subpage-pager-arrow{ transform:translateX(-3px); }
.subpage-pager-next:hover .subpage-pager-arrow{ transform:translateX(3px); }
@media (max-width:640px){
  .subpage-pager{ flex-direction:column; gap:.75rem; text-align:center; }
  .subpage-pager-next{ margin-left:0; }
}

/* ==========================================================================
   About Us five-card row (27 July 2026, rebuilt a second time per Liam's
   live feedback on the first version) -- see the long comment above the
   Jinja loop in templates/pages/about.html for the full brief and design
   rationale. Shares its core mechanism with .roles-card (persistent white
   surface, absolutely-positioned cross-fading layers, bento column reflow
   via :has()), and now ALSO shares roles-card's two-full-layer structure
   (.about-card-original/.about-card-final) rather than the single
   persistent body the first version used -- needed because the title has
   to disappear and the icon has to relocate to the top on hover, which a
   single shared flex column can't express without a layout jump. Only the
   grid's COLUMN tracks reflow (5-across, 1 row) -- there's no row
   dimension to bento here the way the 3x3 Roles grid had, so .about-card
   doesn't need the height:100%/container-type trick roles-card uses to
   fill a reflowing row track; a plain min-height is enough. */
.about-card{
  position:relative; background:none; border:none; padding:0; box-shadow:none;
  min-height:340px;
}
.about-card.reveal{ transition:none; }
.about-card:hover{ transform:none; box-shadow:none; }
.about-card-surface{
  position:absolute; inset:0; overflow:hidden;
  background:var(--white); border:1px solid var(--line-soft);
  border-radius:var(--radius-lg); box-shadow:var(--shadow-sm);
}
/* Baked into the card from the very start now (Liam: "let's bake the
   image into the back of the initial box as well, pre transition") --
   no more solid-colour-only resting state, so no crossfade/opacity is
   needed on this layer at all. The hover "reveal more of the image" the
   card had is a side effect of the card itself widening (bento reflow
   below), not a background-image change here. */
.about-card-photo{
  position:absolute; inset:0; z-index:1;
  background-size:cover; background-position:center; background-repeat:no-repeat;
}
.about-card-original, .about-card-final{
  position:absolute; inset:0; z-index:2;
  display:flex; flex-direction:column; align-items:center; text-align:center;
  padding:1.6rem 1.1rem;
}
.about-card-original{ opacity:1; transition:opacity .3s var(--ease); }
.about-card-final{ opacity:0; pointer-events:none; transition:opacity .3s var(--ease); }
/* Sized up again (27 July 2026 round 3, Liam: "increase the font of the
   initial titles as well") -- from .92x up to 1.2x the base h3 size.
   Font FAMILY is intentionally untouched here -- still var(--font-
   display) -- pending Liam's pick from the alternatives offered for a
   possible sitewide heading font swap; only size changed this round. */
.about-card-title{
  color:var(--white); margin:0; font-size:calc(var(--fs-h3) * 1.2);
  text-shadow:0 2px 12px rgba(0,0,0,.22);
}
/* Icons roughly doubled (Liam: "make the icons... much larger, like
   twice the size they are now") and sized by height so the differing
   per-icon aspect ratios all read as the same visual size -- same
   reasoning as .roles-card-icon img. --icon-scale is a per-card
   correction factor (set inline in about.html) for the visual-weight
   mismatch Liam flagged: identical bounding-box heights still don't
   read as "the same size" when one icon is thin-lined with lots of
   internal negative space (Mission & Values) and another is bold and
   fills its box (Feedback) -- see the long comment in about.html.
   max-width is a safety cap, not the target -- on this card's narrow
   resting width, the one genuinely wide icon (the Who We Partner
   handshake) hits it before it hits the shared height target, so it
   renders a little shorter than the rest. That's a real width
   constraint, not a bug. */
.about-card-icon{
  position:relative; display:flex; align-items:center; justify-content:center;
  height:clamp(100px, 12vw, 160px); margin-top:1rem; width:100%;
}
/* Hover/final icon: round 3 made this deliberately much smaller than
   the resting one; round 4 (27 July 2026, Liam looking at it live)
   walked that back -- "the icons at the top post transition... are too
   small now, we can increase the size" -- so this is bumped back up
   while still staying well under the resting icon and keeping no top
   margin so it hugs the very top of the card. */
.about-card-final .about-card-icon{ margin-top:0; height:clamp(70px, 9vw, 108px); }
.about-card-icon img{
  position:absolute; top:0; left:50%;
  height:100%; width:auto; max-width:90%;
  transform:translateX(-50%) scale(var(--icon-scale, 1));
  filter:drop-shadow(0 10px 24px rgba(0,0,0,.32));
}
/* Hint chevrons pinned to the very bottom of the resting card
   (margin-top:auto in this flex column pushes it there, Liam: "I want
   that at the very bottom of the card") and now pulse in sync rather
   than alternating -- Liam: "I want them both to glow at the same
   time, like it was a press" -- so both svg children share the exact
   same keyframe with no stagger delay between them. */
.about-card-hint{
  margin-top:auto; padding-top:1rem;
  display:flex; align-items:center; justify-content:center; gap:.65rem;
  color:rgba(255,255,255,.78);
  transition:opacity .25s var(--ease);
}
.about-card-hint svg{ animation:aboutHintThrob 1.4s ease-in-out infinite; }
@keyframes aboutHintThrob{
  0%, 100%{ transform:scale(1); opacity:.55; }
  50%{ transform:scale(1.22); opacity:1; }
}
/* Hover layout: icon at the very top (.about-card-final .about-card-icon
   above zeroes its own top margin), description promoted to the
   dominant, much larger element, "Learn more" pinned to the very
   bottom the same way the hint is at rest. Title is dropped from this
   layer entirely (Liam: "get rid of the title... put the icon at the
   very top post expansion"). */
.about-card-final-desc-wrap{ margin-top:1.1rem; }
.about-card-final-desc{
  color:rgba(255,255,255,.96); font-size:1.3rem; line-height:1.45;
  font-weight:500; margin:0;
}
/* Was margin-top:auto (pinned to the very bottom of the card, matching
   the resting hint's own bottom-pin) -- round 4 (27 July 2026) walked
   that back per Liam: "there's too much dead space between the
   description line and the learn more." Pinning to the bottom left a
   large, variable-height gap whenever the description didn't fill the
   card, since auto-margin claims all remaining flex space regardless
   of content. A fixed gap sits it close under the description instead,
   so the two always read as one connected block. */
.about-card-learn-more{
  margin-top:1.75rem; padding-top:0;
  display:inline-flex; align-items:center; gap:.4rem;
  color:var(--white); font-weight:700; font-size:.9rem; white-space:nowrap;
}
.about-card-learn-more svg{ transition:transform var(--dur) var(--ease); }
.about-card-learn-more:hover svg{ transform:translateX(3px); }
/* Card title, re-shown post-transition (3 August 2026, Liam: "have the
   title appear in small font, all caps, at the bottom under the Learn
   More link, centred") -- the title itself already lives once, for
   real, in .about-card-original (see that h3 above); this is a second,
   aria-hidden, visually-styled copy in the final/hover layer only, same
   pattern .about-card-final-desc already uses for the description.
   Sits directly under "Learn more" as a small caption rather than being
   pinned to the very bottom via margin-top:auto -- the two read as one
   grouped block that way, and .about-card-final is already text-align:
   center from its parent, so no extra centring needed here. */
.about-card-final-title{
  margin:.6rem 0 0;
  font-family:var(--font-body); font-weight:700; font-size:.7rem;
  letter-spacing:.12em; text-transform:uppercase;
  color:rgba(255,255,255,.75);
}
@media (hover:hover){
  .about-card:hover .about-card-original{ opacity:0; }
  .about-card:hover .about-card-final{ opacity:1; pointer-events:auto; }
}
.about-card:focus-within .about-card-original{ opacity:0; }
.about-card:focus-within .about-card-final{ opacity:1; pointer-events:auto; }
/* Touch fallback (15 August 2026, mobile/touch pass). .about-card is a
   plain tabindex="0" div, not a link/button, so a tap can't reliably
   trigger the :focus-within rule above on touch browsers -- without a
   fallback the description and "Learn more" link (both live only in
   .about-card-final) would never be reachable on a phone. Same "show
   the hover layer by default" pattern as .roles-card/.teaser-card-expand
   above -- .about-card-final already carries its own small title caption
   underneath the description, so nothing is lost by hiding -original.
   The flex-grow width-expand mechanic itself is already correctly gated
   to min-width:901px only, so this doesn't need to account for that --
   it just controls which of the two stacked text layers is visible. */
@media (hover:none){
  .about-card-original{ opacity:0; }
  .about-card-final{ opacity:1; pointer-events:auto; }
}
@media (prefers-reduced-motion:reduce){
  .about-card-original, .about-card-final, .about-card-hint svg{
    transition:none !important; animation:none !important;
  }
}
@media (max-width:900px){
  .about-card{ min-height:320px; }
  .about-card-icon{ height:clamp(90px, 16vw, 130px); }
}
@media (max-width:640px){
  .about-card{ min-height:300px; }
  .about-card-icon{ height:clamp(80px, 20vw, 110px); }
}
/* Bento-style expand switched from CSS Grid track-size animation to
   Flexbox flex-grow (29 July 2026, Liam: "sometimes they roll out
   smoothly and perfectly and other times they just snap appear...
   really dial it in"). Root cause: transitioning grid-template-columns
   directly (the previous approach, and still what .roles-grid uses) is
   a newer, less mature animation path in the rendering engine than
   flex-grow -- it tweens correctly when a hover runs uninterrupted, but
   when a second trigger arrives WHILE the first transition is still in
   flight (cursor moving from card 2 straight to card 4, or crossing two
   boundaries quickly), the interrupted grid-track interpolation doesn't
   reliably pick up from its current rendered position -- it sometimes
   just jumps straight to the new target instead of continuing to tween.
   That's exactly the "sometimes smooth, sometimes snap" behaviour: the
   .12s anti-flicker delay and .8s duration from earlier rounds were
   real fixes for real problems, but neither touches this, since it's a
   retrigger-robustness issue rather than a speed or flicker one.

   Flex-grow doesn't have this problem -- a much older animatable
   property with a long track record of tweening smoothly from whatever
   its current interpolated value actually is, even mid-retrigger.
   Practical bonus: it also replaces the 10-line, 5-variant
   :has(:nth-child(N)) selector block this used to need (one full row
   layout per possible hovered card) with a single rule -- flex-grow
   only has to be set on the ONE card actually being hovered, and its
   siblings get squeezed automatically as a side effect of the flex
   algorithm, rather than needing the whole row spelled out per case.

   Gated to desktop + real hover, same as before -- .grid-5's own plain,
   evenly-split grid layout (see its responsive rules near the top of
   this file) is untouched below 901px, where "expand left and right"
   doesn't make sense anyway. */
.about-cards-row{ gap:calc(var(--gutter) * .4); }
@media (min-width:901px){
  /* container-type:inline-size makes .about-cards-row's own width
     queryable (as cqw units) by its descendants below -- used by
     .about-card-final's fixed width, which needs a stable reference
     frame that does NOT change as the row's children reflow. */
  .about-cards-row{ display:flex; container-type:inline-size; }
  /* flex-basis:0 + flex-grow-only sizing reuses the exact same ratio
     numbers the old grid-template-columns fr values used (.68 resting,
     1.85 hovered, see that history below) -- flex-grow is a unitless
     proportion between siblings, just like fr was, so the same numbers
     reproduce the identical visual split with no re-tuning needed.
     min-width:0 overrides flexbox's default min-width:auto, which would
     otherwise stop a card from shrinking below its own title/
     description text's intrinsic width once a sibling grows. */
  .about-card{ flex:.68 1 0%; min-width:0; will-change:flex-grow; }
  /* Hover/focus ratio history: round 3 pushed this further apart (Liam:
     "if the post transition cards can be wider, as we minimise the
     other cards we can shrink them back and push them out") -- hovered
     card went to 2.3fr (up from 1.7fr) while the rest dropped to .68fr
     (down from .82fr). Round 4 (27 July 2026, live review) dialled it
     back about 20% -- Liam: "the size of the cards post transition is
     probably a bit too wide" -- so the hovered value settled at 1.85;
     the resting four stay at .68 since only the hover width was ever
     flagged as too big. */
  /* The actual animated property now lives on EACH CARD, not the row --
     unlike grid-template-columns, which lived on .about-cards-row and
     so never collided with .about-card.reveal{transition:none} above
     (that rule exists purely to stop the sitewide .reveal{opacity/
     transform} intro transition bleeding into this component, same
     technique .roles-card uses). Now that flex-grow transitions on
     .about-card itself, that same transition:none would silently kill
     it too if left unaddressed (equal specificity, and it comes first)
     -- so it's explicitly re-enabled here, scoped to desktop, at
     matching .about-card.reveal specificity so it wins on source order
     for the widths where it's actually needed. Same .12s revert-only
     delay and .8s duration as the previous grid version, carried over
     unchanged -- neither of those was the bug, just this property's
     underlying interruption behaviour. */
  .about-card.reveal{ transition:flex-grow .8s var(--ease) .12s; }
  .about-card:hover, .about-card:focus-within{ flex-grow:1.85; }
  /* Reveal-from-centre fix (29 July 2026, Liam: "you can see the text
     sort of resizing in real time... four lines to three lines to two
     lines" as the card expands). Root cause: .about-card-final used
     inset:0, which stretches it to 100% of its immediate parent
     (.about-card) -- and .about-card's width is exactly what's
     animating on hover, so the description text was being laid out
     fresh every frame at whatever width the card happened to be at
     that instant, visibly re-wrapping line by line as it went.

     Fix: give .about-card-final a FIXED width instead, computed once
     against the stable row width (100cqw, see container-type above)
     rather than the animating card width -- (1.85 / 4.57) is this
     card's own share of the row at full hover size, the exact same
     maths the flex-grow ratio above already produces, so the box is
     sized to its true FINAL width from the very first frame and the
     text inside it only ever wraps once. Centred over the card via
     left:50%/translateX(-50%) rather than left/right:0, and clipped by
     .about-card's own overflow:hidden (added below) to whatever the
     card's CURRENT animating width actually is -- so what's visible
     grows outward from the centre to reveal more of an already-
     final-wrapped block of text, instead of the text itself changing
     shape as it goes. */
  .about-card{ overflow:hidden; }
  .about-card-final{
    inset:0 auto 0 50%;
    width:calc((100cqw - var(--gutter) * 1.6) * 1.85 / 4.57);
    transform:translateX(-50%);
  }
}
@media (min-width:901px) and (hover:hover){
  /* Enter direction stays instant (0s delay) -- higher specificity than
     the .12s-delay rule above (:hover adds a pseudo-class) so it wins
     outright regardless of source order, not just on a tie-break. */
  .about-card:hover.reveal{ transition-delay:0s; }
}
.about-card:focus-within.reveal{ transition-delay:0s; }

/* ==========================================================================
   Who We Partner page rebuild (28 July 2026) -- five partner-type sections,
   each pairing one condensed paragraph with a large Law Firm icon (Liam:
   "leave space for icons besides each section... we will display these
   much larger, like pictures", not the small .card-icon treatment used
   elsewhere on the site). Sections alternate .bg-dark-test/.bg-white per
   partner (Boutique dark, Mid-Tier white, Top-Tier dark, Global white,
   In-House dark), and the icon/text column order alternates in lockstep
   via .partner-feature--reverse on the same odd/even sections, so every
   section reads as a fresh zigzag against the one before it.
   Icons stay their processed Mid colour on every background per Liam's
   "make sure we have the colour of them consistently mid" -- no invert
   on the dark sections, so the mid-rose linework sits quietly against
   the rose/ink gradient rather than fighting it. */
.partner-feature{ align-items:center; gap:3.5rem; }
.partner-feature--reverse .partner-feature-media{ order:2; }
.partner-feature--reverse .partner-feature-body{ order:1; }
.partner-feature-media{ display:flex; align-items:center; justify-content:center; }
/* Real icons reinstated (29 July 2026, Liam dropped a "Law Firm Icons"
   folder in) -- replaces both the original plain .partner-feature-icon
   (a bare <img>, never actually shipped) and the 28 July placeholder
   square.

   Round 3 (same day, two follow-up corrections in): rounds 1-2 tried
   growing either the icon alone or the whole card/circle as one fused
   element, using CSS container queries to size the grown state off the
   text column's own height. Liam: "there now seems to be no effects
   whatsoever" (the container-query approach wasn't rendering reliably)
   "...I want the circle and the icon to grow [as SEPARATE things]...
   both reset as we scroll off the section." Full rebuild:

   - .partner-feature-icon-card is now just an inert 300px/190px
     positioning shell (unchanged footprint, so no layout shift) --
     nothing paints on it directly any more. .partner-feature-icon-
     circle and .partner-feature-icon-badge, both absolutely centred
     inside it, are the two things that actually render and animate,
     and they now have INDEPENDENT target sizes -- the circle shrinks
     to scale(0) at full "grown" state while the badge grows past where
     the circle used to be, which a single fused element could never
     express (one element can't have two different target sizes).
   - Sizes are plain fixed px, not container-query-derived -- simpler,
     and (per the "no effects" report) more reliably renders. Badge
     height 153px resting / 216px grown, derived from the icon's actual
     previous rendered size (180px, i.e. 60% of a 300px card) per
     Liam's own numbers: "initial size icons maybe fifteen percent
     smaller... grow to twenty percent larger" -- 180*.85=153,
     180*1.2=216. Circle 210px resting (roomy enough to frame a 153px
     icon with real padding), scaling to 0 at full grow -- Liam gave no
     specific circle number, just "shrinks to nothing", so this one's
     resting size is the actual flagged-as-approximate value if it
     needs adjusting once seen live.
   - Per-section colour logic, confirmed directly with Liam rather than
     guessed a third time: DARK sections (.bg-dark-test) -- icon stays
     WHITE throughout, resting circle is WHITE; growing just shrinks the
     circle away, no colour crossfade ever happens here. LIGHT sections
     (.bg-white) -- icon starts WHITE resting on a Mid-colour circle,
     and growing both shrinks the circle away AND crossfades the icon
     white->Mid, so the final state is a large Mid-colour icon with no
     circle behind it. This is why the crossfade rules below are
     scoped under .bg-white specifically rather than being the
     unscoped default with a dark-section override -- avoids a
     specificity fight entirely, dark sections just never match those
     selectors and stay at their white/white resting values throughout.
   - Trigger mechanism switched from the one-shot sitewide .reveal/
     is-visible pattern to a NEW, separate REPLAYING IntersectionObserver
     (main.js, .partner-scroll-pop -> .in-view) -- per Liam, "both reset
     as we scroll off the section", which the one-shot mechanism can
     never do once it's fired. Same toggle-not-unobserve technique
     .text-underline-reveal already uses elsewhere in main.js, just a
     second dedicated instance. :hover stays as an ADDITIONAL, purely
     CSS-driven trigger for desktop mouse users (naturally already
     resets on mouse-leave, no JS needed there), gated behind
     @media(hover:hover) so it can't get stuck "on" on a touch device;
     .in-view is unconditional since scroll is the primary trigger on
     touch, where there's no hover at all. */
/* Round 4 (29 July 2026, Liam live on the page): "the boutique and
   top-tier sections start with a white icon on a white circle... it
   should be colour icon on white circle. At no point should the same
   colour icon be on the same colour circle." Round 3's dark-section
   logic (icon stays white throughout, only the circle shrinks) broke
   that rule at rest -- white icon directly on the also-white resting
   circle. Fixed by making dark sections fully symmetric with light
   sections instead of a special case: DARK sections now rest as a
   COLOUR icon on a WHITE circle (colour != white, satisfies the rule)
   and crossfade colour->white as they trigger, ending as a large white
   icon once the (also white, matching before) circle has shrunk away
   -- white reads properly against the dark section background. LIGHT
   sections are unchanged (white icon resting on a Mid circle,
   crossfading white->Mid). This is genuinely just the mirror image of
   the light-section logic now, so the crossfade rules below are scoped
   .bg-dark-test/.bg-white as an explicit PAIR rather than one scoped
   override sitting on an unscoped default -- no shared unscoped rule
   left at all, so there's no specificity relationship between the two
   to reason about, just two independent, symmetric cases.

   Also this round: bigger resting AND grown icons ("increase the size
   of the prep and post icons"), a bigger resting circle sized to
   actually guarantee full containment rather than just look roughly
   right ("increase the size of the circles to ensure the icon is
   entirely inside the circle"), and slower/later-triggering motion
   ("slow the timer again and delay the transition a bit more" -- see
   main.js for the rootMargin change, that's the "delay" half; the
   transition durations below are the "slow the timer" half). */
.partner-feature-icon-card{ position:relative; width:100%; max-width:300px; aspect-ratio:1/1; }
/* Circle sized off the actual worst-case icon geometry, not guessed --
   at the new 175px resting height, Global (this set's widest/tallest
   combination once its own 1.15 --icon-scale correction is applied)
   renders at roughly 206x201px, whose corner-to-corner DIAGONAL is
   ~288px -- that's the real number that matters for "entirely inside
   the circle" (a circle has to clear a rectangle's diagonal, not just
   its width or height, for every corner to stay inside it). 300px
   clears that with a small margin to spare, computed directly from the
   asset files the same way icon_scale was. */
.partner-feature-icon-circle{
  position:absolute; top:50%; left:50%;
  width:300px; height:300px; border-radius:50%;
  box-shadow:var(--shadow-sm);
  transform:translate(-50%,-50%) scale(1);
  transition:transform .9s var(--ease), background .45s var(--ease);
}
/* Linen weave (Texture 6) added to both resting circle colours (14
   August 2026, Liam: "on the who we partner page, with the pre-
   transition icons that have the circular background, can we give the
   circular backgrounds the linen texture?"). Colour-matched the same
   light/dark convention used everywhere else this texture appears --
   pink weave on the white circle (dark-section variant), white weave
   on the rose-500 circle (light-section variant). border-radius:50%
   on .partner-feature-icon-circle itself already clips background
   painting to the circle shape, so no overflow:hidden needed. 200px
   tile -- smaller than the 400/700px tiles used on bigger surfaces,
   scaled to this circle's own 300px diameter so a couple of repeats
   show rather than a single indistinct smear. */
.bg-dark-test .partner-feature-icon-circle{
  background-color:var(--white);
  background-image:url('/assets/img/photos/texture-weave-pink.webp');
  background-position:center; background-size:200px 200px; background-repeat:repeat;
}
.bg-white .partner-feature-icon-circle{
  background-color:var(--rose-500);
  background-image:url('/assets/img/photos/texture-weave-white.webp');
  background-position:center; background-size:200px 200px; background-repeat:repeat;
}
.partner-feature-icon-card.in-view .partner-feature-icon-circle{ transform:translate(-50%,-50%) scale(0); }
@media (hover:hover){
  .partner-feature-icon-card:hover .partner-feature-icon-circle{ transform:translate(-50%,-50%) scale(0); }
}
/* Icons weren't rendering at all (29 July 2026, Liam: "there are no
   icons visable at all?"). Root cause: .partner-feature-icon-badge was
   position:absolute with only `left` set and no explicit width, and
   BOTH its children (the mid/white <img> layers) were also position:
   absolute -- meaning the badge had zero normal-flow content to
   shrink-to-fit around, an edge case real browsers don't handle
   consistently (some computed it to a real 0-width box, which can
   cascade into percentage-height children failing to resolve
   depending on engine). Fixed by removing that layer as a sizing
   dependency entirely rather than patching around it: .partner-feature-
   icon-badge is now display:contents (no box of its own at all, purely
   a DOM wrapper so --icon-scale still inherits down to both images),
   and the two <img> layers are sized directly and independently, in
   plain px, with no percentage-of-parent chain for anything to fail
   along. --icon-scale is the same per-icon visual-weight correction
   approach-icon/about-card use, values computed from each icon's own
   opaque-pixel coverage -- see the icon_scale note in
   who_we_partner_data.py; flagged as first-pass, not yet checked
   against a live render. */
.partner-feature-icon-badge{ display:contents; }
/* Resting 153px -> 175px, grown 281px -> 325px (both "increase the size
   of the prep and post icons", roughly the same ~15% step applied to
   both so the resting->grown jump still reads proportionally the same
   as before). Crossfade slowed 0.4s -> 0.6s and the size transition
   0.6s -> 0.9s, matching the circle's own 0.9s above ("slow the timer
   again").

   30 July 2026, Liam: "the post-transition icons for mid-tier and
   top-tier need to be larger". The shared 175px/325px heights above are
   identical for all 5 icons -- the only per-icon size lever is the
   --icon-scale custom property, and it was multiplying BOTH the resting
   and grown state by the same factor. Bumping icon_scale itself for
   just these two would also inflate their RESTING size, risking the
   "icon entirely inside the circle" containment that --icon-scale was
   sized against (mid-tier's resting diagonal at a meaningfully bigger
   scale would run close to the 300px circle with almost no margin).
   So the grown state now reads from a SEPARATE custom property,
   --icon-scale-grown, decoupled from the resting --icon-scale entirely.
   Every partner sets both in the template; three of the five just set
   --icon-scale-grown equal to their existing --icon-scale (no visible
   change), mid-tier and top-tier set a larger one. Falls back to
   --icon-scale if a badge somehow doesn't set it, so this can't ever
   resolve to an invalid empty value. transform is now also part of the
   transition list (previously constant across states, so untransitioned
   was fine -- now it actually changes between rest and grown for every
   icon, so it needs to animate smoothly like height already does). */
.partner-feature-icon-mid, .partner-feature-icon-white{
  position:absolute; top:50%; left:50%; height:175px; width:auto;
  transform:translate(-50%,-50%) scale(var(--icon-scale, 1));
  filter:drop-shadow(0 8px 20px rgba(74,67,65,.22));
  transition:opacity .6s var(--ease), height .9s var(--ease), transform .9s var(--ease);
}
.partner-feature-icon-card.in-view .partner-feature-icon-mid,
.partner-feature-icon-card.in-view .partner-feature-icon-white{
  height:325px;
  transform:translate(-50%,-50%) scale(var(--icon-scale-grown, var(--icon-scale, 1)));
}
@media (hover:hover){
  .partner-feature-icon-card:hover .partner-feature-icon-mid,
  .partner-feature-icon-card:hover .partner-feature-icon-white{
    height:325px;
    transform:translate(-50%,-50%) scale(var(--icon-scale-grown, var(--icon-scale, 1)));
  }
}
/* Resting colour, symmetric pair (see the round-4 comment above
   .partner-feature-icon-circle for the full rationale): dark sections
   rest colour-on-white, light sections rest white-on-colour, and each
   crossfades to the opposite icon colour as it triggers. */
.bg-dark-test .partner-feature-icon-mid{ opacity:1; }
.bg-dark-test .partner-feature-icon-white{ opacity:0; }
.bg-white .partner-feature-icon-mid{ opacity:0; }
.bg-white .partner-feature-icon-white{ opacity:1; }
.bg-dark-test .partner-feature-icon-card.in-view .partner-feature-icon-mid{ opacity:0; }
.bg-dark-test .partner-feature-icon-card.in-view .partner-feature-icon-white{ opacity:1; }
.bg-white .partner-feature-icon-card.in-view .partner-feature-icon-mid{ opacity:1; }
.bg-white .partner-feature-icon-card.in-view .partner-feature-icon-white{ opacity:0; }
@media (hover:hover){
  .bg-dark-test .partner-feature-icon-card:hover .partner-feature-icon-mid{ opacity:0; }
  .bg-dark-test .partner-feature-icon-card:hover .partner-feature-icon-white{ opacity:1; }
  .bg-white .partner-feature-icon-card:hover .partner-feature-icon-mid{ opacity:1; }
  .bg-white .partner-feature-icon-card:hover .partner-feature-icon-white{ opacity:0; }
}
.partner-feature-body .eyebrow{ margin-bottom:.5rem; }
.partner-feature-body h2{ margin-bottom:1rem; }
@media (max-width:820px){
  .partner-feature{ grid-template-columns:1fr; gap:2rem; text-align:center; }
  .partner-feature-media{ order:-1 !important; }
  .partner-feature-body{ order:0 !important; }
  .partner-feature-body .eyebrow{ justify-content:center; }
}

/* ==========================================================================
   Mission & Values page: Our Values, single-column accordion cards
   (28 July 2026, Liam: "let's make these vertically stacked cards...
   start with just the titles and bi line... on hover, they expand to
   show text"). Rebuilt again the same day once the section moved to a
   dark background: "I want these buttons to start on the dark
   background as white text on like a transparent card... just the
   value... centred. As we hover, the box goes out and extends, the
   value shoots over to the left from being centre, the byline comes
   out beside it, and then the drop down has the line of text under it.
   And then we make these white cards with the AS background baked into
   them, sized appropriately for the card."

   Three things happening on hover, all driven by CSS only:
   1. Card itself: transparent (dark section shows through) -> solid
      white with the pink monogram baked in (.bg-white's own recipe --
      texture layer listed FIRST in background-image, gradient/colour
      second, per the layer-order fix from the Our Approach saga -- the
      opaque layer must never be listed before the texture or it hides
      it regardless of alpha). Text colours flip white->Dark/Mid/ink-
      soft to stay readable once the card goes white.
   2. Word + byline row: .value-word-row centres its content via
      justify-content:center at ALL times (this value never changes, so
      there's no snap -- justify-content itself isn't animatable). The
      byline lives inside .value-subline-wrap, a nested grid whose
      SINGLE column goes 0fr->1fr on hover, the horizontal cousin of
      the grid-template-ROWS 0fr->1fr trick already proven elsewhere on
      this site (.teaser-card-expand-desc-wrap, .value-accordion-desc-
      wrap below) for a smooth reveal that maps to the content's real
      size with no snapping/overshoot. As that column grows from 0 to
      its content width, the row's total content grows, and because the
      row stays centred as a whole, the word is pushed toward the left
      edge of that centred pair as a natural side effect -- this is the
      "shoots over to the left" motion, with no separate transform or
      per-word width calculation needed.
   3. Description: identical grid-template-rows 0fr->1fr reveal already
      built for the first version of this component, unchanged. */
.values-stack-list{ display:flex; flex-direction:column; gap:1rem; max-width:960px; margin-inline:auto; }
/* Hovered card isn't confined to match its resting siblings' width
   (28 July 2026, Liam: "don't feel confined to the same width as the
   ones that are not covered... make it bigger so we can really make
   the text bigger and pop more"). Resting cards sit at 680px (matching
   the section's original narrower column) via their own max-width +
   margin-inline:auto for centring; the parent list is widened to 960px
   so there's real room either side for the hovered card to grow into
   -- its max-width/margin below expand to fill that wider column while
   staying centred, rather than being capped at the same 680px. */
.value-accordion{
  position:relative; cursor:pointer; width:100%; max-width:680px; margin-inline:auto;
  background-color:transparent; background-image:none;
  border:1px solid rgba(255,255,255,.3); border-radius:var(--radius-lg);
  /* overflow:hidden added 28 July 2026 to fix "herky jerky" transitions
     Liam flagged, worst on Understanding. Root cause, confirmed by
     measuring actual rendered text widths rather than guessing: the
     card's max-width animates 680px -> 960px over .4s, but the
     value-word-row (word + revealed byline) has no width cap of its
     own -- at hover font sizes, "Understanding" + its byline measure
     ~627px wide, wider than the card's own STARTING 680px box (minus
     padding, ~584px available). Loyalty's word+byline fit even at the
     starting width; Commitment's and Respect's are only marginally
     over; Understanding's is the most overshot of the four by a wide
     margin -- which is exactly why it was the one that visibly
     glitched/spilled rather than growing smoothly: the text was
     briefly wider than the box during the early part of the transition,
     before max-width had caught up. overflow:hidden clips that
     transient overshoot cleanly against the card's own rounded corners
     instead of letting it spill past the border, so growth reads as a
     smooth reveal regardless of any given value word's length. Doesn't
     affect the glow -- box-shadow paints outside the overflow-clipped
     content area regardless of this property. */
  overflow:hidden;
  padding:1.75rem 2rem; box-shadow:none;
  transition:background-color .4s var(--ease), border-color .4s var(--ease),
             box-shadow .4s var(--ease), transform .4s var(--ease), padding .4s var(--ease),
             max-width .4s var(--ease);
}
.value-word-row{ display:flex; justify-content:center; align-items:baseline; }
.value-word{
  margin:0; color:var(--white); white-space:nowrap;
  transition:color .4s var(--ease), font-size .4s var(--ease);
}
/* Reveal slowed down 28 July 2026 (Liam: "can we slow down the byline
   reveal", then "can we slow it down again"). Round 1 bumped .4s -> .65s;
   round 2 bumps .65s -> 1s here and on .value-subline below, so the
   byline grows in and fades in more gradually still. Delay (.1s, set in
   the hover rule) left as-is -- still starts shortly after hover begins,
   just takes longer to finish. */
.value-subline-wrap{
  display:grid; grid-template-columns:0fr; margin-left:0; overflow:hidden;
  transition:grid-template-columns 1s var(--ease), margin-left 1s var(--ease);
}
/* Em dash lead-in, scoped to this component only (28 July 2026, Liam:
   "can we start the bi lines with an emdash, only used here in the
   values section" -- a deliberate, explicit exception to the sitewide
   no-em-dash content rule, applied via CSS ::before rather than typed
   into the copy so it's easy to find/remove later and can never leak
   into any other component's text by accident). */
.value-subline::before{ content:"\2014\00A0"; }
.value-subline{
  min-width:0; overflow:hidden; white-space:nowrap; margin:0;
  color:rgba(255,255,255,.85); font-weight:700; font-size:.9rem;
  opacity:0; transition:opacity .95s var(--ease), color 1s var(--ease), font-size 1s var(--ease);
}
/* grid-template-rows duration/delay tuned 28 July 2026 to fix "herky
   jerky" transitions Liam flagged, worst on Understanding (root-caused
   below in the hover rule's comment). */
.value-accordion-desc-wrap{
  display:grid; grid-template-rows:0fr; margin-top:0;
  transition:grid-template-rows .35s var(--ease), margin-top .35s var(--ease);
}
.value-accordion-desc{
  color:rgba(255,255,255,.85); margin:0; overflow:hidden; min-height:0; text-align:center;
  opacity:0; transform:translateY(6px);
  transition:opacity .35s var(--ease), transform .35s var(--ease), color .4s var(--ease), font-size .4s var(--ease);
}
@media (hover:hover){
  .value-accordion:hover, .value-accordion:focus-within{
    max-width:960px;
    /* Base swapped from pure white to Blush (28 July 2026, Liam: "make
       the white fill-in a little more of a cream colour so it blends a
       little better with the dark background"). --blush-100 is already
       a defined brand token (warm pale pink, #F2E2E0) that just wasn't
       used on this component yet -- reaching for it here rather than a
       one-off colour keeps this on-palette. The translucent white wash
       below still sits on top of it, so the result is a soft cream tone
       rather than full blush -- a nudge, not a repaint, per "a little
       more." Text contrast is effectively unaffected: blush-100 is
       barely darker than white, so rose-600/rose-500 hold the same
       legibility. */
    background-color:var(--blush-100);
    /* "As" monogram swapped for the linen weave (14 August 2026, Liam:
       "on the mission and values page, can we remove the As texture
       from the post-transition values and just give it the linen
       treatment"). Same texture-weave-pink.webp used everywhere else
       this light/blush family of fills appears. */
    background-image:
      linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)),
      url('/assets/img/photos/texture-weave-pink.webp');
    background-size:cover, 400px 400px;
    background-position:center, center top;
    background-repeat:no-repeat, repeat;
    border-color:var(--line-soft);
    /* Soft ambient glow, 28 July 2026 (Liam: "should we give the values
       cards a white glow post transition?"). A wide, diffuse, low-opacity
       shadow layered UNDER the existing shadow-md (shadow-md paints
       first/on top since it's listed first) -- reads as the card
       genuinely lighting up against the dark .bg-dark-test section
       behind it, rather than just lifting off it. Already animates for
       free: box-shadow is already in this rule's transition list from
       the base .value-accordion rule, so the glow fades in/out along
       with everything else, no new transition needed.
       Retinted white->Blush same day (Liam: "did we change the glow to
       blush as well?" -- "yeah lets do that") to match the card fill
       swap above, so the whole hover state reads as one consistent warm
       tone instead of a cream card with a colder white halo around it.
       rgba(242,226,224,...) is --blush-100's own RGB (#F2E2E0). */
    box-shadow:var(--shadow-md), 0 0 70px 6px rgba(242,226,224,.45);
    padding:2.5rem 3rem; transform:translateY(-2px);
  }
  /* Word/byline grow via real font-size, not transform:scale (fixed 28
     July 2026, Liam's screenshot: "the value and the bi-line are
     overlapping"). transform doesn't affect layout -- it only repaints
     the element larger in place -- so the flex row was still reserving
     box space for the UNSCALED text while the scaled paint visibly
     spilled into its neighbour. Switching to a genuine font-size
     increase means the row's own layout (and the subline's 0fr->1fr
     grid-column, which sizes to the subline's real rendered width)
     both account for the larger text correctly, so there's nothing
     left to overlap. */
  .value-accordion:hover .value-word, .value-accordion:focus-within .value-word{
    color:var(--rose-600); font-size:calc(var(--fs-h3) * 1.3);
  }
  .value-accordion:hover .value-subline-wrap, .value-accordion:focus-within .value-subline-wrap{
    grid-template-columns:1fr; margin-left:1rem;
  }
  .value-accordion:hover .value-subline, .value-accordion:focus-within .value-subline{
    opacity:1; color:var(--rose-500); font-size:1.2rem; transition-delay:.1s;
  }
  /* Secondary hardening alongside the overflow:hidden fix above (28 July
     2026, same "herky jerky" report). Independent of the word-row
     overflow issue: the card's own max-width animates 680px -> 960px at
     the same time this description's grid-template-rows:0fr->1fr was
     measuring its target height, and "1fr" resolves to the paragraph's
     intrinsic height AT THE CURRENT WIDTH -- a moving target for the
     whole .4s. Checked which cards' descriptions actually change line
     count across that width range: Commitment/Loyalty/Respect all wrap
     from 3 lines down to 2 as the card widens; Understanding's happens
     not to (2 lines throughout, which is why overflow:hidden above was
     the real fix for its specific glitch, not this). Delaying this
     reveal until the width transition has essentially finished
     (transition-delay:.3s) means the three that DO re-wrap always
     measure against a stable, final width instead of a moving one, so
     they don't get their own version of this same class of glitch. */
  .value-accordion:hover .value-accordion-desc-wrap, .value-accordion:focus-within .value-accordion-desc-wrap{
    grid-template-rows:1fr; margin-top:.85rem; transition-delay:.3s;
  }
  /* Post-transition font sizes nudged up across the board, 28 July 2026
     (Liam: "with the blush background, can we nudge up the size of all
     the fonts in the post transition") -- word 1.15x->1.3x, byline
     1.05rem->1.2rem (both above), description gets a new hover-only bump
     from the shared 1x body size up to 1.15x here (it had no hover
     override before). font-size was added to .value-accordion-desc's own
     transition list so this animates in smoothly like the word/byline
     already do, rather than snapping.
     Delay bumped .15s -> .4s same day as the jank fix above, so the text
     itself only starts fading in just after its row-space has begun
     opening up (which now starts at .3s), rather than fading in while
     the box above it is still resizing. */
  .value-accordion:hover .value-accordion-desc-wrap .value-accordion-desc,
  .value-accordion:focus-within .value-accordion-desc-wrap .value-accordion-desc{
    opacity:1; transform:translateY(0); color:var(--ink-soft); font-size:calc(var(--fs-body) * 1.15); transition-delay:.4s;
  }
}
@media (prefers-reduced-motion:reduce){
  .value-accordion, .value-word, .value-subline-wrap, .value-subline,
  .value-accordion-desc-wrap, .value-accordion-desc{ transition:none !important; }
}
/* Touch fallback (15 August 2026, mobile/touch pass). .value-accordion
   is a plain tabindex="0" div (not <details>, not a button/link), so a
   tap can't reliably trigger :focus-within here either -- same problem
   as .about-card above. Without this, the byline and full description
   (both start at opacity:0/zero-track grid reveals) would never show on
   a phone -- on Mission & Values that's the entire substance of each
   value, not a flourish. Card growth to max-width:960px is skipped here
   deliberately -- that's a desktop-only "make room to breathe" move,
   meaningless on a phone where the card is already full-width -- but
   the fill colour/texture, word/byline/description reveal all apply so
   touch gets the complete content. */
@media (hover:none){
  .value-accordion{
    background-color:var(--blush-100);
    background-image:
      linear-gradient(rgba(255,255,255,.55), rgba(255,255,255,.55)),
      url('/assets/img/photos/texture-weave-pink.webp');
    background-size:cover, 400px 400px;
    background-position:center, center top;
    background-repeat:no-repeat, repeat;
    border-color:var(--line-soft);
    box-shadow:var(--shadow-md), 0 0 70px 6px rgba(242,226,224,.45);
  }
  .value-accordion .value-word{ color:var(--rose-600); font-size:calc(var(--fs-h3) * 1.3); }
  .value-accordion .value-subline-wrap{ grid-template-columns:1fr; margin-left:1rem; }
  .value-accordion .value-subline{ opacity:1; color:var(--rose-500); font-size:1.2rem; }
  .value-accordion .value-accordion-desc-wrap{ grid-template-rows:1fr; margin-top:.85rem; }
  .value-accordion .value-accordion-desc{
    opacity:1; transform:none; color:var(--ink-soft); font-size:calc(var(--fs-body) * 1.15);
  }
}
