/* ==========================================================================
   Talent Match Recruitment
   Option A - built from the Figma comp
   --------------------------------------------------------------------------
   1.  Tokens
   2.  Reset + base
   3.  Utilities
   4.  Buttons
   5.  Header + nav
   6.  Hero
   7.  Puzzle pieces (hero collage, clipped from img/pieces artwork)
   8.  Credentials bar
   9.  About
   10. Testimonials + carousel
   11. How can we help
   12. Footer
   13. Responsive + motion
   14. Interior pages (about, employers, candidates, approach,
       testimonials, jobs, contact, privacy)
   15. Career timeline (about page)
   16. Featured testimonial + review menu (testimonials page)
   17. Accreditations (SEEK recommended, NPA Platinum)
   18. Per-page signature blocks (process, compare, sectors,
       commitments, job filter, pull quote)
   ========================================================================== */

/* 1. Tokens
   ========================================================================== */
:root {
  /* colour - the logo's own gradient, #FE0000 -> #C50304, as supplied by
     Laurie. It replaces a ramp sampled off a flattened PNG of the mark, which
     read the ends as #FF0000 and about #C40000 and assumed pure hue 0.

     The real dark end carries a little green and blue (G=3, B=4), so the ramp
     is not hue-locked. Matching the logo matters more than keeping the
     channels clean, so the tokens below follow it rather than tidy it.

     The comp's original #BB0214 is still wrong - that is hue 354, a crimson,
     and it is what made earlier builds read darker than the mark in the
     navbar. */
  /* Matched to the mark. img/logo-talentmatch.png is #FF0000 across 23,000 of
     its pixels, and the old #E20202 sat visibly duller than it - side by side
     in the navbar the wordmark looked washed next to its own logo. The ramp
     bottom came up with it: at #C50304 a big red surface fell away to a brick
     colour by the time it reached the foot of the gradient. */
  --red-bright: #FF0000;   /* gradient top - the logo red exactly */
  --red:        #FB0102;   /* gradient midpoint. Primary UI red. See note below */
  --cream-piece: #F3F1EF;  /* the cream inside Laurie's puzzle drawings */
  --red-piece:  #FD0002;   /* the red the puzzle pieces sit at - the artwork ships #E00000, which
                              read too dark against the UI red, so both routes are pulled to this */
  --red-dark:   #DC0203;   /* gradient bottom. Ramp only - red never darkens on hover */
  --red-deep:   #9B0203;   /* below the ramp. Unused since the quote card went grey */
  --red-tint:   #FFF1F0;   /* faintest wash, same hue */

  /* Contrast note, because brightening --red cost some. #FB0102 on white is
     4.11:1, where the old #E20202 was 4.96:1. Large text still clears its 3:1
     bar comfortably, and so does anything white sitting ON red. What now sits
     under the 4.5:1 needed for normal-size text is red text on white at body
     sizes - .eyebrow and inline links in .prose. #EF0202 is the brightest red
     that still makes 4.5:1, if that matters more than matching the mark. */

  /* Red surfaces hold their colour on hover and grow a hair instead of
     darkening, so the brand red never shifts shade under the cursor. One knob
     keeps every red control growing by the same amount. Outline and
     stroke-only controls are untouched by this - they still fill red. */
  --red-hover-scale: 1.03;

  /* The logo's arrows are a gradient, not a flat fill. Big red surfaces reuse
     that top-bright / bottom-deep ramp so they echo the mark. */
  /* the logo gradient itself, end to end */
  --red-grad: linear-gradient(160deg, var(--red-bright) 0%, var(--red) 45%, var(--red-dark) 100%);
  --ink:        #1F1A17;   /* headings */
  --ink-900:    #1F1F1F;   /* credentials bar, footer */
  --ink-950:    #141414;   /* career timeline - a step darker, so the band
                              reads as its own panel next to the footer */
  --body:       #4E4A47;   /* body copy */
  --cream:      #FBF8F7;   /* about section + cream puzzle pieces */
  --cream-200:  #EDE9E7;   /* cream puzzle piece fill */
  --grey-200:   #E0DCDA;
  --line:       #E4E0DE;
  --white:      #FFFFFF;

  /* type */
  --font-display: "Inria Serif", Georgia, "Times New Roman", serif;
  --font-body: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  --fs-h1: clamp(2.5rem, 4.9vw, 4.375rem);   /* 40 -> 70 */
  --fs-h2: clamp(1.875rem, 3.1vw, 2.75rem);  /* 30 -> 44 */
  --fs-h3: clamp(1.375rem, 1.8vw, 1.625rem); /* 22 -> 26 */
  --fs-quote: clamp(1.25rem, 2.1vw, 1.875rem);
  --fs-body: 1rem;
  --fs-small: 0.9375rem;
  --fs-label: 0.75rem;

  /* layout */
  --container: 1216px;
  --gutter: clamp(1.25rem, 4vw, 2rem);
  --section-y: clamp(4rem, 7vw, 6rem);
  --header-h: 112px;

  --ease: cubic-bezier(.2, .7, .3, 1);
}

/* 2. Reset + base
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--white);
  color: var(--body);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.75;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img, svg { display: block; max-width: 100%; }
img { height: auto; }

h1, h2, h3 {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-weight: 400;
  line-height: 1.12;
}

p { margin: 0 0 1.25rem; }
p:last-child { margin-bottom: 0; }

/* ol as well as ul - the numbered process steps and the career timeline are
   ordered lists, and the UA's 40px indent throws off both the grid tracks and
   the scroll-snap positions on the timeline rail. */
ul, ol { margin: 0; padding: 0; list-style: none; }

a { color: inherit; }

button { font: inherit; color: inherit; cursor: pointer; }

:focus-visible {
  outline: 3px solid var(--red);
  outline-offset: 3px;
  border-radius: 2px;
}

/* 3. Utilities
   ========================================================================== */
.container {
  width: 100%;
  max-width: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.u-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}



.skip-link {
  position: absolute;
  left: 1rem; top: -4rem;
  z-index: 200;
  padding: .75rem 1.25rem;
  background: var(--red);
  color: var(--white);
  text-decoration: none;
  transition: top .2s var(--ease);
}
.skip-link:focus { top: 1rem; }

.eyebrow {
  margin: 0 0 1rem;
  color: var(--red);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .14em;
  line-height: 1.4;
  text-transform: uppercase;
}
.eyebrow--light { color: var(--white); text-align: center; }

.section-title {
  font-size: var(--fs-h2);
  letter-spacing: -0.01em;
}
.section-title em {
  display: block;
  color: var(--red);
  font-style: italic;
}
.section-title--light { color: var(--white); }
.section-title--light em { display: inline; color: var(--white); }
.section-title--centred { text-align: center; }
.section-title--centred em { display: inline; color: inherit; }

/* 4. Buttons
   ========================================================================== */
.btn {
  --btn-fg: var(--white);
  --btn-bg: var(--red);
  --btn-bd: var(--red);
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.75rem;
  min-height: 52px;
  padding: .75rem 1.5rem;
  border: 1px solid var(--btn-bd);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .1em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  transition: background-color .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease), transform .2s var(--ease);
}
.btn--solid:hover { transform: scale(var(--red-hover-scale)); }

.btn--outline { --btn-fg: var(--ink); --btn-bg: transparent; --btn-bd: var(--red); }
.btn--outline:hover { --btn-fg: var(--white); --btn-bg: var(--red); }

.btn--ghost { --btn-fg: var(--white); --btn-bg: transparent; --btn-bd: rgba(255,255,255,.6); }
.btn--ghost:hover { --btn-fg: var(--red); --btn-bg: var(--white); --btn-bd: var(--white); }

/* primary action sitting ON a red surface - the inverse of btn--solid */
.btn--light { --btn-fg: var(--red); --btn-bg: var(--white); --btn-bd: var(--white); }
.btn--light:hover { --btn-fg: var(--white); --btn-bg: transparent; --btn-bd: var(--white); }

/* Arrow traced from the supplied Vector.png (13x11 - short shaft, open 45 deg
   head). Painted as a mask over currentColor so one rule serves the white
   arrow on red buttons and the dark arrow on outline buttons, and stays sharp
   at any size instead of aliasing like the old rotated-border chevron. */
.btn__arrow {
  flex: none;
  width: 17px; height: 14px;
  background: currentColor;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 11'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M.9 5.5h10.9M7 .95l4.85 4.55L7 10.05'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 11'%3E%3Cpath fill='none' stroke='%23000' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M.9 5.5h10.9M7 .95l4.85 4.55L7 10.05'/%3E%3C/svg%3E") center / contain no-repeat;
  transition: transform .2s var(--ease);
}
.btn--arrow:hover .btn__arrow { transform: translateX(4px); }

/* 5. Header + nav
   ========================================================================== */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--white);
  transition: box-shadow .25s var(--ease);
}
.site-header.is-stuck { box-shadow: 0 1px 0 var(--line), 0 10px 30px -22px rgba(31,26,23,.5); }

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  min-height: var(--header-h);
}

.site-header__logo { flex: none; display: block; position: relative; }
/* 1.2x the original clamp(140px, 12vw, 158px) */
.site-header__logo img { width: clamp(168px, 14.4vw, 190px); }

/* Logo intro film
   -------------------------------------------------------------------------
   The film is laid over the still, in the same box, and starts invisible. JS
   only reveals it once playback has actually begun, so a slow download, a
   refused autoplay or no JS at all leaves the static logo in place rather
   than a hole in the navbar. It is removed the moment it ends, and the still
   underneath is uncovered a frame earlier so the swap never shows through. */
/* The film is a WebM with a real alpha channel, so it composites over the
   navbar on its own - nothing here has to cut a background out of it. */
.site-header__logo-intro {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  pointer-events: none;
  /* Its own compositing layer. The film is a full-colour alpha WebM being
     scaled into a ~170px box with an SVG filter over it, and without this the
     whole header repaints on every decoded frame - which is what the stutter
     was. contain:paint on the link stops that invalidation escaping into the
     nav beside it. */
  transform: translateZ(0);
  backface-visibility: hidden;
  /* The handoff at the end is a fade, not a cut. The still underneath is
     uncovered at full opacity first and the film ends on that exact frame, so
     while this fades out the two are identical and nothing flickers between
     them - in particular there is no white gap, because the still is already
     there. Keep this in step with INTRO_FADE_MS in js/main.js. */
  transition: opacity .32s var(--ease);
}
/* Pending is the beat before the film starts: the slot is deliberately empty,
   so the mark is drawn on from nothing rather than flashing up finished and
   then restarting. JS caps how long this can last and uncovers the still if
   the film never gets going. */
.site-header__logo.is-intro-pending img,
.site-header__logo.is-intro-playing img { opacity: 0; }
.site-header__logo.is-intro-playing .site-header__logo-intro { opacity: 1; }
/* Only while the film is actually running - will-change left on permanently
   keeps a layer alive for an element that is about to be removed. */
.site-header__logo.is-intro-playing .site-header__logo-intro { will-change: opacity; }
.site-header__logo.is-intro-pending,
.site-header__logo.is-intro-playing { contain: paint; }

.site-nav__list {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 2.6vw, 2.5rem);
}
/* The underline is drawn, not switched on: a pseudo-element scaled from 0 to
   full width, left to right. A border-bottom can only fade its colour in, and
   the whole rule appearing at once read as a state toggle rather than as a
   response to the cursor. transform-origin flips on the way out so it retracts
   back the way it came instead of jumping. */
.site-nav__list a {
  position: relative;
  font-size: .875rem;
  font-weight: 500;
  line-height: 1;
  text-decoration: none;
  color: var(--ink);
  padding-block: .5rem;
  transition: color .2s var(--ease);
}
.site-nav__list a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 1px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .28s var(--ease);
}
.site-nav__list a:hover { color: var(--red); }
.site-nav__list a:hover::after,
.site-nav__list a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left center;
}

.site-nav__cta { display: none; }
.site-header__cta { flex: none; }

.nav-toggle {
  display: none;
  flex: none;
  width: 48px; height: 48px;
  padding: 0;
  border: 1px solid var(--line);
  background: var(--white);
  position: relative;
}
.nav-toggle__bars,
.nav-toggle__bars::before,
.nav-toggle__bars::after {
  position: absolute;
  left: 50%;
  width: 20px; height: 2px;
  background: var(--ink);
  transform: translateX(-50%);
  transition: transform .2s var(--ease), opacity .15s var(--ease);
}
.nav-toggle__bars { top: 50%; margin-top: -1px; }
.nav-toggle__bars::before { content: ""; top: -7px; }
.nav-toggle__bars::after { content: ""; top: 7px; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::before { top: 0; transform: translateX(-50%) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bars::after { top: 0; transform: translateX(-50%) rotate(-45deg); }

/* 6. Hero
   ========================================================================== */
/* The collage used to be the tallest thing here and carried the hero's
   whitespace itself, so the padding was almost nothing. It lost its top row
   and is now wider than it is tall, so the band has to supply that room -
   otherwise the headline sits hard against the navbar. Bottom stays a little
   deeper than top, so the hero reads as sitting on the dark bar below rather
   than floating between the two. */
/* Tighter than the standard section rhythm on both edges. The hero is the
   first thing under a 112px header, and the band below it is a thin grey
   credentials strip - the old padding pushed that strip far enough down that
   the first screen was mostly white space around the collage. */
.hero { padding-block: clamp(2.25rem, 4.4vw, 4rem) clamp(2.75rem, 5.4vw, 5rem); }

.hero__inner {
  display: grid;
  /* The copy column is set by the headline, not by taste. "The right match."
     has to hold one line, and at the top of its clamp (76px, from 1434px up)
     it measures ~562px against a 1216px container - 46.2%. That is the worst
     case at any width, because the font caps before the container does. 48%
     clears it with slack for font rounding; go below and the first line breaks
     into two. The collage takes the rest. */
  grid-template-columns: minmax(0, 48%) minmax(0, 1fr);
  align-items: center;
  /* the gutter is kept tight so its slack goes to the collage */
  gap: clamp(1.5rem, 2.6vw, 2.5rem);
}
/* The collage fills its column. It used to be held to 88% and centred, which
   was right when the hero was shorter - now it is the piece doing the work
   and it runs the full height between the navbar and the dark bar. */
.hero__inner > .collage { max-width: 100%; margin-inline: auto; }

/* "The right match." sits on one line, so the line is now three words long
   rather than two and needs room. The lower bound of the clamp is raised
   least, because that is the phone case where the line is tightest. */
.hero__title {
  /* Capped at 76px rather than 84. The headline is what sets the width of
     the copy column - it has to hold "The right match." on one line - so its
     ceiling is also the ceiling on how big the puzzle beside it can be. Eight
     pixels off the cap bought the collage about 11%. */
  font-size: clamp(2.625rem, 5.3vw, 4.75rem);   /* 42 -> 76 */
  /* Regular, not bold. At 84px the display face carries the line on size
     alone, and the red accent does the emphasis - the bold weight was
     shouting over both. "Every time." was already 400 below. */
  font-weight: 400;
  letter-spacing: -0.02em;
  text-wrap: balance;
}
.hero__title-line { display: block; }
/* <b> for the accent rather than a span: it is the emphasised half of the
   line, and it must stay inline so "The right match." reads as one line. */
.hero__title-accent { color: var(--red); font-weight: inherit; }
.hero__title-line--italic { font-weight: 400; font-style: italic; }
/* Per-letter hover on "match."

   The letters are split by js/main.js, so this only ever applies once that has
   run - with no JS the word is a plain <b> and nothing here matches.

   The wave runs off --i, set per letter, and it is an animation rather than a
   transition because the letter has to overshoot and settle rather than travel
   between two states. Hovering the word runs the whole wave; hovering one
   letter holds that letter up on its own, so the word responds either way.

   inline-block is what makes the transform apply at all - an inline box
   ignores translate and scale. */
.hero__letter {
  display: inline-block;
  transform-origin: 50% 80%;   /* pivot near the baseline, so it grows upward */
  will-change: transform;
}
/* Per letter, not per word. Hovering the word ran one wave across all six and
   then stopped, so moving along it did nothing - the whole thing fired on the
   first letter you touched and the rest of the gesture was dead. Each letter
   answers its own hover instead, so the word bounces under the cursor as it
   travels and you can pick out a single letter if you want to.

   Transition rather than animation: a transition reverses cleanly when the
   cursor leaves mid-bounce, where an animation has to finish its cycle first
   and lands a beat behind the pointer. */
.hero__letter {
  transition: transform .34s cubic-bezier(.34, 1.56, .64, 1);
}
.hero__letter:hover {
  transform: translateY(-.14em) scale(1.18);
}


/* 30ch broke this into three short lines in a column that is half the page
   wide. Two lines reads better and uses the space that is already there. */
.hero__lead {
  max-width: 52ch;
  margin: clamp(1.5rem, 2.4vw, 2rem) 0 0;
  font-size: var(--fs-small);
  line-height: 1.75;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: .875rem;
  margin-top: clamp(1.75rem, 2.6vw, 2.25rem);
}

.stat-row {
  display: flex;
  flex-wrap: wrap;
  gap: clamp(1.25rem, 2.4vw, 2rem);
  margin-top: clamp(2.25rem, 3.6vw, 3rem);
}
.stat {
  display: flex;
  flex-direction: column;
  gap: .375rem;
  padding-left: clamp(1.25rem, 2.4vw, 2rem);
  border-left: 1px solid var(--line);
}
.stat:first-child { padding-left: 0; border-left: 0; }

.stat__value {
  display: flex;
  align-items: center;
  gap: .3rem;
  color: var(--red);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 2.9vw, 2.625rem);
  line-height: 1;
}
.stat__star { width: .55em; height: .55em; fill: var(--red); }
/* The same star, but set into a heading rather than a figure. Sized in em so
   it tracks the display type as it scales, and nudged up off the baseline -
   a star centred on the x-height of a lining numeral sits low. */
.title-star {
  display: inline-block;   /* a global svg rule sets block, which broke the line */
  width: .52em;
  height: .52em;
  fill: var(--red);
  vertical-align: .1em;
  margin-left: .06em;
}


.stat__label {
  color: var(--body);
  font-size: .6875rem;
  font-weight: 400;
  letter-spacing: .06em;
  line-height: 1.35;
  text-transform: uppercase;
}

/* 7. Puzzle pieces (hero collage)
   ==========================================================================
   The collage is five of Laurie's piece drawings, positioned. Nothing here is
   drawn by hand any more: each piece is clipped to the outline of its own
   artwork in img/pieces/, and the positions come from solving the jigsaw the
   artwork describes.

   Every tab in the set has a socket cut for it in the piece it touches, so
   the layout is not a matter of taste - each joint fixes one piece against
   its neighbour, and the whole arrangement falls out of five of them. The
   sixth joint was left out of the solve and used as a check: it lands 2 units
   off. The pieces really do interlock.

   The clip paths below are the same geometry as the SVGs in img/pieces/,
   divided through by each drawing's own viewBox. Keep the two in step: the
   SVGs are the readable copy, in the artwork's own pixel coordinates.

   Two rules for editing:

   - Positions and sizes are percentages of the collage box, and every piece
     is sized to its FULL drawing, tabs included, not to its body. That is why
     some pieces have a negative --l or --t: the tab hangs outside the box.
   - clipPathUnits is objectBoundingBox, so a path is a fraction of its
     element and scales with it. The knobs stay round only while a piece keeps
     the ratio of the drawing it came from. Those ratios are noted below, and
     the --w/--h pairs in index.html already hold them.
   ========================================================================== */
.collage {
  position: relative;
  container-type: inline-size;
  width: 100%;
  /* The bounding box of the seven pieces once assembled, in the artwork's own
     pixel units: x -240..354, y 1..483 with media B at the origin. The
     --w/--h in index.html are the drawings' viewBox sizes over this box, so
     one drawing pixel is the same length everywhere and the knobs stay round. */
  aspect-ratio: 594 / 482;

  /* Gaps between the pieces come from --sx/--sy in index.html - see .piece. */
}

/* --sx / --sy inset every piece by the SAME 3.5 drawing px on all four edges,
   which is what puts a 7px gap on every joint and keeps the outer edges of the
   collage lined up with each other.

   They have to be a per-piece pair, not one shared number. A single scale
   shrinks each piece toward its own centre, so the amount it pulls in is
   proportional to its size: the big centre piece backed off nearly twice as
   far as the small corner one, which opened the seams unevenly AND stepped the
   silhouette where a large piece met a small one along the same edge. Solving
   per axis - sx = 1 - 2 x 3.5 / drawing width, sy the same over the height -
   makes the inset a fixed distance instead of a fixed ratio.

   The cost is that x and y scale by slightly different factors, so a round
   knob is technically an ellipse. Worst case in this set is 1.7% out. */
.piece {
  position: absolute;
  left: var(--l);
  top: var(--t);
  width: var(--w);
  height: var(--h);
  transform: scale(var(--sx, 1), var(--sy, 1));
}

/* The desktop arrangement. These were inline styles in index.html, which was
   fine while there was one layout; there are two now (7f) and a media query
   cannot beat an inline style without !important on every line. The markup
   keeps only the entrance vectors, which no breakpoint changes. */
.pz-red-mid      { --l: 12.271%; --t:  0.406%; --w: 35.131%; --h: 48.852%; --sx: .96635; --sy: .97009; }
.pz-media-b      { --l: 40.119%; --t:  0.415%; --w: 60.101%; --h: 48.963%; --sx: .98039; --sy: .97034; }
.pz-media-c      { --l: -0.170%; --t: 40.621%; --w: 48.462%; --h: 59.388%; --sx: .97561; --sy: .97527; }
.pz-cream-bottom { --l: 40.612%; --t: 41.562%; --w: 28.936%; --h: 58.641%; --sx: .95882; --sy: .97518; }
.pz-red-corner   { --l: 62.960%; --t: 48.655%; --w: 36.777%; --h: 51.350%; --sx: .96729; --sy: .97154; }

/* Flat fills. Colour stays on the token rather than baked into the SVG, so
   the pieces follow the palette rather than forking it. */
/* No background: a flat piece is its own drawing, dropped in as an <img>.
   A background here would paint a rectangle behind the shape. */

/* MEDIA PIECES ----------------------------------------------------------
   The three pale blue drawings are the media slots. Because the piece is
   clipped rather than painted, a tab carries real picture and a notch really
   removes it - which is what makes these usable as video frames.

   The tone below is what shows until the media loads, so an empty slot still
   reads as part of the puzzle rather than a hole in the page. */
.piece--media { background: var(--cream-200); }
/* Descendant, not child. The slot that runs two clips wraps them in a
   .piece__seq, so a child combinator stopped matching those videos - they
   lost object-fit and fell back to the default, which is why that piece
   looked wrong. */
.piece--media img,
.piece--media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* fills the whole drawing, tab included */
  transform: translateZ(0);   /* Chrome leaves a hairline along a clipped curve otherwise */
}

/* outlines ---------------------------------------------------------------
   Laurie's drawings are used as the files she supplied - img/pieces/*.svg are
   byte-for-byte her exports, not a redraw. Two ways in, depending on what the
   piece has to hold:

   - A flat piece IS the drawing. It goes in as an <img>, so the shape and the
     colour are hers, straight out of the file.
   - A media piece uses the same file as a mask. The drawing's alpha channel
     is the shape, so the photo or video is cut to her outline exactly,
     antialiased edges and all - including out into the tabs.

   Sizing stays 100% 100% rather than contain, because each piece's --w/--h
   already carries its drawing's aspect ratio; contain would letterbox it and
   pull the joints apart. */
.piece__art {
  display: block;
  width: 100%;
  height: 100%;
}

/* Clipped with a vector path, not masked with the drawing.

   The drawings are bitmaps inside an <svg>. Used as a CSS mask, the browser
   rasterises and rescales that alpha channel, and every notch came out
   stair-stepped with a pale halo - unnoticeable on a flat fill, glaring over
   a photo. A path has no resolution and stays clean at any size.

   The paths are traces of the same drawings, in the <clipPath> block at the
   top of index.html. clip-path: url(#id) only resolves within the document
   that holds them, so copy that block into any page using these. */
.pz-media-b { clip-path: url(#pz-media-b); }  /* 357 x 236 */
.pz-media-c { clip-path: url(#pz-media-c); }  /* 287 x 283 */

/* The flat pieces are bitmaps inside an <svg>, at the size Laurie drew them -
   214px across for the corner piece. A flat colour scales up without
   complaint, so most of a piece is fine, but a knob is the only curve in the
   drawing and it stair-stepped on any 2x display. The corner piece is a
   clipped fill instead, so its knob is a real curve at any size. */
.piece__fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--red-piece);
}
.pz-red-corner { clip-path: url(#pz-red-corner); }  /* 214 x 246 */
.pz-red-mid    { clip-path: url(#pz-red-mid);    }  /* 208 x 234 */
.pz-cream-bottom { clip-path: url(#pz-cream-bottom); }  /* 170 x 282 */
.piece__fill--cream { background: var(--cream-piece); }

/* Anything that cannot resolve the reference gets a plain rectangle - a
   blockier collage, not a broken one. */
/* caption sitting on the bottom-right red piece --------------------------- */
.piece__caption {
  position: absolute;
  /* left inset clears the tab on that piece, which occupies the first 17% */
  inset: auto 8% 12% 24%;
  margin: 0;
  color: var(--white);
  font-family: var(--font-display);
  font-size: 3.6cqw;
  line-height: 1.32;
  text-align: left;
}

/* 7f. The phone arrangement
   ==========================================================================
   The desktop jigsaw does not survive being 390px wide. It is close to square,
   so it takes most of the first screen, and five pieces across that width
   leaves both films the size of a stamp. Below 560px the same five elements
   are re-laid-out as a short, wide block that runs to both screen edges and
   sits flush on the dark credentials bar.

   Same five elements, not a second copy - the videos are not duplicated,
   which matters when one of the two files is 16MB and this is the breakpoint
   where the connection is worst. Only --l/--t/--w/--h and the clip path move.

   The pieces are on a 1000 x 555 unit grid, so the box is 200/111. Joint
   lines at x = 315 and x = 650, y = 223 in the left column and y = 342 in the
   right. Every edge that meets another piece backs off 5 units and every edge
   that meets the screen does not, which is what gives the flat top and flat
   bottom. Knobs are drawn with the seam already in them - tab at R = 40,
   socket at R = 50 about the same centre - so unlike the desktop set these
   need no --sx/--sy, and the ring around a knob is the same 10 units as the
   straight seams rather than a scaled approximation of it.

           +--------------+  +----------------------------+
           |   red        |  |   video A                  |
           |         (tab)|  |                            |
           +------\__/----+  |                            |
           +--------------+  +-------------\__/-----------+
           |   video C    |  +---(tab)---+  +-------------+
           |              |  |  pale     |  |   red       |
           +--------------+  +-----------+  +-------------+

   Selectors are .hero__inner > .collage rather than .collage: the 940px block
   further down sets max-width and margin on the plain class, and it comes
   after this one.
   ========================================================================== */
@media (max-width: 560px) {
  .hero__inner > .collage {
    /* the artwork's own bounds, seams included - nothing to correct for */
    aspect-ratio: 200 / 111;
    /* out of the container's gutter and onto both screen edges */
    width: 100vw;
    max-width: none;
    margin-inline: calc(-1 * var(--gutter));
  }
  /* and down onto the dark bar, which is the point of the flat bottom edge */
  .hero {
    padding-bottom: 0;
    /* The pieces fly in from up to 22% of their own width outside the
       collage. That was free while the collage sat inside the container's
       gutter; edge to edge it hangs 49px past the right of a 375px screen and
       the page can be dragged sideways. clip rather than hidden: hidden would
       make this a scroll container and take overflow-y with it, clip only
       cuts the axis it is given. */
    overflow-x: clip;
  }

  /* the seams are in the paths at this size */
  .collage .piece { --sx: 1; --sy: 1; }

  .pz-red-mid      { --l:  0.0%; --t:  0.0000%; --w: 31.0%; --h: 52.9225%; clip-path: url(#pz-m-red-tl); }
  .pz-media-b      { --l: 32.0%; --t:  0.0000%; --w: 68.0%; --h: 60.7207%; clip-path: url(#pz-m-media-b); }
  .pz-media-c      { --l:  0.0%; --t: 41.0811%; --w: 31.0%; --h: 58.9189%; clip-path: url(#pz-m-media-c); }
  .pz-cream-bottom { --l: 32.0%; --t: 48.8793%; --w: 32.5%; --h: 51.1207%; clip-path: url(#pz-m-cream); }
  /* bottom right is a plain rectangle here - both its outer edges are screen
     edges and neither neighbour hands it a tab */
  .pz-red-corner   { --l: 65.5%; --t: 62.5225%; --w: 34.5%; --h: 37.4775%; clip-path: none; }

  /* Framing: both clips still centre, and that was checked rather than left
     at the default. Top right becomes a 2.02 letterbox against a 1.78 source
     (854x480), so the crop moves to the vertical axis - it takes 28 source px
     off each edge, and the hard hats start 86px down, so they keep their
     headroom. Bottom left is 0.95 against a source that is now square: that
     clip ships pre-cropped to 630x630 (see video/README.md), which is the
     region the desktop piece was already showing, so this slot takes 95% of
     its height and the Opera House stays centred in shot. */

  /* 134 x 81 CSS px on a 390px screen. The caption does not fit in that and
     the piece stops reading as a piece if it is forced to. */
  .piece__caption { display: none; }
}

/* 7b. Live jobs
   ========================================================================== */
.jobs { padding-block: var(--section-y); background: var(--cream); }
.jobs__intro {
  max-width: 60ch;
  margin: 1.25rem auto 0;
  text-align: center;
}

.job-meta {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem 1.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-small);
  color: var(--body);
}
.job-meta li { position: relative; }
.job-meta__salary { font-weight: 500; color: var(--ink); }

.job-card {
  display: flex;
  text-decoration: none;
  color: inherit;
  background: var(--white);
  border: 1px solid var(--line);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease);
}
.job-card:hover {
  border-color: var(--red);
  box-shadow: 0 18px 34px -26px rgba(31,26,23,.55);
  transform: translateY(-2px);
}

.job-card__arrow {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--red);
  transition: transform .2s var(--ease);
  /* chevron drawn with a mask so it inherits the red fill */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M5 12h13M12 5l7 7-7 7'/%3E%3C/svg%3E") center / 17px no-repeat,
          linear-gradient(#000, #000);
  -webkit-mask-composite: xor;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='none' stroke='%23000' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' d='M5 12h13M12 5l7 7-7 7'/%3E%3C/svg%3E") center / 17px no-repeat,
                linear-gradient(#000, #000);
          mask-composite: exclude;
}
.job-card:hover .job-card__arrow { transform: translateX(3px); }

/* card ------------------------------------------------------------------ */
.job-card {
  flex-direction: column;
  height: 100%;
  padding: clamp(1.5rem, 2.4vw, 2rem);
}
.job-card__sector {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--red);
}
.job-card__title {
  margin: .625rem 0 1rem;
  font-size: var(--fs-h3);
  line-height: 1.25;
  color: var(--ink);
}
.job-card .job-meta { flex-direction: column; gap: .35rem; }
.job-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-top: auto;
  padding-top: 1.5rem;
}
.job-card__posted { font-size: var(--fs-label); color: var(--body); }

/* copy left, swipeable rail right --------------------------------------- */
.jobs-split {
  display: grid;
  grid-template-columns: minmax(0, 40%) minmax(0, 1fr);
  align-items: center;
  gap: clamp(2rem, 4vw, 3.5rem);
}
.jobs-split__copy p { max-width: 44ch; }
.jobs-split__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem 1.5rem;
  margin-top: 2rem;
}
.rail-nav { display: flex; gap: .5rem; }
.rail-nav__btn {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--white);
  color: var(--ink);
  transition: background-color .2s var(--ease), color .2s var(--ease), border-color .2s var(--ease);
}
.rail-nav__btn:hover { background: var(--red); border-color: var(--red); color: var(--white); }
.rail-nav__btn svg {
  width: 20px; height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.jobs-rail-wrap { min-width: 0; }

.job-rail {
  display: flex;
  gap: 1.25rem;
  margin: 0;
  padding: .25rem .25rem .75rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  /* native bar hidden in favour of .rail-progress below */
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
}
.job-rail::-webkit-scrollbar { display: none; }
.job-rail:active { cursor: grabbing; }

/* Snap is suppressed while the pointer is down AND while the rail eases to
   its resting card afterwards - if mandatory snap were re-enabled at release
   the browser would jump to the nearest card instantly instead of animating. */
.job-rail.is-dragging,
.job-rail.is-settling { scroll-snap-type: none; }

.job-rail.is-dragging {
  cursor: grabbing;
  scroll-behavior: auto;
  user-select: none;
  -webkit-user-select: none;
}
.job-rail.is-dragging a { pointer-events: none; }

/* Every card in the rail is the same box. The item sets the width, but the
   card inside was sizing to its own content, so a short job title gave a
   narrower card than a long one and the row looked ragged. The card fills
   its item, and the item never grows or shrinks. */
.job-rail__item {
  flex: 0 0 clamp(260px, 30vw, 340px);
  scroll-snap-align: start;
  display: flex;
}
.job-rail__item > .job-card { flex: 1 1 auto; width: 100%; min-width: 0; }

/* scroll indicator ------------------------------------------------------ */
.rail-progress {
  position: relative;
  height: 4px;
  margin: .25rem .25rem 0;
  border-radius: 999px;
  background: var(--grey-200);
  cursor: pointer;
  touch-action: none;
}
.rail-progress__thumb {
  position: absolute;
  top: 0; bottom: 0; left: 0;
  width: 20%;                 /* JS overrides width + transform */
  border-radius: inherit;
  background: var(--red);
  transition: background-color .2s var(--ease);
}
.rail-progress:hover .rail-progress__thumb,
.rail-progress.is-dragging .rail-progress__thumb { background: var(--red); }

/* arrows dim once you reach either end */
.rail-nav__btn[aria-disabled="true"] {
  opacity: .35;
  pointer-events: none;
}
.job-rail__item--end { flex-basis: clamp(200px, 20vw, 240px); }
.job-more {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: .5rem;
  width: 100%;
  padding: clamp(1.5rem, 2.4vw, 2rem);
  text-decoration: none;
  background: var(--red);
  color: var(--white);
  transition: transform .2s var(--ease);
}
.job-more:hover { transform: scale(var(--red-hover-scale)); }
.job-more__count {
  font-family: var(--font-display);
  font-size: 2.5rem;
  line-height: 1;
}
.job-more__text { font-size: var(--fs-small); }

/* 8. Credentials bar
   ========================================================================== */
.credentials {
  background: var(--ink-900);
  color: var(--white);
  padding-block: clamp(1.75rem, 2.6vw, 2.25rem);
}
/* The stats used to be spread edge to edge with space-between, which left
   huge gaps between three items and did not line up with the accreditation
   row underneath - that row starts at the container edge and runs left. Both
   rows now start from the same place and read as one block. */
.credentials__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-start;
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
.credential {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  justify-content: center;
  column-gap: .75rem;
  padding-inline: clamp(1rem, 3vw, 2.5rem);
  border-left: 1px solid rgba(255,255,255,.22);
  text-align: left;
}
/* the first one sits flush with the container edge, so it starts on the same
   line as the accreditation row below it */
.credentials__inner > .credential:first-child {
  border-left: 0;
  padding-left: 0;
}

.credential__value {
  grid-column: 2;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 700;
  line-height: 1.1;
}
.credential__label {
  grid-column: 2;
  font-size: .8125rem;
  font-weight: 500;
  letter-spacing: .06em;
  line-height: 1.3;
  text-transform: uppercase;
  color: rgba(255,255,255,.85);
}
/* The iconless one used to centre itself, which read as a stray middle-aligned
   item once the row stopped being spread across the full width. Everything in
   the bar is left-aligned now. */
.credential:not(.credential--icon) { text-align: left; }
.credential:not(.credential--icon) .credential__value,
.credential:not(.credential--icon) .credential__label { grid-column: 1 / -1; }

.credential__icon {
  grid-row: 1 / span 2;
  grid-column: 1;
  width: 38px; height: 38px;
  fill: var(--red);
}
.credential__icon--stroke {
  fill: none;
  stroke: var(--red);
  stroke-width: 1.7;
  stroke-linejoin: round;
}
.credential--logo { border-left: 1px solid rgba(255,255,255,.22); }
.credential--logo img { width: clamp(120px, 11vw, 156px); }

/* 9. About
   ========================================================================== */
.about {
  background: var(--cream);
  padding-block: var(--section-y);
}
.about__inner {
  display: grid;
  /* The media column was 47%. At that size the portrait group dominated the
     band and the copy beside it ran out of width early; 41% settles him
     against the text rather than over it. */
  grid-template-columns: minmax(0, 41%) minmax(0, 1fr);
  align-items: center;
  gap: clamp(2.5rem, 6vw, 5.5rem);
}

.about__media { position: relative; }
/* 455/569 off the comp. At 75% the portrait was too narrow, which left a gap
   instead of the puzzle mark tucking in behind its bottom-right corner. */
.about__photo {
  position: relative;
  z-index: 2;
  width: 80%;
  margin-top: 6.5%;
}

.dot-grid {
  position: absolute;
  z-index: 1;
  top: 0; right: 0;
  /* sized to whole dots so no row is ever half-clipped */
  --dot-pitch: 19px;
  width: calc(var(--dot-pitch) * 13);
  height: calc(var(--dot-pitch) * 12);
  max-width: 46%;
  background-image: radial-gradient(var(--red) 2.1px, transparent 2.4px);
  background-size: var(--dot-pitch) var(--dot-pitch);
}

/* Laurie's own pieces, used as supplied (img/pieces/about-mark-*.svg). The
   cream one is 212x244 inside a 569-wide media block, so 212/569 keeps it at
   comp scale against the portrait - it replaces a flattened PNG of the same
   size. The red one is a smaller accent that balances it across the diagonal:
   cream sits bottom-right under the portrait, red top-left above it. */
.puzzle-mark {
  position: absolute;
  z-index: 1;          /* behind the portrait (z-index 2), per the comp */
  right: 0; bottom: 0;
  width: 37%;
  height: auto;
}
/* These two interlock, and the position is measured, not judged. The red
   piece's tab is a circle at (12, 47.5) r12 in its own 114x96 box. The cream
   piece has two right-facing sockets; this one plugs into the LOWER of them,
   at (112.2, 194) r15.2 in its 212x244 box, which is the arrangement in
   Laurie's reference. Lining those two centres up gives the offsets below.

   (The upper socket at (182.6, 101.5) is the wrong one - it puts the red
   piece halfway up and pushes it out of the media block entirely.) */
.puzzle-mark--red {
  right: -0.5%; bottom: auto;
  left: auto; top: 84%;
  width: 20%;          /* 114/569, the same scale as the cream mark */
}

/* No cap. The column is already the measure here, and a cap inside it left the
   copy stopping well short of the container's right edge while the portrait sat
   hard against the left - so the band read as sitting left of centre even
   though the grid itself was centred. */
.about__copy p { max-width: none; }
.about__copy .section-title { margin-bottom: 1.75rem; }
.about__copy .btn { margin-top: .75rem; }

/* 10. Testimonials + carousel
   ========================================================================== */
.testimonials {
  /* flat red as the fallback, then the logo's own bright-to-deep ramp on top */
  background: var(--red);
  background-image: var(--red-grad);
  /* Short of the standard section rhythm on purpose. The deck inside already
     carries a lot of its own padding, so the full --section-y on top of that
     made this the tallest band on the page by a distance. */
  padding-block: calc(var(--section-y) * .5);
}
.testimonials .section-title { margin-bottom: clamp(1.5rem, 2.4vw, 2rem); }

.carousel {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) 56px;
  align-items: center;
  /* Column gap only. The dots are a full-width row in this grid, so a shared
     gap stacked on top of their own margin and left them stranded well below
     the panel - about twice the space that sits under them. */
  gap: 0 clamp(1rem, 3vw, 2.75rem);
}
.carousel__viewport {
  overflow: hidden;
  /* Driven in pixels by main.js while a quote opens or closes; the inline
     height is removed once it lands, so this only applies mid-animation. */
  transition: height .42s var(--ease);
}
/* align-items:stretch (the flex default) sizes every slide to the tallest one,
   so the track height never jumps between reviews. */
.carousel__track {
  display: flex;
  transition: transform .5s var(--ease);
}
.carousel__slide {
  flex: 0 0 100%;
  min-width: 0;
  /* the slide stretches, but the red panel inside it has to fill that height
     too - otherwise each box is only as tall as its own copy */
  display: flex;
}

.carousel__arrow {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  padding: 0;
  border: 1px solid rgba(255,255,255,.65);
  border-radius: 50%;
  background: transparent;
  color: var(--white);
  transition: background-color .2s var(--ease), color .2s var(--ease);
}
.carousel__arrow:hover { background: var(--white); color: var(--red); }
.carousel__arrow svg {
  width: 22px; height: 22px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.carousel__dots {
  grid-column: 1 / -1;
  display: flex;
  justify-content: center;
  gap: .75rem;
  margin-top: clamp(1.5rem, 2.4vw, 2rem);
}
/* A sliding window of five dots. With a dozen reviews a dot per slide is a
   row of confetti, so only five are on screen: the current one, two either
   side, and the outermost of those shrink to say the run continues past them.
   JS sets data-rank - 0 for the current dot, 1 and 2 for its neighbours - and
   hides anything further out. */
.carousel__dot {
  flex: none;
  width: 11px; height: 11px;
  padding: 0;
  border: 1px solid var(--white);
  border-radius: 50%;
  background: transparent;
  transition:
    background-color .2s var(--ease),
    width .25s var(--ease),
    height .25s var(--ease),
    opacity .25s var(--ease);
}
.carousel__dot[aria-selected="true"] { background: var(--white); }

.carousel__dot[data-rank="1"] { width: 8px;  height: 8px;  opacity: .75; }
.carousel__dot[data-rank="2"] { width: 5px;  height: 5px;  opacity: .45; }
.carousel__dot[data-rank="out"] {
  width: 0; height: 0;
  margin: 0 -.375rem;   /* collapse the gap it would otherwise leave */
  opacity: 0;
  border-width: 0;
  pointer-events: none;
}

/* keeps the row from jumping as dots resize */
.carousel__dots { align-items: center; min-height: 11px; }

.quote {
  position: relative;
  flex: 1;               /* fill the stretched slide so every panel matches */
  display: flex;
  flex-direction: column;
  min-width: 0;
  margin: 0;
  padding: clamp(1rem, 1.9vw, 1.6rem) clamp(1.5rem, 3.5vw, 3.25rem);
  /* The credentials-bar grey, at 25% opacity so the red panel reads through
     it. "75% transparency" taken literally - if you meant 75% OPACITY, this
     is the one number to change. */
  background: rgba(31, 31, 31, .25);
  text-align: center;
}
/* The quote block takes the slack so the attribution lines up along the
   bottom across reviews of different lengths, and the quote itself sits in
   the middle of whatever space it has rather than clinging to the top. */
.quote blockquote { flex: 1; display: flex; flex-direction: column; justify-content: center; }
.quote__mark {
  display: block;
  margin-bottom: -.6em;
  color: rgba(255,255,255,.28);
  font-family: var(--font-display);
  font-size: 3.25rem;
  line-height: 1;
}
.quote blockquote { margin: 0; }
/* Capped at four lines. All 75 reviews are in this carousel and the slides
   sit in a flex row, so the tallest one sets the height of every panel - nine
   lines meant a wall of empty red under the short quotes, which is most of
   them. Four lines is about the median review, so the panel is close to full
   most of the time; anything longer gets a "See more" from main.js. */
.quote blockquote p {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 0;
  color: var(--white);
  font-family: var(--font-display);
  font-size: var(--fs-quote);
  line-height: 1.55;
}
/* Added by main.js only to the reviews that actually overflow the clamp. */
.quote__more {
  align-self: center;
  margin-top: .9rem;
  padding: 0;
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  color: var(--white);
  font-size: .8125rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: .85;
  transition: opacity .2s var(--ease);
}
.quote__more:hover { opacity: 1; }
.quote.is-open blockquote p { -webkit-line-clamp: unset; }

.quote__by {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .25rem 1rem;
  margin-top: clamp(1.25rem, 2.2vw, 1.75rem);
  padding-top: clamp(1rem, 1.9vw, 1.375rem);
  border-top: 1px solid rgba(255,255,255,.28);
  color: var(--white);
}
.quote__name {
  flex-basis: 100%;
  font-size: .8125rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}
.quote__role { font-size: .8125rem; line-height: 1.4; color: rgba(255,255,255,.85); }
.quote__logo { width: 62px; }

.testimonials__more { margin: clamp(1.5rem, 2.4vw, 2rem) 0 0; text-align: center; }

/* 11. How can we help
   ========================================================================== */
.help { padding-block: var(--section-y); }
.help__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  margin-top: clamp(2.75rem, 5vw, 4rem);
}
.help-card {
  padding: 0 clamp(1rem, 2.4vw, 2rem);
  border-left: 1px solid var(--line);
  text-align: center;
}
.help-card:first-child { border-left: 0; }

/* Supplied PNGs vary in aspect (49x53, 53x55, 46x54, 59x48), so they sit in a
   fixed box and are contained rather than sized directly - keeps them optically
   aligned across the four cards. */
.help-card__icon {
  display: block;
  /* Every icon gets the same box HEIGHT. They are four different drawings at
     four different ratios, so contain sizes each one differently inside it -
     but as long as the box is the same height, the title under it starts at
     the same place on all four cards. Vary the width if one needs more room;
     never the height, or that card's title drops below its neighbours. */
  width: 64px; height: 56px;
  margin: 0 auto 1.5rem;
  object-fit: contain;
}
/* One of the four drawings is much wider than it is tall, so contain fits it
   to the width of the shared box and it lands optically smaller than the other
   three. A wider box gives it back the size it should look - height untouched,
   so it still lines up with the rest. */
.help-card__icon--wide { width: 84px; }
.help-card__title {
  margin-bottom: 1.25rem;
  font-size: var(--fs-h3);
  line-height: 1.25;
}
.help-card p { font-size: var(--fs-small); line-height: 1.7; }

/* 12. Footer
   ========================================================================== */
.site-footer {
  background: var(--ink-900);
  color: rgba(255,255,255,.78);
  padding-block: clamp(3rem, 5vw, 4.5rem) 1.5rem;
  font-size: var(--fs-small);
}
.site-footer__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(2, minmax(0, 1fr));
  gap: clamp(2rem, 5vw, 4rem);
}
/* logo is red on transparent, so it sits straight on the dark bar - no plate */
.site-footer__brand img { width: 172px; }
.site-footer__brand p {
  margin: 1.25rem 0;
  font-family: var(--font-display);
  font-size: 1.125rem;
  color: var(--white);
}
/* Footer memberships: NPA and SEEK side by side, each under a small lead-in
   matching the dark bar. NPA comes down a little and the Talent Match logo
   above goes up, so the wordmark leads and the memberships support it. */
.site-footer__marks {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.5rem 2rem;
  margin-top: clamp(2.25rem, 4vw, 3.25rem);
}
.footer-mark { display: grid; gap: .4rem; }
.footer-mark__label {
  color: rgba(255,255,255,.55);
  font-size: .625rem;
  font-weight: 500;
  letter-spacing: .1em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
}
/* Scoped through .site-footer__marks: the existing .site-footer__brand img
   rule is a class plus a type selector, so it outranks a bare class and was
   holding both marks at 172px. */
.site-footer__marks .site-footer__npa  { width: 108px; }
.site-footer__marks .site-footer__seek { width: 122px; }

/* the wordmark carries the block, so it is larger than it was */
.site-footer__brand > img:first-child { width: clamp(190px, 19vw, 250px); height: auto; }

.site-footer__heading {
  margin-bottom: 1rem;
  color: var(--white);
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.site-footer__col li + li { margin-top: .625rem; }
/* the email address is one long unbreakable token - let it break rather than
   push the footer grid wider than the viewport on narrow screens */
.site-footer__col li { overflow-wrap: anywhere; }
.site-footer__col a { text-decoration: none; transition: color .2s var(--ease); }
.site-footer__col a:hover { color: var(--white); }

/* The LinkedIn lockup replaces the word "LinkedIn" in the Get in touch list.
   White, per the brand's own one-colour lockup: the tile is filled and the
   "in" is a hole in it, so the dark footer reads as the letters. It sits a
   touch under full opacity so it does not shout past the links around it, and
   comes up to full white on hover the way they do. */
.site-footer__social { margin-top: .875rem; }
.site-footer__social a { display: inline-block; }
.linkedin-mark {
  display: block;
  width: 88px;
  height: auto;
  fill: var(--white);
  opacity: .86;
  transition: opacity .2s var(--ease);
}
.site-footer__social a:hover .linkedin-mark { opacity: 1; }
/* The same lockup on a light background, so it takes the brand blue rather
   than white. The "in" is still a hole, so the page shows through it. */
/* The SEEK marks are links now. They sit inside a flex/grid slot that was
   sized for an image, so the anchor has to disappear into that slot rather
   than introduce a box of its own. */
.accred__link, .footer-mark__link { display: block; line-height: 0; }
.accred__link img, .footer-mark__link img { transition: opacity .2s var(--ease); }
.accred__link:hover img, .footer-mark__link:hover img { opacity: .82; }

.linkedin-mark--brand { fill: #0A66C2; opacity: 1; }
.contact-aside__social { margin-top: .25rem; }
.contact-aside__social dd { margin: 0; }
.contact-aside__social a {
  display: inline-block;
  border-bottom: 0;              /* the aside underlines its links; a logo is not text */
  transition: transform .2s var(--ease);
}
.contact-aside__social a:hover { transform: scale(1.06); }
.contact-aside__social .linkedin-mark { width: 96px; }

.site-footer__base {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: .5rem 1.5rem;
  margin-top: clamp(2.5rem, 4vw, 3.5rem);
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255,255,255,.16);
  font-size: .8125rem;
}
.site-footer__base p { margin: 0; }

/* 13. Responsive
   ========================================================================== */
@media (max-width: 1080px) {
  .hero__inner { grid-template-columns: minmax(0, 46%) minmax(0, 1fr); }
  .site-nav__list { gap: 1.25rem; }
  .site-nav__list a { font-size: .8125rem; }
}

@media (max-width: 940px) {
  :root { --header-h: 84px; }

  /* nav collapses into a panel */
  .nav-toggle { display: block; }
  .site-header__cta { display: none; }

  .site-nav {
    position: fixed;
    inset: var(--header-h) 0 auto 0;
    display: grid;
    gap: 1.5rem;
    padding: 1.75rem var(--gutter) 2.25rem;
    background: var(--white);
    border-top: 1px solid var(--line);
    box-shadow: 0 24px 40px -30px rgba(31,26,23,.6);
    transform: translateY(-8px);
    opacity: 0;
    visibility: hidden;
    transition: opacity .2s var(--ease), transform .2s var(--ease), visibility .2s;
  }
  .site-nav.is-open { opacity: 1; visibility: visible; transform: none; }
  .site-nav__list { flex-direction: column; align-items: flex-start; gap: 1rem; }
  .site-nav__list a { font-size: 1rem; }
  .site-nav__cta { display: inline-flex; justify-self: start; }

  .hero__inner { grid-template-columns: minmax(0, 1fr); }
  .hero__lead { max-width: 46ch; }
  .collage { max-width: 560px; margin-inline: auto; }

  .about__inner { grid-template-columns: minmax(0, 1fr); }
  .about__media { max-width: 440px; }

  .help__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.75rem 0; }
  .help-card:nth-child(odd) { border-left: 0; }

  /* jobs: copy stacks above the rail */
  .jobs-split { grid-template-columns: minmax(0, 1fr); }

  .site-footer__inner { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .site-footer__brand { grid-column: 1 / -1; }
}

@media (max-width: 720px) {
  .credentials__inner { justify-content: flex-start; gap: 1.25rem 0; }
  .credential {
    flex: 1 1 45%;
    padding-inline: 1rem;
    justify-content: flex-start;
  }
  .credential:nth-child(odd) { border-left: 0; padding-left: 0; }
  .credential--logo { flex-basis: 100%; border-left: 0; padding-left: 0; }

  .carousel { grid-template-columns: minmax(0, 1fr); }
  .carousel__arrow { display: none; }

  .stat-row { gap: 1.5rem; }
  .stat { flex: 1 1 40%; }
  .stat:nth-child(odd) { padding-left: 0; border-left: 0; }

  .dot-grid { --dot-pitch: 15px; }
}

@media (max-width: 560px) {
  .dot-grid { --dot-pitch: 13px; }
  .help__grid { grid-template-columns: minmax(0, 1fr); }
  .help-card { border-left: 0; }
  .btn { width: 100%; }
  .hero__actions .btn { width: auto; flex: 1 1 220px; }
}

/* 14. Motion
   ========================================================================== */
/* Everything is gated on .js-reveal (set on <html> by the inline head script),
   so the hidden state exists from the very first paint and never flashes.
   No JS, or reduced motion -> the class is absent and content just shows.
   --reveal-delay staggers members of a group. */
.js-reveal [data-reveal] {
  opacity: 0;
  transform: translateY(18px);
  will-change: opacity, transform;
}
/* The transition is deliberately NOT on the rule above.

   Only the hero has its data-reveal in the markup; every other block gets the
   attribute from main.js, after first paint. With the transition attached to
   the hidden state, applying that attribute animated the block from visible to
   hidden - a 0.7s fade-out on load, which the reveal then fought. The result
   read as content that simply appeared, which is what it was.

   main.js adds .is-reveal-armed to <html> one frame after tagging, so the
   hidden state gets painted untransitioned and everything after it animates
   properly. */
/* .8s rather than .7s, and every reveal holds for 150ms before it starts.

   The stagger inside a group is --reveal-delay, set per element by main.js;
   this adds a beat in front of all of it. A block therefore settles into view
   after the scroll that triggered it has finished, instead of racing it, and
   the slower curve keeps the movement from snapping at the end. */
/* --reveal-base is a third term, set on a container in the markup and
   inherited by the tagged nodes inside it. --reveal-delay only staggers
   members of one selector group against each other, so a block built from
   three different groups - a head, a filter and a grid, say - has all three
   starting at 0 and lands as one flat slab. The base spaces the groups. */
.js-reveal.is-reveal-armed [data-reveal] {
  transition:
    opacity .8s var(--ease) calc(var(--reveal-base, 0ms) + var(--reveal-delay, 0ms) + 150ms),
    transform .8s var(--ease) calc(var(--reveal-base, 0ms) + var(--reveal-delay, 0ms) + 150ms);
}
.js-reveal [data-reveal].is-visible {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* the collage is the hero's anchor - it scales up rather than sliding */
.js-reveal [data-reveal="zoom"] { transform: translateY(18px) scale(.97); }

/* hero copy comes in from the left, a beat ahead of the artwork */
.js-reveal [data-reveal="left"] { transform: translateX(-22px); }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .js-reveal [data-reveal] { opacity: 1; transform: none; }
}

/* 14. Interior pages
   ==========================================================================
   Everything below serves about / for-employers / for-candidates /
   our-approach / testimonials / jobs / contact / privacy. It deliberately
   leans on the primitives that already exist - .container, .eyebrow,
   .section-title, .btn, .help-card, .quote - so the interior
   pages cannot drift away from the homepage.
   ========================================================================== */

/* The nav marks where you are. The underline is the same pseudo-element the
   hover draws, held at full width - so hovering the current page has nothing
   left to animate, which is correct: it is already the answer. */
.site-nav__list a[aria-current="page"] { color: var(--red); }
.site-nav__list a[aria-current="page"]::after {
  transform: scaleX(1);
  transform-origin: left center;
}

/* 14a. Page lead
   --------------------------------------------------------------------------
   The tinted page banner that used to open every interior page - breadcrumbs,
   an oversized title, a lead line and a decorative mini-puzzle - has been
   removed. Each page now opens on its own first content section, whose
   heading was promoted to <h1>; the styling comes from .section-title, so
   nothing moved on screen.

   Gone with it: .crumbs, .page-hero*, .knob--page, and the .knob primitive in
   section 7 - the mini-puzzle was its last user, now that the hero collage is
   clipped from artwork rather than assembled out of circles.

   Only privacy.html needs a standfirst under its title, since its own
   headings are subsections rather than a page title. */
.prose__lead {
  max-width: 54ch;
  margin: clamp(1.25rem, 2vw, 1.75rem) 0 0;
  font-size: 1.0625rem;
  line-height: 1.75;
}

/* 14b. Generic content blocks
   -------------------------------------------------------------------------- */
.section { padding-block: var(--section-y); }
.section--cream { background: var(--cream); }
.section--tight { padding-block: clamp(2.5rem, 4.5vw, 4rem); }
.section__head { max-width: 62ch; margin-bottom: clamp(2rem, 3.6vw, 3rem); }
.section__head--centred { margin-inline: auto; text-align: center; }
.section__head--centred .eyebrow { text-align: center; }
/* Keeps the red half of a title on the same line as the black half, for the
   headings short enough that the default line break just wastes a line. */
.section-title--inline-em em { display: inline; }
/* A head that has to be wider than the 62ch reading measure because its title
   is meant to hold one line. The measure still governs the paragraph under it. */
.section__head--wide { max-width: 76ch; }
.section__head--wide p { max-width: 62ch; margin-inline: auto; }
.section__head p { margin-top: 1.25rem; }

.prose { max-width: 68ch; }
/* Subheadings inside a prose block. The privacy policy carries its own
   sections as h2 now that the page banner no longer supplies the h1 - they
   are the top level of that document - so both levels take the same look.

   Scoped to headings that carry no class of their own, because a bare
   `.prose h2` outranks a single class and was shrinking `.section-title`
   down to h3 size wherever a split block used both. Anything with its own
   class opts out and keeps that class's size. */
.prose__h2,
.prose__h3,
.prose h2:not([class]),
.prose h3:not([class]) {
  margin: clamp(2rem, 3vw, 2.75rem) 0 1rem;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.prose__h2:first-child,
.prose__h3:first-child,
.prose h2:not([class]):first-child,
.prose h3:not([class]):first-child { margin-top: 0; }
.prose ul { margin: 0 0 1.25rem; padding: 0; }
.prose li {
  position: relative;
  margin-bottom: .75rem;
  padding-left: 1.75rem;
}
/* red puzzle-dot bullet, echoing the knobs */
.prose li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .6em;
  width: 9px; height: 9px;
  border-radius: 50%;
  background: var(--red);
}
/* Inline prose links only. Buttons live inside prose blocks too, and this
   rule outranks `.btn`, so an unscoped version painted button labels red on
   red and hung a stray underline off them. */
.prose a:not([class]) { color: var(--red); text-decoration: none; border-bottom: 1px solid currentColor; }

/* text beside a picture, flippable with .split--media-first */
.split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}
.split--media-first .split__media { order: -1; }
.split__media { position: relative; }
/* Only plain, unclassed images get the fill treatment. The About portrait
   reuses .about__photo / .puzzle-mark / .dot-grid, which carry their own
   sizing, and a blanket `.split__media img` rule would stomp on it. */
.split__media > img:not([class]) {
  width: 100%;
  max-height: 520px;
  object-fit: cover;
}
/* A squarer crop for the slots where the copy beside the picture runs long.
   The default cap makes a wide, shallow band that leaves the column looking
   half empty next to a tall list; a 1:1 frame crops in and fills it. */
.split__media > img.split__img--tall,
.split__media > video.split__img--tall {
  width: 100%;
  aspect-ratio: 1 / 1;
  max-height: none;
  object-fit: cover;
}
/* A narrower, taller picture beside a column of copy. The default split gives
   the picture half the row and caps its height, which makes a wide shallow
   band - fine against a paragraph, wrong against a stack of headings and
   answers, where it reads as a stripe rather than as a companion. */
.split--narrow-media { grid-template-columns: minmax(0, 1fr) minmax(0, .62fr); }
.split__media > img.split__img--portrait {
  width: 100%;
  aspect-ratio: 4 / 5;
  max-height: none;
  object-fit: cover;
}

/* A 4:5 box keeps only the middle ~42% of a 1200x630 source, so a landscape
   photo needs telling which 42% matters. The approach FAQ shot has the client
   out of focus on the left and the candidate on the right; 88% is the point
   where she sits fully inside the frame - shoulders, blazer and the hand on
   the paperwork - with the handshake still in it and the client reduced to a
   sleeve. Anything lower starts clipping her right side. */
.split__img--offset-right { object-position: 88% center; }

/* PLACEHOLDER tone behind any image that has not been supplied yet */
.split__media--placeholder { background: var(--cream-200); aspect-ratio: 4 / 3; }

/* A picture riding alongside a block of copy. The copy is the content and
   takes the wide column; the picture is there so the band is not a page of
   type, and it is set portrait because the lists it sits next to are tall - a
   landscape crop would read as a stripe against them. align-items:start keeps
   it at the top of the column rather than floating in the middle. */
.media-split {
  display: grid;
  grid-template-columns: minmax(0, .5fr) minmax(0, 1fr);
  align-items: start;
  gap: clamp(1.75rem, 4vw, 3.5rem);
}
.media-split__body { min-width: 0; }

/* Two spacing corrections that only matter once the head is in a column
   beside something else.

   1. `.section__head p` gives every paragraph in a head a top margin, and the
      eyebrow is a paragraph. With nothing above it and no padding on the head
      to stop it, that margin collapses straight out and carries the whole
      column down - here, 20px below the top of the picture beside it. It is
      invisible on a full-width head, which is why it has never shown up.

   2. The head's bottom margin is sized to separate a heading from a grid
      running the full width of the page. In a column the heading and the list
      under it are one piece of writing, and 48px reads as a gap rather than
      as a heading. */
.media-split .section__head > .eyebrow { margin-top: 0; }
.media-split .section__head { margin-bottom: clamp(1.5rem, 2.4vw, 2rem); }
/* The 62ch reading measure is for a paragraph. This head is an eyebrow and a
   title meant to hold one line, and the column is wider than the cap - so the
   cap moves onto the paragraphs, where it belongs. */
.media-split .section__head { max-width: none; }
.media-split .section__head > p:not(.eyebrow) { max-width: 62ch; }

.media-split__figure { margin: 0; }
.media-split__figure img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  /* The shot is landscape and the frame is portrait, so about half the width
     is cropped away. The two people sit centre-right in the original; pulling
     the window across keeps both of them in it. */
  object-position: 74% center;
}
/* Three columns of steps inside a two-thirds column is a squeeze, and they
   are a sequence anyway - stacked they read as first, then, then.

   Stacked, though, the three-across spacing is far too generous: each item is
   two short lines carrying the vertical rhythm of a column heading. So the
   number moves up onto the title line instead of sitting above it, and the
   padding comes in. Same components, about half the height. */
.media-split .steps {
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(1rem, 1.8vw, 1.5rem);
}
.media-split .step { padding-top: .875rem; border-top-width: 2px; }
.media-split .step::before {
  position: absolute;
  top: .875rem;
  margin-bottom: 0;
  font-size: 1rem;
  line-height: 1.35;
}
.media-split .step h3 {
  margin-bottom: .3rem;
  padding-left: 2.25rem;      /* clears the number now sitting beside it */
  font-size: 1.0625rem;
  line-height: 1.35;
}
.media-split .step p { padding-left: 2.25rem; line-height: 1.6; }


@media (max-width: 780px) {
  .media-split { grid-template-columns: minmax(0, 1fr); }
  .media-split__figure { order: -1; }
  .media-split__figure img { aspect-ratio: 16 / 9; }
}

/* 14c. Numbered process
   -------------------------------------------------------------------------- */
.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: clamp(1.75rem, 3vw, 2.5rem);
  counter-reset: step;
}
.step {
  position: relative;
  padding-top: 1.75rem;
  border-top: 3px solid var(--line);
  counter-increment: step;
  transition: border-color .2s var(--ease);
}
.step:hover { border-top-color: var(--red); }
.step::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  margin-bottom: .875rem;
  color: var(--red);
  font-family: var(--font-display);
  font-size: 1.75rem;
  line-height: 1;
}
.step h3 { margin-bottom: .75rem; font-size: var(--fs-h3); line-height: 1.3; }
.step p { font-size: var(--fs-small); line-height: 1.7; }

/* 14d. FAQ
   -------------------------------------------------------------------------- */
/* A centred heading over a left-hung list reads as a mistake, so the list
   centres with it. The questions themselves stay left-aligned - centred body
   text is harder to scan, and these are meant to be scanned. */
.faq--centred { margin-inline: auto; }
.faq { max-width: 78ch; }
.faq__item { border-bottom: 1px solid var(--line); }
.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.375rem 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.1875rem;
  line-height: 1.4;
  cursor: pointer;
  list-style: none;
  transition: color .2s var(--ease);
}
.faq__item summary::-webkit-details-marker { display: none; }
.faq__item summary:hover { color: var(--red); }
/* plus / minus drawn with two bars, so it needs no icon file */
.faq__item summary::after {
  content: "";
  flex: none;
  width: 14px; height: 14px;
  background:
    linear-gradient(var(--red), var(--red)) center / 14px 2px no-repeat,
    linear-gradient(var(--red), var(--red)) center / 2px 14px no-repeat;
  transition: transform .2s var(--ease);
}
.faq__item[open]:not(.is-closing) summary::after { background-size: 14px 2px, 0 0; }
/* The plus folds into a minus rather than snapping. */
.faq__item summary::after { transition: background-size .3s cubic-bezier(.2, .7, .2, 1), transform .3s cubic-bezier(.2, .7, .2, 1); }
.faq__item[open]:not(.is-closing) summary::after { transform: rotate(180deg); }
.faq__item p { padding-bottom: 1.5rem; font-size: var(--fs-small); max-width: 68ch; }

/* 14e. Testimonials page grid
   -------------------------------------------------------------------------- */
.quote-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: clamp(1.25rem, 2.4vw, 1.75rem);
}
.quote-card {
  display: flex;
  flex-direction: column;
  padding: clamp(1.75rem, 2.6vw, 2.25rem);
  background: var(--white);
  border: 1px solid var(--line);
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease), transform .2s var(--ease);
}
.quote-card:hover {
  border-color: var(--red);
  box-shadow: 0 18px 34px -26px rgba(31,26,23,.55);
  transform: translateY(-2px);
}
.quote-card__stars { display: flex; gap: .2rem; margin-bottom: 1.125rem; }
.quote-card__stars svg { width: 16px; height: 16px; fill: var(--red); }
.quote-card blockquote { margin: 0; }
.quote-card blockquote p {
  font-family: var(--font-display);
  font-size: 1.0625rem;
  line-height: 1.6;
  color: var(--ink);
}
.quote-card__by {
  margin-top: auto;
  padding-top: 1.5rem;
  border-top: 1px solid var(--line);
}
.quote-card__name { display: block; font-weight: 700; font-size: var(--fs-small); color: var(--ink); }
.quote-card__role { display: block; font-size: .8125rem; line-height: 1.4; }
.quote-card__source { display: block; margin-top: .5rem; font-size: .75rem; letter-spacing: .06em; text-transform: uppercase; color: var(--red); }

/* rating summary strip */
.rating {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding: clamp(1.5rem, 2.4vw, 2rem);
  margin-bottom: clamp(2rem, 3.6vw, 3rem);
  background: var(--cream);
  border-left: 4px solid var(--red);
}
.rating__score {
  display: flex;
  align-items: center;
  gap: .4rem;
  color: var(--red);
  font-family: var(--font-display);
  font-size: clamp(2.25rem, 3.6vw, 3rem);
  line-height: 1;
}
.rating__score svg { width: .5em; height: .5em; fill: var(--red); }
.rating p { margin: 0; font-size: var(--fs-small); max-width: 46ch; }

/* 14f. Jobs page
   -------------------------------------------------------------------------- */
.job-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: clamp(1.25rem, 2.4vw, 1.75rem);
}
.job-grid .job-card { height: 100%; }

.jobs-empty {
  padding: clamp(2rem, 4vw, 3rem);
  background: var(--cream);
  text-align: center;
}

/* 14g. Contact
   -------------------------------------------------------------------------- */
.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: clamp(2.5rem, 5vw, 4.5rem);
  align-items: start;
}

.form { display: grid; gap: 1.25rem; }
.form__row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1.25rem; }
.field { display: grid; gap: .5rem; }
.field label {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink);
}
.field .req { color: var(--red); }
.field input,
.field select,
.field textarea {
  width: 100%;
  padding: .875rem 1rem;
  font: inherit;
  font-size: var(--fs-small);
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 0;
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.field textarea { min-height: 148px; resize: vertical; }
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-tint);
}
.field__hint { font-size: .8125rem; color: var(--body); }
.form__note { font-size: .8125rem; color: var(--body); }
.form__note a { color: var(--red); }

.contact-aside { background: var(--cream); padding: clamp(1.75rem, 3vw, 2.5rem); }
.contact-aside h2 {
  margin-bottom: 1.5rem;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.contact-list { display: grid; gap: 1.25rem; margin: 0; }
.contact-list div { display: grid; gap: .25rem; }
.contact-list dt {
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--red);
}
.contact-list dd { margin: 0; overflow-wrap: anywhere; }
.contact-list a { color: var(--ink); text-decoration: none; border-bottom: 1px solid var(--line); transition: border-color .2s var(--ease); }
.contact-list a:hover { border-bottom-color: var(--red); }
/* The social mark is a logo, not a text link, so it takes none of the rule
   above. It has to come after .contact-list a rather than before it - the two
   selectors carry the same weight, so order is what decides. */
.contact-list .contact-aside__social a,
.contact-list .contact-aside__social a:hover {
  border-bottom: 0;
  transition: transform .2s var(--ease);
}
.contact-list .contact-aside__social a:hover { transform: scale(1.06); }

/* 14h. Closing call to action, on every interior page
   -------------------------------------------------------------------------- */
.cta-band {
  padding-block: clamp(3rem, 5.5vw, 4.5rem);
  background: var(--red);
  background-image: var(--red-grad);
  color: var(--white);
  text-align: center;
}
.cta-band h2 {
  max-width: 22ch;
  margin-inline: auto;
  color: var(--white);
  font-size: var(--fs-h2);
}
.cta-band h2 em { font-style: italic; }
.cta-band p { max-width: 54ch; margin: 1.25rem auto 0; color: rgba(255,255,255,.88); }
.cta-band__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .875rem;
  margin-top: 2rem;
}

/* 14i. Interior responsive
   -------------------------------------------------------------------------- */
@media (max-width: 940px) {
  .split { grid-template-columns: minmax(0, 1fr); }
  .split--media-first .split__media { order: 0; }
  .contact-grid { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 560px) {
  .form__row { grid-template-columns: minmax(0, 1fr); }
  .rating { flex-direction: column; align-items: flex-start; }
  .cta-band__actions .btn { width: 100%; }
}

/* 15. Career timeline (about page)
   ==========================================================================
   A dark, full-bleed band with a horizontal rule running edge to edge, a dot
   per milestone sitting on that rule, and the copy hanging below it.

   The rail reuses the shared [data-rail] engine in js/main.js - arrows, drag,
   snap - and adds data-rail-page so the arrows turn a whole page of four
   rather than nudging one card, because the entries are a sequence.

   Vertical rhythm is driven by three custom properties so the rule and the
   dots always line up no matter how the year type scales:

     year block height  = --tl-year-fs   (line-height is 1)
     gap above the rule = --tl-lead
     gap below the rule = --tl-trail

   so the rule sits at --tl-year-fs + --tl-lead from the top of an item, and
   the body below simply pads by --tl-lead + --tl-trail. Change the type scale
   and everything still meets.
   ========================================================================== */
.timeline {
  --tl-year-fs: clamp(2rem, 3.4vw, 2.75rem);
  --tl-lead: clamp(1.75rem, 3vw, 2.5rem);
  --tl-trail: clamp(1.75rem, 3vw, 2.75rem);
  --tl-gap: clamp(1.5rem, 3vw, 2.5rem);
  --tl-cols: 4;

  padding-block: var(--section-y);
  background: var(--ink-950);
  color: rgba(255,255,255,.72);
  overflow: hidden;   /* clips the 100vw rule so it cannot cause a scrollbar */
}

/* head ------------------------------------------------------------------- */
.timeline__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}

.timeline__title {
  margin: 0;
  color: var(--white);
  /* sans for the name, serif for the last word - the two site faces together */
  font-family: var(--font-body);
  font-size: clamp(1.875rem, 4.2vw, 3.5rem);
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.05;
}
.timeline__title em {
  font-family: var(--font-display);
  font-style: normal;
  font-weight: 400;
  letter-spacing: -0.01em;
}

.timeline__nav { display: flex; flex: none; gap: .25rem; }
.timeline__btn {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  padding: 0;
  background: none;
  border: 0;
  color: var(--white);
  transition: opacity .2s var(--ease), transform .2s var(--ease);
}
.timeline__btn svg { width: 38px; height: 22px; }
.timeline__btn[aria-disabled="true"] { opacity: .28; pointer-events: none; }
.timeline__btn--prev:hover { transform: translateX(-4px); }
.timeline__btn--next:hover { transform: translateX(4px); }

/* track + rule ----------------------------------------------------------- */
.timeline__track { position: relative; }

/* the rule is a static backdrop - the items scroll past it, it does not move */
.timeline__rule {
  position: absolute;
  z-index: 1;
  top: calc(var(--tl-year-fs) + var(--tl-lead));
  left: 50%;
  width: 100vw;
  height: 1px;
  background: rgba(224, 0, 0, .38);
  transform: translateX(-50%);
}

/* rail ------------------------------------------------------------------- */
.timeline__rail {
  position: relative;
  z-index: 2;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--tl-cols) - 1) * var(--tl-gap)) / var(--tl-cols));
  gap: var(--tl-gap);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.timeline__rail::-webkit-scrollbar { display: none; }
/* the shared rail engine turns snap off while it animates a settle */
.timeline__rail.is-settling { scroll-snap-type: none; }
.timeline__rail.is-dragging { cursor: grabbing; }

.timeline__item {
  position: relative;
  scroll-snap-align: start;
  min-width: 0;
}

.timeline__year {
  display: block;
  color: var(--white);
  font-size: var(--tl-year-fs);
  font-weight: 500;
  line-height: 1;      /* so the block height is exactly --tl-year-fs */
  letter-spacing: -0.01em;
}

.timeline__dot {
  position: absolute;
  top: calc(var(--tl-year-fs) + var(--tl-lead));
  left: 0;
  width: 11px; height: 11px;
  border-radius: 50%;
  background: var(--red);
  transform: translateY(-50%);
}

.timeline__body { padding-top: calc(var(--tl-lead) + var(--tl-trail)); }

.timeline__label {
  margin: 0 0 .875rem;
  color: var(--white);
  font-family: var(--font-body);
  font-size: clamp(1.125rem, 1.5vw, 1.375rem);
  font-weight: 700;
  line-height: 1.25;
}
.timeline__body p {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.7;
  color: rgba(255,255,255,.7);
}


@media (max-width: 940px) {
  .timeline { --tl-cols: 2; }
}
@media (max-width: 560px) {
  /* 1.6, not 1. One milestone per screen made the rail read as a slideshow of
     unrelated cards - you could not see that 1991 was followed by 1993, which
     is the whole point of a timeline. At 1.6 the current entry is full width
     and the next one is visibly cut off at the edge, which both gives you the
     sequence and shows the rail can be scrolled.

     The arrows page by rail.clientWidth and CSS snap then settles to the
     nearest item, so a fractional column count still lands on an item edge. */
  .timeline { --tl-cols: 1.6; --tl-gap: 1.25rem; }
  .timeline__head { flex-direction: column; align-items: flex-start; }
  .timeline__btn { width: 48px; height: 48px; }
}

/* 16. Reviews deck (testimonials page)
   ==========================================================================
   Replaces the featured-review tab menu. That was a vertical list of buttons
   beside one quote, which works for five reviews and falls apart at eighty -
   the menu becomes a wall, and every review has to be written into the page
   by hand.

   This is a deck instead: big cards, swipe or drag through them, filter by
   audience. Cards render from the REVIEWS array in js/main.js, so adding the
   real eighty is a data job, not a markup job.

   The scrolling is not implemented here. The deck is a [data-rail] and
   inherits touch panning, grab-to-drag, the arrow buttons and the draggable
   progress bar from the rail module - the same one behind the homepage jobs
   rail. Only the card, the filter row and the counter are new.
   ========================================================================== */
/* Shorter on top than the shared section rhythm. This page opens straight on
   a centred heading, so the full --section-y under a 112px header read as a
   band of empty white before anything started. */
.reviews { padding-block: clamp(1.5rem, 2.6vw, 2.25rem) var(--section-y); }

.reviews__lead {
  max-width: 56ch;
  margin: clamp(1rem, 1.8vw, 1.5rem) auto 0;
  font-size: 1.0625rem;
  line-height: 1.75;
}

/* filter row and position readout ---------------------------------------- */
.reviews__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  margin-top: clamp(2rem, 3.5vw, 2.75rem);
}
.reviews__filters { display: flex; flex-wrap: wrap; gap: .5rem; }
.reviews__filters[hidden] { display: none; }

.reviews__pos { display: flex; align-items: center; gap: 1rem; }
.reviews__count {
  margin: 0;
  color: var(--body);
  font-size: .8125rem;
  font-variant-numeric: tabular-nums;   /* stops the row twitching as it counts */
  white-space: nowrap;
}

/* the deck ---------------------------------------------------------------
   Full-bleed on purpose: cards run to both edges of the viewport so a swipe
   has somewhere to go, and the container padding is reproduced as scroll
   padding so the first card still lines up with the heading above it. */
.reviews__deck {
  display: flex;
  align-items: flex-start;
  gap: clamp(1rem, 2vw, 1.5rem);
  margin: clamp(1.5rem, 2.5vw, 2rem) 0 0;
  padding: .25rem max(1.5rem, calc((100vw - var(--container)) / 2)) 1.25rem;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-padding-left: max(1.5rem, calc((100vw - var(--container)) / 2));
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  cursor: grab;
}
.reviews__deck::-webkit-scrollbar { display: none; }
.reviews__deck:active { cursor: grabbing; }

/* snap is suppressed while dragging and while the rail eases to rest - see
   the note on .job-rail, the behaviour is identical */
.reviews__deck.is-dragging,
.reviews__deck.is-settling { scroll-snap-type: none; }
.reviews__deck.is-dragging {
  cursor: grabbing;
  scroll-behavior: auto;
  user-select: none;
  -webkit-user-select: none;
}
.reviews__deck:focus-visible { outline: 2px solid var(--red); outline-offset: 4px; }

/* Card width is derived, not capped - see the note inside. */
.reviews__item {
  /* Three cards across the container at desktop widths: the visible band is
     the container, less the two gaps between the three cards. The max() keeps
     a readable floor on phones, where one card plus a peek is right. */
  flex: 0 0 max(255px, calc((min(100vw - 3rem, var(--container)) - 2 * clamp(1rem, 2vw, 1.5rem)) / 3));
  scroll-snap-align: start;
  display: flex;
}

/* the card --------------------------------------------------------------- */
.rcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  width: 100%;
  padding: clamp(1.5rem, 2.2vw, 2rem);
  background: var(--cream);
  border: 1px solid var(--line);
}
.rcard__mark {
  position: absolute;
  top: clamp(.5rem, 1.5vw, 1rem);
  right: clamp(1rem, 2.5vw, 1.75rem);
  color: var(--red);
  font-family: var(--font-display);
  font-size: clamp(3.5rem, 7vw, 5rem);
  line-height: 1;
  opacity: .18;
  pointer-events: none;
}
.rcard__stars { display: flex; gap: .2rem; }
.rcard__stars svg { width: 16px; height: 16px; fill: var(--red); }

/* Not flex:1 any more. A one-line review was being stretched to the height of
   the longest card in view, leaving a big empty gap above its attribution.
   Cards now size to their own content and the deck aligns them at the top. */
.rcard__quote { margin: 0; }
.rcard__quote p {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1rem, 1.35vw, 1.1875rem);
  line-height: 1.4;
}

.rcard__by {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
}
.rcard__who { display: grid; gap: .15rem; min-width: 0; }
.rcard__name {
  color: var(--ink);
  font-size: clamp(.9375rem, 1.1vw, 1.0625rem);   /* 15% down */
  font-weight: 700;
  line-height: 1.2;
}
.rcard__role { color: var(--body); font-size: .875rem; line-height: 1.4; }
.rcard__logo { flex: none; width: auto; height: 26px; object-fit: contain; }

/* Deliberately legible, not a whisper. This marks filler as filler, and it
   has to survive a screenshot - see the note in js/main.js. */
.rcard__flag {
  flex: none;
  align-self: center;
  padding: .25rem .5rem;
  background: var(--red-tint);
  color: var(--red-dark);
  font-size: .625rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* the progress bar sits under the deck, inside the container again */
.reviews .rail-progress { margin-top: .5rem; }

/* 16b. Proof strip
   -------------------------------------------------------------------------- */
.proof {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
  padding: clamp(1.75rem, 3vw, 2.5rem) 0 0;
  margin-top: clamp(2.5rem, 4.5vw, 3.5rem);
  border-top: 1px solid rgba(255,255,255,.16);
}
.proof__item { display: grid; gap: .35rem; }
.proof__value {
  display: flex;
  align-items: center;
  gap: .3rem;
  color: var(--white);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 2.8vw, 2.5rem);
  line-height: 1;
}
.proof__value svg { width: .55em; height: .55em; fill: var(--red); }
.proof__label {
  font-size: .6875rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(255,255,255,.55);
}

/* 17. Accreditations
   ==========================================================================
   A second row inside the dark credentials bar: the two standing recognitions
   rather than the running totals above them.

   Both marks are supplied artwork. The SEEK rosette replaced a type-set star
   that stood in while there was no badge file.
   ========================================================================== */
/* The bar carries only these two now, so they are the whole design rather
   than a footnote under a row of figures. Side by side and centred as a pair,
   with a rule between them, and each one equal width so the pair sits
   symmetrically about the centre line rather than being dragged off it by the
   longer of the two. */
.credentials__accred {
  display: flex;
  flex-wrap: wrap;
  align-items: stretch;
  justify-content: center;
  gap: clamp(1.5rem, 4vw, 3.5rem);
  /* about.html still runs the figures above this row, so the rule and the
     spacing that separate the two stay on the base */
  margin-top: clamp(1.75rem, 2.8vw, 2.25rem);
  padding-top: clamp(1.75rem, 2.8vw, 2.25rem);
  border-top: 1px solid rgba(255,255,255,.16);
}
/* on the homepage this row is the whole bar - nothing above it to divide off */
.credentials--accred-only .credentials__accred {
  border-top: 0;
  margin-top: 0;
  padding-top: 0;
}

/* Sized to content rather than to a shared basis: the SEEK lockup is a wide
   three-part mark and the NPA one is a compact wordmark, so forcing equal
   widths squeezed one and stranded the other. justify-content on the row
   still centres the pair. */
.accred {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  flex: 0 1 auto;
  max-width: 46ch;
  transition: transform .2s var(--ease);
}
/* Each credential lifts a little under the cursor. They are not links, so this
   is only there to say the bar is worth reading rather than to promise a
   click - hence a grow and nothing else. Transform only, so the row beside it
   never reflows and the hairline divider stays put. */
/* The reveal system must not claim .accred itself - it reveals the row that
   holds them (.credentials__accred) instead. Both use transform, and
   `.js-reveal [data-reveal].is-visible { transform: none }` outranks this
   rule, so tagging the credential rather than its container silently killed
   the hover the moment the bar scrolled into view. */
.accred:hover { transform: scale(1.04); }
/* a hairline between the pair, drawn on the second one so it never dangles
   at the end of the row - and dropped once they stack */
.accred + .accred {
  border-left: 1px solid rgba(255,255,255,.16);
  padding-left: clamp(1.5rem, 4vw, 3.5rem);
}
@media (max-width: 640px) {
  .accred + .accred { border-left: 0; padding-left: 0; }
  .accred { flex-basis: 100%; }
}

/* Both marks scale up now that this row is the whole bar rather than a
   footnote. Sized against each other by eye rather than to a single number:
   NPA is a wide wordmark and the SEEK rosette is square, so matching their
   widths would leave the rosette twice the visual weight. */
.accred__logo {
  flex: none;
  width: clamp(84px, 7.4vw, 116px);   /* NPA wordmark, 518 x 230 */
}
/* SEEK's own lockup - arrow mark, wordmark, and the RECOMMENDED rosette set
   into the corner. 1484 x 578, so it is wider than it is tall and needs real
   width to stay legible. The wordmark inside it is white, which is why this
   only works on the dark bar. Widths here are tuned to land at roughly the
   same height as the NPA mark beside it, not to the same width. */
.accred__logo--seek {
  width: clamp(116px, 11.5vw, 156px);
}

.accred__text { display: grid; gap: .15rem; }
/* The only line of copy left beside each mark, so it is the message rather
   than a footnote - full white, and sized up from the caption it used to be. */
.accred__note {
  font-size: .9375rem;
  line-height: 1.45;
  color: var(--white);
}

@media (max-width: 560px) {
  .credentials__accred { gap: 1.25rem; }
}

/* 18. Per-page signature blocks
   ==========================================================================
   The interior pages started out sharing one generic kit, which made them
   read as the same page with different words. Each block below belongs to
   one page and gives it a shape of its own:

     .process    our-approach   the six steps, as a sticky-headed vertical run
     .compare    for-employers  the usual way vs how we work, side by side
     .sectors    for-employers  hairline grid of sectors
     .commit     for-candidates the promises, as a ticked list
     .jobfilter  jobs           sector chips that filter the grid
     .pullquote  about          a line of Laurie's, set large
   ========================================================================== */

/* 18a. Process (our-approach)
   -------------------------------------------------------------------------- */
.process {
  display: grid;
  grid-template-columns: minmax(0, 330px) minmax(0, 1fr);
  gap: clamp(2.5rem, 6vw, 5rem);
  align-items: start;
}
/* the heading rides along while the steps scroll past it */
.process__intro { position: sticky; top: calc(var(--header-h) + 1.5rem); }
.process__intro p { margin-top: 1.25rem; font-size: var(--fs-small); }

.process__step {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.5rem);
  padding: clamp(1.5rem, 2.8vw, 2.25rem) 0;
  border-top: 1px solid var(--line);
}
.process__step:last-child { border-bottom: 1px solid var(--line); }

.process__num {
  min-width: 2.5ch;
  color: var(--red);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 3.2vw, 2.75rem);
  line-height: 1;
}
.process__step h3 {
  margin-bottom: .75rem;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.process__step p { margin: 0; font-size: var(--fs-small); }

/* 18b. Compare (for-employers)
   -------------------------------------------------------------------------- */
/* The two panels interlock: a knob on the red one seats into a socket bitten
   out of the grey one.

   The geometry is the hero collage's, not an approximation of it. A ball of
   radius --r sits with its centre --d OUTSIDE the panel edge it grows from,
   where d = 0.893r. That is the 45% cut the collage uses: the edge crosses the
   ball at 45% of its radius, so the ball stands proud on a neck instead of
   bulging off the side as a bump. It protrudes r + d, about 1.9 times its own
   radius.

   The socket is the same ball grown by one gutter, on the same centre - which
   is what makes the ring around the knob equal the seam running between the
   panels. Because the ball's centre is well outside the red edge, it lands
   well INSIDE the grey one, so the bite is a near-circle with a mouth
   narrower than itself. Centre the socket on the gutter instead and it bites
   both panels equally, which is what a dot in a saucer looks like.

   The gutter is small on purpose. It is the seam between two pieces of one
   puzzle, and it has to stay small relative to the ball or the socket needed
   to clear it swallows the panel.

   Stacking: the grey panel and its socket sit at 1, the red panel at 2, so the
   red edge stays straight and the socket only ever eats the grey. The knob is
   a child of the red panel, so it paints above both. */
.compare {
  --gutter: .5rem;
  --pad: clamp(1.75rem, 3vw, 2.5rem);
  --r: 2.125rem;                              /* knob radius */
  /* 0.72 rather than the collage's 0.893: the tab sits ~6px further into the
     red panel, so less of it hangs across into the grey one. */
  --d: calc(var(--r) * 0.72);                 /* how far its centre sits outside the edge */
  --tab: calc(var(--r) + var(--d));           /* how far the knob reaches past the edge */
  --socket-r: calc(var(--r) + var(--gutter)); /* the ball, grown by one seam */
  /* How far past the panel edge the socket circle reaches, and so how deep
     inside the panel its centre sits. */
  --socket-out: calc(var(--socket-r) - var(--d) + var(--gutter));
  --socket-depth: calc(var(--socket-r) - var(--socket-out));
  /* Half the width of the socket's MOUTH - where the circle crosses the panel
     edge. Not the same as the socket radius: the centre sits inside the panel,
     so the mouth is narrower than the circle it belongs to. Getting this wrong
     is what left a gap between the end of the straight edge and the end of the
     arc - the edge stopped for the full diameter while the arc only filled the
     mouth, leaving about 6px dead at each end.

       d            = 2.125 * 0.72     = 1.53rem
       socket-r     = 2.125 + 0.5      = 2.625rem
       socket-out   = 2.625 - 1.53 + 0.5 = 1.595rem
       socket-depth = 2.625 - 1.595    = 1.03rem
       mouth        = sqrt(2.625^2 - 1.03^2) = 2.4145rem

     Worked out once rather than in CSS, because the unit division sqrt() needs
     is not something to rely on. Recompute it if --r or --gutter change. */
  --mouth: 2.4145rem;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
  gap: var(--gutter);
}
.compare__col {
  position: relative;
  padding: var(--pad);
  border: 1px solid var(--line);
}
.compare__col--theirs { z-index: 1; }
.compare__col--ours   { z-index: 2; }

/* The grey panel's right edge, and the socket bitten out of it.

   The edge is drawn as its own hairline rather than as border-right, because
   a border runs the full height and cannot stop for the socket - it carried
   straight on through the bite and ran into the red piece, so the two pieces
   read as one shape with a circle drawn on top rather than as two pieces that
   fit together. A gradient with a transparent band gives the edge a real gap
   exactly as tall as the socket.

   Relying on the socket's fill to cover that border was not an option: the red
   panel's box now reaches back across this edge (it is stretched left to carry
   its knob), so what covers what there depends on stacking rather than on the
   shape, and the border survived. Not drawing it in the first place is the
   only version that cannot go wrong.

   The socket itself is then just the curve - no fill needed, since there is no
   longer anything to hide. The ring it draws is what shows the even gap around
   the knob: the arc inside the panel, and the arc crossing the seam. */
.compare__col--theirs { border-right: 0; }
.compare__col--theirs::before {
  content: "";
  position: absolute;
  top: -1px;
  bottom: -1px;
  right: -1px;
  width: 1px;
  background: linear-gradient(
    to bottom,
    var(--line) 0 calc(50% - var(--mouth)),
    transparent calc(50% - var(--mouth)) calc(50% + var(--mouth)),
    var(--line) calc(50% + var(--mouth)) 100%
  );
}
.compare__col--theirs::after {
  content: "";
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  right: calc(-1 * var(--socket-out));
  width: calc(var(--socket-r) * 2);
  height: calc(var(--socket-r) * 2);
  border-radius: 50%;
  border: 1px solid var(--line);
  /* Only the arc that is actually part of this piece.

     The socket's centre sits inside the panel, so the circle reaches back out
     past the right edge - and drawing the whole ring meant that outer sliver
     was drawn too, running across the seam and into the red piece. It is not
     part of the grey outline: the boundary comes down the edge, turns in at
     the mouth, follows the arc round inside the piece, and comes back out at
     the other mouth. Clipping at the panel edge leaves exactly that, with the
     two cut ends meeting the straight hairline where the mouth is. */
  clip-path: inset(0 var(--socket-out) 0 0);
}

/* The knob is not a circle laid on top of the red panel - it is the red panel.

   Drawn as a separate element it carried its own copy of the gradient, so the
   full bright-to-dark ramp ran across 68px of knob and met the panel's ramp at
   a visible seam. Instead the panel's box is stretched left far enough to
   contain the knob (negative margin, matching padding, so the copy does not
   move), which means one gradient paints across body and knob together. The
   mask then keeps two things out of that box: the body, and a circle where the
   knob goes. Everything between them is thrown away, so the tab reads as part
   of the piece rather than as something stuck to it. */
.compare__col--ours {
  margin-left: calc(-1 * var(--tab));
  padding-left: calc(var(--pad) + var(--tab));
  -webkit-mask-image:
    linear-gradient(#000, #000),
    radial-gradient(circle var(--r) at calc(var(--tab) - var(--d)) 50%, #000 99.5%, transparent 100%);
          mask-image:
    linear-gradient(#000, #000),
    radial-gradient(circle var(--r) at calc(var(--tab) - var(--d)) 50%, #000 99.5%, transparent 100%);
  -webkit-mask-position: var(--tab) 0, 0 0;
          mask-position: var(--tab) 0, 0 0;
  -webkit-mask-size: calc(100% - var(--tab)) 100%, 100% 100%;
          mask-size: calc(100% - var(--tab)) 100%, 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-composite: source-over;
          mask-composite: add;
}

/* Stacked, there is no gutter between the columns to put a joint in - so the
   knob comes off and the panel goes back to being a rectangle. */
@media (max-width: 640px) {
  .compare__col--theirs { border-right: 1px solid var(--line); }
  .compare__col--theirs::before,
  .compare__col--theirs::after { display: none; }
  .compare__col--ours {
    margin-left: 0;
    padding-left: var(--pad);
    -webkit-mask-image: none;
            mask-image: none;
  }
}
.compare__col--ours {
  background: var(--red);
  background-image: var(--red-grad);
  border-color: transparent;
  color: rgba(255,255,255,.9);
}

.compare__tag {
  display: block;
  margin-bottom: .875rem;
  color: var(--red);
  font-size: var(--fs-label);
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
}
.compare__col--ours .compare__tag { color: rgba(255,255,255,.75); }

.compare__col h3 {
  margin-bottom: 1.5rem;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.compare__col--ours h3 { color: var(--white); }

.compare__list li {
  position: relative;
  margin-bottom: .75rem;
  padding-left: 1.75rem;
  font-size: var(--fs-small);
  line-height: 1.65;
}
.compare__list li::before {
  content: "";
  position: absolute;
  left: 0; top: .6em;
  width: 9px; height: 9px;
  border-radius: 50%;
}
.compare__col--theirs .compare__list li::before { background: var(--grey-200); }
.compare__col--ours .compare__list li::before { background: var(--white); }

/* 18c. Sectors (for-employers)
   --------------------------------------------------------------------------
   1px grid gap over a line-coloured background paints the hairlines, so the
   dividers never double up at the joins the way per-cell borders do. */
.sectors {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1px;
  background: var(--line);
  border: 1px solid var(--line);
}
.sector {
  display: grid;
  align-content: start;
  gap: .75rem;
  padding: clamp(1.5rem, 2.6vw, 2rem);
  background: var(--white);
  transition: background .2s var(--ease);
}
.sector:hover { background: var(--cream); }
.sector__rule { width: 30px; height: 3px; background: var(--red); }
.sector__name {
  color: var(--ink);
  font-family: var(--font-display);
  font-size: 1.25rem;
  line-height: 1.25;
}
.sector__roles { font-size: var(--fs-small); line-height: 1.6; }

/* 18d. Commitments (for-candidates)
   -------------------------------------------------------------------------- */
.commit__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start;
  gap: 1.25rem;
  padding: clamp(1.25rem, 2.4vw, 1.75rem) 0;
  border-bottom: 1px solid var(--line);
}
.commit__item:first-child { border-top: 1px solid var(--line); }

/* A light response on hover. These are commitments rather than links, so the
   move says "this is a list of separate promises, here is the one you are
   reading" and nothing more - the row eases across, its tick fills out, and
   the rule under it takes the brand colour. Transform on the row rather than
   margin or padding, so nothing below it reflows as the cursor travels. */
/* Both rules light up - the one under the row (its own border-bottom) and
   the one over it, which belongs to the row above, or to the first row's own
   border-top. The content eases across rather than the row, so the two red
   rules stay square with each other instead of the lower one sliding off. */
.commit__item { transition: border-color .25s var(--ease); }
.commit__item > * { transition: translate .25s var(--ease); }
.commit__item:hover { border-bottom-color: var(--red); }
.commit__item:first-child:hover,
.commit__item:has(+ .commit__item:hover) { border-color: var(--red); }
.commit__item:has(+ .commit__item:hover) { border-top-color: var(--line); }
.commit__item:hover > * { translate: .375rem 0; }
.commit__tick { transition: transform .25s var(--ease), translate .25s var(--ease); }
.commit__item:hover .commit__tick { transform: scale(1.12); }

@media (prefers-reduced-motion: reduce) {
  .commit__item:hover > * { translate: none; }
  .commit__item:hover .commit__tick { transform: none; }
}

.commit__tick {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--red);
}
.commit__tick svg {
  width: 15px; height: 15px;
  fill: none;
  stroke: var(--white);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.commit__item strong {
  display: block;
  margin-bottom: .3rem;
  color: var(--ink);
  font-size: 1.0625rem;
  line-height: 1.35;
}
.commit__item p { margin: 0; font-size: var(--fs-small); }

/* 18e. Job filter (jobs)
   -------------------------------------------------------------------------- */
.jobfilter {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: clamp(1.75rem, 3vw, 2.5rem);
}
/* Shared by the jobs filter and the reviews filter on the testimonials page.
   Sized to sit as a real control rather than a caption - the 13px version read
   as small print next to the deck it filters. */
.jobfilter__btn {
  padding: .8rem 1.4rem;
  background: transparent;
  border: 1px solid var(--line);
  color: var(--body);
  font-size: .9375rem;
  font-weight: 600;
  line-height: 1.2;
  transition: background-color .2s var(--ease), border-color .2s var(--ease), color .2s var(--ease);
}
.jobfilter__btn:hover { border-color: var(--red); color: var(--ink); }
.jobfilter__btn[aria-pressed="true"] {
  background: var(--red);
  border-color: var(--red);
  color: var(--white);
}
.jobfilter__count { opacity: .65; }

/* 18f. Pull quote (about)
   -------------------------------------------------------------------------- */
.pullquote {
  max-width: 44ch;
  margin: clamp(2rem, 4vw, 3rem) 0 0;
  padding-left: clamp(1.25rem, 3vw, 2rem);
  border-left: 3px solid var(--red);
}
.pullquote p {
  margin: 0;
  color: var(--ink);
  font-family: var(--font-display);
  font-size: clamp(1.25rem, 2.1vw, 1.6875rem);
  font-style: italic;
  line-height: 1.4;
}
.pullquote cite {
  display: block;
  margin-top: 1rem;
  color: var(--red);
  font-size: .75rem;
  font-style: normal;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* 18g. Responsive
   -------------------------------------------------------------------------- */
@media (max-width: 940px) {
  .process { grid-template-columns: minmax(0, 1fr); }
  .process__intro { position: static; }
}

/* The reviews deck can hold eighty cards, so its progress bar is doing real
   navigation, not just reporting position - you drag or click it to jump.
   Give it more of a target than the jobs rail needs. */
.reviews .rail-progress { height: 6px; cursor: pointer; }
.reviews .rail-progress__thumb { border-radius: 3px; }

/* 7d. Assembling the collage
   ==========================================================================
   The collage used to fade up as one block, which is a waste of a puzzle. Each
   piece now arrives separately and snaps into its slot, so the hero builds
   itself the way the artwork implies.

   Direction is per piece and set inline: --from-x / --from-y push it out along
   the edge it belongs to, and --from-r gives it a little rotation so it reads
   as being placed by hand rather than sliding on rails. --order sequences it.

   The order starts at the big media piece top right and works outward. It
   used to sit still as the anchor every other joint was solved against, back
   when there was a row above it; now that it is a corner piece it arrives
   from the top right like the rest, and nothing in the collage is static.

   The easing overshoots slightly (the 1.2 in the curve) which is what gives
   the piece its snap as it lands. Opacity settles faster than position, so a
   piece is solid before it stops moving and you see it seat itself.
   ========================================================================== */
.js-reveal [data-assemble] .piece,
.js-reveal [data-assemble] .puzzle-mark,
.js-reveal [data-assemble] .dot-grid,
.js-reveal .npa-band__mark[data-assemble] img,
.js-reveal [data-assemble] .about__photo {
  opacity: 0;
  transform:
    translate(var(--from-x, 0), var(--from-y, 0))
    rotate(var(--from-r, 0deg))
    scale(.94);
  transition:
    opacity .42s var(--ease) calc(var(--order, 0) * 105ms),
    transform .78s cubic-bezier(.22, 1.2, .36, 1) calc(var(--order, 0) * 105ms);
  will-change: transform, opacity;
}
.js-reveal [data-assemble].is-assembled .piece,
.js-reveal [data-assemble].is-assembled .puzzle-mark,
.js-reveal [data-assemble].is-assembled .dot-grid,
.js-reveal .npa-band__mark[data-assemble].is-assembled img,
.js-reveal [data-assemble].is-assembled .about__photo {
  opacity: 1;
  transform: none;
}
/* A piece settles at its gap inset, not at none - that is its resting state,
   not an animation step. Everything else above really does settle at none. */
.js-reveal [data-assemble].is-assembled .piece {
  transform: scale(var(--sx, 1), var(--sy, 1));
}
/* nothing left to composite once the puzzle is together */
.js-reveal [data-assemble].is-assembled .piece,
.js-reveal [data-assemble].is-assembled .puzzle-mark,
.js-reveal [data-assemble].is-assembled .dot-grid,
.js-reveal .npa-band__mark[data-assemble].is-assembled img,
.js-reveal [data-assemble].is-assembled .about__photo { will-change: auto; }

/* The NPA mark is one big logo rather than a set of pieces, so there is no
   stagger to run - it rises and resolves on its own. It travels a little
   further than a puzzle piece does because nothing lands beside it to give
   the movement a scale. */
.js-reveal .npa-band__mark[data-assemble] img { --from-y: 14%; }

/* 7e. Hero video framing
   ==========================================================================
   Framing is per slot, because the two clips are nothing like each other and
   each frame crops a different way.

   Bottom left: 2160x3840, portrait 9:16, in a near-square frame - cover throws
   away about 73% of the height. The crop is pulled up from centre to keep the
   two people in shot; dead centre put them low and cut the tops of their heads.

   Middle: 1920x1080, landscape, in a 1.513 frame - only about 15% of the width
   goes, so plain centre is right and it needs no correction.

   Retune if either clip is replaced.
   ========================================================================== */
.piece__video { background: var(--cream-200); }
/* Nothing needs a crop correction with the current clips - they are all
   landscape-ish and centre well. Re-tune per slot if footage changes. */

/* 16d. Reviews: uniform cards, expandable, fat scrub bar
   ==========================================================================
   Cards are all one height so the deck reads as a row rather than a skyline.
   A review too long for that height is clamped and gets a "See more" that
   expands only that card - the deck aligns to the top, so growing one does
   not drag its neighbours taller with it.
   ========================================================================== */
.reviews__deck { align-items: flex-start; }

/* A fixed height, not a floor - min-height let longer reviews grow past it
   and the row came out as a skyline again.

   The number is derived rather than picked: six lines of quote, plus the card
   padding, plus the fixed chrome above and below the quote (stars row, the
   See more button, the attribution block, and the gaps between them). Driving
   it off the quote type means the cut still lands on line six as the font
   scales, instead of needing a new height per breakpoint. */
.rcard { height: calc(8.4 * clamp(1rem, 1.35vw, 1.1875rem) + clamp(1.5rem, 2.2vw, 2rem) * 2 + 156px); }
.rcard.is-expanded { height: auto; }

/* The quote takes whatever height the fixed card has left and clips there.
   Driving it off flex rather than a line count means it adapts to the card
   size at any breakpoint instead of needing a new -webkit-line-clamp per
   width. The gradient mask fades the cut instead of slicing a line in half. */
.rcard__quote {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(#000 86%, transparent 100%);
          mask-image: linear-gradient(#000 86%, transparent 100%);
}
.rcard.is-expanded .rcard__quote {
  overflow: visible;
  -webkit-mask-image: none;
          mask-image: none;
}

/* attribution sits on the floor of the card whatever the quote does */
.rcard__by { margin-top: auto; }

.rcard__more {
  align-self: flex-start;
  margin-top: .5rem;
  padding: 0;
  background: none;
  border: 0;
  color: var(--red);
  font-size: .8125rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  cursor: pointer;
  border-bottom: 1px solid currentColor;
  transition: transform .2s var(--ease);
}
.rcard__more:hover { transform: scale(var(--red-hover-scale)); }

/* Scrub bar: the visible line stays thin, but the element carrying the
   pointer handlers is 18px tall so it is actually grabbable - the 4px version
   was a nuisance on a deck this long. The track is drawn with a gradient
   rather than a background colour so the padding stays transparent. */
.reviews .rail-progress {
  height: 18px;
  margin-top: .25rem;
  border-radius: 0;
  background:
    linear-gradient(var(--grey-200), var(--grey-200)) center / 100% 6px no-repeat;
}
.reviews .rail-progress__thumb {
  top: 50%;
  bottom: auto;
  height: 6px;
  transform-origin: left center;
  border-radius: 999px;
  margin-top: -3px;
}
.reviews .rail-progress:hover { background-size: 100% 8px; }

/* 7f. Rail arrows on the rail
   ==========================================================================
   The jobs arrows used to sit beside the "See all jobs" button, at the far
   left of a split section whose cards are on the right. They read as part of
   that button rather than as controls for the cards. They now float on the
   rail itself, one at each end, which is where a horizontal scroller's
   controls are expected to be.
   ========================================================================== */
.jobs-rail-wrap { position: relative; }

.rail-nav__btn--float {
  position: absolute;
  top: calc(50% - 1.25rem);   /* centred on the cards, not the scroll bar */
  z-index: 3;
  transform: translateY(-50%);
  background: var(--white);
  box-shadow: 0 2px 14px rgba(0, 0, 0, .13);
}
.rail-nav__btn--prev { left: -.75rem; }
.rail-nav__btn--next { right: -.75rem; }

/* Nothing to go back to, or nowhere further to go: fade the arrow out and
   take it off the pointer, so a dead control is not sitting over a card. */
.rail-nav__btn--float[aria-disabled="true"] {
  opacity: 0;
  pointer-events: none;
}

@media (max-width: 640px) {
  /* on a phone the rail is swiped, and floating arrows just cover the cards */
  .rail-nav__btn--float { display: none; }
}

/* Signature in place of a set-type attribution. The role stays as a small
   label under it, which is what carries the actual job title - the signature
   is the name, and it is a picture, so the alt text does that job for anyone
   who cannot see it. */
.pullquote__by {
  display: grid;
  justify-items: start;
  gap: .35rem;
  margin-top: 1.25rem;
  font-style: normal;
}
.pullquote__sig {
  width: clamp(150px, 17vw, 210px);
  height: auto;
}
.pullquote__role {
  color: var(--red);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

/* Centred variant of the hero stat row, for the about page - there is no
   column of copy beside it there, so it centres on the page instead. The
   dividers between items only make sense between them, hence the reset on
   the first child rather than a gap. */
.stat-row--centred {
  justify-content: center;
  margin-top: 0;
}
.stat-row--centred .stat { text-align: center; }
.stat-row--centred .stat__value { justify-content: center; }

/* Its own band, so the figures are not crowded against the copy above or the
   dark bar below. */
.stats-band { padding-block: clamp(2.5rem, 5vw, 4rem); }

@media (max-width: 620px) {
  /* stacked, the vertical rules between items read as clutter */
  .stat-row--centred { flex-direction: column; align-items: center; gap: 1.75rem; }
  .stat-row--centred .stat { padding-left: 0; border-left: 0; }
}

/* About page: the portrait stands on its own.
   The dot grid and the two puzzle marks were removed there - they were
   competing with the pieces in the hero rather than adding to them - so the
   photo takes the whole media column instead of the 80% it left room for. */
.about-page .split__media {
  display: block;
  aspect-ratio: auto;
}
.about-page .about__photo {
  position: static;
  width: 100%;
  height: auto;
  max-width: none;
}

/* About page spacing and stat size.
   The stats band sat between two blocks that each bring their own generous
   padding, so the gaps above and below it compounded into a large empty
   stripe. It carries the spacing itself now, and the section above it stops
   short. The figures are also the page's headline numbers here rather than a
   footnote beside hero copy, so they are set larger than the hero's. */
.about-page .section:first-of-type { padding-bottom: clamp(1rem, 2vw, 1.75rem); }

.stats-band { padding-block: clamp(1.25rem, 2.4vw, 2rem) clamp(1.75rem, 3.2vw, 2.75rem); }

.stat-row--centred .stat__value { font-size: clamp(2.5rem, 4.4vw, 4rem); }
.stat-row--centred .stat__label { font-size: .875rem; }
.stat-row--centred .stat { padding-inline: clamp(1.5rem, 3.5vw, 3rem); }
.stat-row--centred .stat:first-child { padding-left: 0; }
.stat-row--centred .stat:last-child { padding-right: 0; }

/* 7g. Two clips in one slot
   ==========================================================================
   The bottom left piece runs two films in sequence rather than looping one.
   Both fill the slot, stacked; only the playing one is visible, and they
   cross-fade at the handover so the cut is not a hard jump.
   ========================================================================== */
.piece__seq {
  position: relative;
  width: 100%;
  height: 100%;
}
.piece__seq .piece__video {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity .6s var(--ease);
}
.piece__seq .piece__video.is-playing { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  /* no cross-fade for anyone who asked for less movement */
  .piece__seq .piece__video { transition: none; }
}

/* The dark bar carries only the two accreditations on the about page, so they
   are the whole band there rather than a footnote under a row of figures -
   and they were sized for the latter. Bigger marks and bigger type. */
.about-page .accred__logo       { width: clamp(150px, 15vw, 200px); }
.about-page .accred__logo--seek { width: clamp(150px, 15vw, 200px); }
.about-page .accred             { gap: 1.35rem; max-width: 52ch; }
.about-page .accred__note       { font-size: .9375rem; }
.about-page .credentials        { padding-block: clamp(2.25rem, 3.4vw, 3rem); }

/* A small lead-in over each mark - "Recommended on" above SEEK, "Platinum
   member" above NPA. Deliberately quiet: it introduces the logo rather than
   competing with it, and the logo carries the weight.

   Note the SEEK wordmark is white, so that mark only works on the dark bar. */
.accred__mark {
  display: grid;
  justify-items: center;
  gap: .4rem;
  flex: none;
}
.accred__mark-label {
  color: rgba(255,255,255,.62);
  font-size: .6875rem;
  font-weight: 500;
  letter-spacing: .1em;
  line-height: 1;
  text-transform: uppercase;
  white-space: nowrap;
  /* Letter-spacing is applied after the last glyph too, so the box the grid
     centres is a tracking-width wider than the ink and the label reads a hair
     left of the logo. Pulling that trailing space back out of the box centres
     what you can actually see. */
  margin-right: -.1em;
}
.accred__logo--seek { width: clamp(112px, 11vw, 150px); }
.about-page .accred__logo--seek { width: clamp(132px, 13vw, 176px); }

/* The two clips in the bottom-left slot are opposite shapes, so they crop
   opposite ways in a square frame and need separate framing.
     hero-c  854x480  landscape - loses the sides, centre is right
     hero-c2 2160x3840 portrait - loses top and bottom; pulled up off centre
                                  so heads are not cut off
   Both are heavily cropped because the frame is square and neither clip is.
   A squarer source would use the piece far better. */
.piece__video--portrait { object-position: center 38%; }

/* The card animates between its collapsed and expanded heights; main.js
   supplies the two pixel values, since CSS cannot transition to height:auto.
   The quote's fade-out goes with it so the mask does not pop off. */
.rcard {
  transition:
    height .34s var(--ease),
    -webkit-mask-image .34s var(--ease),
            mask-image .34s var(--ease);
}
@media (prefers-reduced-motion: reduce) {
  .rcard { transition: none; }
}

/* NPA sits beside SEEK, so the two marks are sized to look comparable rather
   than to a shared number - NPA is a compact stacked lockup, SEEK a wide one. */
.accred__logo--npa { width: clamp(104px, 10.5vw, 140px); }
.about-page .accred__logo--npa { width: clamp(124px, 12.5vw, 168px); }

/* NPA band (about page)
   The old .split put the logo and the copy at opposite edges of a 1216px
   container with a large gap in the middle - fine when the left column holds
   a photo, wrong when it holds one mark. The pair is centred as a group now,
   and the logo is about twice the size it was. */
.npa-band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: clamp(2rem, 5vw, 4.5rem);
}
.npa-band__mark {
  flex: 0 1 auto;
  display: flex;
  justify-content: center;
}
.npa-band__mark img {
  width: clamp(240px, 26vw, 380px);   /* was capped at 280px */
  height: auto;
}
.npa-band__copy { flex: 1 1 34ch; max-width: 52ch; }

@media (max-width: 780px) {
  .npa-band { flex-direction: column; text-align: center; }
  .npa-band__copy { max-width: 60ch; }
}

/* A .section-title inside a prose column had no bottom margin, so the first
   paragraph sat straight under the descenders of the heading. The eyebrow
   above it needs a little room too. */
.prose .section-title { margin-bottom: clamp(1.25rem, 2.2vw, 1.75rem); }
.prose .eyebrow       { margin-bottom: clamp(.6rem, 1.2vw, .9rem); }
.npa-band__copy .section-title { margin-bottom: clamp(1rem, 1.8vw, 1.5rem); }

/* The first section on an interior page used to sit under the tinted banner,
   so it carried a full section's worth of top padding. With the banner gone
   that padding has nothing above it and leaves the page starting a long way
   down. The homepage is unaffected - its first section is .hero, which sets
   its own. */
main > .section:first-child { padding-top: clamp(1.75rem, 3.2vw, 2.75rem); }

/* Long job titles in the rail
   Cards in a rail stretch to the tallest of them, so one four-line title -
   "Business Development Manager, Radio Communications" - made every card
   370px tall and left the short ones mostly empty. Titles are held to two
   lines here so a single long ad cannot set the height of the row.

   Only in the rail. The grid on jobs.html is the actual list, and truncating
   a job title there would hide what the role is. */
.job-rail .job-card__title {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

/* Jobs rail: hover outline, smoother dragging, grabbable scrub bar
   ==========================================================================
   Hover outline - the hovered card is lifted 2px and sits in a scroll
   container, so its top edge is the one at risk of being clipped by the
   scrollport or covered by a neighbour. Raising it out of the flow and giving
   the rail room above guarantees all four sides of the outline are drawn.
   ========================================================================== */
.job-rail { padding-top: .75rem; }
.job-card:hover {
  position: relative;
  z-index: 2;
}

/* Dragging - mandatory snap pulls against the pointer the whole way across,
   which is what makes a drag feel like it is fighting back. Proximity snaps
   only when you let go near a card, so the drag itself is free. */
.job-rail { scroll-snap-type: x proximity; }

/* Scrub bar - same treatment as the reviews deck: the line stays thin, but
   the element carrying the pointer handlers is tall enough to grab. */
.jobs-rail-wrap .rail-progress {
  height: 18px;
  margin-top: .25rem;
  border-radius: 0;
  background:
    linear-gradient(var(--grey-200), var(--grey-200)) center / 100% 6px no-repeat;
}
.jobs-rail-wrap .rail-progress__thumb {
  top: 50%;
  bottom: auto;
  height: 6px;
  margin-top: -3px;
  border-radius: 999px;
}
.jobs-rail-wrap .rail-progress:hover { background-size: 100% 8px; }

/* 19. Phone layout corrections
   ==========================================================================
   Things that are right in two columns and wrong in one. Kept together at the
   end rather than scattered through the sections above, because every rule
   here exists to undo a desktop assumption rather than to state a new one.
   ========================================================================== */
@media (max-width: 780px) {
  /* In a column flex container flex-basis is a HEIGHT, so `flex: 1 1 34ch`
     was setting this block to 34ch tall - about 56px more than its own text -
     and the extra hung under the last line as dead space. */
  .npa-band__copy { flex: 0 1 auto; }

  /* Laurie's portrait reads as an illustration of the copy, not as the way in
     to it, so the heading goes first once the two columns stack. */
  .about-lead .split__copy { order: -1; }
}

@media (max-width: 720px) {
  /* The mark and the line about it stack and centre, rather than sitting as a
     narrow logo beside a paragraph that is wider than it is. */
  .accred { flex-direction: column; gap: .625rem; text-align: center; }
  .accred__text { text-align: center; }

  /* Three across, always. Wrapping put two on one line and one on the next,
     which reads as a mistake rather than as a row of figures - and the odd one
     out then centred on a different axis from the pair above it. */
  .stat-row { flex-wrap: nowrap; gap: .5rem; }
  .stat { flex: 1 1 0; min-width: 0; padding-left: .625rem; }
  .stat:first-child { padding-left: 0; border-left: 0; }
  .stat:nth-child(odd) { padding-left: .625rem; border-left: 1px solid var(--line); }
  .stat:first-child { padding-left: 0; border-left: 0; }
  .stat__value { font-size: 1.5rem; }
  .stat__label { font-size: .625rem; letter-spacing: .04em; }
}

/* About page stats on a phone.
   -------------------------------------------------------------------------
   This block has to sit after the one above, not with the other
   .stat-row--centred rules further up, and that is deliberate: the rules it
   is correcting are all the same specificity as it is, so source order is the
   only thing that decides. Moving it earlier silently undoes it.

   Two things were wrong. The third item kept a divider - .stat:nth-child(odd)
   above sets border-left, and it outranked the border-left:0 in the stacked
   rule by being later in the file, so a single grey line hung beside the 4.95
   and nothing else. And the three items each carried a different padding-left
   (0, 24px, 10px) left over from the horizontal layout, so although each one
   centred its own text they sat at three different offsets on screen and the
   column read as ragged rather than centred.

   They also go back on one line. Stacked, three figures ran most of the
   screen height for what is a single glance of information; the hero manages
   three across at this width, and so can this once the type comes down to
   match. The stack is kept below 360px, where three genuinely will not fit. */
@media (max-width: 720px) {
  .stat-row--centred {
    flex-direction: row;
    flex-wrap: nowrap;
    justify-content: center;
    align-items: flex-start;
    gap: .5rem;
  }
  /* first-child and last-child are named explicitly because the rules they
     are overriding are (0,2,1) and would otherwise win on specificity */
  .stat-row--centred .stat,
  .stat-row--centred .stat:first-child,
  .stat-row--centred .stat:last-child {
    flex: 1 1 0;
    min-width: 0;
    padding-inline: .25rem;
    border-left: 0;
    text-align: center;
  }
  .stat-row--centred .stat__value {
    justify-content: center;
    font-size: clamp(1.375rem, 6.2vw, 1.75rem);
  }
  .stat-row--centred .stat__label { font-size: .625rem; letter-spacing: .04em; }
}

@media (max-width: 359px) {
  /* Below this three across stops being tight and starts being unreadable, so
     they stack - still centred, still without dividers. */
  .stat-row--centred { flex-direction: column; align-items: center; gap: 1.5rem; }
  .stat-row--centred .stat,
  .stat-row--centred .stat:first-child,
  .stat-row--centred .stat:last-child { flex: 0 0 auto; width: 100%; }
  .stat-row--centred .stat__value { font-size: 2rem; }
}

@media (max-width: 560px) {
  /* One column, so there is no second column for the copy to be ranged
     against - centred is the only alignment that looks deliberate. */
  .hero__copy { text-align: center; }
  .hero__lead { margin-inline: auto; }
  .hero__actions { justify-content: center; }
  .stat-row { justify-content: center; }
  /* .stat__value is a flex row (it has to be - the figure and the star sit on
     one baseline), so it does not take the centred text-align the rest of the
     hero copy inherits here. The figure sat hard left over a centred label. */
  .stat__value { justify-content: center; }

  /* .btn goes full width on phones, which is right for a form or a CTA pair
     and wrong for a single link sitting under a carousel - there it just
     stretches to the screen edges and stops looking like a button. */
  .testimonials__more .btn { width: auto; }
}

/* A shorter closing band. The homepage already ends on a lot of section, so
   its call to action is a last word rather than a chapter of its own - the
   same type at the same size, in about two thirds the height. */
.cta-band--tight {
  padding-block: clamp(2rem, 3.4vw, 2.75rem);
}
.cta-band--tight p { margin-top: .875rem; }
.cta-band--tight .cta-band__actions { margin-top: 1.375rem; }
.cta-band--media h2,
.cta-band--media p { margin-inline: 0; }
.cta-band--media .cta-band__actions { justify-content: flex-start; }
.cta-band__media img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

@media (max-width: 860px) {
  .cta-band--media .container { grid-template-columns: minmax(0, 1fr); text-align: center; }
  .cta-band--media h2, .cta-band--media p { margin-inline: auto; }
  .cta-band--media .cta-band__actions { justify-content: center; }
  .cta-band__media { order: -1; }
}

/* 20. Puzzle accents
   ==========================================================================
   A single jigsaw piece, used as a decoration on the interior pages so the
   idea in the hero carries through the site instead of stopping after the
   first screen.

   The shape is the same language as the collage: a square with one tab and
   one socket, the ball cut at 45% of its radius so it sits proud on a neck.
   In the drawing's own 100 x 84 box the tab is r 9.51 centred at (90.5, 42)
   and the socket is the same size at (42, 73.5).

   Rules for using one:

   - It is decoration and nothing else. aria-hidden, never a link, never
     carrying meaning, and pointer-events off so it cannot eat a click.
   - It sits behind the content (z-index 0 against the section's own stacking
     context), so text always wins.
   - One per section at most, and about two per page, each bleeding off an
     edge. More than one in a band reads as a pattern rather than an accent.
   - It goes at low opacity. If you can read it before you read the heading,
     it is too strong.
   - Phones do not get them: at that width there is no margin for something to
     sit in, so it would either crowd the copy or be cropped to a smear.
   ========================================================================== */
.pz-accent {
  position: absolute;
  z-index: 0;
  width: clamp(108px, 11.7vw, 171px);
  height: auto;
  pointer-events: none;
  /* One 120-unit box for every piece type. The body is 2..82 and a knob
     reaches -16 or 100 depending on which edge it is on, so whichever variant
     a page uses, the stroke has the same two units of clearance on all four
     sides. Without that the knob's tip sat exactly on the viewport edge and
     the outer half of the stroke was clipped off square - the arc ran round
     correctly and then went flat at the tip. */
}
/* Filled - the piece reads as a block of tone. Kept very light: this is a
   tint behind the page, not an element on it. */
.pz-accent--fill      { fill: var(--red);   --accent-opacity: .07; stroke: none; }
.pz-accent--fill-ink  { fill: var(--ink);   --accent-opacity: .05; stroke: none; }
.pz-accent--fill-cream{ fill: var(--cream-200); opacity: 1; }
/* Outline - a drawn piece. It carries further than a fill at the same
   opacity, because an edge is sharper than a tone, so it runs lighter. */
.pz-accent--line {
  fill: none;
  stroke: var(--red);
  stroke-width: 2;
  --accent-opacity: .198;
}
.pz-accent--line-ink { fill: none; stroke: var(--ink); stroke-width: 2; --accent-opacity: .108; }
/* For the red and dark bands, where an ink line disappears and a red one is
   invisible. Runs lighter again: white on saturated red carries a long way. */
.pz-accent--line-light { fill: none; stroke: var(--white); stroke-width: 2; --accent-opacity: .144; }

/* Placements. Percentages rather than pixels so a piece keeps its relationship
   to the band as the band changes height. */
/* The corner placements sit wholly inside the band. A rotated piece clipped
   by the section edge leaves the outline open where it crosses, and two cut
   ends in the middle of a stroke read as a rendering fault rather than as a
   piece running off the page. If you want one to bleed, use --bc or --tc,
   which are cut across a flat run of the shape where the crop looks meant. */
/* The angle goes on `rotate`, not inside `transform`. The pieces animate in
   with the same code the hero collage uses, and that code drives `transform` -
   so if the placement put its angle there too, arriving at the rest state
   (transform: none) would snap every piece back to square. As its own property
   the angle composes with the animation instead of competing with it.

   --from-x / --from-y say which way each one flies in from: outward, along the
   diagonal it sits on, so it reads as a piece dropping into place. */
.pz-accent--tr { top: 1.75rem;    right: 1.75rem; rotate: 8deg;   --from-x: 18%;  --from-y: -14%; }
.pz-accent--br { bottom: 1.75rem; right: 1.75rem; rotate: -12deg; --from-x: 16%;  --from-y: 16%; }
.pz-accent--bl { bottom: 1.75rem; left: 1.75rem;  rotate: 16deg;  --from-x: -18%; --from-y: 14%; }
.pz-accent--tl { top: 1.75rem;    left: 1.75rem;  rotate: -6deg;  --from-x: -16%; --from-y: -16%; }
/* For text-dense sections, where every corner has copy in it. These sit half
   outside the band, in its padding, and the host clips the rest - so what
   shows is an edge of a piece rather than a piece behind a paragraph. */
.pz-accent--bc { bottom: -3.25rem; right: 16%; rotate: -10deg; --from-y: 20%; }
.pz-accent--tc { top: -3.25rem; left: 20%; rotate: 14deg; --from-y: -20%; }
/* For a two-column band whose second column runs out before the first does,
   leaving a quiet block of space on that side halfway down. */
.pz-accent--mr { top: 58%; right: 2.5rem; rotate: -9deg; --from-x: 20%; }
/* A quarter turn on top of whatever rotation the placement already gives it. */
.pz-accent--turn { rotate: 90deg; }    /* both replace the placement angle */
.pz-accent--flip { rotate: 180deg; }

/* The host has to establish a containing block, and clip anything that hangs
   over the edge so a piece never widens the page. */
/* clip, not hidden: hidden makes the section a scroll container, which
   quietly switched off position: sticky for anything inside it (the process
   intro on our-approach). clip trims the overhang without that side effect. */
.has-accent { position: relative; overflow: hidden; overflow: clip; }
.has-accent > .container { position: relative; z-index: 1; }

@media (max-width: 860px) {
  .pz-accent { display: none; }
}

/* A jigsaw field for the closing red band.

   The tile is a 2x2 block of cells, not one cell repeated. A single cell tiles
   into a grid where every knob points the same way, which reads as one stamp
   printed over and over rather than as a puzzle; here each of the four grid
   lines carries its knobs in a different direction, so no two neighbouring
   pieces are the same shape.

   The grid lines sit at 50 and 150 rather than on the tile edges, so a knob is
   never cut in half by the repeat - except the ones deliberately drawn at both
   0 and 200 on the same line, where the two clipped halves meet across the
   join and complete each other. Knob r 11 at 0.893r out, the same 45% cut as
   everything else here.

   It is confined to the margins either side of the copy rather than laid
   under it and masked back out. A pattern behind type costs contrast however
   faint it is, and the band already has a job to do. Below the width where
   the copy fills the band the strips resolve to zero and disappear on their
   own. */
.cta-band--pattern { position: relative; overflow: hidden; }
.cta-band--pattern > .container { position: relative; z-index: 1; }
.cta-band--pattern::before,
.cta-band--pattern::after,
.testimonials--pattern::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  width: max(0px, calc((100% - 46rem) / 2));
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Cpath d='M50,-4.96 A11,11 0 1 1 50,4.96 V95.04 A11,11 0 1 0 50,104.96 V195.04 A11,11 0 1 1 50,204.96 V295.04 A11,11 0 1 1 50,304.96 M150,-4.96 A11,11 0 1 0 150,4.96 V95.04 A11,11 0 1 1 150,104.96 V195.04 A11,11 0 1 1 150,204.96 V295.04 A11,11 0 1 0 150,304.96 M250,-4.96 A11,11 0 1 1 250,4.96 V95.04 A11,11 0 1 1 250,104.96 V195.04 A11,11 0 1 0 250,204.96 V295.04 A11,11 0 1 1 250,304.96 M-4.96,50 A11,11 0 1 0 4.96,50 H95.04 A11,11 0 1 1 104.96,50 H195.04 A11,11 0 1 0 204.96,50 H295.04 A11,11 0 1 0 304.96,50 M-4.96,150 A11,11 0 1 1 4.96,150 H95.04 A11,11 0 1 0 104.96,150 H195.04 A11,11 0 1 1 204.96,150 H295.04 A11,11 0 1 1 304.96,150 M-4.96,250 A11,11 0 1 0 4.96,250 H95.04 A11,11 0 1 0 104.96,250 H195.04 A11,11 0 1 1 204.96,250 H295.04 A11,11 0 1 0 304.96,250' fill='none' stroke='%23ffffff' stroke-width='1.6'/%3E%3C/svg%3E");
  background-size: 288px 288px;
  pointer-events: none;
}
/* The two sides do not sit at the same strength. The band's gradient runs
   from bright red on the left to a deeper red on the right, so a white line
   of one opacity reads lighter against the dark side than the bright one.
   The left strip carries a little more to even them out by eye. */
.cta-band--pattern::before { left: 0;  opacity: .324; }
.cta-band--pattern::after  { right: 0; opacity: .306; }


/* Build credit
   --------------------------------------------------------------------------
   Sits in the footer base row beside the copyright. The mark is a full-colour
   logo, so it is dropped in as an image rather than taking currentColor like
   the SEEK and LinkedIn marks do - it has its own gradient and recolouring it
   would misrepresent it. Sized off the text so it stays optically level with
   the line it sits in. */
/* The credit sits with the copyright rather than as its own column, so the
   base row reads as one legal line on the left and the privacy link on the
   right - not three things spread across the width. */
.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .35rem 1.25rem;
}
.site-credit { margin: 0; }   /* takes the footer's own text colour, same as the copyright beside it */
.site-credit a {
  display: inline-flex;
  align-items: center;
  gap: .4em;
  color: inherit;
  text-decoration: none;
  transition: color .2s var(--ease);
}
.site-credit a:hover { color: var(--white); }
.site-credit img {
  width: auto;
  height: 1.15em;
  /* the artwork carries its own padding; this pulls the wordmark in tight */
  margin-block: -.4em;
}

/* The line under the sectors list. Centred and set back from the list itself -
   it is an aside about what is NOT there, so it should not read at the same
   weight as the eight things that are. */
.sectors__note {
  max-width: 60ch;
  margin: clamp(1.75rem, 3vw, 2.5rem) auto 0;
  text-align: center;
  opacity: .9;
}

/* The accents arrive the way the hero pieces do - out of the same direction
   they sit in, on the same curve - but they cannot share the collage's rules.
   That animation ends at opacity 1, and the whole point of an accent is that
   it rests at a tenth of that. So the resting value is a variable each variant
   sets, and the entrance fades to THAT rather than to full.

   The hook is on the piece itself rather than on its section, because the
   observer behind it wants 35% of the element in view before it fires: fine
   for a compact collage, but a full-height band might never reach it on a
   short window, and the piece would sit invisible for good. */
.pz-accent { opacity: var(--accent-opacity, .12); }

.js-reveal .pz-accent[data-assemble] {
  opacity: 0;
  transform: translate(var(--from-x, 0), var(--from-y, 0)) scale(.94);
  transition:
    opacity .42s var(--ease),
    transform .78s cubic-bezier(.22, 1.2, .36, 1);
  will-change: transform, opacity;
}
.js-reveal .pz-accent[data-assemble].is-assembled {
  opacity: var(--accent-opacity, .12);
  transform: none;
  will-change: auto;
}

/* The same response the commitments on the candidates page give. These are
   the six stages of the process - discrete steps in a sequence - so the move
   marks the one you are reading without implying it is a link. Transform
   rather than margin, so nothing below reflows as the cursor travels. */
.process__step {
  transition:
    transform .25s var(--ease),
    border-color .25s var(--ease);
}
.process__step:hover { transform: translateX(.375rem); }

/* Both rules the step sits between, not just the one it owns.

   Each step draws only its own border-top; the line under it belongs to the
   step below. So hovering has to colour this step's top border AND the next
   step's - and for the last one, which carries a border-bottom of its own,
   that instead. Without the sibling rule only the line above a step lit up,
   which reads as the wrong row being marked. */
.process__step:hover,
.process__step:hover + .process__step { border-top-color: var(--red); }
.process__step:last-child:hover { border-bottom-color: var(--red); }

@media (prefers-reduced-motion: reduce) {
  .process__step:hover { transform: none; }
}

/* September 2026 review additions */
.npa-link { display: inline-block; vertical-align: middle; }
.npa-link:focus-visible { outline: 2px solid currentColor; outline-offset: 6px; }

.form-status { padding: 16px 20px; border-left: 3px solid var(--ink); background: var(--cream); }
.form-status--success { border-color: #26734b; background: #eff8f2; color: #195535; }
.form-status--error { border-color: var(--red); background: var(--red-tint); }
.form [type="submit"]:disabled { opacity: .65; cursor: wait; }
@media (min-width: 1100px) { .sectors { grid-template-columns: repeat(4,minmax(0,1fr)); } }
@media (min-width: 600px) and (max-width: 1099px) { .sectors { grid-template-columns: repeat(2,minmax(0,1fr)); } }
@media (max-width: 599px) { .sectors { grid-template-columns: minmax(0,1fr); } }

.jobs-feed-note { margin: 1.5rem 0 0; font-size: .875rem; }
.jobs-feed-note p { margin-bottom: 1rem; }
.jobs-source-link { display: inline-block; margin-top: .75rem; text-underline-offset: 4px; }
.jobs-rail-wrap .jobs-feed-note { margin: 0 0 1rem; }

.stat > a { color: inherit; text-decoration: none; }
.stat > a.stat__label:hover { text-decoration: underline; text-underline-offset: .15em; }

/* Keep the complete tagline on two lines at every hero column width. */
.hero__copy { container-type: inline-size; }
/* Headline scale. 76px on three short lines outweighed everything around
   it - a 5:1 jump to a 15px paragraph, and a copy column taller than the
   puzzle beside it. 70px at most now, easing down with the viewport to 40px
   on phones; the cqi term keeps each nowrap line inside a narrow column. */
.hero__title {
  font-size: clamp(2.5rem, min(4.9vw, 14cqi), 4.375rem);
  line-height: 1.08;
}
/* The paragraph comes up to meet it: 17-18px on desktop rather than the
   small-print 15px, so the step from headline to lead reads as hierarchy,
   not as a drop. */
.hero__lead {
  font-size: clamp(1rem, .9rem + .35vw, 1.125rem);
  line-height: 1.65;
}
.hero__title-line { white-space: nowrap; }

.keep-together { white-space: nowrap; }
/* The clip ships pre-cut to the same 1:1 frame the boardroom photo filled
   (720x720, cropped high enough to keep everyone's heads in), so cover has
   nothing left to crop and the poster hands over to the film pixel for pixel. */
.candidates__video { display: block; height: auto; background: var(--cream-200); }

/* Hover.

   The puzzle stays put. Lifting or scaling a single piece pulled it out of
   its joints, and the assembly transition on these elements outranks any
   transition set here, so the movement snapped instead of easing. The
   pieces and Laurie's portrait now have no hover at all; the only motion is
   the picture drifting inside a media piece, which the piece's own clip
   keeps inside its outline.

   The help cards keep a light cue - the icon rises a few pixels and the title
   takes the brand red - with no card movement, tint or shadow. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .collage .piece--media img,
  .collage .piece--media video {
    transition: scale .9s cubic-bezier(.2, .7, .2, 1);
  }
  .collage .piece--media:hover img,
  .collage .piece--media:hover video { scale: 1.04; }

  .help-card__icon { transition: translate .35s cubic-bezier(.2, .7, .2, 1); }
  .help-card__title { transition: color .25s ease; }
  .help-card:hover .help-card__icon { translate: 0 -4px; }
  .help-card:hover .help-card__title { color: var(--red); }
}

/* ==========================================================================
   Interaction polish (23 September)
   ========================================================================== */

/* Letter hover on phrases: each word is one unbreakable box, so the line can
   only wrap between words (see the splitter in main.js). */
.hero__word { display: inline-block; white-space: nowrap; }

/* Scroll progress along the foot of the navbar. main.js adds the element and
   sets its scaleX; the header is sticky, so it is already a positioned box. */
.site-header__progress {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  z-index: 2;
  height: 2px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: left center;
  pointer-events: none;
}

/* Photographs zoom inside their frame; the frame itself never grows. */
.zoom-frame { overflow: hidden; }

/* The FAQ pieces sit in the side margins beside a centred list, so they only
   show where the margin is wide enough to hold them clear of the copy. */
.pz-accent--ml { top: 30%; left: 2.5rem; rotate: 11deg; --from-x: -20%; }
.pz-accent--faq-r { top: 62%; }
@media (max-width: 1180px) {
  .pz-accent--ml, .pz-accent--faq-r { display: none; }
}

/* Closing red bands: the emphasised phrase takes the second line, and the
   lines balance so neither is ever left holding a single word. A short
   heading that fits on one line opts out with --inline. */
.cta-band h2 { text-wrap: balance; }
.cta-band h2 em { display: block; text-wrap: balance; }
.cta-band h2.cta-band__title--inline { max-width: 30ch; }
.cta-band h2.cta-band__title--inline em { display: inline; }

/* "Tell us what you need." stays on one line wherever it fits. */
@media (min-width: 600px) {
  .section-title--one-line { white-space: nowrap; }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  :root { --glide: cubic-bezier(.2, .7, .2, 1); }

  .zoom-frame > img,
  .zoom-frame > video { transition: scale 1.1s var(--glide); }
  .zoom-frame:hover > img,
  .zoom-frame:hover > video { scale: 1.05; }
  /* The about-page portrait also carries the assembly transition, which
     outranks the plain rule above, so the zoom joins it here. */
  .js-reveal .zoom-frame[data-assemble] > .about__photo {
    transition:
      opacity .42s var(--ease) calc(var(--order, 0) * 105ms),
      transform .78s cubic-bezier(.22, 1.2, .36, 1) calc(var(--order, 0) * 105ms),
      scale 1.1s var(--glide);
  }

  /* Home "About" block: every element drifts a little in its own direction,
     so the arrangement opens up around Laurie rather than moving as one.
     translate/rotate compose with the assembly transform instead of fighting
     it, and the assembly's own transitions are repeated so they survive. */
  .about__media > .dot-grid,
  .about__media > .about__photo,
  .about__media > .puzzle-mark { transition: translate 1s var(--glide), rotate 1s var(--glide); }
  .js-reveal .about__media[data-assemble] > .dot-grid,
  .js-reveal .about__media[data-assemble] > .about__photo,
  .js-reveal .about__media[data-assemble] > .puzzle-mark {
    transition:
      opacity .42s var(--ease) calc(var(--order, 0) * 105ms),
      transform .78s cubic-bezier(.22, 1.2, .36, 1) calc(var(--order, 0) * 105ms),
      translate 1s var(--glide),
      rotate 1s var(--glide);
  }
  .about__media:hover > .dot-grid { translate: 5px -4px; }
  .about__media:hover > .about__photo { translate: -3px -3px; }
  .about__media:hover > .puzzle-mark { translate: 5px 4px; rotate: 2deg; }
  .about__media:hover > .puzzle-mark--red { translate: 7px -3px; rotate: -4deg; }

  /* NPA mark on the about page grows a touch. */
  .npa-band .npa-link { transition: scale .5s var(--glide); }
  .npa-band .npa-link:hover { scale: 1.06; }

  /* Footer memberships grow from their baseline corner. */
  .footer-mark .npa-link,
  .footer-mark__link { transform-origin: left bottom; transition: scale .35s var(--glide); }
  .footer-mark .npa-link:hover,
  .footer-mark__link:hover { scale: 1.08; }
  .footer-mark__link:hover img { opacity: 1; }

  /* Journey steps: the red rule draws across, the number lifts and the
     heading eases in. */
  .step::after {
    content: "";
    position: absolute;
    left: 0; right: 0; top: -3px;
    height: 3px;
    background: var(--red);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform .55s var(--glide);
  }
  .step:hover { border-top-color: var(--line); }
  .step:hover::after { transform: scaleX(1); }
  .step::before { transition: translate .45s var(--glide); }
  .step:hover::before { translate: 0 -4px; }
  .step h3 { transition: translate .45s var(--glide), color .3s var(--ease); }
  .step:hover h3 { translate: 4px 0; color: var(--red); }

  /* Laurie's pull quote: the bar thickens, the line and signature ease in. */
  .pullquote { transition: box-shadow .45s var(--glide); }
  .pullquote:hover { box-shadow: inset 2px 0 0 var(--red); }
  .pullquote p,
  .pullquote__sig { transition: translate .6s var(--glide); }
  .pullquote:hover p { translate: 5px 0; }
  .pullquote:hover .pullquote__sig { translate: 8px 0; }
}

/* Footer links: the same drawn underline as the navbar, in white. */
.site-footer__col li:not(.site-footer__social) a,
.site-credit a,
.site-footer__base > p > a { position: relative; }
.site-footer__base > p > a { text-decoration: none; transition: color .2s var(--ease); }
.site-footer__base > p > a:hover { color: var(--white); }
.site-footer__col li:not(.site-footer__social) a::after,
.site-credit a::after,
.site-footer__base > p > a::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -3px;
  height: 1px;
  background: var(--white);
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .28s var(--ease);
}
.site-footer__col li:not(.site-footer__social) a:hover::after,
.site-footer__col li:not(.site-footer__social) a:focus-visible::after,
.site-credit a:hover::after,
.site-credit a:focus-visible::after,
.site-footer__base > p > a:hover::after,
.site-footer__base > p > a:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left center;
}

/* Home client feedback: shorter band. Three lines of quote instead of four
   (anything longer keeps its "See more"), a smaller opening mark and a
   tighter attribution, so the panel reads as a strip rather than a block. */
.testimonials .section-title { margin-bottom: clamp(1rem, 1.8vw, 1.5rem); }
.quote { padding-block: clamp(.875rem, 1.5vw, 1.25rem); }
.quote__mark { font-size: 2.5rem; margin-bottom: -.5em; }
.quote blockquote p { -webkit-line-clamp: 3; line-height: 1.5; }
.quote__more { margin-top: .6rem; }
.quote__by {
  margin-top: clamp(.875rem, 1.4vw, 1.125rem);
  padding-top: clamp(.75rem, 1.2vw, 1rem);
}
.testimonials__more { margin-top: clamp(1.125rem, 1.8vw, 1.5rem); }

/* Stat row (home hero, about page): each figure answers on its own - the
   number lifts a touch, its label firms up and the SEEK star gives a
   small turn. The dividers stay grey. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .stat__value { transition: translate .45s cubic-bezier(.2, .7, .2, 1); }
  .stat__label { transition: color .3s var(--ease); }
  .stat__star { transition: rotate .6s cubic-bezier(.2, .7, .2, 1), scale .6s cubic-bezier(.2, .7, .2, 1); }
  .stat:hover .stat__value { translate: 0 -3px; }
  .stat:hover .stat__label { color: var(--ink); }
  .stat:hover .stat__star { rotate: 72deg; scale: 1.15; }
}

/* Home client feedback: the closing bands' jigsaw field, but laid across the
   whole band and faded out towards the middle, since this band's carousel
   runs nearly edge to edge and leaves no clean margin strips to confine it
   to. What shows is the outer edges and the space around the arrows; the
   heading and the review sit on clear red. */
.testimonials--pattern { position: relative; overflow: hidden; overflow: clip; }
.testimonials--pattern > .container { position: relative; z-index: 1; }
.testimonials--pattern::before {
  left: 0; right: 0;
  width: auto;
  opacity: .29;
  -webkit-mask-image: linear-gradient(90deg, #000 0, #000 8%, transparent 30%, transparent 70%, #000 92%, #000 100%);
          mask-image: linear-gradient(90deg, #000 0, #000 8%, transparent 30%, transparent 70%, #000 92%, #000 100%);
}

/* ==========================================================================
   Hero entrance

   Paced as one sequence rather than everything at once:
     0.25s  the headline, line by line, each rising from behind its own edge
     0.45s  the puzzle starts arriving, piece by piece, on a long settle
     1.2s   the paragraph, then the buttons, then the stats, into focus
   It lands at about three seconds. One slow curve throughout - no
   overshoot - so it glides in rather than snaps.
   ========================================================================== */
:root { --cine: cubic-bezier(.16, 1, .3, 1); }

/* Headline. The h1 itself stays put; its lines do the moving. Each line
   clips its own contents (with room for descenders and the italic's
   overhang), and the clip comes off once the lines have landed, so the
   letter hover on "Talent," is free to rise past it. */
.js-reveal .hero__title[data-reveal="lines"] { opacity: 1; transform: none; }
.js-reveal .hero__title[data-reveal="lines"]:not(.is-settled) .hero__title-line {
  clip-path: inset(-.15em -.6em -.32em -.6em);
}
.js-reveal .hero__line-inner {
  display: inline-block;
  opacity: 0;
  transform: translateY(135%) rotate(2.5deg);
  transform-origin: 0 100%;
}
.js-reveal.is-reveal-armed .hero__line-inner {
  transition:
    transform 1.35s var(--cine) calc(250ms + var(--line, 0) * 170ms),
    opacity .9s var(--ease) calc(250ms + var(--line, 0) * 170ms);
}
.js-reveal .hero__title.is-visible .hero__line-inner { opacity: 1; transform: none; }

/* Paragraph, buttons, stats: a short rise out of a soft focus. The delays
   are in the markup (--reveal-delay), after the headline. */
.js-reveal .hero [data-reveal="left"]:not(.is-visible) {
  transform: translateY(14px);
  filter: blur(8px);
}
.js-reveal.is-reveal-armed .hero [data-reveal="left"] {
  transition:
    opacity 1.4s var(--cine) calc(var(--reveal-delay, 0ms) + 150ms),
    transform 1.4s var(--cine) calc(var(--reveal-delay, 0ms) + 150ms),
    filter 1.2s var(--cine) calc(var(--reveal-delay, 0ms) + 150ms);
}

/* Puzzle: the same arrival paths, but slower, spaced further apart and
   without the springy overshoot the pieces further down the site keep.
   Transition only - the start and rest positions are the shared ones. */
.js-reveal .hero [data-assemble] .piece {
  transition:
    opacity 1.1s var(--ease) calc(450ms + var(--order, 0) * 190ms),
    transform 1.7s var(--cine) calc(450ms + var(--order, 0) * 190ms);
}

/* `.section__head p` gives every paragraph in a head a top margin, and the
   eyebrow is a paragraph - so every section opening on an eyebrow carried an
   extra 20px above it, on top of the section's own padding. The top of those
   sections read heavier than the bottom. The eyebrow now starts flush, as
   the media-split heads already did. */
.section__head > .eyebrow:first-child { margin-top: 0; }

/* Services first on the employers page, photograph on the right. The copy
   comes first in the markup, so the heading is the first thing read; on
   phones the figure still moves above it (order: -1 in the 780px block). */
@media (min-width: 781px) {
  .media-split--media-right { grid-template-columns: minmax(0, 1fr) minmax(0, .5fr); }
}

/* Employers FAQ: its own pair, so it does not repeat the candidates page -
   a red tint piece high on the left, an ink outline low on the right, at
   different angles. Hidden where the margins are too narrow to hold them. */
.pz-accent--faq-e1 { top: 16%; left: 3.25rem; rotate: -18deg; --from-x: -20%; --from-y: -12%; }
.pz-accent--faq-e2 { bottom: 12%; right: 2rem; rotate: 24deg; --from-x: 20%; --from-y: 14%; }
@media (max-width: 1180px) {
  .pz-accent--faq-e1, .pz-accent--faq-e2 { display: none; }
}

/* The pieces start off to the side and fly in. Mid-entrance that pushed the
   page wider than the viewport and flashed a horizontal scrollbar; clip
   trims the sides without making the hero a scroll container. */
.hero { overflow-x: clip; }

/* Tablet, where the hero stacks into one column: the headline has the full
   width to itself, so the 38px floor reads undersized. */
@media (min-width: 600px) and (max-width: 940px) {
  .hero__title { font-size: clamp(2.75rem, 6.8vw, 3.625rem); }
}

/* Timeline milestones (about): each answers its own hover. The year lifts
   and takes the brand red, the dot on the rule swells with a soft ring, the
   title eases across and the note brightens. The rule itself stays still. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .timeline__year { transition: translate .45s cubic-bezier(.2, .7, .2, 1), color .3s var(--ease); }
  .timeline__dot {
    transition: scale .45s cubic-bezier(.2, .7, .2, 1), box-shadow .45s cubic-bezier(.2, .7, .2, 1);
  }
  .timeline__label { transition: translate .45s cubic-bezier(.2, .7, .2, 1); }
  .timeline__body p { transition: color .3s var(--ease); }

  .timeline__item:hover .timeline__year { translate: 0 -4px; color: var(--red); }
  .timeline__item:hover .timeline__dot { scale: 1.45; box-shadow: 0 0 0 6px rgba(224, 0, 0, .22); }
  .timeline__item:hover .timeline__label { translate: 4px 0; }
  .timeline__item:hover .timeline__body p { color: rgba(255, 255, 255, .9); }
}

/* Employers: the red piece peeking out from behind the photograph. The
   wrapper is the positioning box; the figure sits above the piece, so only
   the part past the photo's lower right corner shows. Stronger than the
   section accents - it reads as part of the picture, not as a tint. */
.media-split__media { position: relative; }
.media-split__media > .media-split__figure { position: relative; z-index: 1; }
.pz-accent--peek {
  right: -2.75rem;
  bottom: -2.5rem;
  rotate: -14deg;
  --accent-opacity: .9;
  --from-x: 18%;
  --from-y: 16%;
}
@media (max-width: 780px) {
  .media-split__media { order: -1; }
}

/* The sectors note is one line now, so the 60ch cap comes off; where the
   screen is too narrow for that, the wrap balances rather than orphaning. */
.sectors__note { max-width: none; text-wrap: balance; }

/* Highlight colour. Selected text takes the brand red instead of the
   browser's blue; on the red bands, where red on red would vanish, it flips
   to white with red text. Form controls and the mobile tap flash follow. */
::selection { background: var(--red); color: var(--white); }
.cta-band ::selection,
.testimonials ::selection { background: var(--white); color: var(--red); }
:root {
  accent-color: var(--red);
  -webkit-tap-highlight-color: rgba(224, 0, 0, .18);
}

/* Navbar: the links (and the underline drawn under them) and the Contact us
   button rise a couple of pixels on hover. `translate` rather than
   `transform`, so it composes with anything else already on them. */
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .site-nav__list a,
  .site-header__cta,
  .site-nav__cta {
    transition:
      color .2s var(--ease),
      background-color .2s var(--ease),
      border-color .2s var(--ease),
      transform .2s var(--ease),
      translate .3s cubic-bezier(.2, .7, .2, 1),
      box-shadow .3s cubic-bezier(.2, .7, .2, 1);
  }
  /* The text rises and the underline stays on its line: the link lifts 3px
     and its ::after underline is pushed back down by the same amount, so the
     gap opens between the word and the rule drawing in beneath it. */
  /* inline-block, or the translate is ignored: a transform does nothing to a
     plain inline box, so the word stayed put while its underline (an
     absolutely positioned block) took the counter-move on its own */
  .site-nav__list a { display: inline-block; }
  .site-nav__list a:hover { translate: 0 -3px; }
  .site-nav__list a::after { transition: transform .28s var(--ease), translate .3s cubic-bezier(.2, .7, .2, 1); }
  .site-nav__list a:hover::after { translate: 0 3px; }
  .site-header__cta:hover,
  .site-nav__cta:hover {
    translate: 0 -2px;
    box-shadow: 0 8px 18px -10px rgba(224, 0, 0, .55);
  }
}

/* Footer logo draw-on (main.js wraps the still and lays the film over it).
   The same hand-over as the navbar: the still is hidden while the film is
   pending or playing, and the film fades off the finished frame. */
.site-footer__logo { position: relative; display: inline-block; line-height: 0; }
.site-footer__brand > .site-footer__logo > img { width: clamp(190px, 19vw, 250px); height: auto; }
.site-footer__logo-intro {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  pointer-events: none;
  transition: opacity .32s var(--ease);
}
.site-footer__logo.is-intro-pending > img,
.site-footer__logo.is-intro-playing > img { opacity: 0; }
.site-footer__logo.is-intro-playing .site-footer__logo-intro { opacity: 1; }

/* Our approach: the process intro pins a little closer under the navbar. */
.process__intro { top: calc(var(--header-h) + .75rem); }

/* ==========================================================================
   Timing and fees (our-approach)
   Each answer leads with its headline fact in the display face - "2 to 3
   weeks", "Agreed in writing" - under a small label and beside an icon, with
   the detail beneath. Rows are ruled like the commitments list and answer
   hover the same way. The photograph gets the red piece peeking out from
   behind its lower left corner.
   ========================================================================== */
.expect__list { list-style: none; margin: clamp(1.75rem, 3vw, 2.5rem) 0 0; padding: 0; }
.expect__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.25rem clamp(1.25rem, 2.2vw, 1.75rem);
  align-items: start;
  padding: clamp(1.5rem, 2.6vw, 2rem) 0;
  border-top: 1px solid var(--line);
  transition: border-color .3s var(--ease);
}
.expect__item:last-child { border-bottom: 1px solid var(--line); }
.expect__icon {
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  border: 1.5px solid var(--red);
  border-radius: 50%;
  color: var(--red);
  transition: background-color .35s var(--ease), color .35s var(--ease), rotate .5s cubic-bezier(.2, .7, .2, 1), scale .5s cubic-bezier(.2, .7, .2, 1);
}
.expect__icon svg { width: 26px; height: 26px; fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.expect__item h3 {
  margin: .25rem 0 .5rem;
  color: var(--body);
  font-family: var(--font-body);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .12em;
  line-height: 1.3;
  text-transform: uppercase;
}
.expect__figure {
  margin: 0 0 .625rem;
  color: var(--red);
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 2.8vw, 2.375rem);
  line-height: 1.05;
  transition: translate .45s cubic-bezier(.2, .7, .2, 1);
}
.expect__item p:not(.expect__figure) { margin: 0; font-size: var(--fs-small); line-height: 1.7; max-width: 52ch; }

.expect__media { position: relative; }
.expect__photo { position: relative; z-index: 1; }
.expect__photo img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 88% center;
}
.pz-accent--peek-bl {
  left: -2.75rem;
  bottom: -2.5rem;
  rotate: 12deg;
  --accent-opacity: .9;
  --from-x: -18%;
  --from-y: 16%;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  /* Each row draws only its own top rule; the one under it belongs to the
     next row, or to the last row's own bottom border. So the hovered row
     lights its top rule and whichever rule sits below it. */
  .expect__item:hover,
  .expect__item:hover + .expect__item { border-top-color: var(--red); }
  .expect__item:last-child:hover { border-bottom-color: var(--red); }
  .expect__item:hover .expect__icon { background-color: var(--red); color: var(--white); rotate: -8deg; scale: 1.06; }
  .expect__item:hover .expect__figure { translate: 4px 0; }
}

/* ==========================================================================
   Sectors (for-employers): a livelier hover. A cream wash sweeps in from the
   lower left corner, the red rule stretches out, the name lifts and takes the
   red, and a faint jigsaw piece turns into the lower right corner.
   ========================================================================== */
.sector { position: relative; overflow: hidden; isolation: isolate; }
.sector > * { position: relative; z-index: 1; }
.sector::before,
.sector::after { content: ""; position: absolute; pointer-events: none; }
.sector::before {
  inset: 0;
  z-index: 0;
  background: var(--cream);
  clip-path: circle(0% at 0% 100%);
}
.sector::after {
  right: -18px; bottom: -18px;
  z-index: 0;
  width: 96px; height: 96px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H82 V37.72 A9.51,9.51 0 1 0 82,46.28 V82 H46.28 A9.51,9.51 0 1 1 37.72,82 H2 Z' fill='none' stroke='%23e00000' stroke-width='2'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0;
  translate: 30% 30%;
  rotate: 25deg;
}
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .sector:hover { background: var(--white); }
  .sector::before { transition: clip-path .7s cubic-bezier(.2, .7, .2, 1); }
  .sector::after { transition: opacity .5s var(--ease), translate .7s cubic-bezier(.2, .7, .2, 1), rotate .7s cubic-bezier(.2, .7, .2, 1); }
  .sector__rule { transition: width .55s cubic-bezier(.2, .7, .2, 1); }
  .sector__name { transition: translate .45s cubic-bezier(.2, .7, .2, 1), color .3s var(--ease); }
  .sector__roles { transition: color .3s var(--ease); }

  .sector:hover::before { clip-path: circle(150% at 0% 100%); }
  .sector:hover::after { opacity: .28; translate: 0 0; rotate: -8deg; }
  .sector:hover .sector__rule { width: 64px; }
  .sector:hover .sector__name { translate: 0 -3px; color: var(--red); }
  .sector:hover .sector__roles { color: var(--ink); }
}

/* For employers: the page title and its intro are the opening statement, so
   the services beneath get a clearer break from them than a heading gets from
   its own content. The head's own bottom margin and this one collapse into
   whichever is larger, so this is the whole gap, not an addition to it. */
.section__head--centred + .media-split { margin-top: clamp(5rem, 9vw, 8rem); }

/* The two pieces flanking the employers page title: a red outline high on
   the left, a faint ink piece a little lower on the right, turned against
   each other. Only where the margins either side of the centred title are
   wide enough to hold them clear of the copy. */
.pz-accent--intro-l { top: 2.25rem; left: 5%; rotate: -14deg; --from-x: -20%; --from-y: -10%; }
.pz-accent--intro-r { top: 5.5rem; right: 5%; rotate: 18deg; --from-x: 20%; --from-y: -10%; }
@media (max-width: 1180px) {
  .pz-accent--intro-l, .pz-accent--intro-r { display: none; }
}

/* The comparison's two labels carry a verdict: a cross against the usual way,
   a tick against ours. The cross sits in a quiet grey ring so it reads as
   "not this" without shouting; the tick is a solid white disc on the red
   panel, the same weight as the commitments ticks on the candidates page. */
.compare__tag { display: flex; align-items: center; gap: .6rem; }
.compare__mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 24px; height: 24px;
  border-radius: 50%;
}
.compare__mark svg {
  width: 14px; height: 14px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.compare__mark--no { border: 1.5px solid var(--grey-200); color: var(--body); }
.compare__mark--yes { background: var(--white); color: var(--red); }

/* Each sector turns a different piece into its corner - eight shapes, each a
   different mix of tabs and sockets on its four sides, alternating between an
   outline and a faint filled piece, each settling at its own angle. The
   shared ::after above does the positioning and the motion; these only swap
   the drawing, its strength and where it comes to rest. */
.sector:nth-child(1)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 1 46.28,2 H82 V37.72 A9.51,9.51 0 1 0 82,46.28 V82 H2 Z' fill='none' stroke='%23e00000' stroke-width='2'/%3E%3C/svg%3E"); --piece-op: .3; --piece-rot: -8deg; }
.sector:nth-child(2)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H82 V37.72 A9.51,9.51 0 1 1 82,46.28 V82 H46.28 A9.51,9.51 0 1 1 37.72,82 H2 V46.28 A9.51,9.51 0 1 0 2,37.72 Z' fill='%23e00000'/%3E%3C/svg%3E"); --piece-op: .12; --piece-rot: 12deg; }
.sector:nth-child(3)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 0 46.28,2 H82 V37.72 A9.51,9.51 0 1 0 82,46.28 V82 H46.28 A9.51,9.51 0 1 1 37.72,82 H2 V46.28 A9.51,9.51 0 1 1 2,37.72 Z' fill='none' stroke='%23e00000' stroke-width='2'/%3E%3C/svg%3E"); --piece-op: .3; --piece-rot: -16deg; }
.sector:nth-child(4)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 1 46.28,2 H82 V82 H46.28 A9.51,9.51 0 1 0 37.72,82 H2 V46.28 A9.51,9.51 0 1 1 2,37.72 Z' fill='%23e00000'/%3E%3C/svg%3E"); --piece-op: .12; --piece-rot: 6deg; }
.sector:nth-child(5)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 1 46.28,2 H82 V37.72 A9.51,9.51 0 1 1 82,46.28 V82 H46.28 A9.51,9.51 0 1 1 37.72,82 H2 V46.28 A9.51,9.51 0 1 1 2,37.72 Z' fill='none' stroke='%23e00000' stroke-width='2'/%3E%3C/svg%3E"); --piece-op: .3; --piece-rot: -4deg; }
.sector:nth-child(6)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 0 46.28,2 H82 V37.72 A9.51,9.51 0 1 1 82,46.28 V82 H2 V46.28 A9.51,9.51 0 1 0 2,37.72 Z' fill='%23e00000'/%3E%3C/svg%3E"); --piece-op: .12; --piece-rot: -12deg; }
.sector:nth-child(7)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 1 46.28,2 H82 V37.72 A9.51,9.51 0 1 0 82,46.28 V82 H46.28 A9.51,9.51 0 1 0 37.72,82 H2 Z' fill='none' stroke='%23e00000' stroke-width='2'/%3E%3C/svg%3E"); --piece-op: .3; --piece-rot: 14deg; }
.sector:nth-child(8)::after { background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='-18 -18 120 120'%3E%3Cpath d='M2,2 H37.72 A9.51,9.51 0 1 0 46.28,2 H82 V82 H46.28 A9.51,9.51 0 1 1 37.72,82 H2 V46.28 A9.51,9.51 0 1 0 2,37.72 Z' fill='%23e00000'/%3E%3C/svg%3E"); --piece-op: .12; --piece-rot: -6deg; }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .sector:hover::after { opacity: var(--piece-op, .28); rotate: var(--piece-rot, -8deg); }
}

/* Room for the timeline hover. The rail scrolls sideways, so it clips at its
   own edges - the dot on the leftmost milestone sits right on that edge, and
   its hover ring was cut in half; the rising year had no headroom either.
   The rail gets padding on the left and top and is pulled back by the same
   amount with negative margins, so every milestone stays exactly where it
   was; the snap point moves in with the padding, so a settled rail still
   lines its first milestone up with the heading. */
.timeline__rail {
  --tl-bleed-x: 18px;
  --tl-bleed-y: 8px;
  padding-top: var(--tl-bleed-y);
  padding-left: var(--tl-bleed-x);
  margin-top: calc(-1 * var(--tl-bleed-y));
  margin-left: calc(-1 * var(--tl-bleed-x));
  scroll-padding-left: var(--tl-bleed-x);
}

/* ==========================================================================
   Custom dropdown (contact form) - built by main.js over the real <select>,
   which stays in the form visually hidden.
   ========================================================================== */
.cselect { position: relative; }
.cselect__native {
  position: absolute !important;
  width: 1px !important; height: 1px !important;
  padding: 0 !important; margin: -1px !important;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0 !important;
}
.cselect__button {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  width: 100%;
  padding: .875rem 1rem;
  background: var(--white);
  border: 1px solid var(--line);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-small);
  line-height: 1.5;
  text-align: left;
  cursor: pointer;
  transition: border-color .2s var(--ease), box-shadow .2s var(--ease);
}
.cselect__button:hover { border-color: var(--grey-200); }
.cselect__button:focus-visible,
.cselect.is-open .cselect__button {
  outline: none;
  border-color: var(--red);
  box-shadow: 0 0 0 3px var(--red-tint);
}
/* a red chevron drawn from two strokes; it turns over when the list opens */
.cselect__chevron {
  flex: none;
  width: 10px; height: 10px;
  margin-top: -4px;
  border-right: 2px solid var(--red);
  border-bottom: 2px solid var(--red);
  rotate: 45deg;
  transition: rotate .3s cubic-bezier(.2, .7, .2, 1), margin-top .3s cubic-bezier(.2, .7, .2, 1);
}
.cselect.is-open .cselect__chevron { rotate: 225deg; margin-top: 4px; }

.cselect__list {
  position: absolute;
  z-index: 20;
  top: calc(100% + 6px);
  left: 0; right: 0;
  max-height: 280px;
  overflow-y: auto;
  margin: 0;
  padding: .375rem 0;
  list-style: none;
  background: var(--white);
  border: 1px solid var(--line);
  border-top: 2px solid var(--red);
  box-shadow: 0 22px 40px -24px rgba(31, 26, 23, .45);
  opacity: 0;
  visibility: hidden;
  translate: 0 -6px;
  transition: opacity .2s var(--ease), translate .25s cubic-bezier(.2, .7, .2, 1), visibility 0s linear .25s;
}
.cselect.is-open .cselect__list {
  opacity: 1;
  visibility: visible;
  translate: 0 0;
  transition: opacity .2s var(--ease), translate .25s cubic-bezier(.2, .7, .2, 1), visibility 0s;
}
.cselect__list:focus { outline: none; }
.cselect__option {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .75rem 1rem .75rem 1.25rem;
  color: var(--ink);
  font-size: var(--fs-small);
  line-height: 1.45;
  cursor: pointer;
  transition: background-color .15s var(--ease), color .15s var(--ease);
}
/* the highlighted row: a red rule at its left edge on a faint red wash */
.cselect__option::before {
  content: "";
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--red);
  transform: scaleY(0);
  transition: transform .2s cubic-bezier(.2, .7, .2, 1);
}
.cselect__option.is-active { background: var(--red-tint); }
.cselect__option.is-active::before { transform: scaleY(1); }
/* the current choice is red with a tick */
.cselect__option[aria-selected="true"] { color: var(--red); font-weight: 600; }
.cselect__option[aria-selected="true"]::after {
  content: "";
  flex: none;
  width: 6px; height: 11px;
  margin-right: .25rem;
  border-right: 2px solid var(--red);
  border-bottom: 2px solid var(--red);
  rotate: 45deg;
  translate: 0 -2px;
}
@media (prefers-reduced-motion: reduce) {
  .cselect__list, .cselect__chevron, .cselect__option::before { transition: none; }
}

/* Contact accents: one outline tucked in under the contact panel, one filled
   piece in the far corner, turned against each other so they read as two
   pieces dropped on the page rather than a stacked pair. */
.pz-accent--contact-a { top: 60%; right: 23%; rotate: -24deg; --from-x: 14%; --from-y: 10%; }
.pz-accent--contact-b { bottom: 1.75rem; right: 3.5rem; rotate: 21deg; --from-x: 18%; --from-y: 16%; }

/* Contact form small print. The message hint, the resume line and the privacy
   note read as one block under the message box: the form's 1.25rem grid gap
   and each paragraph's own bottom margin were stacking to ~40px between them.
   Margins off, and the two free-standing lines pulled up so every gap in the
   block matches the .5rem between a field and its own hint. */
.form > .field__hint,
.form > .form__note { margin: -.75rem 0 0; }
.field__hint,
.form__note { line-height: 1.55; }

/* About: the outline piece in the open space beside Laurie's signature. */
.pz-accent--about-quote { bottom: clamp(.25rem, 1.5vw, 1.25rem); right: 5%; rotate: 14deg; --from-x: 16%; --from-y: 14%; }
@media (max-width: 1180px) {
  .pz-accent--about-quote { display: none; }
}

/* Employers: the services photograph sits 3px lower, per review. */
.media-split--media-right .media-split__media { margin-top: 3px; }

/* The verdict marks, quieter: smaller, finer strokes, and no solid disc - the
   cross is a pale ring and a light stroke, the tick a soft white outline on
   the red rather than a white spot. */
.compare__mark { width: 20px; height: 20px; }
.compare__mark svg { width: 11px; height: 11px; stroke-width: 2; }
.compare__mark--no { border: 1px solid var(--line); color: rgba(78, 74, 71, .6); }
.compare__mark--yes { background: transparent; border: 1px solid rgba(255, 255, 255, .55); color: rgba(255, 255, 255, .9); }


/* Refinements
   ==========================================================================
   Formerly the "Bespoke" look, now the site's design. It sits last so it
   refines everything above it; each rule is scoped with :root, which keeps
   the specificity the look had when it was switched on by an attribute.

   The original, made bespoke. Red stays the one colour - on the bands, the
   buttons, the hero pieces - and everything around it is refined rather than
   recoloured. There is no second colour; the only other tones are the warm
   neutrals the site already had.

   - Ground. White, with the original cream for the alternating sections.
   - Type. Manrope for body and UI (Roboto read generic); Inria Serif stays
     for display, a touch larger and tighter. Every eyebrow carries a short
     red rule, so each section opens with the same small signature.
   - The red, finished. Both hero pieces and every red surface use the logo's
     own bright-to-deep gradient rather than a flat fill. Buttons stay red and
     lift with a soft red glow instead of growing.
   - Signature details: a puzzle socket bitten out of the top of each red and
     dark band (the site's motif built into the page rather than scattered on
     it), and cards whose red top rule draws in on hover.
   - Fixes riding along: centred headings keep the red phrase on its own line,
     the about-page 4.95 is red like its neighbours, the timeline title is in
     the display face.
   ========================================================================== */
:root {
  /* white ground and the original cream for alternating sections */
  --paper: #FFFFFF;
  --paper-2: var(--cream);
  --font-body: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --fs-h2: clamp(2rem, 3.4vw, 3.125rem);
  --band-pad: clamp(3.75rem, 6.5vw, 5.5rem);
  --glide: cubic-bezier(.2, .7, .2, 1);
  --socket-color: var(--paper);
}
:root body { background: var(--paper); }

/* Type ---------------------------------------------------------------------- */
:root .section-title {
  font-size: var(--fs-h2);
  letter-spacing: -0.02em;
  line-height: 1.08;
  text-wrap: balance;
}
/* Centred headings put the red phrase on its own line and in red, like the
   rest. Not the light (on-red) ones: there the italic word sits mid-sentence
   and stays white. */
:root .section-title--centred:not(.section-title--light) em { display: block; color: var(--red); }

/* The eyebrow opens every section with the same mark: a short rule, then the
   label - red on the page, white on the red bands. Centred heads get the rule
   on both sides. */
:root .eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .8rem;
  font-weight: 700;
  letter-spacing: .2em;
}
:root .eyebrow::before,
:root .section__head--centred .eyebrow::after,
:root .eyebrow--light::after {
  content: "";
  flex: none;
  width: 28px;
  height: 1px;
  background: currentColor;
}
:root .eyebrow--light::before,
:root .eyebrow--light::after { opacity: .7; }
:root .section__head--centred { text-align: center; }
/* inline-flex drops the eyebrow out of the block flow; this keeps it on its
   own line above the heading, and centred where the head is centred */
:root .section__head .eyebrow,
:root .testimonials .eyebrow { display: flex; width: fit-content; }
:root .section__head--centred .eyebrow,
:root .testimonials .eyebrow { margin-inline: auto; }

/* Buttons ------------------------------------------------------------------- */
/* Still red, in the logo's gradient. Instead of growing on hover they lift a
   couple of pixels and cast a soft red glow; outlines fill red. */
:root .btn {
  min-height: 54px;
  padding-inline: 1.625rem;
  font-weight: 700;
  letter-spacing: .12em;
  transition:
    background-color .25s var(--ease),
    color .25s var(--ease),
    border-color .25s var(--ease),
    translate .3s var(--glide),
    box-shadow .3s var(--glide);
}
:root .btn--solid { background-image: var(--red-grad); }
:root .btn--solid:hover,
:root .site-header__cta:hover,
:root .site-nav__cta:hover {
  transform: none;
  translate: 0 -2px;
  box-shadow: 0 14px 26px -14px rgba(251, 1, 2, .65);
}
:root .btn--outline { --btn-fg: var(--ink); --btn-bd: var(--red); }
:root .btn--outline .btn__arrow { background: var(--red); }
/* hover as the original: red fill, white text and arrow, no lift or glow */
:root .btn--outline:hover { --btn-fg: var(--white); --btn-bg: var(--red); --btn-bd: var(--red); translate: none; box-shadow: none; }
:root .btn--outline:hover .btn__arrow { background: var(--white); }
:root .btn--light:hover,
:root .btn--ghost:hover { translate: 0 -2px; }

/* Header -------------------------------------------------------------------- */
:root .site-header.is-stuck { box-shadow: 0 1px 0 var(--line), 0 14px 30px -26px rgba(31, 26, 23, .45); }
:root .site-nav__list a { font-weight: 600; }
:root .site-header__cta,
:root .site-nav__cta { background-image: var(--red-grad); }
@media (max-width: 940px) {
  :root .site-nav { background: var(--paper); }
}

/* Hero ---------------------------------------------------------------------- */
:root .hero {
  background: radial-gradient(900px 520px at 88% 30%, rgba(251, 1, 2, .05), transparent 62%);
}
:root .stat-row { padding-top: clamp(1.25rem, 2vw, 1.5rem); border-top: 1px solid var(--line); }
:root .stat { border-left-color: var(--line); }
:root .stat__label { font-weight: 600; letter-spacing: .1em; }
:root .stat > a.stat__value { color: var(--red); }
/* both red pieces carry the logo's gradient, so they have the same depth as
   the mark in the navbar */
:root .piece--red .piece__fill { background-image: var(--red-grad); }
:root .piece__fill--cream { background: var(--cream-piece); background-image: none; }

/* About (home) -------------------------------------------------------------- */
:root .about { background: var(--paper-2); }

/* How can we help / principles: cards -------------------------------------- */
:root .help__grid { gap: clamp(.875rem, 1.5vw, 1.25rem); }
:root .help-card,
:root .help-card:first-child,
:root .help-card:nth-child(odd) {
  position: relative;
  padding: clamp(1.75rem, 2.6vw, 2.25rem) clamp(1.5rem, 2.2vw, 1.875rem);
  background: var(--white);
  border: 1px solid var(--line);
  text-align: center;
}
/* The card's own hover transition, kept apart from the block above: that
   block names :first-child and :nth-child(odd), which made it outrank the
   scroll reveal's transition - so the 1st and 3rd cards lost their fade-in
   and just appeared while the 2nd and 4th animated. */
:root .help-card { transition: translate .45s var(--glide), box-shadow .45s var(--glide), border-color .3s var(--ease); }
/* Once the reveal is armed a card needs both: the fade and rise in, with its
   stagger, and the hover movement. */
.js-reveal.is-reveal-armed .help-card[data-reveal] {
  transition:
    opacity .8s var(--ease) calc(var(--reveal-base, 0ms) + var(--reveal-delay, 0ms) + 150ms),
    transform .8s var(--ease) calc(var(--reveal-base, 0ms) + var(--reveal-delay, 0ms) + 150ms),
    translate .45s var(--glide),
    box-shadow .45s var(--glide),
    border-color .3s var(--ease);
}
:root .section--cream .help-card { background: var(--paper); }
:root .help-card::before {
  content: "";
  position: absolute;
  top: -1px; left: -1px; right: -1px;
  height: 3px;
  background: var(--red-grad);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .55s var(--glide);
}
:root .help-card__icon { margin: 0 auto 1.5rem; }
:root .help-card__title { font-size: clamp(1.25rem, 1.6vw, 1.5rem); }
@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  :root .help-card:hover {
    translate: 0 -6px;
    border-color: transparent;
    box-shadow: 0 26px 44px -30px rgba(31, 26, 23, .45);
  }
  :root .help-card:hover::before { transform: scaleX(1); }
}

/* The bands -----------------------------------------------------------------
   Red, as the original, but each carries a puzzle socket bitten out of its top
   edge - the site's motif built into the page rather than scattered over it.
   The socket takes the colour of the section above, so it reads as a notch
   that section plugs into.

   It is written out on each band rather than held in a root custom property:
   a property that references another is resolved where it is declared, so a
   root-level socket would ignore the per-band colour below. */
:root .testimonials,
:root .cta-band {
  background-image:
    radial-gradient(circle 23px at 50% 0, var(--socket-color) 22.5px, transparent 23px),
    var(--red-grad);
}
:root :is(.section--cream, .jobs, .about) + :is(.cta-band, .testimonials, .timeline) { --socket-color: var(--paper-2); }
:root .cta-band { padding-block: var(--band-pad); }
:root .testimonials { padding-block: calc(var(--band-pad) * .8); }
:root .cta-band h2 { font-size: clamp(2rem, 3.4vw, 3rem); letter-spacing: -0.02em; line-height: 1.1; }
:root .cta-band--pattern::before,
:root .cta-band--pattern::after { opacity: .3; }
:root .testimonials--pattern::before { opacity: .2; }
:root .quote__mark { color: rgba(255, 255, 255, .45); font-size: 3.5rem; margin-bottom: -.55em; }

/* timeline: the original dark, with the same socket */
:root .timeline {
  background-image: radial-gradient(circle 23px at 50% 0, var(--socket-color) 22.5px, transparent 23px);
}
:root .timeline__title {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -0.02em;
}
:root .timeline__title em { color: var(--red); }
/* the years keep the original face (Roboto 500), not the bespoke body font */
:root .timeline__year { font-family: "Roboto", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif; font-weight: 500; }

/* footer */
:root .site-footer__heading { letter-spacing: .2em; color: rgba(255, 255, 255, .55); }

/* Interior components -------------------------------------------------------- */
:root .compare__col--theirs { background: var(--paper); }
:root .step::before,
:root .process__num { font-family: var(--font-display); }
:root .sector { background: var(--white); }
:root .faq__item summary { font-size: 1.25rem; }
:root .quote-card,
:root .job-card,
:root .jobfilter__btn { background: var(--white); }
:root .jobfilter__btn[aria-pressed="true"] { background: var(--red); background-image: var(--red-grad); }
:root .contact-aside { background: var(--paper-2); border-top: 3px solid var(--red); }
:root .field input,
:root .field select,
:root .field textarea { background: var(--white); }


/* "How can we help." is short enough to stay on one line. */
:root .help .section-title em { display: inline; }

/* Outline buttons on the red closing bands get a solid red ground, so the
   jigsaw pattern behind the band does not show through them. Their hover
   (white fill, red text) is unchanged. */
.cta-band .btn--ghost:not(:hover) {
  --btn-bg: var(--red);
  /* the band's own gradient, lined up by main.js - see "painted with the
     band itself" there; without JS it is simply the flat red above */
  background-image: var(--red-grad);
  background-size: var(--band-w, 100%) var(--band-h, 100%);
  background-position: var(--band-x, 0) var(--band-y, 0);
}

/* For employers: the three service titles, a step up from 17px. The number
   beside each takes the same line height, so it stays level with its title. */
.media-split .step h3 { font-size: 1.25rem; line-height: 1.35; }
.media-split .step::before { font-size: 1.0625rem; line-height: calc(1.25rem * 1.35); }

/* The arrival wave (main.js): each letter lifts and settles once, staggered
   first to last. Same lift as the hover, so the wave reads as the word
   showing what it does. Applied only while .is-waving is on the word. */
@keyframes letter-wave {
  0%   { transform: none; }
  45%  { transform: translateY(-.14em) scale(1.14); }
  100% { transform: none; }
}
.is-waving .hero__letter {
  animation: letter-wave .65s cubic-bezier(.34, 1.3, .64, 1) calc(var(--i, 0) * 45ms) both;
}
@media (prefers-reduced-motion: reduce) {
  .is-waving .hero__letter { animation: none; }
}

/* Contact panel: the phone number and email take the navbar's underline - a
   red rule drawing in from the left on hover and retracting the way it came -
   instead of a static grey line that turned red. The LinkedIn logo is left
   out: it is a picture, and has its own scale on hover. */
.contact-list dd > a:not(.contact-aside__social a) {
  position: relative;
  border-bottom: 0;
  transition: color .2s var(--ease);
}
.contact-list dd > a:not(.contact-aside__social a)::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -2px;
  height: 1px;
  background: var(--red);
  transform: scaleX(0);
  transform-origin: right center;
  transition: transform .28s var(--ease);
}
.contact-list dd > a:not(.contact-aside__social a):hover { color: var(--red); }
.contact-list dd > a:not(.contact-aside__social a):hover::after,
.contact-list dd > a:not(.contact-aside__social a):focus-visible::after {
  transform: scaleX(1);
  transform-origin: left center;
}



/* ==========================================================================
   Review cards - the "After" design (:root, set by the
   review-tool switch on the testimonials page)

   - White cards with a soft shadow instead of cream boxes, so they lift off
     the page; on hover they rise and a red rule draws across the top.
   - The faint corner quote mark becomes a full-strength red one.
   - The reviewer gets a round initial badge beside their name.
   ========================================================================== */
:root .rcard {
  background: var(--white);
  border-color: var(--line);
  box-shadow: 0 1px 2px rgba(31, 26, 23, .04), 0 18px 36px -30px rgba(31, 26, 23, .35);
  transition:
    height .34s var(--ease),
    -webkit-mask-image .34s var(--ease),
            mask-image .34s var(--ease),
    translate .45s cubic-bezier(.2, .7, .2, 1),
    box-shadow .45s cubic-bezier(.2, .7, .2, 1),
    border-color .3s var(--ease);
}
:root .rcard::before {
  content: "";
  position: absolute;
  top: -1px; left: -1px; right: -1px;
  height: 3px;
  background: var(--red);
  background-image: var(--red-grad);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .55s cubic-bezier(.2, .7, .2, 1);
}
:root .rcard__mark {
  top: clamp(.75rem, 1.6vw, 1.25rem);
  right: clamp(1.25rem, 2vw, 1.75rem);
  color: var(--red);
  font-size: clamp(3.5rem, 6vw, 4.5rem);
  opacity: 1;
  transition: rotate .5s cubic-bezier(.2, .7, .2, 1), scale .5s cubic-bezier(.2, .7, .2, 1);
}
:root .rcard__stars { min-height: 40px; align-items: center; }
:root .rcard__quote p { font-size: clamp(1.0625rem, 1.4vw, 1.25rem); line-height: 1.45; }

:root .rcard__by {
  align-items: center;
  justify-content: flex-start;
  gap: .75rem;
  padding-top: 1.125rem;
}
:root .rcard__by::before {
  content: attr(data-initial);
  display: grid;
  place-items: center;
  flex: none;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--red-tint);
  color: var(--red);
  font-family: var(--font-display);
  font-size: 1.125rem;
  line-height: 1;
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  :root .rcard:hover {
    translate: 0 -6px;
    border-color: transparent;
    box-shadow: 0 1px 2px rgba(31, 26, 23, .04), 0 30px 50px -30px rgba(31, 26, 23, .45);
  }
  :root .rcard:hover::before { transform: scaleX(1); }
  :root .rcard:hover .rcard__mark { rotate: -8deg; scale: 1.06; }
}
/* The deck scrolls sideways, so it clips at its own top edge - a card lifting
   6px would lose its top border and the red rule. Room above, taken back out
   of the margin so the deck does not move. */
:root .reviews__deck {
  padding-top: 1rem;
  margin-top: calc(clamp(1.5rem, 2.5vw, 2rem) - .75rem);
}

/* Home client feedback: the review panel is a solid deep red rather than a
   see-through grey tint, so the jigsaw pattern behind the band stops at its
   edge instead of showing through the quote. */
.testimonials .quote { background: #C20A0A; }
/* Taller cards in the new design: room for about two more lines of review
   before "See more", sized off the new quote type and the taller reviewer
   row (the 40px initial badge). Expanded cards still grow to fit. */
:root .rcard:not(.is-expanded) {
  height: calc(11.4 * clamp(1.0625rem, 1.4vw, 1.25rem) + clamp(1.5rem, 2.2vw, 2rem) * 2 + 176px);
}

/* ==========================================================================
   Resume upload panel (contact form) - opens for candidates only
   The open/close is a 0fr -> 1fr grid row, so it eases to its real height
   without a fixed max-height. main.js toggles .is-open and disables the
   input while closed.
   ========================================================================== */
.resume-panel {
  display: grid;
  grid-template-rows: 0fr;
  margin-top: -1.25rem;             /* no gap in the form while closed */
  opacity: 0;
  transition: grid-template-rows .4s cubic-bezier(.2, .7, .2, 1), opacity .3s var(--ease), margin-top .4s cubic-bezier(.2, .7, .2, 1);
}
.resume-panel.is-open { grid-template-rows: 1fr; margin-top: 0; opacity: 1; }
.resume-panel__inner { min-height: 0; overflow: hidden; display: grid; gap: .5rem; }

.resume-drop {
  position: relative;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 1rem 1.125rem;
  background: var(--cream);
  border: 1px dashed var(--grey-200);
  cursor: pointer;
  transition: border-color .2s var(--ease), background-color .2s var(--ease);
}
.resume-drop:hover,
.resume-panel.is-dragging .resume-drop { border-color: var(--red); background: var(--red-tint); }
.resume-drop:focus-within { border-color: var(--red); box-shadow: 0 0 0 3px var(--red-tint); }
/* the real input covers the panel invisibly, so a click anywhere opens the
   picker and keyboard focus lands on a genuine file control */
.resume-drop__input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  cursor: pointer;
}
.resume-drop__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 40px; height: 40px;
  border-radius: 50%;
  background: var(--white);
  color: var(--red);
  box-shadow: inset 0 0 0 1px var(--line);
}
.resume-drop__icon svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.resume-drop__text { display: grid; gap: .15rem; min-width: 0; flex: 1; }
.resume-drop__title {
  overflow: hidden;
  color: var(--ink);
  font-size: var(--fs-small);
  font-weight: 700;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.resume-drop__meta { color: var(--body); font-size: .8125rem; line-height: 1.45; }
.resume-drop__action {
  flex: none;
  color: var(--red);
  font-size: .75rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
}
.resume-panel.has-file .resume-drop { border-style: solid; border-color: var(--red); background: var(--white); }
.resume-panel.has-file .resume-drop__icon { background: var(--red); color: var(--white); box-shadow: none; }
.resume-panel.has-error .resume-drop__meta { color: var(--red); }
.resume-drop__clear {
  justify-self: start;
  padding: 0;
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  color: var(--body);
  font-size: .8125rem;
  cursor: pointer;
}
.resume-drop__clear:hover { color: var(--red); }
@media (prefers-reduced-motion: reduce) {
  .resume-panel { transition: none; }
}

/* Job ads: the opening of the description under the details, held to three
   lines and closed with an ellipsis, so a card reads as the start of the ad
   rather than a list of labels. */
.job-card__desc {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  margin: 1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  color: var(--body);
  font-size: .9375rem;
  line-height: 1.6;
}
