@charset "UTF-8";
/* ============================================================================
   palier.css - compiled output of scss/palier.scss

   BUILD:  sass scss/palier.scss pages/slcfr/2026/css/palier.css --style=compressed

   This checked-in file is the compiled result, written by hand because there is
   no Sass binary on the authoring machine. It is byte-for-byte what the SCSS
   compiles to in expanded style, plus the components the build added that the
   original partials did not cover (they are marked ADDED below). If you change
   the SCSS, recompile over this file; if you change this file, port it back.

   Sections, in the order the partials are @used:
     tokens, fonts, base, components, stepper, layout, pages
   ============================================================================ */

/* ---------------------------------------------------------------- _tokens --- */
/* ============================================================================
   REALIGNED ON SLCUK, 2026-09-09 (MD).

   The palette, the radius and the typeface are no longer this site's own - they
   are lifted off stair-lift-comparison.co.uk's PPC lander and homepage, which
   are the best converting stairlift pages in the estate. Where a value here
   differs from CLAUDE.md's brand section, SLCUK is the reason.

   Changed, and what each one was:
     navy       #29235C -> #29235C   the hero ground and every heading
     blue       #1F7BC3 -> #1F7BC3   step labels, links, progress bar
     sky        (new)      #7FD4FF   H1 second line + hero ticks. SLCUK's
                                     differentiator colour: the one thing on
                                     the navy panel that is not white.
     cta-deep   #9E1F5C -> #9E1F5C   the dark end of the card-head gradient
     ink-soft   #5b6480 -> #5b6480
     line       #E4E8F2 -> #E4E8F2
     bg         #FFFFFF -> #F3F3F3   the page ground is a light grey and cards
                                     are white, which is what makes a white
                                     card read as an object rather than as the
                                     page. Half the SLCUK card treatment is
                                     this one value.
     tile       (new)      #F4F8FD   tap-tile fill
     tile-line  (new)      #DCEAF7   tap-tile border
     radius     14px    -> 16px
   ============================================================================ */
:root {
  --pl-navy: #29235C;
  --pl-blue: #1F7BC3;
  --pl-blue-tint: #E9F1F9;
  --pl-sky: #7FD4FF;
  --pl-cta: #D5317B;
  --pl-cta-deep: #9E1F5C;
  --pl-ink: #29235C;
  --pl-ink-soft: #5b6480;
  --pl-line: #E4E8F2;
  --pl-tile: #F4F8FD;
  --pl-tile-line: #DCEAF7;
  --pl-bg: #F3F3F3;
  --pl-white: #ffffff;
  --pl-ok: #1E8E3E;
  --pl-warn: #B54708;
  --pl-err: #C62828;
  --pl-radius: 16px;
  --pl-tap: 56px;
  --pl-shadow: 0 10px 30px rgba(41, 35, 92, .10);
  --pl-dur: 220ms;
  --pl-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}
@media (prefers-reduced-motion: reduce) {
  :root { --pl-dur: 0ms; }
}

/* ----------------------------------------------------------------- _fonts --- */
/* ============================================================================
   MONTSERRAT, one family, two tiers. Replaces Bricolage Grotesque + Nunito
   Sans, which were this site's own choice; Poppins is what both converting
   SLCUK pages use, at 400/600/800/900.

   THE TIER SPLIT IS NOT SLCUK's. SLCUK's tier 1 is Basic Latin only and its
   accented letters live in tier 2, which is correct on an English page - the
   accents only ever turn up in user-typed input, so the bigger file is never
   fetched during the paint that matters. On a FRENCH page the accents are in
   the H1. Shipping that split verbatim would render the French headline out of
   the tier that is neither preloaded nor small: a second blocking request in
   front of the one number /devis/ is measured on.

   So the split is moved. Tier 1 = Basic Latin + every accented letter French
   uses + French punctuation (guillemets, U+202F, the typographic apostrophe),
   27.6 KB, PRELOADED. Tier 2 = the rest of latin, for user-typed input only.
   Rebuild both with tools/build_fonts.py; --check proves the markup's
   characters are all in tier 1.
   ============================================================================ */



/* ------------------------------------------------------------------ _base --- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
body {
  margin: 0;
  background: #FFFFFF;
  color: #29235C;
  /* 18px floor. The audience is 55-80 and the old site ran body copy down to
     0.8rem at mobile widths via a cascade of max-width overrides. */
  font: 400 1.125rem/1.6 'Poppins', Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
  font-family: 'Poppins', Arial, sans-serif;
  line-height: 1.08;
  letter-spacing: -0.02em;
  margin: 0 0 0.5em;
  color: #29235C;
}
h1 { font-size: clamp(2.2rem, 5vw, 3.6rem); font-weight: 800; }
h2 { font-size: clamp(1.6rem, 3.2vw, 2.4rem); font-weight: 800; }
h3 { font-size: 1.35rem; font-weight: 600; }
p { margin: 0 0 1em; max-width: 64ch; }
a { color: #1F7BC3; text-underline-offset: 3px; }
a:hover { color: #29235C; }
img, svg { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
:focus-visible { outline: 3px solid #D5317B; outline-offset: 3px; border-radius: 4px; }
.visually-hidden {
  position: absolute !important; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.container { width: min(100% - 40px, 1120px); margin-inline: auto; }
.container--narrow { width: min(100% - 40px, 760px); margin-inline: auto; }
.small { font-size: 0.95rem; color: #5b6480; }

/* ------------------------------------------------------------ _components --- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  min-height: 56px; padding: 0 28px; border: 0; border-radius: 999px;
  font: 700 1.125rem 'Poppins', Arial, sans-serif;
  cursor: pointer; text-decoration: none; white-space: nowrap;
  transition: background var(--pl-dur) var(--pl-ease), transform var(--pl-dur) var(--pl-ease), box-shadow var(--pl-dur);
}
.btn:active { transform: scale(0.97); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.45; pointer-events: none; }
/* SLCUK trimmed its CTA shadows on the MD's note: 0 8px 18px/.34 -> 0 5px
   12px/.26. A heavy shadow under a pink pill on a white card reads as a 2019
   button. Hover goes to BLUE, not to a darker pink - the pink is the action
   colour and dimming it on hover makes the button look disabled. */
.btn--cta { background: var(--pl-cta); color: #fff; font-weight: 800; box-shadow: 0 5px 12px rgba(213, 49, 123, 0.26); }
.btn--cta:hover { background: var(--pl-blue); color: #fff; }
.btn--blue { background: #1F7BC3; color: #fff; }
.btn--blue:hover { background: #29235C; color: #fff; }
.btn--ghost { background: #E9F1F9; color: #29235C; }
.btn--ghost:hover { background: #dce8f4; }
.btn--line { background: transparent; color: #29235C; border: 2px solid #29235C; }
.btn--block { width: 100%; }
.btn--lg { min-height: 60px; font-size: 1.125rem; font-weight: 900; padding: 0 32px; box-shadow: 0 6px 14px rgba(213, 49, 123, 0.28); }

.field { margin-bottom: 20px; }
.field label { display: block; font-weight: 700; margin-bottom: 8px; color: #29235C; }
.field .hint { display: block; margin-top: 6px; font-size: 0.95rem; color: #5b6480; }
.field .error { display: none; margin-top: 6px; font-size: 0.95rem; color: #C62828; font-weight: 600; }
/* The is-valid / is-invalid states are declared once, in the validation block
   at the end of this file, where they also carry the tick. They were declared
   here too until 2026-09-09; the later block won and these three were dead. */
.input {
  width: 100%; min-height: 56px; padding: 0 18px; background: #fff;
  border: 2px solid #E4E8F2; border-radius: 16px;
  transition: border-color var(--pl-dur);
}
.input:focus { border-color: #1F7BC3; outline: 3px solid rgba(30, 112, 183, 0.25); outline-offset: 0; }
.input::placeholder { color: #8f8da6; }
.input--code { letter-spacing: 0.15em; font-size: 1.4rem; text-align: center; max-width: 220px; }

/* SLCUK's tile: a tinted fill on a soft blue border, not white on grey. On a
   white card a white tile has only its 1px border to say "this is tappable",
   and on a phone that reads as a list rather than a set of buttons. */
.opt {
  display: grid; grid-template-columns: 64px 1fr auto; align-items: center; gap: 16px;
  width: 100%; min-height: 84px; padding: 16px 20px; text-align: left;
  background: var(--pl-tile); border: 2px solid var(--pl-tile-line); border-radius: 12px; cursor: pointer;
  transition: border-color var(--pl-dur), box-shadow var(--pl-dur), transform var(--pl-dur) var(--pl-ease);
}
.opt__icon { width: 64px; height: 64px; color: #1F7BC3; }
.opt__title { font: 700 1.15rem 'Poppins', Arial, sans-serif; color: #29235C; }
.opt__sub { font-size: 0.95rem; color: #5b6480; }
.opt__check {
  width: 28px; height: 28px; border-radius: 50%; border: 2px solid var(--pl-tile-line);
  display: grid; place-items: center; color: transparent; transition: all var(--pl-dur);
}
.opt:hover { border-color: #1F7BC3; }
.opt[aria-pressed="true"], .opt.is-selected {
  border-color: #1F7BC3; box-shadow: 0 0 0 4px rgba(30, 112, 183, 0.15);
}
.opt[aria-pressed="true"] .opt__check, .opt.is-selected .opt__check {
  background: #1F7BC3; border-color: #1F7BC3; color: #fff;
}
/* Hidden until selected: an unselected tile showing a grey tick reads as
   already answered. The mark inherits the parent's colour, which is
   transparent until the tile is pressed. */
.opt__check .tick-mark { width: 0.7em; height: 0.38em; border-width: 0 0 0.16em 0.16em; }
.opt-grid { display: grid; gap: 12px; }

.chip {
  display: inline-flex; align-items: center; gap: 8px; padding: 8px 14px;
  background: #E9F1F9; color: #29235C; border-radius: 999px; font-weight: 600; font-size: 1rem;
}
.chip svg { width: 18px; height: 18px; color: #1F7BC3; }
.chips { display: flex; flex-wrap: wrap; gap: 10px; }

.card { background: #fff; border: 1px solid #E4E8F2; border-radius: 16px; padding: 24px; }
.card--raised { border: 0; box-shadow: 0 6px 24px rgba(31, 26, 94, 0.12); }
.card--tint { background: #E9F1F9; border: 0; }
/* On the content pages the body ground is now #F3F3F3, and a #E9F1F9 tinted
   panel on it is almost the same value - the aside CTA disappeared into the
   page. Inside .cbody it becomes the navy panel it should have been: it is the
   only call to action in that column, so it is the one thing there allowed to
   be loud. */
.cbody .article__aside .card--tint { background: var(--pl-navy); color: #fff; }
.cbody .article__aside .card--tint h3 { color: #fff; }
.cbody .article__aside .card--tint p { color: rgba(255, 255, 255, .82); }

.price {
  font: 800 clamp(1.8rem, 4vw, 2.6rem) 'Poppins', Arial, sans-serif;
  color: #29235C; letter-spacing: -0.02em;
}
.price small { display: block; font: 400 0.95rem 'Poppins', Arial, sans-serif; color: #5b6480; letter-spacing: 0; }

/* .progress lives in _stepper now, as the 7px navy-to-blue gradient bar SLCUK
   uses. It was declared HERE as well, 6px and flat blue, and because _stepper
   is @used after _components the later rule silently won - two declarations,
   one of them dead, which is how a stylesheet starts lying about itself. The
   wizard is the only progress bar on the site, so this one is gone rather
   than kept as a base. */

.faq { border-top: 1px solid #E4E8F2; }
.faq details { border-bottom: 1px solid #E4E8F2; }
.faq summary {
  list-style: none; cursor: pointer; padding: 20px 44px 20px 0;
  font: 700 1.15rem 'Poppins', Arial, sans-serif; color: #29235C; position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ''; position: absolute; right: 8px; top: 50%; width: 12px; height: 12px;
  border-right: 3px solid #1F7BC3; border-bottom: 3px solid #1F7BC3;
  transform: translateY(-70%) rotate(45deg); transition: transform var(--pl-dur);
}
.faq details[open] summary::after { transform: translateY(-30%) rotate(-135deg); }
.faq .faq__body { padding: 0 0 20px; }

.trust {
  display: flex; gap: 20px 32px; flex-wrap: wrap; justify-content: center;
  padding: 16px 0; font-weight: 600; color: #29235C;
}
.trust span { display: inline-flex; align-items: center; gap: 8px; }
.trust svg { width: 20px; height: 20px; color: #1E8E3E; }

.notice { padding: 16px 20px; border-radius: 16px; background: #E9F1F9; border-left: 4px solid #1F7BC3; }
.notice--warn { background: #FFF4E5; border-color: #B54708; }

/* --------------------------------------------------------------- _stepper --- */
/* ============================================================================
   THE LANDER IS NOW THE SLCUK SHAPE. Rewritten 2026-09-09 (MD): follow the
   SLCUK PPC lander and homepage, which convert best in the estate.

   What changed structurally, and why none of it is cosmetic:

   1. TWO COLUMNS, NOT ONE. Was a single centred column with the question
      filling the screen. Now a navy hero split 1fr / 470px: the argument on
      the left, the wizard card on the right, both above the fold. The card is
      the hero - it is not behind a scroll or a click.
   2. THE WIZARD IS A CARD ON THE HERO, not a section of the page. White on
      navy, a 5px translucent white halo, a tight contact shadow and a wide
      soft one. The halo is what separates it from the photograph on its right
      edge, where a drop shadow has nothing to sit against.
   3. THE CARD HEAD IS PINK. SLCUK learned this the hard way: a navy-to-blue
      head on a navy hero dissolves into the background and the card stops
      reading as an object. Pink is the only colour in the palette that nothing
      else in the hero uses, and it cannot compete with the Continue button
      because step 1 auto-advances and renders no Continue button at all.
   4. THE BUTTONS MOVED INSIDE THE CARD. Was a bar pinned to the bottom of the
      viewport. SLCUK keeps them in the card and that is the converting
      arrangement, so they are in the card.
   5. FOCUS MODE. Once past step 1, body.wiz-started hides everything below the
      hero, so the page has one thing on it.

   ONE DELIBERATE DEPARTURE, and it is a real one. SLCUK sets its hero sub at
   17.5px, its ticks at 15.5px and its wizard sub at 14.5px. CLAUDE.md's brand
   section says body text on this site never goes below 18px, because the
   audience is 55-80 and Lighthouse accessibility 100 is an acceptance
   criterion. So every SENTENCE here is floored at 1.125rem/18px and only the
   micro-LABELS - eyebrow, step counter, badge captions, field hints - are
   allowed under it, at SLCUK's own sizes. The layout is SLCUK's; the reading
   sizes are this audience's. Flagged in DEPLOY.md rather than silently
   shipping 15.5px to a 75-year-old.
   ============================================================================ */
.lander { min-height: 100dvh; display: flex; flex-direction: column; background: var(--pl-bg); }

/* ---- sticky bar. Its depth is subtracted from every screen for the whole
   visit, so it is 57px and not the 68px the site header uses. ---- */
.lander__top {
  position: sticky; top: 0; z-index: 20; background: var(--pl-white);
  border-bottom: 1px solid var(--pl-line);
}
.lander__bar {
  max-width: 1180px; margin: 0 auto; padding: 8px 22px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.lander__top .logo img { height: 34px; width: auto; }
.lander__right { display: flex; align-items: center; gap: 14px; }
.lander__phone { font-weight: 800; color: var(--pl-blue); text-decoration: none; white-space: nowrap; }
/* Below 420px the bar is logo + CTA only. The phone goes rather than the
   button: the button is the thing the page is for, and two items plus a 34px
   logo do not fit a 360px bar without one of them wrapping. */
@media (max-width: 419px) { .lander__right .lander__phone { display: none; } }
/* Shallower CTA in the bar only; the in-page ones keep their full size. */
.lander__top .btn--cta { min-height: 44px; padding: 0 20px; font-size: 0.95rem; box-shadow: 0 3px 8px rgba(213, 49, 123, .24); }

/* ---- hero ---- */
.hero { position: relative; overflow: hidden; background: var(--pl-navy); color: #fff; }
/* The photo is framed RIGHT so its subject clears the copy, and the copy sits
   on a SOLID navy panel that fades to a light tint over the photo. A
   half-transparent wash over a face is what makes a hero look muddy. */
.hero::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: url('/pages/slcfr/2026/images/hero-desktop.webp') right center/cover no-repeat;
}
.hero::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  background:
    linear-gradient(96deg, rgba(41,35,92,1) 0%, rgba(41,35,92,1) 40%, rgba(41,35,92,.86) 54%, rgba(41,35,92,.34) 72%, rgba(41,35,92,.20) 100%),
    linear-gradient(180deg, rgba(41,35,92,.22) 0%, rgba(41,35,92,0) 32%, rgba(41,35,92,.34) 100%);
}
/* No photo below 900px: the copy panel covers the whole width there anyway, so
   the image would be pure download. Mobile gets the flat navy, which is what
   the gradient resolves to under the copy in any case. */
@media (max-width: 899px) { .hero::before { display: none; } .hero::after { background: none; } }

.hero__inner {
  position: relative; z-index: 2; max-width: 1180px; margin: 0 auto;
  padding: 28px 22px 36px; display: grid; gap: 28px; align-items: start;
}
@media (min-width: 900px) {
  .hero__inner { grid-template-columns: 1fr 470px; gap: 44px; padding: 44px 22px 56px; }
  /* Offset so the card top lines up with the H1, clearing the eyebrow pill. */
  .hero__form { margin-top: 46px; }
}
.hero__copy { display: flex; flex-direction: column; }

.eyebrow {
  display: inline-flex; align-items: center; gap: 8px; align-self: flex-start;
  background: rgba(255, 255, 255, .14); color: #fff; padding: 7px 14px;
  border-radius: 999px; font-size: 0.78rem; font-weight: 800;
  letter-spacing: .04em; text-transform: uppercase; margin-bottom: 16px;
}
/* Scoped to .hero__copy: the wizard's step headings are also inside .hero and
   were inheriting this letter-spacing and text-shadow. */
.hero__copy h1,
.hero__copy h2 {
  font-size: clamp(1.85rem, 4.4vw, 2.65rem); line-height: 1.08; font-weight: 900;
  letter-spacing: -.02em; margin: 0 0 14px; color: #fff;
  text-shadow: 0 2px 14px rgba(0, 0, 0, .3); text-wrap: balance;
}
/* Two messages, two lines: the comparison promise in white, the
   differentiator on its own line in the accent blue. */
.hero__copy h1 em,
.hero__copy h2 em {
  display: block; color: var(--pl-sky); font-style: normal;
  font-size: .62em; letter-spacing: -.01em; margin-top: 7px;
}
.hero__sub {
  font-size: 1.125rem; line-height: 1.55; color: #e9ecf8; max-width: 46ch;
  margin: 0 0 22px; text-shadow: 0 1px 8px rgba(0, 0, 0, .3);
}
.ticks { list-style: none; display: flex; flex-direction: column; gap: 10px; margin: 0 0 28px; padding: 0; }
.ticks li { display: flex; gap: 10px; align-items: flex-start; font-size: 1.125rem; line-height: 1.45; color: #eef1fb; }
.ticks .c { color: var(--pl-sky); flex: 0 0 auto; margin-top: 0.42em; }
.badges { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 520px; }
.badge { background: rgba(255, 255, 255, .09); border: 1px solid rgba(255, 255, 255, .2); border-radius: 12px; padding: 14px; }
.badge b { display: block; font-size: 1rem; font-weight: 900; margin-bottom: 2px; }
.badge span { font-size: 0.8rem; line-height: 1.4; color: #cfd6ee; }
@media (max-width: 420px) { .badges { grid-template-columns: 1fr; } }

/* ---- the wizard card ---- */
.wiz-card {
  background: var(--pl-white); color: var(--pl-ink); border-radius: var(--pl-radius);
  box-shadow: 0 0 0 5px rgba(255, 255, 255, .18),
              0 10px 20px -8px rgba(0, 0, 0, .40),
              0 34px 64px -16px rgba(0, 0, 0, .62);
  overflow: hidden; scroll-margin-top: 66px;
}
/* Dropped to 3px under 520px so the halo does not eat the gutter. */
@media (max-width: 519px) {
  .wiz-card { box-shadow: 0 0 0 3px rgba(255,255,255,.18), 0 10px 20px -8px rgba(0,0,0,.40), 0 34px 64px -16px rgba(0,0,0,.62); }
}
.wiz-card__head { background: linear-gradient(135deg, var(--pl-cta) 0%, var(--pl-cta-deep) 100%); color: #fff; padding: 18px 26px; }
.wiz-card__head h2 { font-weight: 900; font-size: 1.5rem; line-height: 1.15; margin: 0; color: #fff; }
/* Lifted to .92 alpha so the small line clears AA on the light end of the
   gradient, where white on pink is 4.6:1. */
.wiz-card__head p { font-size: 0.9rem; color: rgba(255, 255, 255, .92); margin: 5px 0 0; }
.wiz-card__body { padding: 24px 28px 28px; }
@media (max-width: 519px) { .wiz-card__body { padding: 20px 18px 22px; } .wiz-card__head { padding: 16px 18px; } }
.wiz-steplabel { font-weight: 800; font-size: 0.78rem; letter-spacing: .09em; text-transform: uppercase; color: var(--pl-blue); }

.progress { height: 7px; background: #EEF0F7; border-radius: 999px; overflow: hidden; margin: 9px 0 20px; }
.progress__bar { display: block; height: 100%; width: 0; background: linear-gradient(90deg, var(--pl-navy), var(--pl-blue)); border-radius: 999px; transition: width 300ms var(--pl-ease); }

.stepper { position: relative; overflow: hidden; }
.step {
  position: absolute; inset: 0 0 auto 0;
  opacity: 0; transform: translateX(40px); pointer-events: none;
  transition: opacity var(--pl-dur) var(--pl-ease), transform var(--pl-dur) var(--pl-ease);
}
.step.is-active { position: relative; opacity: 1; transform: none; pointer-events: auto; }
.step.is-leaving { opacity: 0; transform: translateX(-40px); }
.step.is-back { transform: translateX(-40px); }
.step h2 { font-size: 1.375rem; line-height: 1.2; font-weight: 900; margin: 0; letter-spacing: normal; text-shadow: none; color: var(--pl-ink); }
.step .step__sub { font-size: 1.125rem; line-height: 1.5; color: var(--pl-ink-soft); margin: 8px 0 20px; }
.step__hint { font-size: 0.85rem; line-height: 1.5; color: var(--pl-ink-soft); margin-top: 12px; }

/* ---- the action row, now inside the card ---- */
.actionbar { display: grid; gap: 10px; margin-top: 20px; }
.actionbar .btn--cta { width: 100%; }
.actionbar .btn--back {
  width: 100%; min-height: 48px; background: #fff; border: 2px solid var(--pl-tile-line);
  color: var(--pl-navy); font-weight: 700; border-radius: 12px;
}
.actionbar .btn--back:hover { border-color: var(--pl-blue); color: var(--pl-blue); }

/* ============================================================================
   THE LIGHT TREATMENT - .lander--light

   An OPT-IN modifier on the same markup. Add the class and the hero inverts;
   nothing else moves, and the navy treatment stays the default because it is
   what SLCUK ships and what converts there today.

   This is not "the same design with a white background". Five things have to
   change together or it falls apart, and each one is a decision:

   1. THE ACCENT LINE CANNOT STAY SKY. #7FD4FF on white is about 1.7:1 - it is
      a colour designed to sit on navy. The H1's second line goes to
      --pl-blue (#1F7BC3), which is 4.6:1 on white and reads as the same
      "this is the differentiator" signal. Same for the tick marks.
   2. THE PHOTO WASH INVERTS. The navy hero commits: solid navy under the copy,
      a fast fade, then a light tint over the photograph. Here it is solid
      WHITE under the copy and the same fade, so the photo still shows through
      on the right and the copy still sits on a solid ground rather than a
      half-transparent smear over somebody's face.
   3. THE CARD LOSES ITS HALO AND GAINS A BORDER. The 5px translucent white
      ring exists to separate the card from a dark hero and a photograph. On a
      white hero it is invisible, so the card would float with nothing holding
      its edge - it gets a 1px line and a softer shadow instead.
   4. THE BADGES BECOME CARDS. rgba(255,255,255,.09) on white is nothing. They
      become tinted panels with a real border.
   5. THE CARD HEAD STAYS PINK. It is the one part of the treatment that works
      identically on both grounds, and it is still the only colour in the hero
      that nothing else uses.

   WHAT YOU GIVE UP by choosing this: the navy panel is doing work that is easy
   to miss. It is a large block of brand colour above the fold on a page with
   no logo recognition and no brand equity in France, and it makes the white
   card the single brightest object on the screen. On a white hero the card
   competes with its own background, and the only thing left carrying the eye
   to it is the pink head.
   ============================================================================ */
.lander--light .hero { background: var(--pl-white); color: var(--pl-ink); }
.lander--light .hero::after {
  background:
    linear-gradient(96deg, rgba(255,255,255,1) 0%, rgba(255,255,255,1) 40%, rgba(255,255,255,.88) 54%, rgba(255,255,255,.40) 72%, rgba(255,255,255,.22) 100%),
    linear-gradient(180deg, rgba(255,255,255,.30) 0%, rgba(255,255,255,0) 34%, rgba(255,255,255,.40) 100%);
}
/* The eyebrow was a translucent white pill on navy. On white it needs real
   ink and a real edge, so it becomes the tinted chip the rest of the site
   already uses. */
.lander--light .eyebrow {
  background: var(--pl-blue-tint); color: var(--pl-navy);
  border: 1px solid #CFE0F0;
}
.lander--light .hero__copy h1,
.lander--light .hero__copy h2 { color: var(--pl-navy); text-shadow: none; }
.lander--light .hero__copy h1 em,
.lander--light .hero__copy h2 em { color: var(--pl-blue); }
.lander--light .hero__sub { color: var(--pl-ink-soft); text-shadow: none; }
.lander--light .ticks li { color: var(--pl-ink); }
.lander--light .ticks .c { color: var(--pl-blue); }
.lander--light .badge {
  background: var(--pl-blue-tint); border: 1px solid #CFE0F0;
}
.lander--light .badge b { color: var(--pl-navy); }
.lander--light .badge span { color: var(--pl-ink-soft); }
/* No halo on a white ground - a line and a softer shadow instead. */
.lander--light .wiz-card {
  box-shadow: 0 1px 0 rgba(41,35,92,.04), 0 18px 44px -14px rgba(41,35,92,.30);
  border: 1px solid var(--pl-line);
}
/* The page ground below the hero is already #F3F3F3, so a white hero needs a
   line under it or the two whites/greys meet with nothing between them. */
.lander--light .hero { border-bottom: 1px solid var(--pl-line); }

/* ============================================================================
   IMPROVEMENT PASS, 2026-09-09. MD: the lander "can all be improved".

   Seven changes, and the reasoning for each is with it. Five of the seven are
   things the SLCUK PPC lander already does that this one did not.
   ============================================================================ */

/* ---- 1. TWO TILES, SIDE BY SIDE ------------------------------------------
   With outdoor gone, step 1 is a binary choice, and a binary choice stacked
   as two full-width rows is a list you read. Side by side it is a pair you
   compare, which is what the question actually asks - and it puts both answers
   plus the diagrams above the fold on a phone with no scrolling.

   The tile's internals change with it: the 64px-icon / text / check row makes
   no sense in a narrow column, so the content stacks and centres and the tick
   moves to the corner. The diagram gets nearly twice the room, which is the
   real win - these are plan-view drawings and at 64px the difference between
   them was a smudge. */
.opt-grid--two { grid-template-columns: 1fr 1fr; gap: 12px; }
.opt-grid--two .opt {
  /* One column: diagram on top, label under it. The `auto` third column the
     base .opt declares is gone with the check, which is absolute here. */
  grid-template-columns: 1fr; justify-items: center; text-align: center;
  gap: 12px; padding: 18px 12px 16px; min-height: 0; position: relative;
}
.opt-grid--two .opt__icon { width: 104px; height: 100px; }
/* The 1-turn diagram is landscape (84x62) where the other is portrait, so it
   gets the shorter slot or it letterboxes inside its own box. */
.opt-grid--two .opt__icon--wide { width: 104px; height: 74px; }
/* The <br> between title and sub is a layout hint for the row layout. Hiding
   it here is only safe because both parts are blocks below - without that,
   they run together inline and wrap mid-phrase, which is the second half of
   what went wrong in the screenshot: "Escalier" / "droit" / "Une seule" /
   "volee," / "sans" / "virage". */
.opt-grid--two .opt br { display: none; }
.opt-grid--two .opt__title { display: block; font-size: 1.0625rem; line-height: 1.2; }
.opt-grid--two .opt__sub { display: block; font-size: 0.875rem; line-height: 1.35; margin-top: 3px; }
/* PADDING FOR THE TICK. Centred text in a ~200px tile runs the full width, so
   an absolutely positioned tick in the corner lands on top of the title -
   which it did. 26px of clearance either side is the tick plus its margin, and
   it is symmetric so the text stays optically centred.

   TARGETS .opt__label, NOT `> span`. The first version of this rule was
   `.opt-grid--two .opt > span`, and .opt__check IS a direct child span - so the
   tick got 26px of padding on each side too: 24px wide plus 52px of padding,
   still 24px tall. That is why the circle rendered as an oval. The text
   wrapper had no class of its own, so it has one now rather than the rule
   guessing at position. */
.opt-grid--two .opt__label { display: block; padding: 0 26px; }
/* Explicit, because an absolutely positioned grid item with a border is
   exactly where a stretched circle comes from. */
.opt__check { box-sizing: border-box; flex: 0 0 auto; }
/* Corner, not column. absolute so it cannot add a row to the stack. */
.opt-grid--two .opt__check { position: absolute; top: 10px; right: 10px; width: 24px; height: 24px; }

/* Under 420px each tile is about 165px wide. The diagrams survive that; two
   centred labels do not, so it goes back to one tile per row with the diagram
   beside the text. Raised from 380px after the screenshot: at 400px the
   centred version was still wrapping "tournant" onto its own line. */
@media (max-width: 419px) {
  .opt-grid--two { grid-template-columns: 1fr; }
  .opt-grid--two .opt {
    grid-template-columns: 92px 1fr; justify-items: start; align-items: center;
    text-align: left; padding: 14px 16px; gap: 16px;
  }
  .opt-grid--two .opt__label { padding: 0 30px 0 0; }
  .opt-grid--two .opt__icon { width: 92px; height: 88px; }
  .opt-grid--two .opt__icon--wide { width: 92px; height: 66px; }
  .opt-grid--two .opt__check { top: 50%; margin-top: -12px; }
}

/* ---- 2. A MOBILE HERO IMAGE ---------------------------------------------
   Below 900px the desktop hero was never requested, and correctly so - the
   copy panel covers the full width, so a wide photograph behind it is pure
   download. But that left the phone with NO image above the fold, on a site
   that carries photography because the client asked for it.

   So mobile gets its own 4:3 crop of the same frame, 31.7 KB, sitting behind
   the copy with a heavier scrim than desktop needs: white text on a
   photograph at phone contrast wants more ground under it, not less. */
@media (max-width: 899px) {
  .hero::before {
    display: block;
    background: url('/pages/slcfr/2026/images/hero-mobile.webp') center 38%/cover no-repeat;
  }
  .hero::after {
    background:
      linear-gradient(180deg, rgba(41,35,92,.72) 0%, rgba(41,35,92,.88) 42%, rgba(41,35,92,.96) 100%);
  }
}
@media (max-width: 899px) {
  .lander--light .hero::after {
    background: linear-gradient(180deg, rgba(255,255,255,.80) 0%, rgba(255,255,255,.93) 42%, rgba(255,255,255,.98) 100%);
  }
}

/* ---- 3. FIELD GROUPS ON THE CONTACT STEP --------------------------------
   Seven fields arrived as seven fields after a two-tap start, and that is
   where a form of this length loses people. They are the same seven, grouped
   into three named blocks - who you are, where you live, how to reach you - so
   the step reads as three short things instead of one long one.

   <fieldset>/<legend> rather than a div and a heading: it is what the grouping
   actually is, and a screen reader announces the legend with every field
   inside it. Browser default fieldset styling is reset because it is a border
   nobody wants. */
.fgroup { border: 0; padding: 0; margin: 0 0 4px; }
.fgroup + .fgroup { margin-top: 4px; padding-top: 18px; border-top: 1px solid var(--pl-line); }
.fgroup > legend {
  padding: 0; margin-bottom: 14px; font-weight: 800; font-size: 0.78rem;
  letter-spacing: .08em; text-transform: uppercase; color: var(--pl-blue);
}
/* The grid has to be re-established inside each group - a fieldset is not a
   grid container by inheritance. */
.fgroup .contact-grid { display: grid; gap: 0 16px; }
@media (min-width: 600px) {
  .fgroup .contact-grid { grid-template-columns: 1fr 1fr; }
  .fgroup .contact-grid .field--full { grid-column: 1 / -1; }
}

/* ---- 4. THE FINAL CTA -----------------------------------------------------
   The page ended on a trust strip. SLCUK's lander ends on a CTA, and the
   reason is mechanical: someone who has read to the bottom has read the whole
   argument and has nothing to act on. Anchors back to the card, which is at
   the top, so it doubles as the way back up. */
.final {
  background: var(--pl-navy); color: #fff; text-align: center;
  padding: 52px 0 56px;
}
.final h2 { color: #fff; margin: 0 0 10px; }
.final p { color: rgba(255, 255, 255, .82); margin: 0 auto 24px; max-width: 46ch; }
.final .small { color: rgba(255, 255, 255, .62); margin-top: 14px; margin-bottom: 0; }

/* ---- 5. THE EXIT-INTENT MODAL --------------------------------------------
   Ported from the SLCUK lander, which took it from Desktop.aspx. Armed only
   after 2s AND a real mouseenter, one-shot per session, and never on touch -
   see the JS for why each of those conditions is there.

   [hidden] is restated because this element is given a display. That is the
   exact trap PPC-LANDER-CHECKLIST.md section D names, and a modal that ignores
   [hidden] is a modal that is simply on. */
.eim { position: fixed; inset: 0; z-index: 200; display: grid; place-items: center; padding: 20px; }
.eim[hidden] { display: none !important; }
.eim__scrim { position: absolute; inset: 0; background: rgba(20, 17, 46, .62); backdrop-filter: blur(3px); }
.eim__box {
  position: relative; width: min(100%, 460px); background: #fff;
  border-radius: var(--pl-radius); padding: 30px 28px 26px; text-align: center;
  box-shadow: 0 30px 70px -20px rgba(0, 0, 0, .55);
  animation: eimIn 240ms var(--pl-ease);
}
@keyframes eimIn { from { opacity: 0; transform: translateY(14px) scale(.98); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .eim__box { animation: none; } }
.eim__box h2 { font-size: 1.375rem; margin: 0 0 8px; }
.eim__box p { color: var(--pl-ink-soft); margin: 0 0 20px; }
.eim__box .btn { width: 100%; }
/* A text button, not a styled link: it does not navigate. And it is the LAST
   thing in the box, because a dismiss that sits above the action is a dismiss
   people press by accident. */
.eim__no {
  display: block; width: 100%; margin-top: 14px; background: none; border: 0;
  font: 600 0.95rem 'Poppins', Arial, sans-serif; color: var(--pl-ink-soft);
  text-decoration: underline; cursor: pointer; padding: 8px;
}
.eim__no:hover { color: var(--pl-navy); }
/* The close X, so the box can be dismissed without reading the small print. */
.eim__x {
  position: absolute; top: 8px; right: 8px; width: 40px; height: 40px;
  background: none; border: 0; cursor: pointer; color: var(--pl-ink-soft);
  font: 400 1.5rem/1 'Poppins', Arial, sans-serif; border-radius: 50%;
}
.eim__x:hover { background: var(--pl-blue-tint); color: var(--pl-navy); }

/* ---- 6. THE AUTOFILL HARVEST FIELDS -------------------------------------
   Offscreen, NOT display:none, and not the honeypot's visually-hidden either:
   a browser will not autofill a field it considers hidden, and the entire
   point of these is to be autofilled. Positioned off-canvas, zero-size, and
   kept out of the tab order and the accessibility tree. */
.autofill-harvest {
  position: absolute; left: -9999px; top: 0; width: 1px; height: 1px;
  overflow: hidden; opacity: 0; pointer-events: none;
}

/* ---- focus mode: past step 1 the page has one thing on it ---- */
body.wiz-started .below-hero { display: none; }

.reveal {
  margin-top: 20px; padding: 0 20px; border-radius: 16px; background: var(--pl-blue-tint);
  display: grid; gap: 6px; opacity: 0; transform: translateY(12px);
  max-height: 0; overflow: hidden;
  transition: opacity 300ms var(--pl-ease), transform 300ms var(--pl-ease), max-height 400ms var(--pl-ease), padding 300ms;
}
.reveal.is-open { opacity: 1; transform: none; max-height: 340px; padding: 20px; }
.reveal .reveal__label { font-weight: 700; color: #29235C; }
.reveal .reveal__coverage { display: flex; align-items: center; gap: 8px; color: #1E8E3E; font-weight: 700; }
/* ADDED: coverage is tri-state. Unknown must not look like a green tick, and
   "not covered" must not look like an error the visitor caused. */
.reveal .reveal__coverage--unknown { color: #5b6480; }
.reveal .reveal__coverage--no { color: #B54708; }

.searching { text-align: center; padding: 28px 0; }
.searching .wiz-spin { width: 46px; height: 46px; border: 4px solid var(--pl-tile-line); border-top-color: var(--pl-blue); border-radius: 50%; margin: 0 auto 4px; animation: wizspin 1s linear infinite; }
@keyframes wizspin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .searching .wiz-spin { animation: none; } }
.searching ul { list-style: none; padding: 0; margin: 20px auto 0; max-width: 360px; text-align: left; }
.searching li {
  display: flex; align-items: center; gap: 12px; padding: 10px 0;
  color: #5b6480; opacity: 0.4; transition: opacity 300ms;
}
.searching li.is-done { opacity: 1; color: #29235C; }
.searching li.is-done .dot { background: #1E8E3E; }
.searching .dot { width: 14px; height: 14px; border-radius: 50%; background: #E4E8F2; flex: 0 0 auto; }

.contact-grid { display: grid; gap: 0 16px; }
@media (min-width: 600px) {
  .contact-grid { grid-template-columns: 1fr 1fr; }
  .contact-grid .field--full { grid-column: 1 / -1; }
}
.consent { display: flex; gap: 12px; align-items: flex-start; font-size: 0.95rem; color: #5b6480; }
.consent input { width: 24px; height: 24px; margin-top: 2px; accent-color: #1F7BC3; flex: 0 0 auto; }

.done { text-align: center; padding: 24px 0; }
.done .done__icon { width: 72px; height: 72px; margin: 0 auto 16px; color: #1E8E3E; }

/* ---------------------------------------------------------------- _layout --- */
/* The bar is sticky, so its depth comes off every screen for the whole visit.
   SLCUK trimmed theirs from ~67px to 57px on the MD's note - 12px padding to
   8px, 42px logo to 34px, and a smaller CTA box inside the bar than the
   in-page ones get. Same three changes here: 68px -> 57px.

   The blur is gone with it. A backdrop-filter on a sticky element repaints the
   strip behind it on every scroll frame, and it was buying a frosted effect
   over a solid white page. */
.site-header {
  position: sticky; top: 0; z-index: 20; background: #fff;
  border-bottom: 1px solid #E4E8F2;
}
.site-header .container { display: flex; align-items: center; justify-content: space-between; gap: 20px; min-height: 57px; }
.site-header .logo img { height: 34px; width: auto; }
.site-header nav { display: none; gap: 26px; }
.site-header nav a { font-weight: 600; color: #29235C; text-decoration: none; }
@media (min-width: 900px) { .site-header nav { display: flex; } }
/* Shallower than the in-page CTAs, which keep their 56px box. */
.site-header .btn { min-height: 41px; padding: 0 20px; font-size: 0.95rem; box-shadow: 0 3px 8px rgba(213, 49, 123, .24); }
.site-header nav a:hover { color: var(--pl-blue); }

/* .pagehero is GONE, and so is the collision it was renamed to escape.
   -------------------------------------------------------------------------
   It was created on 2026-09-09 when `.hero` turned out to be declared twice -
   as the navy conversion hero in _stepper and as a light blue-to-white
   gradient here - with the later, lighter one winning and every lander
   rendering white text on a near-white ground.

   The rename fixed that. Then the content pages moved to .chero, and
   Barometre was the last page using .pagehero, so the whole block is now dead.
   Removed rather than kept "in case": dead CSS that still looks authoritative
   is how somebody spends an afternoon wondering which hero rule applies.
   -------------------------------------------------------------------------
*/
}
.section { padding: 56px 0; }
.section--tint { background: #E9F1F9; }
.section--navy { background: #29235C; color: #fff; }
.section--navy h2, .section--navy h3 { color: #fff; }
@media (min-width: 900px) { .section { padding: 80px 0; } }
.section__intro { max-width: 64ch; margin-bottom: 32px; }
.grid { display: grid; gap: 20px; }
@media (min-width: 600px) { .grid--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 600px) { .grid--3 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .grid--3 { grid-template-columns: repeat(3, 1fr); } }

.steps-how { counter-reset: step; display: grid; gap: 24px; padding: 0; }
@media (min-width: 900px) { .steps-how { grid-template-columns: repeat(3, 1fr); } }
.steps-how li { list-style: none; counter-increment: step; padding-left: 64px; position: relative; }
.steps-how li::before {
  content: counter(step); position: absolute; left: 0; top: 0;
  width: 48px; height: 48px; border-radius: 50%; background: #29235C; color: #fff;
  font: 800 1.3rem 'Poppins', Arial, sans-serif;
  display: grid; place-items: center;
}
.cta-band { background: #29235C; color: #fff; text-align: center; padding: 56px 0; }
.cta-band h2 { color: #fff; }
.cta-band p { margin-inline: auto; color: rgba(255, 255, 255, 0.8); }
.site-footer { background: #29235C; color: rgba(255, 255, 255, 0.78); padding: 48px 0 32px; font-size: 0.95rem; }
.site-footer a { color: #fff; }
.site-footer h4 { color: #fff; font-size: 1rem; margin-bottom: 12px; }
.site-footer .footer-grid { display: grid; gap: 28px; }
@media (min-width: 900px) { .site-footer .footer-grid { grid-template-columns: 2fr 1fr 1fr 1fr; } }
.site-footer ul { list-style: none; padding: 0; margin: 0; }
.site-footer ul li { margin: 6px 0; }
.site-footer .legal { margin-top: 32px; padding-top: 20px; border-top: 1px solid rgba(255, 255, 255, 0.15); }
.breadcrumb { font-size: 0.95rem; color: #5b6480; padding: 16px 0; }
.breadcrumb a { color: #5b6480; }
/* Inside a content hero the breadcrumb sits on the navy gradient, so it needs
   its own colours - inheriting #5b6480 on that ground is 2.1:1 and effectively
   invisible. Kept as one rule rather than restyling .breadcrumb globally,
   because the guide and question pages still use it on white. */
.chero .breadcrumb { color: rgba(255, 255, 255, .72); padding: 0 0 14px; }
.chero .breadcrumb a { color: rgba(255, 255, 255, .82); }
.chero .breadcrumb a:hover { color: #fff; }
.breadcrumb ol { list-style: none; display: flex; flex-wrap: wrap; gap: 6px; padding: 0; margin: 0; }
.breadcrumb li + li::before { content: '\203A'; margin-right: 6px; }

/* ============================================================================
   _media - the photographic sections.

   Added 2026-09-09 on a client note: French audiences respond to imagery, and
   everything below the hero was text cards. This is the component that carries
   it.

   FOUR THINGS HERE ARE ABOUT NOT BREAKING THE PAGE, NOT ABOUT LOOKS:

   1. EVERY FIGURE RESERVES ITS BOX. aspect-ratio on the picture plus width and
      height attributes on the img. The CLS budget on /devis/ is 0.05 and an
      image that arrives without a reserved box spends the entire budget on its
      own. This is also why the ratio is a token per slot rather than "whatever
      the file is".
   2. NOTHING BELOW THE HERO IS EAGER. loading="lazy" and decoding="async" on
      all of them. The hero photograph is the LCP element and is preloaded;
      anything else competing for that first connection is working against the
      number the lander is measured on.
   3. object-fit: cover WITH object-position. A photograph cropped by a grid
      cell crops from the centre by default, which is how you lose the subject
      of a portrait-ish frame on a wide screen.
   4. THE ORDER FLIPS, THE DOM DOES NOT. .media--flip moves the figure to the
      right with grid-column, so the reading order stays text-then-image for a
      screen reader whichever side the picture is on.
   ============================================================================ */
.media { display: grid; gap: 28px; align-items: center; }
@media (min-width: 900px) {
  .media { grid-template-columns: 1fr 1fr; gap: 48px; }
  /* Figure second in the DOM, first on screen. */
  .media--flip .media__fig { grid-column: 1; grid-row: 1; }
  .media--flip .media__body { grid-column: 2; grid-row: 1; }
}
.media__fig { margin: 0; }
.media__fig picture {
  display: block; overflow: hidden; border-radius: var(--pl-radius);
  aspect-ratio: 3 / 2; background: var(--pl-blue-tint);
  box-shadow: 0 1px 0 rgba(41, 35, 92, .04), 0 18px 40px -18px rgba(41, 35, 92, .28);
}
.media__fig img { width: 100%; height: 100%; object-fit: cover; object-position: center; display: block; }
/* figcaption is declared once, in the captions block further down - it was
   0.85rem here, which is under the 18px reading floor for a line the pair
   depends on being read. */
.media__body > :first-child { margin-top: 0; }
.media__body h2 { margin-top: 0; }
/* On navy the tint showing through before the image loads is wrong, and so is
   the shadow - it has nothing to fall on. */
.section--navy .media__fig picture {
  background: rgba(255, 255, 255, .08);
  box-shadow: 0 18px 40px -18px rgba(0, 0, 0, .5);
}

/* A photograph used as a band across the full width - the coverage beat. Taller
   ratio on mobile, wider on desktop, because a 3:2 band on a phone eats the
   whole screen for a picture nobody scrolled for. */
.band { position: relative; overflow: hidden; }
.band picture { display: block; aspect-ratio: 4 / 3; background: var(--pl-blue-tint); }
@media (min-width: 700px) { .band picture { aspect-ratio: 21 / 8; } }
.band img { width: 100%; height: 100%; object-fit: cover; object-position: center 62%; display: block; }
.band__over {
  position: absolute; inset: 0; display: grid; align-items: end;
  /* Solid at the foot, clear at the top: the text sits on a ground rather than
     on a wash over the middle of the photograph. */
  background: linear-gradient(180deg, rgba(41,35,92,0) 30%, rgba(41,35,92,.72) 74%, rgba(41,35,92,.92) 100%);
}
.band__over .container { padding-bottom: 28px; padding-top: 28px; }
.band__over h2, .band__over p { color: #fff; }
.band__over h2 { margin: 0 0 6px; text-shadow: 0 2px 12px rgba(0,0,0,.35); }
.band__over p { margin: 0; max-width: 52ch; color: rgba(255,255,255,.92); }

/* ---- a mini card with a photograph on top -------------------------------
   Used for the component explainers on the home page. The image is flush to
   the card's edges, so the card carries no padding of its own and the copy
   block supplies it - which is why this is a modifier rather than an inline
   style on each one. Three of those inline styles is the point at which it
   becomes a class. */
.mini--photo { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.mini--photo .mini__pic { display: block; margin: 0; }
.mini--photo .mini__pic picture { display: block; aspect-ratio: 16 / 9; background: var(--pl-blue-tint); }
.mini--photo .mini__pic img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mini--photo .mini__body { padding: 18px 20px 20px; }
.mini--photo .mini__body > :first-child { margin-top: 0; }
.mini--photo .mini__body > :last-child { margin-bottom: 0; }

/* ---- figure captions ----------------------------------------------------
   The situation pair and the component figures both caption. A caption is not
   body copy: it is smaller, it sits tight under its image, and its lead-in
   phrase is bold so the pair can be scanned without reading either in full. */
.media__fig figcaption {
  font-size: 0.95rem; line-height: 1.5; color: var(--pl-ink-soft);
  margin-top: 10px;
}
.media__fig figcaption strong { color: var(--pl-navy); }

/* A .media holding two figures rather than text-and-figure. Same grid, but
   the columns must not stretch to match each other's height or the shorter
   caption pushes its image out of line. */
.media > .media__fig { align-self: start; }

/* The three price/mechanism cards gain a small square image above the copy.
   Square, not 3:2 - three wide crops stacked in a row read as one broken
   panorama. */
.card__fig { margin: -24px -24px 18px; display: block; overflow: hidden; }
.card__fig picture { display: block; aspect-ratio: 16 / 9; background: var(--pl-blue-tint); }
.card__fig img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ============================================================================
   _content - THE SEO / AEO / GEO PAGE DESIGN, following SLCUK.

   MD, 2026-09-09: "the main site can we for aeo, geo, seo pages etc need too
   follow slcuk more with better design".

   The reference is Pages/SLCUK/GEO/Location.aspx, which is a genuinely
   different design language from what these pages had. Ours were a plain
   .section, a prose column and a tinted card. SLCUK's are NUMBERS-FORWARD: a
   gradient hero carrying the page's headline figures, then a light body ground
   with the argument told in stat cards, spotlight panels and mini grids rather
   than in paragraphs.

   That is the right shape for these pages specifically, and not just a nicer
   one. A GEO page has to be worth indexing on 200 near-identical URLs, and
   what makes one of them different from the next is its DATA - the price range
   for the departement, the request count, the local aid body. Design that puts
   the data first is design that makes the difference visible. A prose column
   hides it.

   TWO THINGS ARE TAKEN FROM SLCUK EXACTLY, because they are load-bearing:

     * THE GRADIENT HERO, 135deg navy -> mid -> blue. Not the lander's flat navy
       with a photograph: a content page has no single hero image and 200 of
       them cannot each have one, so the gradient is what gives every page a
       hero without an asset per page.
     * THE YELLOW EYEBROW, #FFD700. SLCUK's content pages use it and nothing
       else on this site does. On a navy gradient it is the only colour with
       enough separation to read as a label rather than as more heading, and
       sky is already spoken for by the H1's second line.

   AND ONE THING IS NOT: SLCUK's stat panels are populated from ten years of
   data. Here SLCFR_Coverage ships empty, SLCFR_Barometre ships Published=0 and
   SLCFR_Reviews ships empty, so every component below renders NOTHING rather
   than a zero when its figure is missing. A stat card reading "0 demandes" is
   worse than an absent stat card, and on a GEO page it is worse than the page
   not existing.
   ============================================================================ */

/* The eyebrow colour is scoped to content heroes rather than added to :root -
   it has exactly one job and putting it in the global token set invites it
   onto a button. */
.chero { --chero-eyebrow: #FFD700; }

.chero {
  position: relative; overflow: hidden;
  background: linear-gradient(135deg, #1f1a5e 0%, #2d2782 40%, var(--pl-blue) 100%);
  color: #fff; padding: 34px 0 38px;
}
@media (min-width: 900px) { .chero { padding: 46px 0 50px; } }
/* A very soft radial lift in the top right, so a flat gradient does not read
   as a flat gradient. pointer-events none because it covers the CTA. */
.chero::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: radial-gradient(120% 90% at 88% 0%, rgba(127, 212, 255, .18), rgba(127, 212, 255, 0) 60%);
}
.chero > .container { position: relative; z-index: 2; }
.chero__eyebrow {
  display: block; font-size: 0.72rem; font-weight: 800; letter-spacing: .15em;
  text-transform: uppercase; color: var(--chero-eyebrow); margin-bottom: 10px;
}
.chero h1 {
  font-size: clamp(1.9rem, 4vw, 2.75rem); font-weight: 900; color: #fff;
  line-height: 1.12; margin: 0 0 12px; letter-spacing: -.02em; text-wrap: balance;
}
/* The second line, same device as the lander's H1 em. */
.chero h1 span { display: block; color: var(--pl-sky); font-size: .62em; font-weight: 800; margin-top: 6px; }
.chero__sub { font-size: 1.125rem; line-height: 1.55; color: #e9ecf7; max-width: 58ch; margin: 0 0 22px; }
.chero__ticks { list-style: none; margin: 18px 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 24px; }
.chero__ticks li { display: flex; align-items: baseline; gap: 8px; color: #dfe4f5; font-size: 1rem; font-weight: 600; }
.chero__ticks .tick-mark { color: var(--pl-sky); }
.chero .btn--cta { box-shadow: 0 6px 16px rgba(0, 0, 0, .28); }

/* Hero stat strip. auto-fit so three, four or two figures all sit correctly -
   the number of stats a GEO page has depends on what data it actually holds. */
.chero__stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 12px; margin-top: 24px;
}
.hstat {
  background: rgba(255, 255, 255, .08); border: 1px solid rgba(255, 255, 255, .18);
  border-radius: 14px; padding: 16px 14px; text-align: center;
}
.hstat b {
  display: block; font-size: clamp(1.6rem, 3.2vw, 2.2rem); font-weight: 900;
  color: #fff; line-height: 1.05; font-variant-numeric: tabular-nums;
}
.hstat i {
  display: block; font-style: normal; font-size: 0.72rem; font-weight: 800;
  letter-spacing: .1em; text-transform: uppercase; color: var(--chero-eyebrow); margin-top: 6px;
}
.hstat span { display: block; font-size: 0.8rem; line-height: 1.4; color: #dbe1f2; margin-top: 4px; }

/* ---- a quiet hero, for the legal and utility pages -----------------------
   A navy gradient on the cookie policy is a gradient nobody asked for, and it
   makes a page that exists to be read look like a page that exists to sell.
   These get the same STRUCTURE - eyebrow, H1, sub, breadcrumb in the same
   place - on white, so the site still feels like one site. */
.chero--quiet {
  background: var(--pl-white); color: var(--pl-ink);
  border-bottom: 1px solid var(--pl-line); padding: 24px 0 26px;
}
.chero--quiet::before { display: none; }
.chero--quiet h1 { color: var(--pl-navy); font-size: clamp(1.6rem, 3vw, 2.15rem); }
.chero--quiet h1 span { color: var(--pl-blue); }
.chero--quiet .chero__eyebrow { color: var(--pl-blue); }
.chero--quiet .chero__sub { color: var(--pl-ink-soft); }
.chero--quiet .breadcrumb { color: var(--pl-ink-soft); padding: 0 0 12px; }
.chero--quiet .breadcrumb a { color: var(--pl-ink-soft); }

/* ---- a white panel inside a navy hero -----------------------------------
   For the guide price on the thank-you pages. White on the navy gradient is
   the highest-contrast object available, which is what makes it read as the
   thing that was delivered rather than as another block of copy.

   It is deliberately NOT the hero itself on /demande-envoyee/ - see the note
   in tools/genpages.py. The action outranks the reward on that page. */
.chero__panel {
  background: #fff; color: var(--pl-ink); border-radius: var(--pl-radius);
  padding: 20px 24px; margin-top: 22px; max-width: 560px;
  box-shadow: 0 0 0 4px rgba(255, 255, 255, .14), 0 18px 44px -16px rgba(0, 0, 0, .5);
}
.chero__panel > :first-child { margin-top: 0; }
.chero__panel > :last-child { margin-bottom: 0; }
/* The guide-price component already styles its own three lines; inside the
   panel it just needs its outer box removing so it does not nest a card in a
   card. */
.chero__panel .guide-price { background: none; border: 0; box-shadow: none; padding: 0; margin: 0; }

/* The body ground. A light grey behind white cards, for the same reason the
   lander has one: a white card on white is not an object. */
.cbody { background: var(--pl-bg); padding: 40px 0 56px; }
@media (min-width: 900px) { .cbody { padding: 52px 0 72px; } }

/* ---- the AEO answer block ----------------------------------------------
   The 40-60 word answer with one figure that a question page leads with. White
   card, thick blue rail, square on the rail side - SLCUK's .geo-answer shape.
   It is the block most likely to be lifted into an AI answer or a featured
   snippet, so it is also the block that must never contain a hedge. */
.answer {
  background: #fff; border-left: 5px solid var(--pl-blue);
  border-radius: 0 16px 16px 0; padding: 22px 26px; margin: 0 0 28px;
  box-shadow: 0 1px 0 rgba(41, 35, 92, .04), 0 10px 30px -14px rgba(41, 35, 92, .22);
}
.answer > :first-child { margin-top: 0; }
.answer > :last-child { margin-bottom: 0; }
.answer p { font-size: 1.1875rem; line-height: 1.55; font-weight: 500; color: var(--pl-navy); }

/* ---- number cards -------------------------------------------------------
   Replaces .key-facts' plain cards. auto-fit again: a page renders the figures
   it has and no placeholders. */
.nums { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin: 0 0 24px; }
.num {
  background: #fff; border: 1px solid var(--pl-line); border-radius: 16px;
  padding: 22px 20px; text-align: center;
}
.num b {
  display: block; font-size: clamp(1.6rem, 3vw, 2.1rem); font-weight: 900;
  color: var(--pl-navy); line-height: 1.05; margin-bottom: 8px;
  font-variant-numeric: tabular-nums; letter-spacing: -.02em;
}
.num span { display: block; font-size: 0.9rem; line-height: 1.45; color: var(--pl-ink-soft); font-weight: 600; }

/* ---- the spotlight pair -------------------------------------------------
   Two big figures side by side with a gradient rail, for the comparison a
   price page is actually about: straight against curved, or this departement
   against the national range. SLCUK's .geo-pair / .geo-sp. */
.spair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; margin: 0 0 24px; }
@media (max-width: 760px) { .spair { grid-template-columns: 1fr; } }
.spot {
  position: relative; overflow: hidden;
  background: linear-gradient(160deg, #fff, #f7f9fd);
  border: 1px solid var(--pl-line); border-radius: 16px; padding: 24px 26px;
}
.spot::after {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 5px;
  background: linear-gradient(180deg, var(--pl-blue), var(--pl-cta));
}
.spot__k {
  display: block; font-size: 0.74rem; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--pl-cta); margin: 0 0 12px;
}
.spot__big { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.spot__big b {
  font-size: clamp(1.9rem, 4vw, 2.6rem); font-weight: 900; color: var(--pl-navy);
  line-height: 1; letter-spacing: -.03em; font-variant-numeric: tabular-nums;
}
.spot__big em { font-style: normal; font-size: 0.95rem; font-weight: 700; color: var(--pl-ink-soft); }
.spot__sub { margin: 16px 0 0; padding: 14px 0 0; border-top: 1px dashed var(--pl-line); }
.spot__sub b { font-size: 1.4rem; font-weight: 900; color: var(--pl-blue); line-height: 1; font-variant-numeric: tabular-nums; }
.spot__sub span { font-size: 0.9rem; color: var(--pl-ink-soft); font-weight: 600; }

/* ---- mini grid ---------------------------------------------------------- */
.mini-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin: 0 0 24px; }
@media (max-width: 900px) { .mini-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .mini-grid { grid-template-columns: 1fr; } }
/* min-width 0 or a long unbroken word in one cell widens the whole grid. */
.mini-grid > * { min-width: 0; }
.mini {
  background: #fff; border: 1px solid var(--pl-line); border-radius: 16px;
  padding: 20px 22px;
}
.mini h3 { font-size: 1.0625rem; font-weight: 800; color: var(--pl-navy); margin: 0 0 7px; }
.mini p { font-size: 0.95rem; line-height: 1.55; color: var(--pl-ink-soft); margin: 0; }

/* ---- the lede ----------------------------------------------------------- */
.lede { font-size: 1.125rem; line-height: 1.6; color: var(--pl-ink-soft); margin: -4px 0 22px; }

/* Content pages put their prose on the light ground, so the prose column gets
   a white card of its own rather than floating on grey. */
.cbody .article__body {
  background: #fff; border: 1px solid var(--pl-line); border-radius: var(--pl-radius);
  padding: 28px 30px 32px;
}
@media (max-width: 599px) { .cbody .article__body { padding: 22px 20px 26px; } }
.cbody .article__body > :first-child { margin-top: 0; }

/* ============================================================================
   _stair-types - "Droit ou tournant", to 01-DROIT-OU-TOURNANT.md
   ============================================================================ */
.stair-types__grid { display: grid; gap: 28px; }
@media (min-width: 760px) { .stair-types__grid { grid-template-columns: 1fr 1fr; gap: 32px; } }
.stair-type-card {
  display: flex; flex-direction: column;
  background: #fff; border: 1px solid var(--pl-line); border-radius: var(--pl-radius);
  overflow: hidden;
}
/* THE FRAME IS 3:2 AND THE FIT IS `contain`, NOT `cover`.
   The two sources have different ratios - the straight shot is 3:2, the
   curved one is SQUARE - and the spec is explicit: "Do not blindly apply
   object-fit: cover and cut off the rail." A neutral ground fills what
   contain leaves, and both cards keep an identical frame height so the pair
   reads as a pair. */
.stair-type-card__media {
  aspect-ratio: 3 / 2; background: var(--pl-blue-tint);
  border-bottom: 1px solid var(--pl-line);
}
.stair-type-card__media picture { display: block; width: 100%; height: 100%; }
.stair-type-card__media img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* column layout so the CTA rows line up across both cards however the copy
   wraps - the spec asks for that and it is what stops the pair looking
   accidental at narrow widths */
.stair-type-card__body { padding: 22px 24px 24px; display: flex; flex-direction: column; flex: 1; }
.stair-type-card__body h3 { font-size: clamp(1.35rem, 2.4vw, 1.6rem); margin: 0 0 14px; }
.stair-type-card__facts { margin: 0 0 20px; }
.stair-type-card__facts > div {
  display: grid; gap: 2px; padding: 10px 0; border-top: 1px solid var(--pl-line);
}
.stair-type-card__facts > div:first-child { border-top: 0; padding-top: 0; }
@media (min-width: 480px) {
  .stair-type-card__facts > div { grid-template-columns: 8.5rem 1fr; gap: 4px 16px; align-items: baseline; }
}
/* weight and spacing, not all-caps microtext - the spec says so, and on a
   55-80 audience uppercase 12px is unreadable anyway */
.stair-type-card__facts dt { font-weight: 800; color: var(--pl-navy); font-size: 1rem; margin: 0; }
.stair-type-card__facts dd { margin: 0; color: var(--pl-ink-soft); font-size: 1rem; line-height: 1.5; }
.stair-type-card__cta { margin: auto 0 0; }
.stair-type-card__cta .btn {
  /* the CTA is long; it must wrap to two lines rather than clip, and the
     arrow must not squeeze the text */
  white-space: normal; text-align: left; min-height: 56px; line-height: 1.3;
}
.stair-type-card__cta .btn .arrow-mark { flex: 0 0 auto; }

/* ---- the drawn arrow ----------------------------------------------------
   The spec asks for a decorative arrow on these two CTAs. U+2192 is NOT in
   Poppins - tools/build_fonts.py --check failed the build on it, exactly
   as it did for the tick U+2713 - so it would have rendered from an OS
   fallback at a weight and size we do not control.
   Drawn instead: two borders on a rotated box, the same technique .tick-mark
   uses, inheriting currentColor so it matches the button label. */
.arrow-mark {
  display: inline-block; box-sizing: border-box;
  width: 0.42em; height: 0.42em;
  border-top: 0.13em solid currentColor;
  border-right: 0.13em solid currentColor;
  transform: rotate(45deg);
  margin-left: 0.15em;
}

/* ============================================================================
   _composition - "La construction en detail", to 02-CONSTRUCTION-EN-DETAIL.md
   ============================================================================ */
.ctabs { margin: 0 0 28px; }
.ctabs__list {
  list-style: none; margin: 0; padding: 0; display: grid;
  grid-template-columns: repeat(3, 1fr); border-bottom: 1px solid var(--pl-line);
}
@media (min-width: 700px) { .ctabs__list { display: flex; gap: 8px; } }
.ctabs__tab {
  display: flex; align-items: center; justify-content: center; text-align: center;
  min-height: 44px; padding: 12px 14px; font-weight: 700; font-size: 1rem;
  color: var(--pl-ink-soft); text-decoration: none;
  border-bottom: 3px solid transparent; margin-bottom: -1px;
}
.ctabs__tab:hover { color: var(--pl-navy); }
/* Selected state carries THREE signals, not colour alone: the blue, the 3px
   underline and the weight. The spec requires it not to depend on colour. */
.ctabs__tab.is-current, .ctabs__tab[aria-selected="true"] {
  color: var(--pl-blue); border-bottom-color: var(--pl-blue); font-weight: 800;
}
.ctabs__tab:focus-visible { outline: 3px solid var(--pl-cta); outline-offset: -3px; }

.cpanel { display: grid; gap: 24px; }
.cpanel[hidden] { display: none !important; }
/* Before JS every panel is visible and this is a stack of three. After JS one
   shows at a time. Both states use the same rule. */
.cpanel + .cpanel { margin-top: 40px; }
@media (min-width: 860px) {
  .cpanel { grid-template-columns: 58fr 42fr; gap: 40px; align-items: start; }
}
.cpanel__media { margin: 0; }
.cpanel__zoom { display: block; border-radius: 12px; overflow: hidden; border: 1px solid var(--pl-line); }
.cpanel__zoom picture { display: block; aspect-ratio: 3 / 2; background: #fff; }
/* contain again: these are diagrams, and a cropped diagram loses a label */
.cpanel__zoom img { width: 100%; height: 100%; object-fit: contain; display: block; }
.cpanel__zoom:focus-visible { outline: 3px solid var(--pl-cta); outline-offset: 2px; }
.cpanel__body h3 { font-size: clamp(1.35rem, 2.6vw, 1.75rem); margin: 0 0 12px; }
.cpanel__body p { font-size: 1.125rem; line-height: 1.6; }
.cpanel__check {
  margin: 20px 0 0; padding: 4px 0 4px 18px; border-left: 3px solid var(--pl-blue);
}
.cpanel__check-k {
  display: block; font-weight: 800; font-size: 0.9rem; color: var(--pl-blue);
  margin-bottom: 6px;
}
.cpanel__check p { margin: 0; font-size: 1.0625rem; }
/* a secondary underlined link, deliberately NOT the pink enquiry CTA */
.cpanel__more { margin: 18px 0 0; }
.cpanel__more a { font-weight: 700; color: var(--pl-blue); text-decoration: underline; }
.composition__note {
  margin: 32px 0 0; font-size: 0.9rem; line-height: 1.6; color: var(--pl-ink-soft);
  max-width: 76ch;
}
/* A 150-250ms opacity settle when a panel appears. Nothing rotates or tilts. */
@media (prefers-reduced-motion: no-preference) {
  .cpanel:not([hidden]) { animation: cpanelIn 190ms var(--pl-ease); }
  @keyframes cpanelIn { from { opacity: 0; } to { opacity: 1; } }
}

/* ============================================================================
   _seatstack - the layered seat animation.

   Three transparent layers on one square canvas, absolutely stacked. Exploded
   is the DEFAULT state, because that is what the panel is explaining and it is
   what renders with no JS. Pressing the button brings them together.

   The offsets are percentages of the frame, so the illustration holds together
   at any size without a media query per breakpoint.
   ============================================================================ */
.seatstack__frame {
  position: relative; aspect-ratio: 1 / 1; max-width: 420px; margin: 0 auto;
}
.seatstack__layer { position: absolute; inset: 0; display: block; }
.seatstack__layer img { width: 100%; height: 100%; object-fit: contain; display: block; }
/* Exploded. The cover lifts furthest because it is the piece a visitor is
   being invited to look under. */
.seatstack__layer--cover { transform: translateY(-13%); }
.seatstack__layer--foam  { transform: translateY(0); }
.seatstack__layer--base  { transform: translateY(13%); }
.seatstack.is-assembled .seatstack__layer--cover,
.seatstack.is-assembled .seatstack__layer--foam,
.seatstack.is-assembled .seatstack__layer--base { transform: translateY(0); }

@media (prefers-reduced-motion: no-preference) {
  .seatstack__layer { transition: transform 560ms var(--pl-ease); }
  /* A small stagger so the pieces settle rather than snap together as one
     block: the base first, the cover last, which is the order they would
     actually be assembled in. */
  .seatstack__layer--base  { transition-delay: 0ms; }
  .seatstack__layer--foam  { transition-delay: 70ms; }
  .seatstack__layer--cover { transition-delay: 140ms; }
}
/* prefers-reduced-motion: the toggle still WORKS, it just arrives instantly.
   Removing the control would remove the information; removing the motion is
   what was asked for. */

.seatstack__legend {
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px 18px;
  margin: 16px 0 0; font-size: 0.95rem; color: var(--pl-ink-soft);
}
.seatstack__key { display: inline-flex; align-items: center; gap: 7px; font-weight: 600; }
/* The swatch keys the legend to the illustration. It is decorative, so it is
   a ::before rather than an element, and the label alone still reads. */
.seatstack__key::before {
  content: ''; width: 13px; height: 13px; border-radius: 4px; flex: 0 0 auto;
  border: 1px solid rgba(41, 35, 92, .18);
}
.seatstack__key--cover::before { background: #C4A484; }
.seatstack__key--foam::before  { background: #2E9BE0; }
.seatstack__key--base::before  { background: #3A3F45; }
.seatstack__actions { margin: 18px 0 0; text-align: center; }
.seatstack__actions[hidden] { display: none !important; }
.seatstack__actions .btn { min-height: 48px; }

/* ----------------------------------------------------------------- _pages --- */
/* .article is a plain block below 900px - no rule needed, and an empty one
   declares nothing. The two-column layout starts at the breakpoint. */
@media (min-width: 900px) {
  .article { display: grid; grid-template-columns: 1fr 320px; gap: 48px; align-items: start; }
}
.article .article__body { max-width: 68ch; }
/* The aside is the only CTA in the prose column, and a 2,000-word guide
   scrolls past it in the first screen. Sticky from the breakpoint where there
   IS a second column - never on mobile, where it would pin a card over the
   text. 74px clears the sticky header. */
@media (min-width: 900px) {
  .article .article__aside { position: sticky; top: 74px; align-self: start; }
}
.article .article__body h2 { margin-top: 1.6em; }
.article .article__body h3 { margin-top: 1.3em; }
.article .article__aside { display: grid; gap: 16px; }
@media (min-width: 900px) { .article .article__aside { position: sticky; top: 88px; } }
.toc ol { padding-left: 20px; margin: 0; }
.toc ol li { margin: 6px 0; }
/* .key-facts is the OLD key-facts strip: three plain cards with a price in
   each. It is kept because SLCFRRender.KeyFacts still emits it, and it is
   restyled here to sit on the light content ground rather than being a second
   design for the same job as .nums. When KeyFacts is rewritten to emit .nums,
   this goes. */
.key-facts { display: grid; gap: 16px; margin: 0 0 24px; }
@media (min-width: 600px) { .key-facts { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } }
.key-facts .card { padding: 22px 20px; text-align: center; border-radius: 16px; }
.key-facts .card .small { font-size: 0.85rem; color: #5b6480; font-weight: 600; }
.key-facts .price { display: block; font-size: clamp(1.5rem, 3vw, 2rem); margin: 6px 0 4px; }

/* .answer is declared once, in _content, following SLCUK's .geo-answer: a
   WHITE card on the light body ground with a BLUE rail. This block had it as a
   tinted panel with a pink rail, and since _content is @used first, this later
   copy silently won - the preflight CSS gate is what surfaced it.
   Pink is the action colour on this site. Using it as the rail on the block a
   visitor is meant to READ makes the answer look like a call to action, and
   leaves the actual CTA competing with it. */
.related ul { list-style: none; padding: 0; }
.related li { border-bottom: 1px solid #E4E8F2; }
.related li a { display: block; padding: 12px 0; font-weight: 600; text-decoration: none; color: #29235C; }

/* .territory-hero .stats is GONE. The GEO hero is .chero now and its stats are
   .chero__stats / .hstat, rendered by Territoire.aspx.vb.StatsFor. These four
   rules survived the hero change and matched nothing - dead CSS that still
   looks authoritative when you grep for "stat" and wonder which rule is in
   force. The .territory-hero class itself is also gone from the markup. */
.recent { list-style: none; padding: 0; }
.recent li { display: flex; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid #E4E8F2; font-size: 0.95rem; }
.recent li span:last-child { color: #5b6480; }
.nearby { display: flex; flex-wrap: wrap; gap: 10px; }
.nearby a {
  display: inline-flex; align-items: center; padding: 8px 14px;
  background: #E9F1F9; color: #29235C; border-radius: 999px;
  font-weight: 600; font-size: 1rem; text-decoration: none;
}

.figure { margin: 0 0 32px; }
.figure figcaption { font-size: 0.95rem; color: #5b6480; margin-top: 8px; }
.bar-chart { display: grid; gap: 10px; }
.bar-chart .row { display: grid; grid-template-columns: 140px 1fr 56px; align-items: center; gap: 12px; font-size: 0.95rem; }
.bar-chart .bar { height: 26px; background: #1F7BC3; border-radius: 6px; }
.bar-chart .val { font-weight: 700; color: #29235C; text-align: right; }
/* ADDED: on a narrow screen a 140px label column leaves ~90px of bar, which is
   unreadable. Stack the label above the bar instead. */
@media (max-width: 600px) {
  .bar-chart .row { grid-template-columns: 1fr 56px; grid-template-areas: "label label" "bar val"; }
  .bar-chart .row .lbl { grid-area: label; }
  .bar-chart .row .bar-wrap { grid-area: bar; }
  .bar-chart .row .val { grid-area: val; }
}

.result { padding: 24px; border-radius: 16px; }
.result--yes { background: #E8F5EC; border-left: 5px solid #1E8E3E; }
.result--maybe { background: #E9F1F9; border-left: 5px solid #1F7BC3; }
.result--no { background: #FFF4E5; border-left: 5px solid #B54708; }

/* ============================================================================
   ADDED BY THE BUILD - components the original partials did not cover
   ============================================================================ */

/* Launch guard banner. Rendered by SLCFR.master when SLCFRConfig.IsReadyForLaunch
   returns anything, and ONLY outside production. Loud on purpose: an unset GTM
   container or Twilio SID must not be discoverable only by someone reading a
   config file. */
.launch-guard {
  background: #4A1D1D; color: #FFE9E9; padding: 14px 20px; font-size: 0.95rem;
  border-bottom: 3px solid #C62828;
}
.launch-guard b { color: #fff; }
.launch-guard ul { margin: 8px 0 0; padding-left: 20px; }
.launch-guard li { margin: 2px 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.85rem; }

/* TODO:content strip. On a page whose Article.TodoContent is true. The page is
   already noindex and out of the sitemap; this is for whoever opens it. */
.todo-strip {
  background: #FFF4E5; border-left: 5px solid #B54708; border-radius: 16px;
  padding: 16px 20px; margin: 0 0 28px; font-size: 1rem; color: #6b3a05;
}
.todo-strip b { display: block; margin-bottom: 4px; color: #B54708; }
.todo-strip p { max-width: none; margin: 0 0 6px; }
.todo-strip p:last-child { margin: 0; }

/* Empty states - /avis/ with no verified reviews, a barometre page with nothing
   published. Says what is missing and why, rather than rendering a zero. */
.empty-state {
  border: 2px dashed #E4E8F2; border-radius: 16px; padding: 32px 24px;
  text-align: center; color: #5b6480;
}
.empty-state h3 { color: #29235C; margin-bottom: 8px; }
.empty-state p { margin-inline: auto; }

/* Verified review card. The attribution line is not optional decoration - it is
   what makes the review checkable, so it is part of the component. */
.review { border: 1px solid #E4E8F2; border-radius: 16px; padding: 20px 24px; background: #fff; }
.review__stars { color: #B54708; letter-spacing: 2px; font-size: 1.1rem; }
.review__title { font-weight: 700; color: #29235C; margin: 8px 0 4px; }
.review__body { margin: 0 0 12px; }
.review__attr { font-size: 0.9rem; color: #5b6480; }
.review-method {
  font-size: 0.9rem; color: #5b6480; background: #E9F1F9;
  border-radius: 16px; padding: 12px 16px; margin-bottom: 24px;
}

/* Aide page "what this does not cover" block. Every aide page has one, and it
   comes before the benefits, not after. */
.excludes { background: #FFF4E5; border-left: 5px solid #B54708; border-radius: 0 16px 16px 0; padding: 20px 24px; margin: 24px 0; }
.excludes h3 { color: #B54708; margin-bottom: 8px; }
.excludes ul { margin: 0; padding-left: 20px; }
.excludes li { margin: 6px 0; }

/* Tick list, used across guides and aides. */
.tick-list { list-style: none; padding: 0; margin: 0 0 1em; }
.tick-list li { position: relative; padding-left: 1.9em; margin-bottom: 0.5em; }
.tick-list li::before {
  content: ''; position: absolute; left: 0.15em; top: 0.55em;
  box-sizing: border-box; width: 0.7em; height: 0.38em;
  border-left: 0.16em solid #1E8E3E;
  border-bottom: 0.16em solid #1E8E3E;
  transform: rotate(-45deg);
}
/* ---- the drawn checkmark -----------------------------------------------
   U+2713 is not in Poppins - see tools/build_fonts.py --check. Rather than
   ship one glyph out of a fallback font at a weight and size we do not
   control, the tick is two borders on a rotated box. Set the size with
   width/height and the weight with border-width; the mark is positioned from
   its own top-left, so callers place it like any other box.

   The rotation means the box's height reads as the tick's WIDTH, hence the
   3:2 ratio rather than a square. */
.tick-mark {
  display: inline-block; box-sizing: border-box;
  width: 0.62em; height: 0.34em;
  border-left: 0.14em solid currentColor;
  border-bottom: 0.14em solid currentColor;
  transform: rotate(-45deg);
  /* Optical centring: a rotated mark's bounding box is not its visual centre. */
  margin-bottom: 0.14em;
}


/* Rate-check pointer on aide pages: which body holds the current figure. Framed
   as help for the visitor, because it is. */
.rate-check {
  font-size: 0.95rem; color: #29235C; background: #E9F1F9;
  border-radius: 16px; padding: 14px 18px; margin: 20px 0;
}
.rate-check b { display: block; margin-bottom: 4px; }

/* Inline stepper CTA, dropped into a guide after the second H2 and at the end
   (CLAUDE.md phase 2). */
.inline-cta {
  background: #E9F1F9; border-radius: 16px; padding: 24px; margin: 32px 0;
  display: grid; gap: 12px; justify-items: start;
}
.inline-cta h3 { margin: 0; }
.inline-cta p { margin: 0; }

/* GEO hub region list. */
.region-grid { display: grid; gap: 12px; }
@media (min-width: 600px) { .region-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 900px) { .region-grid { grid-template-columns: repeat(3, 1fr); } }
.region-grid a {
  display: block; padding: 16px 20px; border: 1px solid #E4E8F2; border-radius: 16px;
  text-decoration: none; color: #29235C; font-weight: 600; background: #fff;
}
.region-grid a:hover { border-color: #1F7BC3; }
.region-grid a span { display: block; font-weight: 400; font-size: 0.9rem; color: #5b6480; margin-top: 2px; }

/* Simulator, on /aides/simulateur/. Radios styled as full-width tap targets:
   the audience taps, it does not click a 13px radio. */
.sim-q { margin-bottom: 32px; }
.sim-q > p { font-weight: 700; color: #29235C; font-size: 1.15rem; margin-bottom: 4px; max-width: none; }
.sim-q .sim-help { font-size: 0.95rem; color: #5b6480; margin-bottom: 12px; }
.sim-opts { display: grid; gap: 10px; }
.sim-opts label {
  display: flex; align-items: center; gap: 12px; min-height: 56px;
  padding: 12px 18px; border: 2px solid #E4E8F2; border-radius: 16px;
  background: #fff; cursor: pointer; font-weight: 600; color: #29235C;
}
.sim-opts label:hover { border-color: #1F7BC3; }
.sim-opts input { width: 24px; height: 24px; accent-color: #1F7BC3; flex: 0 0 auto; }
.sim-opts input:checked + span { color: #29235C; }

/* OTP entry, on /verifier-code/. */
/* ---- the focus pages: /ok/{token}/ and /verifier-code/ -------------------
   One object, centred, nothing else. These are the two pages where the visitor
   has already decided and is completing a step, so there is no argument left
   to make and no CTA to compete with.

   The grey ground plus a white card is what makes the single thing on the page
   read as an object. Before this they were centred text on bare white - the
   only two pages left that looked unfinished next to the rest of the site. */
.focus-page { background: var(--pl-bg); min-height: 60vh; padding: 48px 20px 72px; }
.focus-card {
  max-width: 560px; margin: 0 auto; background: #fff;
  border: 1px solid var(--pl-line); border-radius: var(--pl-radius);
  padding: 36px 32px; text-align: center;
  box-shadow: 0 1px 0 rgba(41, 35, 92, .04), 0 18px 44px -18px rgba(41, 35, 92, .26);
}
@media (max-width: 519px) { .focus-card { padding: 28px 20px; } }
.focus-card > :first-child { margin-top: 0; }
.focus-card > :last-child { margin-bottom: 0; }
.focus-card h1 { font-size: clamp(1.45rem, 3.4vw, 1.9rem); }
/* The code input is centred by .otp-wrap already; inside the card it also
   needs the label centred, which text-align on the card does not reach
   because .field label is display:block with its own alignment. */
.focus-card .field label { text-align: center; }

/* padding comes from .focus-card now - .otp-wrap adding its own on top gave
   the verification page 76px of dead space above the heading. */
.otp-wrap { max-width: 480px; margin: 0 auto; text-align: center; }
.otp-wrap .input--code { margin: 0 auto 8px; }
.otp-resend { margin-top: 20px; font-size: 0.95rem; color: #5b6480; }

/* Print: the legal pages and the aide pages get printed and taken to
   appointments. Drop the chrome and the CTAs. */
@media print {
  .site-header, .site-footer, .actionbar, .inline-cta, .cta-band, .launch-guard { display: none !important; }
  body { font-size: 11pt; }
  a { color: #000; text-decoration: underline; }
  .article { display: block; }
}

/* Active nav item. Driven off [aria-current], set by Header.ascx.vb, so the
   accessible state and the visible state cannot drift apart. */
.site-header nav a[aria-current="page"] {
  color: #1F7BC3;
  text-decoration: underline;
  text-decoration-thickness: 3px;
  text-underline-offset: 6px;
}

/* Breadcrumb current page is not a link, so it needs its own weight. */
.breadcrumb li[aria-current="page"] { color: #29235C; font-weight: 600; }

/* blockquote, used for the verbatim consent text on the privacy page. */
blockquote { margin: 20px 0; }
blockquote p { max-width: none; }

/* Brand lockup: the mark as inline SVG, the wordmark as real text.
   The wordmark is text and not part of the SVG because an SVG loaded through
   <img> is isolated and cannot reach the page's webfonts - a <text> element in
   a lockup file silently loses Poppins while every heading around
   it keeps it. See SLCFRRender.Logo. */
/* The .logo-lockup rules are gone with the placeholder they styled: the mark
   was an inline SVG and the wordmark was HTML text, because no logo had been
   supplied. The real one arrived 2026-09-10 as a raster, so there is nothing
   left to lay out - just an <img> the header sizes. */
.site-header .logo img, .lander__top .logo img { height: 34px; width: auto; display: block; }
.site-footer .logo img { height: 40px; width: auto; display: block; }

/* ============================================================================
   THE STAIR DIAGRAM TILES - removed 2026-09-09, and this note is the point.

   This block used to size the step-1 tiles for the OLD three-up vertical
   layout: a 76px icon column beside the text. It read

       .opt-grid[data-field="stairs"] .opt { grid-template-columns: 76px 1fr auto; ... }

   and it was a REAL BUG the moment step 1 became two tiles side by side.
   Specificity 0,3,0 against .opt-grid--two .opt at 0,2,0, and it sat 560 lines
   LATER in the file, so it won twice over. The two-tile layout set
   grid-template-columns: 1fr and this quietly put the icon back in a column -
   which is exactly what the cramped, overlapping tiles in the screenshot were.

   The icon sizing it used to do now lives in the .opt-grid--two block, where
   the layout that needs it is defined. One home per rule. Same failure as the
   duplicated .hero and .progress selectors: two rules that each looked correct
   on their own.
   ============================================================================ */

/* Selection has to change more than a border - checklist section D: "Tap/hover
   state must change at least two properties, one of them a ring." The tile
   already gains a ring and a filled check; the diagram's stroke follows
   currentColor, so it darkens to navy at the same moment. */
.opt[aria-pressed="true"] .opt__icon { color: #29235C; }
@media (hover: hover) {
  /* Behind hover:hover or a phone keeps a sticky false selection after a tap. */
  .opt:hover .opt__icon { color: #29235C; }
}

/* ============================================================================
   THE GUIDE PRICE - thank-you pages only.

   No price appears during the form (MD instruction; PPC-LANDER-CHECKLIST.md
   section B). This is the first figure a visitor in the funnel sees, so it is
   given real weight - and the qualifier under it is part of the component, not
   optional copy, because a range presented without "this is not a quote" is a
   price claim.
   ============================================================================ */
.guide-price {
  display: grid; gap: 6px;
  background: var(--pl-blue-tint, #E9F1F9);
  border-left: 5px solid var(--pl-cta, #D5317B);
  border-radius: 0 16px 16px 0;
  padding: 22px 26px; margin: 0 0 28px;
}
.guide-price__k { font-weight: 700; font-size: .95rem; color: #29235C; }
.guide-price__v {
  font-family: 'Poppins', Arial, sans-serif;
  font-weight: 800; font-size: clamp(1.9rem, 5vw, 2.7rem);
  letter-spacing: -.03em; color: #29235C;
  font-variant-numeric: tabular-nums;
}
.guide-price__n { font-size: .92rem; color: #5b6480; max-width: 62ch; }

/* ============================================================================
   THE FRENCH ADDRESS BLOCK + INLINE VALIDATION STATES

   The client requires the street line, so the contact step gained Adresse,
   Complement d'adresse and Ville. Format notes are in Stepper.ascx; this is
   the presentation.
   ============================================================================ */

/* "(facultatif)" - the ONLY optional field on the step, so it says so.
   Marking the optional one is better than marking five required ones: less
   ink, and nobody has to work out what the absence of a mark means. */
.opt-tag { font-weight: 400; color: #5b6480; font-size: .9rem; }

/* The postcode recap above Ville. A French form puts code postal and ville on
   one line; ours captured the postcode a step earlier, so it is shown back
   with a way to change it rather than asked for twice. */
.cp-recap {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  margin: 0 0 8px; font-size: .92rem; color: #5b6480; max-width: none;
}
.cp-recap b { color: #29235C; font-variant-numeric: tabular-nums; letter-spacing: .04em; }
.cp-edit {
  background: none; border: 0; padding: 0; font: inherit;
  color: #1F7BC3; text-decoration: underline; text-underline-offset: 2px;
  cursor: pointer;
  /* 44px of vertical hit area without moving the baseline - this sits in a
     dense block and the audience is 55-80. */
  min-height: 44px; display: inline-flex; align-items: center;
}
.cp-edit:hover { color: #D5317B; }

/* ---------------------------------------------------------------------------
   VALIDATION STATES

   Inline validation runs on `change`; typing only ever clears. See
   js/palier-validate.js for why not `blur`.

   The VALID state changes TWO things - border colour AND a tick - because a
   border-colour-only state is invisible to a good share of this audience, and
   the checklist makes the same point about tap states.
   --------------------------------------------------------------------------- */
.field { position: relative; }

.field.is-valid .input {
  border-color: #1E8E3E;
  /* Room for the tick, so a long value never runs under it. */
  padding-right: 44px;
}
.field.is-valid::after {
  content: '';
  box-sizing: border-box;
  width: 13px; height: 7px;
  border-left: 3px solid #1E8E3E;
  border-bottom: 3px solid #1E8E3E;
  transform: rotate(-45deg);
  position: absolute;
  right: 16px;
  /* Sits on the input, not the label or the hint: the label is one line of
     ~28px and the input is 56px tall below it. */
  top: calc(1.7em + 18px);
  /* The mark takes its colour from the border pair above, not from color:
     there is no glyph left to colour. */
  font-weight: 700;
  pointer-events: none;
}
/* The code inputs are centred and narrow; a tick at the right edge of the
   FIELD would float in empty space well away from them. */
.field.is-valid .input--code { padding-right: 18px; }
.field.is-valid:has(.input--code)::after { display: none; }

.field.is-invalid .input { border-color: #C62828; }
.field.is-invalid .input:focus { outline-color: rgba(198, 40, 40, .25); }

/* role="alert" is not set on these - they are validation hints the visitor
   reads when they look, not announcements worth interrupting for on every
   keystroke. The invalid input is described by them instead. */
.field.is-invalid .error { display: block; }
/* GUARD, per the checklist: "Restate [hidden] on anything given a display. An
   author `display` beats the UA stylesheet, so a styled error chip un-hides
   every empty error slot." Nothing sets [hidden] today; this makes sure that
   adding it later still works. */
.field .error[hidden] { display: none !important; }
