/* ═══════════════════════════════════════════════════════════════════════
   SBK Techno — 2025 design system
   Palette held from the brand: #2255bc · #19408e · #001025 · #00053e
   · #040404 — one single dark ground: the whole site lives on --s0 #001025.
   Dark-dominant, editorial scale contrast, bento composition.
   ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* UA-native pieces (scrollbars, form controls) pin to the one dark
     ground — head.php already declares the matching theme-color. */
  color-scheme: dark;

  /* Identity blue. #2255bc is 2.81:1 on the page ground — FILLS AND BORDERS
     ONLY, never text. Anything blue that must be READ uses --blue-text.
     Blue FILLS carry a --line-ctrl rim: the fill edge is brand colour, but
     the boundary it draws must clear WCAG 1.4.11's 3:1 non-text minimum —
     so identification rides the rim (and the 6.79:1 mark inside), never
     the fill's own 2.81:1 edge. */
  --blue:      #2255bc;
  --blue-lt:   #4d7ff0;
  --blue-text: #6E9BF0;   /* 6.91:1 on --s0: links, eyebrows, accents */

  /* Dark surface ramp — the whole site lives on these */
  --s0: #001025;   /* deepest  */
  --s1: #000a1e;
  --s2: #04123a;

  --line-d: rgba(255,255,255,.22);        /* decorative hairline */
  --line-ctrl: rgba(255,255,255,.42);     /* interactive boundary on the dark grounds (>=3:1); NOT a boundary for the blue fills */

  --fg:      #fff;
  --fg-2:    rgba(255,255,255,.72);
  --fg-3:    rgba(255,255,255,.52);

  /* Status grammar — one owner per meaning (color spec). */
  --ok:   #4FB286;
  --warn: #D9A03F;
  --err:  #E0655C;

  /* Composed surfaces: the chrome's tints over the one dark ground. */
  --on-blue:  #fff;                 /* text on the blue fills */
  /* Text on the LIGHT blue hover fill (--blue-lt) — a light fill wants a
     dark label. */
  --on-blue-lt: #001025;
  --hdr-bg:   rgba(0,16,37,.7);
  --hdr-stuck: rgba(0,16,37,.94);
  --veil-bg:  rgba(0,5,30,.6);
  --ghost-bg: rgba(255,255,255,.06);
  --ghost-bg-hi: rgba(255,255,255,.14);
  --wash:     rgba(255,255,255,.1);
  --wash-hi:  rgba(255,255,255,.36);  /* hover rim — dual-sided >=3:1; the binding side is the ghost/pause fill over the hero mesh at 3.06:1 (.35 reads 2.97 — do not lower) */
  /* The chat's two speech fills: opaque mixes over --s1, so their text
     contrast is fixed instead of stacking on whatever the panel paints.
     --fg reads 15.82:1 on the team fill and 15.75:1 on the visitor's;
     --fg-2 reads 8.80/8.69:1, --fg-3 5.27/5.17:1, --blue-text 5.73/5.71:1. */
  --bubble:     color-mix(in srgb, var(--fg) 10%, var(--s1));
  --bubble-you: color-mix(in srgb, var(--blue) 30%, var(--s1));
  /* Tile hover: the accent edge and the lift. The dark surface already
     reads as a glow, so the accent edge carries the hover and the lift
     lands on a deep ground-tinted shadow. */
  --hover-edge: rgba(77,127,240,.5);
  --hover-lift: 0 22px 56px rgba(0,5,30,.5);
  /* Static surface elevation (cards, panels, popups) — the same treatment the
     lift uses: a floating surface must read as lifted off the ground. */
  --elev: 0 24px 70px rgba(0,5,30,.5);
  --grid-line: rgba(255,255,255,.022);
  --panel:      rgba(4,18,58,.6);    /* glass panels over the mesh */
  --panel-done: rgba(4,26,44,.6);
  /* Watermark opacity — one faintness: the page's fixed mark hangs at it
     (body::before is the one consumer; the old footer's mark is gone). */
  --mark-op: .13;
  --scroll-thumb: rgba(255,255,255,.2);
  --scroll-thumb-hi: rgba(255,255,255,.32);
  --num-grad: linear-gradient(180deg,#fff,rgba(255,255,255,.55));
  --rim:      rgba(110,155,240,.45);
  /* The hero mesh field — soft glows behind the scene, tuned for the one
     dark ground. */
  --mesh:
    radial-gradient(120% 70% at 50% -12%, #0a2040 0%, transparent 62%),
    radial-gradient(90% 60% at 92% 8%, #091c38 0%, transparent 58%),
    linear-gradient(180deg, #001025 0%, #03102a 55%, #001025 100%);

  /* Safe areas + header height as tokens, so no rule hardcodes them. */
  --hdr-h: 72px;
  --sa-r: env(safe-area-inset-right, 0px);
  --sa-b: env(safe-area-inset-bottom, 0px);

  /* The fallback order is METRIC-led, not generic-led: Chrome on Linux
     resolves the system-ui generic to DejaVu Sans, whose "Par le formulaire"
     measures 121px at 13px and 140px at 15px — wider than the directory
     tile's text box at every band (101/116/116/123px at 360/390/421/435),
     so the label clipped its tile edge by 15.6px at 360. "Helvetica Neue"
     and Arial are metric-compatible with the shipped face and fit every
     band; they precede the generic, while Android's system-ui remains the
     last resort. Do not move a generic ahead of Arial. */
  --font: "degular", -apple-system, BlinkMacSystemFont, "Segoe UI", Inter,
          "Helvetica Neue", Arial, system-ui, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;

  /* Editorial type scale — extreme contrast between display and utility.
     Display is capped so the headline always fits one screen: the old 6.75rem
     ceiling reached 108px at wide viewports and pushed the hero to 1313px,
     taller than the viewport. The fluid steps are CLAMPS and this block
     owns them; every lead prose voice (the hero sub, a section head's
     lead, the legal page's lead) reads --t-lead, one voice — the two
     deliberate exceptions outside the --t-* owners are the numerals'
     own step (.num .meter — "the stats their own scale", at its site) and a
     document's own step (.legal__sec h2, at its own comment). The scattered micro-sizes (the mono micro-labels,
     the heading voices, the × glyphs) stay as-is, un-named. */
  --t-d1: clamp(2.75rem, 1.4rem + 4.2vw, 5.5rem);
  --t-d2: clamp(1.9rem, 1.2rem + 2.6vw, 3.25rem);
  --t-d3: clamp(1.4rem, 1.15rem + 1.1vw, 1.95rem);
  --t-lead: clamp(1.0625rem, 1rem + .4vw, 1.25rem);
  --t-b:  clamp(1rem, .96rem + .18vw, 1.075rem);
  --t-s:  .9375rem;
  --t-xs: .8125rem;

  /* The measure grows with the glass: 1360px below, but wide viewports are
     not left with two empty margins — the cap scales up to 1880px. */
  --wrap: clamp(1360px, 84vw, 1880px);
  --pad:  clamp(1.25rem, 4.2vw, 3.5rem);
  --gap:  clamp(1rem, 1.6vw, 1.4rem);
  --sec:  clamp(2.75rem, 1.5rem + 4vw, 5rem);   /* dense: no empty scroll zones */
  /* ── « the cut standard » — one geometry: the 45° chamfer ───────────
     Every rounded corner is dead. One cut language, three scales; the
     contact and expertise marks are bare 24-grid ink (the old 48-box gem
     tiles are gone), the fab alone keeps the gem octagon, and prose stays
     soft (see the quiet zones where the fills clip). */
  --cut-s: 8px;    /* fields, toggles, burger, small chat controls, inputs */
  --cut:   12px;   /* buttons, keys, cckey rows, chips, the fab's peers */
  --cut-l: 16px;   /* cards, panels, the form, the callcard, the chat card */

  /* The fill shapes: the four 45° cuts at each scale's leg, one polygon
     per scale so the sheen's clip-path:inherit and every rim share the
     same bytes. */
  --cut-s-fill: polygon(var(--cut-s) 0, calc(100% - var(--cut-s)) 0, 100% var(--cut-s), 100% calc(100% - var(--cut-s)), calc(100% - var(--cut-s)) 100%, var(--cut-s) 100%, 0 calc(100% - var(--cut-s)), 0 var(--cut-s));
  --cut-fill:   polygon(var(--cut) 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, var(--cut) 100%, 0 calc(100% - var(--cut)), 0 var(--cut));
  --cut-l-fill: polygon(var(--cut-l) 0, calc(100% - var(--cut-l)) 0, 100% var(--cut-l), 100% calc(100% - var(--cut-l)), calc(100% - var(--cut-l)) 100%, var(--cut-l) 100%, 0 calc(100% - var(--cut-l)), 0 var(--cut-l));
  /* The speech tail: the same 8px 45° cut, used ONCE per bubble at the
     corner nearest the speaker — the team's top-left, the visitor's
     top-right. The other corners stay square. */
  --cut-speech-team: polygon(var(--cut-s) 0, 100% 0, 100% 100%, 0 100%, 0 var(--cut-s));
  --cut-speech-you: polygon(0 0, calc(100% - var(--cut-s)) 0, 100% var(--cut-s), 100% 100%, 0 100%);
  /* The fab is the switchboard's answer key: the gem tiles' own octagon
     proportion — leg 14 on the 48px box (29% of the side) — whatever the
     key's width. */
  --cut-gem-fill: polygon(14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px), calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px);
  /* The skip's banner cuts its bottom edge only (it hangs from the top). */
  --cut-skip-fill: polygon(0 0, 100% 0, 100% calc(100% - var(--cut-s)), calc(100% - var(--cut-s)) 100%, var(--cut-s) 100%, 0 calc(100% - var(--cut-s)));
  /* The plan stamp's two opposite corners — the documented two-corner
     variant of the one cut language, named so no literal can drift beside
     the scale. The clipped border was the old 7px literal's price; the
     stamp carries no border and lets the tinted fill be its whole edge. */
  --cut-stamp: 8px;
  --cut-stamp-fill: polygon(var(--cut-stamp) 0, 100% 0, 100% calc(100% - var(--cut-stamp)), calc(100% - var(--cut-stamp)) 100%, 0 100%, 0 var(--cut-stamp));
  /* The skip's focus twin: the same 1px/4px dual boundary, its bottom
     corners cut at --cut-s and its top edge flush with the viewport. */
  --cut-ring-skip-focus: polygon(1px 1px, calc(100% - 1px) 1px, calc(100% - 1px) calc(100% - 8.414px), calc(100% - 8.414px) calc(100% - 1px), 8.414px calc(100% - 1px), 1px calc(100% - 8.414px), 4px calc(100% - 9.656px), 9.656px calc(100% - 4px), calc(100% - 9.656px) calc(100% - 4px), calc(100% - 4px) calc(100% - 9.656px), calc(100% - 4px) 4px, 4px 4px);

  /* The rim shapes — the DUAL-CLIP ring: one polygon holding an outer
     boundary (inset d-out) traced clockwise and an inner boundary (inset
     d-in) traced counter-clockwise; the nonzero fill rule leaves the
     middle empty, so the band is a hairline that follows the chamfer.
     THE MATH: a boundary inset by d keeps its cut edge parallel to the
     outer one only when its leg is L + 0.414·d (0.414 = √2 − 1) — at the
     naive leg the diagonal renders at 0.71× the band. Rest rims: band 1px
     at the edge. Focus rings: the same pseudo re-parameterized — a 3px
     band offset 1px inside the edge (outer boundary at 1px, inner at 4px;
     the thickness clears WCAG 2.4.13's 2px-perimeter area at every cut
     scale, where clipped corners shaved the old 2px band to 0.91-0.98),
     contrast ≥ 3:1 on every fill, morphed by a 160ms one-shot beat (the
     site's motion law). The blue fills (.btn--fill, .fab, .chat__saybtn)
     take a white band instead: --blue-lt reads 1.813:1 on --blue and
     1.000:1 on its own light hover fill, while white reads 6.788:1 and
     3.744:1. */
  --cut-ring-s: polygon(var(--cut-s) 0, calc(100% - var(--cut-s)) 0, 100% var(--cut-s), 100% calc(100% - var(--cut-s)), calc(100% - var(--cut-s)) 100%, var(--cut-s) 100%, 0 calc(100% - var(--cut-s)), 0 var(--cut-s), calc(var(--cut-s) + .414px) 1px, 1px calc(var(--cut-s) + .414px), 1px calc(100% - var(--cut-s) - .414px), calc(var(--cut-s) + .414px) calc(100% - 1px), calc(100% - var(--cut-s) - .414px) calc(100% - 1px), calc(100% - 1px) calc(100% - var(--cut-s) - .414px), calc(100% - 1px) calc(var(--cut-s) + .414px), calc(100% - var(--cut-s) - .414px) 1px);
  --cut-ring-s-focus: polygon(calc(var(--cut-s) + .414px) 1px, calc(100% - var(--cut-s) - .414px) 1px, calc(100% - 1px) calc(var(--cut-s) + .414px), calc(100% - 1px) calc(100% - var(--cut-s) - .414px), calc(100% - var(--cut-s) - .414px) calc(100% - 1px), calc(var(--cut-s) + .414px) calc(100% - 1px), 1px calc(100% - var(--cut-s) - .414px), 1px calc(var(--cut-s) + .414px), calc(var(--cut-s) + 1.656px) 4px, 4px calc(var(--cut-s) + 1.656px), 4px calc(100% - var(--cut-s) - 1.656px), calc(var(--cut-s) + 1.656px) calc(100% - 4px), calc(100% - var(--cut-s) - 1.656px) calc(100% - 4px), calc(100% - 4px) calc(100% - var(--cut-s) - 1.656px), calc(100% - 4px) calc(var(--cut-s) + 1.656px), calc(100% - var(--cut-s) - 1.656px) 4px);
  --cut-ring: polygon(var(--cut) 0, calc(100% - var(--cut)) 0, 100% var(--cut), 100% calc(100% - var(--cut)), calc(100% - var(--cut)) 100%, var(--cut) 100%, 0 calc(100% - var(--cut)), 0 var(--cut), calc(var(--cut) + .414px) 1px, 1px calc(var(--cut) + .414px), 1px calc(100% - var(--cut) - .414px), calc(var(--cut) + .414px) calc(100% - 1px), calc(100% - var(--cut) - .414px) calc(100% - 1px), calc(100% - 1px) calc(100% - var(--cut) - .414px), calc(100% - 1px) calc(var(--cut) + .414px), calc(100% - var(--cut) - .414px) 1px);
  --cut-ring-focus: polygon(calc(var(--cut) + .414px) 1px, calc(100% - var(--cut) - .414px) 1px, calc(100% - 1px) calc(var(--cut) + .414px), calc(100% - 1px) calc(100% - var(--cut) - .414px), calc(100% - var(--cut) - .414px) calc(100% - 1px), calc(var(--cut) + .414px) calc(100% - 1px), 1px calc(100% - var(--cut) - .414px), 1px calc(var(--cut) + .414px), calc(var(--cut) + 1.656px) 4px, 4px calc(var(--cut) + 1.656px), 4px calc(100% - var(--cut) - 1.656px), calc(var(--cut) + 1.656px) calc(100% - 4px), calc(100% - var(--cut) - 1.656px) calc(100% - 4px), calc(100% - 4px) calc(100% - var(--cut) - 1.656px), calc(100% - 4px) calc(var(--cut) + 1.656px), calc(100% - var(--cut) - 1.656px) 4px);
  --cut-ring-l: polygon(var(--cut-l) 0, calc(100% - var(--cut-l)) 0, 100% var(--cut-l), 100% calc(100% - var(--cut-l)), calc(100% - var(--cut-l)) 100%, var(--cut-l) 100%, 0 calc(100% - var(--cut-l)), 0 var(--cut-l), calc(var(--cut-l) + .414px) 1px, 1px calc(var(--cut-l) + .414px), 1px calc(100% - var(--cut-l) - .414px), calc(var(--cut-l) + .414px) calc(100% - 1px), calc(100% - var(--cut-l) - .414px) calc(100% - 1px), calc(100% - 1px) calc(100% - var(--cut-l) - .414px), calc(100% - 1px) calc(var(--cut-l) + .414px), calc(100% - var(--cut-l) - .414px) 1px);
  /* No --cut-ring-l-focus: no --cut-l surface is itself focusable — the
     cards' interactive children (buttons, fields, links) carry their own
     cut classes and their own rings. */
  --cut-ring-gem: polygon(14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px), calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px, 14.414px 1px, 1px 14.414px, 1px calc(100% - 14.414px), 14.414px calc(100% - 1px), calc(100% - 14.414px) calc(100% - 1px), calc(100% - 1px) calc(100% - 14.414px), calc(100% - 1px) 14.414px, calc(100% - 14.414px) 1px);
  --cut-ring-gem-focus: polygon(14.414px 1px, calc(100% - 14.414px) 1px, calc(100% - 1px) 14.414px, calc(100% - 1px) calc(100% - 14.414px), calc(100% - 14.414px) calc(100% - 1px), 14.414px calc(100% - 1px), 1px calc(100% - 14.414px), 1px 14.414px, 15.656px 4px, 4px 15.656px, 4px calc(100% - 15.656px), 15.656px calc(100% - 4px), calc(100% - 15.656px) calc(100% - 4px), calc(100% - 4px) calc(100% - 15.656px), calc(100% - 4px) 15.656px, calc(100% - 15.656px) 4px);
  /* WCAG 2.5.5 — the minimum every standard interactive target meets; the
     documented exceptions: the hero's 30px pause knob, the callcard's ~32px
     hang control, and the footer's 24px text links and the facts rail's
     inline TV link (~24px). */
  --tap: 44px;

  /* Motion — one easing vocabulary for the whole site.
     out    = decisive entry (things arriving)
     in-out = symmetric (things that move both ways, e.g. drawers)
     spring = a touch of overshoot reserved for direct manipulation */
  --ease:     cubic-bezier(.22,.68,.36,1);
  --ease-out: cubic-bezier(.16,1,.3,1);
  --ease-emphasis: cubic-bezier(.2,0,0,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);

  /* « la lumière » — the pointer field's durations and bounds. press = the
     key bottoms out; in = arriving on a state (hover); out = settling back
     (springy release). Bounds: drift ≤ --mag-clamp, lean ≤ the tilt caps.
     Tilt naming follows the lean's AXIS: --tilt-max-x caps the lean along x
     (the --ry half), --tilt-max-y the lean along y (the --rx half), so the
     header's .tgl can take the full ±7°rx/±5°ry. Per-class strengths
     override
     --mag-x/--mag-y (pull) and --tilt-x/--tilt-y (lean) where a surface needs
     its own weight — see .btn--ghost/.btn--line, .fab, .plan. */
  --dur-press: 90ms;
  --dur-in: 180ms;
  --dur-out: 380ms;
  /* The one depth the physical objects lean into (perspective). */
  --persp: 900px;
  --mag-clamp: 12px;
  --tilt-max-x: 5deg;
  --tilt-max-y: 7deg;
  --mag-x: .16;
  --mag-y: .22;
  --tilt-x: var(--tilt-max-x);
  --tilt-y: var(--tilt-max-y);
}

/* ─────────────────────────────────────────────────────────── reset ── */
*,*::before,*::after { box-sizing: border-box; }

/* The hidden attribute is the contract every toggle rides (the chat
   panel, the log, the composer, the forms). An author display rule
   beats the browser's own [hidden]{display:none} — the chat composer
   rendered in a fresh panel precisely because .chat__say{display:flex}
   outranked it — so the guard belongs here, once, at the root. */
[hidden] { display:none !important; }

html {
  scroll-behavior:smooth;
  scroll-padding-top:calc(var(--hdr-h) + 20px);
  -webkit-text-size-adjust:100%;
  /* No scroll-snap. `scroll-snap-type: y proximity` proved undependable: current
     Chrome drops the `proximity` keyword and keeps just `y`, i.e. MANDATORY
     snapping — the page could only come to rest on section tops, so scrolling
     read as stuck/forced. Free scrolling behaves the same everywhere. */
  overflow-x:hidden;             /* clip here — body would become the scroller */
}

body {
  margin: 0;
  background: var(--s0);
  color: var(--fg);
  font-family: var(--font);
  font-size: var(--t-b);
  font-weight: 500;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* overflow-x's placement and its reason live on the html rule's own
     line note; a clip on body would nest a scroll container between
     html and the content. */
}
body.lock { overflow: hidden; }

img,svg { display:block; max-width:100% }
img { height:auto }
ul,ol { margin:0; padding:0; list-style:none }
p { margin:0 0 1em }
p:last-child { margin-bottom:0 }

h1,h2,h3 {
  margin:0 0 .45em;
  font-weight:600;
  line-height:1.1;
  letter-spacing:-.03em;
  text-wrap:balance;
}
h1 { font-size: var(--t-d1); letter-spacing:-.042em; line-height:1.02 }
h2 { font-size: var(--t-d2); letter-spacing:-.034em }
h3 { font-size: var(--t-d3); letter-spacing:-.022em; line-height:1.15 }

/* Accent word inside a heading: outlined, not coloured — keeps contrast */
/* Base colour is the brand blue, so if -webkit-text-stroke is ever dropped the
   word still reads instead of vanishing (colour:transparent must not be the
   unconditional base). Stroke tracks font-size: a fixed 1.5px is 2.6x heavier
   relative to em at the small end of the clamp and closes the counters. */
h1 em, h2 em { font-style:normal; color:var(--blue-lt) }
@supports (-webkit-text-stroke: 1px black) {
  h1 em, h2 em {
    color:transparent;
    -webkit-text-stroke:max(1px, .018em) var(--blue-lt);
  }
}

a { color:var(--blue-text); text-decoration:none; transition:color .2s var(--ease) }
@media (hover: hover) {
a:hover { color:var(--fg) }
}
/* In-prose links — the document's citations: blue, underlined, never a
   button (WCAG 1.4.1: colour alone never distinguishes a link from its
   sentence). One rule for every prose surface: the support walkthrough,
   the support note, the form's noscript channels, the policy. */
:is(.walk__body, .tv-note, .form__note, .legal__sec) a:not(.btn) {
  color:var(--blue-text);
  text-decoration:underline;
  text-underline-offset:3px;
}

:focus-visible { outline:2px solid var(--blue-lt); outline-offset:3px }
/* A plain in-prose link has no radius of its own — round its ring. Anything
   that carries a radius (pill button, card, key) keeps it: the focus state
   must never reshape the element it marks. */
:where(a:not([class]):focus-visible) { border-radius:5px }

.skip {
  position:absolute; left:50%; top:0; translate:-50% -120%;
  z-index:300; padding:.8rem 1.4rem;
  background:var(--blue); color:var(--on-blue); border:1px solid var(--line-ctrl);
  clip-path:var(--cut-skip-fill);
  font-weight:600; transition:translate .25s var(--ease);
}
.skip:focus { translate:-50% 0; color:var(--on-blue) }
/* The skip is clipped, so the global outline dies at the cut's edge; its
   keyboard indication is a skip-shaped ring painted INSIDE the cut — the
   same 3px white band the blue fills carry (6.788:1 on --blue). */
.skip:focus-visible { outline:none }
.skip:focus-visible::before {
  content:""; position:absolute; inset:0; pointer-events:none;
  background:var(--on-blue); clip-path:var(--cut-ring-skip-focus);
}

/* Scrollbar and selection were UA defaults: a bright light thumb on a #001025
   surface. Chrome also paints autofilled fields light unless we take the fill. */
::selection { background:color-mix(in srgb, var(--blue-lt) 32%, transparent); color:var(--fg) }
* { scrollbar-color:var(--scroll-thumb) transparent; scrollbar-width:thin }
*::-webkit-scrollbar { width:11px; height:11px }
*::-webkit-scrollbar-track { background:transparent }
*::-webkit-scrollbar-thumb {
  background:var(--scroll-thumb); border-radius:2px;
  border:3px solid transparent; background-clip:content-box;
}
*::-webkit-scrollbar-thumb:hover { background:var(--scroll-thumb-hi); background-clip:content-box }

.fld input:-webkit-autofill,
.fld input:-webkit-autofill:hover,
.fld input:-webkit-autofill:focus {
  -webkit-text-fill-color:var(--fg);
  -webkit-box-shadow:0 0 0 1000px var(--s0) inset;
  caret-color:var(--fg);
}

.wrap { width:100%; max-width:var(--wrap); margin-inline:auto; padding-inline:var(--pad) }
.sec {
  padding-block: var(--sec);
  position:relative;
  /* Sections are sized by their content, so the space between two of them is
     one constant rhythm at every boundary. (A forced 100svh left each section
     with a different amount of slack — measured: 442px at one boundary, 160px
     at the next. The hairline below marks the change of scene, and it sits at
     the exact midpoint of the gap since both paddings are the same token.) */
}
/* The boundary rule — one owner, at the top of every section. */
.sec::before {
  content:""; position:absolute; inset:0 0 auto; height:1px;
  background:linear-gradient(90deg, transparent, var(--rim) 30%, var(--rim) 70%, transparent);
  pointer-events:none;
}

/* ───────────────────────────────────────────────────── utilities ── */
.tag {
  display:inline-flex; align-items:center; gap:.5rem;
  margin:0 0 1.25rem;
  font-family:var(--mono);
  font-size:var(--t-xs); font-weight:500;
  letter-spacing:.1em; text-transform:uppercase;
  color:var(--blue-text);   /* --blue-lt reads 4.34:1 at the mesh's brightest tint (5.10:1 on s0) — under AA's 4.5:1 for a 13px label; --blue-text keeps >=5.88:1 across the same range */
}
.pulse {
  width:7px; height:7px; border-radius:50%;
  background:var(--blue-lt); box-shadow:0 0 0 0 color-mix(in srgb, var(--blue-lt) 70%, transparent);
  /* Static. This dot sits next to a fixed fact; a pulse implies a live feed
     and burns compositor time forever. */
}


/* ───────────────────────────────────────────────────────── buttons ── */
.btn {
  position:relative;
  display:inline-flex; align-items:center; justify-content:center; gap:.5em;
  min-height:var(--tap);
  padding:.85em 1.5em;
  border:0; clip-path:var(--cut-fill);
  font:inherit; font-weight:600; font-size:var(--t-s); line-height:1.1;
  cursor:pointer; white-space:nowrap;
  transition:background-color .25s var(--ease), color .25s var(--ease),
             opacity .25s var(--ease), transform .25s var(--ease),
             translate var(--dur-out) var(--ease-spring),
             scale var(--dur-out) var(--ease-spring);
  /* magnetic pull (--mx/--my, écrit par main.js) rides on top of the hover
     lift, so both compose instead of fighting over `transform`: the magnet
     owns `transform`, the lift/press own `translate`/`scale` (--py/--psc —
     one socket for every key, « la pression » below). */
  transform:translate3d(var(--mx,0px),var(--my,0px),0);
}

/* ── « the cut standard » at the component level ──────────────────────
   The fill clips the element; the HAIRLINE RIM is the dual-clip pseudo
   (::before, one per component class, sharing the ring custom properties
   — see the tokens). Box-shadow is dead paint under a clip-path, so every
   clipped surface's elevation is gone by construction: the whisper-dark
   card glows never read on this ground (measured ≈1.15:1 on --s0), and
   the one halo that matters (the fab's) is re-cast as a shape-following
   drop-shadow on its socket, below. Keyboard focus re-parameterizes the
   same pseudo into the 2px offset blue band and the global outline dies
   ONLY on these scoped selectors — never anywhere else. */
:is(.btn, .key__pad, .hero__pause, .chat__saybtn, .cinfo__cue,
    .hero__line)::before,
:is(.tgl, .burger)::before,
:is(.dlbox, .callcard, .chat__card, .cinfo__thumb)::before {
  content:""; position:absolute; inset:0; pointer-events:none;
  background:var(--line-ctrl);
  transition:background-color .25s var(--ease), clip-path 160ms var(--ease);
}
:is(.btn, .key__pad, .hero__pause, .chat__saybtn,
    .cinfo__cue, .hero__line)::before { clip-path:var(--cut-ring); }
:is(.tgl, .burger)::before { clip-path:var(--cut-ring-s); }
:is(.dlbox, .callcard, .chat__card, .cinfo__thumb)::before { clip-path:var(--cut-ring-l); }
.fab::before { content:""; position:absolute; inset:0; pointer-events:none;
  background:var(--line-ctrl); clip-path:var(--cut-ring-gem);
  transition:background-color .25s var(--ease), clip-path 160ms var(--ease); }

/* Secondary/outline CTAs answer the light more lightly than filled ones. */
.btn--ghost, .btn--line { --mag-x:.12; --mag-y:.12 }

.btn--fill {
  background:var(--blue); color:var(--on-blue);
}
@media (hover: hover) {
.btn--fill:hover { background:var(--blue-lt); color:var(--on-blue-lt) }
}

.btn--ghost { background:var(--ghost-bg); color:var(--fg) }
@media (hover: hover) {
.btn--ghost:hover { background:var(--ghost-bg-hi) }
.btn--ghost:hover::before,
.btn--line:hover::before { background:var(--wash-hi) }
}

.btn--line { background:transparent; color:var(--fg) }
@media (hover: hover) {
.btn--line:hover { background:var(--wash); color:var(--fg) }
}

/* (The focus contract — the ring's re-parameterization and the scoped
   outline suppressions — lives in ONE late section, « the focus law »,
   just before the print block: late so no component's hover recolor can
   ever out-rank the blue band, one place so the whole law reads.) */

.btn--sm { padding:.62em 1.1em; font-size:.875rem; min-height:var(--tap) }
.btn--lg { padding:1.05em 1.9em; font-size:1rem }
.btn--full { width:100% }
.btn[disabled] { opacity:.6; cursor:default; transform:none; translate:none; scale:1 }
/* The busy word is the only disabled state that is working, not waiting
   on the visitor: the cursor says which one it is. */
.btn[disabled][aria-busy="true"] { cursor:progress }

/* ────────────────────────────────────────────────── header ── */

.hdr {
  position:fixed; inset:0 0 auto; z-index:100;
  border-bottom:1px solid transparent;
  transition:border-color .35s var(--ease);
}
/* The glass band is its own layer, not the header's own style: a
   backdrop-filter on .hdr makes the header the containing block for its
   fixed descendants — and the mobile drawer lives inside it, so its
   `inset:var(--hdr-h) 0 0` collapsed into the header's box and clipped the
   menu to the header's padding. */
.hdr::before {
  content:""; position:absolute; inset:0; z-index:-1;
  background:var(--hdr-bg);
  backdrop-filter:blur(18px) saturate(170%);
  -webkit-backdrop-filter:blur(18px) saturate(170%);
  transition:background-color .35s var(--ease);
}
.hdr.stuck { border-bottom-color:var(--line-d) }
.hdr.stuck::before { background:var(--hdr-stuck) }

.hdr__in { display:flex; align-items:center; gap:1.5rem; min-height:var(--hdr-h) }

.logo { flex-shrink:0; line-height:0 }
.logo__i { display:block; height:52px; width:auto; transition:opacity .25s var(--ease) }
@media (hover: hover) {
.logo:hover .logo__i { opacity:.8 }
}

.nav { display:flex; gap:.25rem; margin-left:auto }
/* Link styling is scoped to non-button links: a .btn inside .nav (the drawer
   CTAs) keeps its own colors, padding and transitions — a bare `.nav a`
   (0-1-1) would outrank `.btn--fill` (0-1-0) and dim it. */
.nav a:not(.btn) {
  position:relative; display:inline-flex; align-items:center;
  min-height:var(--tap); padding:.5rem .9rem;
  color:var(--fg-2); font-size:var(--t-s); font-weight:500;
  white-space:nowrap;
  transition:color .3s var(--ease-out);
}
.nav a:not(.btn)::after {
  content:""; position:absolute; left:.9rem; right:.9rem; bottom:.2rem;
  height:2px; border-radius:2px; background:var(--blue-lt);
  transform:scaleX(0); transform-origin:50% 50%;
  transition:transform .4s var(--ease-out);
}
@media (hover: hover) {
.nav a:not(.btn):hover { color:var(--fg) }
}
@media (hover: hover) {
.nav a:not(.btn):hover::after { transform:scaleX(1) }
}
.nav a:not(.btn).on::after { transform:scaleX(1) }
.nav a:not(.btn).on { color:var(--fg) }

/* The standard's nav text item: the wrapper is the callcard's anchor (the
   <a> inside carries the nav's own link idiom — the underline sweep —
   unchanged), and it centers in the header row like its sibling links. */
.nav__join { position:relative; display:flex; align-items:center }

/* Drawer-only actions. Hidden on desktop where the header already shows them,
   so the same CTA is never duplicated in two places at once. */
.nav__act { display:none }

/* The action block: the language toggle, the TeamViewer CTA and the burger.
   « Nous joindre » left this row for the nav's own text (the CTA diet's
   death A) — the standard's desktop triggers are the nav item and the fab,
   so no card anchors here anymore. */
.hdr__act { display:flex; align-items:center; gap:.55rem }
.burger {
  display:none; flex-direction:column; justify-content:center; gap:5px;
  /* 11px is the bars' inset — a distinct constant, not a token. */
  width:var(--tap); height:var(--tap); padding:0 11px;
  background:transparent; border:0; clip-path:var(--cut-s-fill);
  cursor:pointer;
  transition:background-color .25s var(--ease),
             translate var(--dur-out) var(--ease-spring),
             scale var(--dur-out) var(--ease-spring);
}
@media (hover: hover) {
.burger:is(:hover, :focus-visible) { background:var(--wash) }
.burger:is(:hover, :focus-visible)::before { background:var(--wash-hi) }
}
.burger i { display:block; height:2px; width:100%; background:var(--fg); border-radius:2px; transition:transform .3s var(--ease), opacity .2s }
/* Markup is two bars: one X stroke each (a 3-bar rule animated a bar that
   does not exist and left the second one flat through the whole transition). */
.burger[aria-expanded="true"] i:nth-child(1) { transform:translateY(3.5px) rotate(45deg) }
.burger[aria-expanded="true"] i:nth-child(2) { transform:translateY(-3.5px) rotate(-45deg) }

.veil {
  position:fixed; inset:0; z-index:90;
  background:var(--veil-bg); backdrop-filter:blur(3px);
  opacity:0; transition:opacity .3s var(--ease);
  pointer-events:none;          /* invisible scrim must never eat clicks */
}
.veil.on { opacity:1; pointer-events:auto }

/* ───────────────────────────────────────── animated mesh field ── */
.mesh {
  position:absolute; inset:0; z-index:-1;
  overflow:hidden; pointer-events:none;
  /* Flat, static, and blended. The previous three drifting blurred lobes were
     a permanently-repainting full-screen layer that never idled — the single
     most expensive thing on the page and the clearest "generated" signal. */
  background:var(--mesh);
}
/* A single hairline rule, drawn once, as the only "motion" in the backdrop.
   The span is inline inside .mesh (position:absolute, overflow:hidden) —
   absolute + block makes it the full-area layer of that nearest positioned
   ancestor, clipped and masked; the 2.2%-opacity 96px hairlines are
   deliberately near-subliminal. */
.mesh__grid {
  position:absolute; display:block;
  inset:0;
  background-image:
    linear-gradient(var(--grid-line) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
  background-size:96px 96px;
  -webkit-mask-image:radial-gradient(ellipse 90% 60% at 50% 0%, #000 0%, transparent 70%);
  mask-image:radial-gradient(ellipse 90% 60% at 50% 0%, #000 0%, transparent 70%);
}

/* ─────────────────────────────────────────────────────────── hero ── */
.hero {
  position:relative; isolation:isolate;
  /* One scene — the copy and its call-log card, with « En chiffres » gathered
     right below them at the designed gap. The hero hugs its content: the
     eyebrow sits just under the header and the numbers close the pitch, so no
     dead space opens above or below the cluster. */
  display:flex; flex-direction:column; justify-content:flex-start;
  gap:clamp(2.5rem,6vh,5rem);
  padding-block:calc(var(--hdr-h) + 1.5rem) var(--sec);
}
.hero__in {
  display:grid; gap:clamp(2rem,4vw,4rem);
  grid-template-columns:minmax(0,1.35fr) minmax(0,.65fr);
  align-items:center;
}
.hero__copy { max-width:none }

.hero h1 { color:var(--fg); margin-bottom:.38em }
.hero h1 .ln { will-change:transform }

/* ─────────────────────────────── hero : slogans rotatifs ──
   « la ligne traverse » — a signal bar sweeps across the headline while the
   three line slots roll out and the next slogan rolls in behind it. Slots
   keep a fixed height (3 lines), so nothing around the headline ever jumps. */
/* Réserve de 3 lignes = propriété du ROTATEUR (mêmes lignes fixes que
   main.js). Les autres titres .hero (support, politique) n'ont pas à la payer. */
.hero:not(.tv-hero):not(.legal-hero) h1 { position:relative; min-height:3.06em }
.hero h1::after {
  content:""; position:absolute; top:-.14em; bottom:-.14em; left:0;
  width:3.25rem; pointer-events:none; opacity:0;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--blue-lt) 45%, transparent) 55%, transparent);
  filter:blur(7px);
}
.hero h1.sweep::after { animation:heroSweep .95s var(--ease-emphasis) }
@keyframes heroSweep {
  0%   { transform:translate3d(-4rem,0,0); opacity:0 }
  12%  { opacity:.85 }
  86%  { opacity:.85 }
  100% { transform:translate3d(calc(var(--sweep-w, 30rem) + 6rem),0,0); opacity:0 }
}
/* the accent word lands a beat after its line — the last word said */
.em-land { animation:emLand .7s var(--ease-out) .16s both }
@keyframes emLand {
  from { transform:translateY(.22em); opacity:0 }
  to   { transform:translateY(0); opacity:1 }
}
/* The two hero sub-lines — the home pitch and the support page's — share
   one voice; the support one just caps its own measure. */
.hero__sub, .tv-hero__sub {
  color:var(--fg-2);
  font-size:var(--t-lead); line-height:1.55;
}
.hero__sub { margin-bottom:2.2em; }
/* The hero's action is INK, built as the switchboard plate the line is:
   one chamfered plate and one tel: link; a quiet mono channel label and an
   integrated « Appeler » cue on its cap; the number itself at a confident
   tabular scale. The plate's top-left cut is filled blue — the one 45°
   accent, and the only ornament. No leader, no second display line, no
   key: the headline keeps the display voice, the plate holds the fact
   that answers it. */
.hero__direct {
  width:min(100%, 22rem);
  margin:.2rem 0 0;
}
.hero__line {
  position:relative; isolation:isolate;
  display:flex; flex-direction:column; gap:.5rem;
  width:100%; min-height:var(--tap);
  padding:.8rem 1rem .9rem;
  background:var(--s0);
  clip-path:var(--cut-fill);
  color:var(--fg); text-decoration:none;
  /* The phone layout centers the hero's prose; the plate is an object and
     keeps its own left register (the .s / .cinfo exception). */
  text-align:left;
  transition:background-color .3s var(--ease), translate .18s var(--ease);
}
/* The cap is two reading facts, not two controls: the channel's name at
   the left, the action at the right. The whole plate is the one link. */
.hero__line-hd {
  display:flex; align-items:center; justify-content:space-between;
  gap:.4rem .75rem; flex-wrap:wrap;
}
.hero__line-k {
  flex:none; white-space:nowrap;
  font-family:var(--mono); font-size:.6875rem; font-weight:500;
  letter-spacing:.14em; text-transform:uppercase; color:var(--fg-3);
}
.hero__line-cue {
  display:inline-flex; align-items:center; gap:.35rem; flex:none;
  font-family:var(--mono); font-size:.625rem; font-weight:500;
  letter-spacing:.12em; text-transform:uppercase; white-space:nowrap;
  color:var(--blue-text);
}
.hero__line-go { flex:none; transition:transform .3s var(--ease-out) }
.hero__line-num {
  font-family:var(--mono);
  font-size:clamp(1.5rem, 1.05rem + 1.15vw, 1.75rem);
  font-weight:600; letter-spacing:-.01em; line-height:1.05;
  font-variant-numeric:tabular-nums lining-nums;
  white-space:nowrap; color:var(--fg);
}
/* The one ornament: the top-left chamfer is filled blue. The plate's own
   cut removes the corner; this triangle fills exactly the visible half of
   the 12px cut, so the blue IS the cut, not a badge beside it. */
.hero__line::after {
  content:""; position:absolute; top:0; left:0;
  width:var(--cut); height:var(--cut);
  background:var(--blue-lt);
  clip-path:polygon(100% 0, 100% 100%, 0 100%);
  pointer-events:none;
}
@media (hover: hover) {
.hero__line:hover { background:var(--s1) }
.hero__line:hover .hero__line-cue { color:var(--fg) }
.hero__line:hover .hero__line-go { transform:translateX(2px) }
}
.hero__line:focus-visible { background:var(--s1) }
.hero__line:focus-visible .hero__line-cue { color:var(--fg) }
.hero__line:focus-visible .hero__line-go { transform:translateX(2px) }
.hero__line:active { translate:0 1px }

/* WCAG 2.2.2 — the slogan ticker's pause control: a small toggle under the
   headline. aria-pressed swaps the two glyphs; the label stays constant. */
.hero__pause {
  display:inline-flex; align-items:center; justify-content:center;
  width:var(--tap); height:var(--tap); padding:0; margin:-.15rem 0 1.15rem;
  background:var(--ghost-bg); color:var(--fg-2);
  border:0; clip-path:var(--cut-fill);
  cursor:pointer;
  transition:background-color .25s var(--ease), color .25s var(--ease),
             translate var(--dur-out) var(--ease-spring),
             scale var(--dur-out) var(--ease-spring);
}
@media (hover: hover) {
  .hero__pause:is(:hover, :focus-visible) { background:var(--ghost-bg-hi); color:var(--fg) }
  .hero__pause:is(:hover, :focus-visible)::before { background:var(--wash-hi) }
}
.hero__pause .hero__pause-ico--play { display:none }
.hero__pause[aria-pressed="false"] .hero__pause-ico--play { display:block }
.hero__pause[aria-pressed="false"] .hero__pause-ico--pause { display:none }
/* The control is the headline's one opt-out: the roll plays for every visitor,
   so the WCAG 2.2.2 pause must always be reachable. */

/* Headline line reveal — each line slides up out of its own clip */
/* padding-block + equal negative margin: the clip that makes the reveal work
   would otherwise shear the descenders off q/p/y in the headline. */
.ln {
  display:block; overflow:hidden;
  padding-block:.14em; margin-block:-.14em;
}
.ln__i { display:inline-block }






/* ─────────────────────────────────────── scroll-driven motion ──
   Content never fades in (no half-revealed states anywhere): blocks glide up
   as they enter, and they are fully settled by the time the view rests.
   Scroll-timeline only — without it the page is simply static. */
@supports (animation-timeline: view()) {
  .shead, .svc > *, .plan, .num, .cinfo > li, .call__row, .walk__step, .legal__sec {
    animation:scrub-rise linear both;
    animation-timeline:view();
    animation-range:entry 4% entry 74%;
  }
  @keyframes scrub-rise {
    from { transform:translate3d(0,26px,0) }
    to   { transform:translate3d(0,0,0) }
  }
  /* the hero glow drifts as the page leaves it — the scene has depth */
  .mesh {
    animation:mesh-drift linear both;
    animation-timeline:scroll(root block);
    animation-range:0 110vh;
  }
  @keyframes mesh-drift {
    from { translate:0 0; scale:1 }
    to   { translate:0 -10vh; scale:1.06 }
  }
}

/* ─────────────────────────────────────────── section headings ── */
/* Section prose runs the full editorial width — the text under every section
   takes the space it needs instead of wrapping into a narrow column. */
.shead { max-width:none; margin-bottom:clamp(1.75rem,2.6vw,2.75rem) }
.shead__lead { color:var(--fg-2); font-size:var(--t-lead); max-width:none }

/* ───────────────────────────────────────── services grid ── */
/* Uniform 3×2: every row fills completely, no orphan cells. */
.svc {
  display:grid; gap:var(--gap);
  grid-template-columns:repeat(3,minmax(0,1fr));
}
/* The service cells ride the .perk recipe scaled — ONE SURFACE: no panel,
   no lift, no rim; the hairline tops the cell, the 26px bare ink glyph is
   the marker, the border-top wake is the hover (the .plan/.perk move), and
   the grid's own row gap carries the cell's foot. */
.s {
  border-top:1px solid var(--line-d);
  padding:clamp(.95rem,1.6vw,1.25rem) 0 0;
  transition:border-color .35s var(--ease);
}
@media (hover: hover) {
.s:hover { border-top-color:var(--hover-edge) }
}
.s:focus-within { border-top-color:var(--hover-edge) }
/* The card is informational, not a button: it carries no role, no click
   affordance and no tab stop. Its detail is permanently visible (the rule
   below), so there is no keyboard reveal to focus. */
/* The expertise marker rides BESIDE the title — a bare ink glyph on the
   24 grid, no badge, no tile: the six svc-* drawings (butt caps, miter
   joins, the 45° shoulder). The glyph aligns to the title's cap; the
   copy hangs under the title's own left edge, so the register is aligned
   by construction, not by eye. */
.s__hd { display:flex; align-items:flex-start; gap:.72rem; margin:0 0 .6em }
.s__hd h3 { margin:0 }
/* One icon box, four sizes: the bare ink glyph's optical frame is the
   same construct everywhere — centered, never shrunk, always the brand
   blue. Each site sets its own box; the svg inside is sized per
   component (26px in the expertise header and the contact facts). */
.s__ico, .cinfo__i, .dlbox__ico, .perk__ico {
  display:grid; place-items:center; flex-shrink:0; color:var(--blue-lt);
}
.s__ico { width:26px; height:26px; margin-top:3px }
.s__ico svg { width:26px; height:26px }
.s p { color:var(--fg-2); font-size:var(--t-s); margin-bottom:0; margin-left:calc(26px + .72rem) }

/* ───────────────────────────── proof (honest figures) ──
   Replaces the previous fake "console" — mac-window chrome and a live-looking
   status dot implied a 24/7 NOC that a five-person shop does not run. These are
   real, sourced, static numbers with no live affordance at all. ONE SURFACE:
   the panel that once framed them is gone — the eyebrow, the hairline rows
   and the hairline-topped foot sit on the hero grid as ink (the hero keeps
   exactly one box: the consultation keycap). */
/* The mono eyebrow — the register's small-caps label: one voice for the
   proof header, the form header, the subject legend, the team keys'
   role lines and the poste keys' apply verb; each tunes its tracking
   and its spacing below. */
.proof__hd, .key__role, .key__verb {
  font-family:var(--mono); font-size:var(--t-xs);
  text-transform:uppercase; color:var(--fg-3);
}
.proof__hd {
  margin:0 0 1.1rem;
  letter-spacing:.12em;
}
.proof__foot {
  margin:1.1rem 0 0; padding-top:1rem;
  border-top:1px solid var(--line-d);
  font-size:var(--t-xs); color:var(--fg-3); line-height:1.6;
}


/* ─────────────────────────────────────────────── stats ── */
/* Explicit counts, not auto-fit: auto-fit produced a 3+1 orphan row between
   roughly 632px and 855px, stranding the fourth stat alone. */
.nums__grid {
  display:grid; gap:clamp(1.5rem,3vw,3rem);
  grid-template-columns:repeat(4,minmax(0,1fr));
  text-align:center;
}
/* En chiffres lives in the hero now: a flat rail, hairline-separated. */
.hero__nums .tag { margin-bottom:1.15rem }
.hero__nums .nums__grid {
  text-align:left; gap:0;
  padding-top:clamp(1.5rem,2.5vw,2.25rem);
  border-top:1px solid var(--line-d);
}
.hero__nums .num {
  align-items:flex-start;
  padding-inline:clamp(1rem,2.2vw,2.25rem);
  border-left:1px solid var(--line-d);
}
.hero__nums .num:first-child { padding-left:0; border-left:0 }
@media (max-width:900px) {
  .nums__grid { grid-template-columns:repeat(2,minmax(0,1fr)) }
  /* The row gap is also the pendant lamp's berth: the team cell hangs its
     fixture above the pair, so the second row keeps a little headroom. */
  .hero__nums .num { padding:.75rem 0 1.25rem; border-left:0 }
}
.num { display:flex; flex-direction:column; gap:.4rem; align-items:center }

/* The switchboard meter — the owner's chosen mechanism. Each place is a 1em
   window over a 0-9 strip; the markup carries the final position, so no-JS,
   paper and the screen-reader span read the value without a script. The
   site's numeral gradient rides each window (the 1em background size keeps
   the 180deg fade whole per digit instead of smearing it down the strip). */
.num .meter, .num__fig {
  font-size:clamp(2.5rem,1.7rem + 2.8vw,4.5rem); font-weight:600;
  letter-spacing:-.04em; line-height:1;
  font-variant-numeric:tabular-nums lining-nums;
}
.num__ink, .drum__strip::before {
  background:var(--num-grad);
  -webkit-background-clip:text; background-clip:text; color:transparent;
}
.meter { display:flex; align-items:center; color:var(--fg) }
.drum {
  position:relative; display:block; flex:none;
  width:.64em; height:1em; overflow:hidden;
}
.drum__strip {
  display:block;
  transform:translateY(calc(var(--pos, 0) * -1em));
  transition:transform 90ms linear;
}
.drum__strip::before {
  content:"0\A 1\A 2\A 3\A 4\A 5\A 6\A 7\A 8\A 9\A 0";
  display:block; white-space:pre; text-align:center;
  line-height:1em; height:11em;
  background-size:100% 1em; background-repeat:repeat-y;
}
/* The carry: units roll on every frame, tens step, the higher places snap. */
.drum--step .drum__strip { transition-duration:160ms; transition-timing-function:steps(1,end) }
.drum--snap .drum__strip { transition-duration:220ms; transition-timing-function:var(--ease-emphasis) }
.drum--unit .drum__strip { transition:none }
.meter__gap { display:block; width:.2em }
.meter__suf, .meter__sep { display:block }
.meter__suf { padding-left:.03em }
.num__live { display:flex; align-items:flex-start; gap:.5rem; color:var(--fg) }
/* The lamp's containing block is this in-flow host, not the grid: a grid's
   abs-positioned child can resolve 100% against its grid area, which put the
   fixture inside the pair on phones. */
.num__live--team { position:relative; display:block; width:100% }

/* The roster's two readings sit under one pendant lamp: 4 technicians and
   2 network administrators. The lamp is a 6px octagonal bulb with a cord and
   a shade painted by its pseudos; its wash and the two pools behind the
   figures are pure CSS, so no-JS still reads both numbers. The light stays
   behind the figures, never over the ink. */
.team {
  position:relative;
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  gap:clamp(.65rem,1.5vw,1.2rem);
  width:100%;
}
.team__fig {
  position:relative; z-index:1;
  display:flex; flex-direction:column; align-items:center;
  gap:.18rem; min-width:0; text-align:center;
}
.team__lbl {
  color:var(--fg-3); font-size:var(--t-s); line-height:1.3;
  max-width:19ch; text-align:center;
}

/* The lamp is a full-width, zero-height rig above the pair: its bulb, shade
   and cord hang from the centre, while the beam sizes itself against the
   whole team cell — never against a 6px fixture — so both figures stay
   inside the light at every breakpoint. */
.lamp {
  position:absolute; left:0; right:0; bottom:calc(100% + .45rem);
  display:block; height:0; margin:0;
  background:none; clip-path:none; filter:none;
  z-index:0; pointer-events:none;
}
/* The halo is its own unclipped layer: a drop-shadow on the clipped LED
   would be cut away with it, which is why the old bulb read as a dead dot. */
.lamp__glow {
  position:absolute; left:50%; bottom:-16px;
  width:44px; height:44px; margin-left:-22px;
  background:radial-gradient(circle,
    color-mix(in srgb, #fff 55%, var(--blue-lt)) 0%,
    color-mix(in srgb, var(--blue-lt) 70%, transparent) 32%,
    transparent 70%);
  filter:blur(2px);
  opacity:0; transform:scale(.35); transform-origin:center;
  pointer-events:none;
}
.lamp__bulb {
  position:absolute; left:50%; bottom:0;
  width:6px; height:6px; margin-left:-3px;
  background:var(--blue-lt);
  /* The site's octagonal LED, not a radius: the closed radius law stays. */
  clip-path:polygon(30% 0,70% 0,100% 30%,100% 70%,70% 100%,30% 100%,0 70%,0 30%);
  opacity:.16; transform:scale(.72); transform-origin:center;
  transition:opacity .35s var(--ease), transform .45s var(--ease-spring);
}
/* The shade hangs above the bulb, the cord above the shade. */
.lamp::before {
  content:""; position:absolute; left:50%; bottom:6px;
  width:20px; height:9px; margin-left:-10px;
  background:linear-gradient(180deg, color-mix(in srgb, var(--fg-3) 72%, #000), var(--fg-3));
  clip-path:polygon(34% 0,66% 0,100% 100%,0 100%);
  filter:brightness(1);
  transition:filter .5s var(--ease);
}
.lamp::after {
  content:""; position:absolute; left:50%; bottom:15px;
  width:1px; height:9px; margin-left:-.5px;
  background:var(--line-d);
}
.lamp__beam {
  position:absolute; left:50%; top:0;
  width:min(160%,40rem); height:8rem;
  translate:-50% 0;
  /* A true cone: the apex meets the bulb, the base spans both figures. No
     flat top edge, no gap between the light and its source. */
  clip-path:polygon(48% 0,52% 0,100% 100%,0 100%);
  background:
    radial-gradient(ellipse 55% 26% at 50% 0%,
      color-mix(in srgb, #fff 55%, var(--blue-lt)) 0%,
      color-mix(in srgb, var(--blue-lt) 55%, transparent) 32%,
      transparent 72%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--blue-lt) 40%, transparent) 0%,
      color-mix(in srgb, var(--blue-lt) 18%, transparent) 45%,
      color-mix(in srgb, var(--blue-lt) 6%, transparent) 75%,
      transparent 100%);
  filter:blur(10px);
  -webkit-mask-image:radial-gradient(ellipse 100% 100% at 50% 0%, #000 0%, rgba(0,0,0,.75) 55%, transparent 90%);
  mask-image:radial-gradient(ellipse 100% 100% at 50% 0%, #000 0%, rgba(0,0,0,.75) 55%, transparent 90%);
  opacity:0; transform-origin:50% 0; pointer-events:none;
}
/* The final state is declared, not only reached by keyframes: if an
   extension or an `animation:none` rule kills the animation, the lamp is
   still visibly on. */
.lamp.is-on .lamp__glow {
  opacity:1; transform:none;
  animation:lampGlow 1.1s steps(1,end) both;
}
.lamp.is-on .lamp__bulb {
  opacity:1; transform:none;
  animation:lampStrike 1.1s steps(1,end) both;
}
.lamp.is-on::before { filter:brightness(1.4) }
.lamp.is-on .lamp__beam {
  opacity:.92;
  animation:lampBeam 1.3s var(--ease-out) both;
}
@keyframes lampStrike {
  0%, 6%   { opacity:.16 }
  8%       { opacity:1 }
  12%      { opacity:.25 }
  18%      { opacity:1 }
  23%      { opacity:.55 }
  34%,100% { opacity:1 }
}
@keyframes lampGlow {
  0%, 6%   { opacity:0 }
  8%       { opacity:1 }
  12%      { opacity:.2 }
  18%      { opacity:1 }
  23%      { opacity:.5 }
  34%,100% { opacity:1 }
}
@keyframes lampBeam {
  0%       { opacity:0; transform:scaleY(.08) }
  8%       { opacity:.9; transform:scaleY(.35) }
  13%      { opacity:.3; transform:scaleY(.42) }
  21%      { opacity:1; transform:scaleY(.7) }
  28%      { opacity:.55; transform:scaleY(.82) }
  44%      { opacity:1; transform:scaleY(1) }
  60%,100% { opacity:.92; transform:scaleY(1) }
}
@keyframes lampPool {
  0%       { opacity:0 }
  8%       { opacity:1 }
  14%      { opacity:.35 }
  22%      { opacity:1 }
  34%,100% { opacity:1 }
}
/* No-JS and failed-ready states get a lit fixture, not a dead one: the
   numbers are the content, the lamp is their light. */
html:not(.js) .lamp__glow,
html.js-failed:not(.js-ready) .lamp__glow { opacity:1; transform:none }
html:not(.js) .lamp__bulb,
html.js-failed:not(.js-ready) .lamp__bulb { opacity:1; transform:none }
html:not(.js) .lamp__beam,
html.js-failed:not(.js-ready) .lamp__beam { opacity:.92 }
html:not(.js) .lamp::before,
html.js-failed:not(.js-ready) .lamp::before { filter:brightness(1.4) }
html:not(.js) .num--team .team__fig::before,
html.js-failed:not(.js-ready) .num--team .team__fig::before { opacity:1 }
.num__label { color:var(--fg-3); font-size:var(--t-s); max-width:32ch }
/* The wash is paint behind the two numbers; the caption and the hairline
   stay above it so the light never sits over their ink. */
.num__label--team { position:relative; z-index:1; align-self:center; text-align:center; margin-top:.1rem }
.num--team::after { position:relative; z-index:1; align-self:center }
@media (max-width:520px) {
  .team { grid-template-columns:1fr; gap:.45rem }
  .lamp { bottom:calc(100% + .3rem) }
  .lamp__bulb { width:5px; height:5px; margin-left:-2.5px }
  .lamp::before { width:18px; height:8px; margin-left:-9px; bottom:5px }
  .lamp::after { bottom:13px; height:4px }
  .lamp__beam { width:min(80%,15rem); height:12rem }
}

/* The band's one piece of theatre: as each figure settles, a hairline draws
   left-to-right beneath it. Restrained by design — the number is the event. */
.num::after {
  content:""; display:block;
  width:2.5rem; height:1px; margin-top:.15rem;
  background:linear-gradient(90deg,var(--blue-lt),var(--line-d));
  transform:scaleX(0); transform-origin:left center;
  transition:transform .9s var(--ease-out);
}
.num.is-set::after { transform:scaleX(1) }

/* ─────────────────────────────────────────── plans ── */
/* ──────────────────────────────────── offres : le registre ──
   Two offers per line, flat cells separated by hairlines: no card, no box,
   no lift. The hairline is the frame. */
.plans {
  display:grid;
  gap:clamp(1.75rem,3vw,2.5rem) clamp(2.5rem,5vw,5rem);
  grid-template-columns:repeat(2,minmax(0,1fr));
  /* the perspective the forfait cards lean into (« la lumière »): the cards'
     `transform` belongs to the reveal, so their lean rides the independent
     `rotate` property and gets its depth from here. */
  perspective:var(--persp);
}
/* Wide viewports: all four offers in one register row, instead of two rows of
   very wide cells with stretched bullet lines. */
@media (min-width:1700px) {
  .plans { grid-template-columns:repeat(4,minmax(0,1fr)); column-gap:clamp(2rem,3vw,3.5rem) }
}
/* Narrower viewports: one offer per row. Emitted AFTER the base rule above so
   it actually wins the cascade (same specificity — the old copy of this rule
   sat before the base and was silently overridden by it). */
@media (max-width:900px) {
  .plans { grid-template-columns:minmax(0,1fr); gap:0 }
}
.plan {
  display:flex; flex-direction:column;
  padding-block:clamp(1.5rem,2.5vw,2rem);
  border-top:1px solid var(--line-d);
  transition:border-color .35s var(--ease);
  /* Own stacking context even at rest (`rotate` is `none` until the card
     leans), so the sheen can't slip behind an ancestor's ground. */
  isolation:isolate;
  /* gentle lean: ±5°rx / ±4°ry — a big card barely moves */
  --tilt-x:4deg;
  --tilt-y:5deg;
}
@media (hover: hover) {
.plan:hover { border-top-color:var(--hover-edge) }
}
.plan h3 { margin:0 }
.plan__for { color:var(--fg-3); font-size:var(--t-s); font-weight:400; margin:0 0 0 .55rem }
.plan__term { display:flex; flex-direction:column; align-items:flex-start; gap:.35rem; text-align:left }
/* The one plan whose terms differ from the section rule — stamped in its
   header. The other cards inherit « sans contrat » from the section lead. */
.plan__stamp {
  display:inline-flex; margin:.65rem 0 0;
  padding:.32rem .7rem;
  font-family:var(--mono); font-size:.625rem; font-weight:600;
  letter-spacing:.14em; text-transform:uppercase; color:var(--blue-text);
  background:color-mix(in srgb, var(--blue-lt) 14%, transparent);
  border:0; clip-path:var(--cut-stamp-fill);
}
/* ── the mobile fold: one native disclosure pattern ──
   The HTML ships every long panel OPEN — the desktop, the no-JS reader and
   the paper keep the full reading. Only the phone (main.js, ≤900px, the
   drawer's own breakpoint) closes the long menus to their summary; the
   native <summary> is the control, so keyboard, touch and find-in-page all
   follow the platform, and the first plan stays open as the offer's anchor.
   The chevron is a plus that turns into the × at 45°. */
.fold > summary {
  --chev:18px;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:.75rem 1rem; min-height:var(--tap);
  cursor:pointer; list-style:none; -webkit-user-select:none; user-select:none;
  -webkit-tap-highlight-color:transparent; touch-action:manipulation;
}
.fold > summary::-webkit-details-marker { display:none }
.fold > summary::marker { content:"" }
.fold > summary .tag { margin:0 }
.fold > summary:focus-visible { outline:2px solid var(--blue-lt); outline-offset:3px }
.fold__chev {
  position:relative; flex:none; width:var(--chev); height:var(--chev); margin-left:auto;
  transition:rotate .25s var(--ease);
}
.fold__chev::before, .fold__chev::after {
  content:""; position:absolute; left:3px; right:3px; top:calc(50% - .5px);
  height:1px; background:var(--blue-lt);
}
.fold__chev::after { rotate:90deg }
.fold[open] .fold__chev { rotate:45deg }
/* The plan header is one grid: the name, the term at the left under it
   (the desktop's own footnote), and the fold control at the right. The
   phone centers the name and the term while the chevron holds the right
   edge. One owner: the fold's generic flex no longer fights a plan base. */
.fold > summary.plan__hd {
  display:grid;
  grid-template-columns:minmax(0,1fr) auto;
  grid-template-areas:"name chev";
  align-items:start; column-gap:1rem; row-gap:.35rem;
}
/* The term row exists only on the plan that has a term: without :has, the
   empty row added a gap under three headers and lowered the first chevron. */
.fold > summary.plan__hd:has(.plan__term) { grid-template-areas:"name chev" "term chev" }
.plan__hd h3 { grid-area:name }
.plan__hd .plan__term { grid-area:term; justify-self:start }
.plan__hd .fold__chev { grid-area:chev; align-self:start; justify-self:end; margin-left:0 }
.plan__list { display:grid; gap:.55rem; margin:.95rem 0 0; }
.plan__list li { position:relative; padding-left:1.6rem; color:var(--fg-2); font-size:var(--t-s) }
/* The register's one bullet shape — the forfait lists, the legal lists,
   the posting detail (card and no-JS block) share the gem dot; each tunes
   its placement and size. */
.plan__list li::before, .legal__list li::before,
.callcard__grp li::before, .key__detail li::before, .common__grp li::before {
  content:""; position:absolute;
  /* the register's one bullet shape is the gem octagon at the tiles' own
     30% proportion — resolution-independent, every dot size one rule */
  clip-path: polygon(30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 30%);
  background:var(--blue-lt);
}
.plan__list li::before { left:0; top:.42em; width:8px; height:8px; }
.callcard__grp li::before, .key__detail li::before, .common__grp li::before { left:.1rem; top:.42em; width:7px; height:7px; }

/* ────────────────────────────────── hover unfolds (le détail en plus) ──
   ONE reveal contract for every detail line on the site (the service card,
   a forfait, a team key). Hover / focus reveals ADDITIONAL detail; touch
   gets the lines permanently — nothing essential hides behind a hover.
   The slot always holds its full height: revealing is a fade-and-slide
   inside reserved space, so hovering can never move the page or disturb
   scrolling (a max-height unfold reflowed everything below).
   .is-open is the tap parity main.js adds to .plan cards; on a team key it
   marks « le standard » being answered, whose card supersedes the line —
   hence the last rule. The service card is informational and has no
   is-open state. */
.s__more, .plan__more, .key__more {
  margin:.85rem 0 0;
  opacity:0; translate:0 7px;
  transition:opacity .45s var(--ease), translate .5s var(--ease-emphasis);
}
/* The expertise detail is permanently visible: an informational card has
   no keyboard control to reveal it, and a hover-only reading is not a
   reading. Plans and team keys keep their own hover/focus reveals (they
   have real triggers). */
.s__more { opacity:1; translate:none; }
@media (hover: hover) {
.s:hover .s__more, .plan:hover .plan__more, .key:hover .key__more {
  opacity:1; translate:0 0;
}
}
.plan:focus-within .plan__more, .plan.is-open .plan__more,
.key:focus-within .key__more {
  opacity:1; translate:0 0;
}
/* The keyboard reveal is immediate: the fade belongs to hover, not to a
   focus that lands on an invisible target. */
.key:focus-within .key__more { transition:none; }
@media (hover: none), (pointer: coarse) {
  .s__more, .plan__more, .key__more { opacity:1; translate:none }
}
.key.is-open .key__more { opacity:0 }
.s__more::before, .plan__more::before {
  content:""; display:block; width:2.25rem; height:1px;
  margin-bottom:.7rem; background:var(--blue-text); opacity:.55;
}
.s__more, .plan__more { color:var(--fg-2); font-size:var(--t-s); line-height:1.55 }
/* The expertise card is informational and has no tab stop: its detail is
   permanently visible above (`.s__more { opacity:1 }`), so there is no
   focus-owned reveal to override here. Plans and team keys keep their own
   focus-within reveals because they carry real triggers. */

[data-grav], .plan { position: relative; }

/* ── la marque en filigrane ──
   One big faint S sits behind the whole page as a fixed backdrop, pinned to
   the viewport so it stays perfectly still while the page scrolls past it
   (position:fixed, NOT background-attachment:fixed — the latter judders on
   iOS). At z-index:-1 it lives under the content and over the --s0 ground,
   so the glass panels and the page gaps let it glow through. Same
   transparency as the old footer mark (--mark-op). */
body::before {
  content:""; position:fixed; inset:0; z-index:-1;
  /* Lossless WebP: the gradient watermark halves with zero visual change
     (ImageDelivery — the PNG was 64.6 kB for a 13%-opacity backdrop). */
  background-image:url("/assets/img/icone.webp");
  background-position:center;
  background-size:min(85vmin, 1150px);
  background-repeat:no-repeat;
  opacity:var(--mark-op); pointer-events:none; user-select:none;
}

/* ──────────────────────────────────────────────── la lumière ──
   One soft light follows the pointer across the whole page, not just the
   tiles: a fixed pool of light that never clips (it lives above the page
   content and below the fixed chrome). The clip-path is round 11's
   glitch fix: the light stopped painting inside the glass band's
   backdrop sample region — every fine-pointer move near the band
   rewrote --lx/--ly and forced a per-frame recomposite of the band's
   100vw×72px blur+saturate sample, a structural cost, and the clip
   removes the light from the sampled region instead of chasing the
   repaint. The scroll-coupled recomposite (content sliding under the
   glass) REMAINS BY DESIGN — the frosted-header contract. --hdr-h is
   the band height's single owner and the drawer's own
   inset:var(--hdr-h) 0 0 rule already pins the band-bottom
   assumption, so the clip cannot desync from the band it spares;
   body::after dies in the print block either way. */
body::after {
  content:""; position:fixed; inset:0; z-index:50; pointer-events:none;
  background:radial-gradient(620px circle at var(--lx,-600px) var(--ly,-600px),
             color-mix(in srgb, var(--blue-lt) 11%, transparent), transparent 68%);
  clip-path:inset(var(--hdr-h) 0 0 0);
  opacity:0; transition:opacity .6s var(--ease-out);
}
body.lit::after { opacity:1 }

/* ─────────────────────────────── la lumière glisse : le sheen ──
   The specular half of the one light: every glass surface carries a soft
   radial highlight sitting exactly under the cursor, so the whole page
   glints in unison as the pointer moves — one lit surface, not twenty.
   main.js writes element-local --sx/--sy (the viewport light minus each
   surface's origin) once per frame. background-attachment:fixed is NOT
   usable here: measured in Chrome, it degrades to element-local positioning
   under any transform, translate, scale or will-change:transform — and every
   surface below is transformed (that is what makes them lean/drift).
   Subtle by construction (≤14% accent), behind content, never interactive;
   cursor feedback, like --lx/--ly itself.
   The gradient is sized `closest-side`, which is load-bearing: its fade ends
   exactly at the surface's nearest edge, so the border can never slice the
   light. Sized to the farthest corner (the previous build), the glow still had
   most of its brightness left where it met the edge, and the trim read as a
   hard-edged slab of light on every surface.
   The offre cells (.plan) are deliberately NOT here: they are flat hairline
   cells with border-radius 0, so `border-radius:inherit` turned the sheen into
   a hard-edged rectangle. The glare belongs on rounded glass, not flat cells. */
@media (hover: hover) and (pointer: fine) {
  :is(.btn, .tgl, .key__pad, .menu .cckey, .dlbox)::after,
  .fab:not([data-live])::after {
    content:""; position:absolute; inset:0; clip-path:inherit;
    background:radial-gradient(circle closest-side at var(--sx, -600px) var(--sy, -600px),
               color-mix(in srgb, var(--blue-lt) 14%, transparent), transparent);
    pointer-events:none; z-index:-1;
  }
}
/* « the cut standard »: the sheen follows the chamfer — clip-path:inherit
   resolves the host's fill polygon against the pseudo's own box (inset:0,
   the same border box), exactly the way border-radius:inherit once did.
   The fab's ::after is excepted while a conversation is LIVE: chat.js's
   state owner (data-live) turns that pseudo into the live dot, which must
   never inherit the gem's clip. The offre cells (.plan) stay out on
   purpose (flat hairline cells — the glare belongs on glass, not them). */

.nav a.nav-tv { display: none; }

@media (max-width: 900px) {
.nav a.nav-tv { display: block; }
}

/* Every row of the keyboard is the same height: a longer role that wraps, or a
   longer title, must not leave one key taller than its neighbours. Row sizing
   is the grid's job, not the copy's. */
.paddock { display: grid; gap: var(--gap); grid-template-columns: repeat(2, minmax(0px, 1fr)); grid-auto-rows: 1fr; list-style: none; margin: 0px; padding: 0px; }

@media (max-width: 900px) {
.paddock { grid-template-columns: minmax(0px, 1fr); }
}
/* Wide viewports: eight keys, two rows of four — no orphan cell — instead of
   two rows of very wide, empty keys. */
@media (min-width: 1700px) {
.paddock { grid-template-columns: repeat(4, minmax(0px, 1fr)); }
}

.key { position: relative; display: grid; gap: 0.55rem; min-width: 0px; }

.key--main { grid-column: 1 / -1; }

.key__pad { display: grid; grid-template-columns: minmax(0px, 1fr) auto; align-items: center; gap: clamp(1rem, 2vw, 1.5rem); padding: clamp(1.1rem, 1.8vw, 1.5rem); background: var(--s0); border: 0; clip-path: var(--cut-fill); color: inherit; text-decoration: none; transform: perspective(var(--persp))
    rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
    translate3d(var(--tx,0px),var(--ty,0px),0)
    scale(var(--sc,1)); transition: background-color .35s var(--ease-out); will-change: transform; }

/* Focus twins: :focus-visible polish is listed with :hover inside the
   (hover:hover) gate — hover styling must never stick on touch. Keyboard
   focus stays visible everywhere through the rim pseudo's focus
   re-parameterization (the focus law section, at the end of the file).
   One rule per state pair, never two copies. */
@media (hover: hover) {
.key__pad:is(:hover, :focus-visible) { background: var(--s1); }
}

/* The who container holds the name and its metadata; under an open
   callcard only the NAME dims (a color change on the h3 below), never
   the container itself — no opacity ever moves here, so no transition
   either (the fade lives in the h3's own base rule). */
.key__who { min-width: 0px; }

/* The role is a subtitle: always stacked under the name (`column`). The
   previous wrapping row put it beside the name when the tile was wide and
   below it when the tile was narrow, so tiles landing in different rows had
   different heights. The color transition is the dim's fade owner: the
   name dims through COLOR (the close fades .55→1 with no pop), and the
   role span keeps its own --fg-3 — the color dim splits what an opacity
   dim on the container never could. */
.key__who h3 { display: flex; flex-direction: column; align-items: flex-start; gap: 0.35rem; margin: 0px 0px 0.15em; font-size: 1.0625rem; font-weight: 600; letter-spacing: -0.015em; transition: color .35s var(--ease); }

.key__role { font-weight: 500; letter-spacing: 0.08em; transition: color .35s var(--ease-out); }

@media (hover: hover) {
.key__pad:is(:hover, :focus-visible) .key__role { color: var(--fg-2); }
}

.key__go { color: var(--fg-3); flex-shrink: 0; opacity: 0; transform: translateX(-8px); transition: opacity .35s var(--ease-out), transform .35s var(--ease-spring),
             color .35s var(--ease-out); }

@media (hover: hover) {
.key__pad:is(:hover, :focus-visible) .key__go { opacity: 1; transform: none; color: var(--blue-lt); }
}

/* The key's unfold line: geometry only — the reveal contract lives with its
   siblings under « hover unfolds » above. */
.key__more {
  margin: 0px; min-height: var(--tap);
  display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
  padding-left: clamp(1.1rem, 1.8vw, 1.5rem);
  font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.02em;
  color: var(--fg-3);
}

.key__dig { display: inline-flex; align-items: baseline; gap: 0.35rem; font-family: var(--mono); font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; color: var(--fg); transition: color .35s var(--ease-out), text-shadow .35s var(--ease-out); }

.key__dig i { font-style: normal; font-family: var(--mono); font-size: 0.5625rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg-3); transition: color .35s var(--ease-out); }

@media (hover: hover) {
.key:is(:hover, :focus-within) .key__dig { color: var(--blue-lt); text-shadow: color-mix(in srgb, var(--blue-lt) 60%, transparent) 0px 0px 26px; }
.key:is(:hover, :focus-within) .key__dig i { color: var(--fg-2); }
}

.key__sep { color: var(--fg-3); }

.key__mail, .key__link { display: inline-flex; align-items: center; padding-block: 0.75rem; color: var(--fg-3); border-bottom: 1px solid transparent; overflow-wrap: anywhere; transition: color .3s var(--ease-out), border-color .3s var(--ease-out); }

@media (hover: hover) {
.key__mail:is(:hover, :focus-visible), .key__link:is(:hover, :focus-visible) { color: var(--fg); border-bottom-color: var(--wash-hi); }
}

/* The featured key's hover is lift-only BY the source-order tie: this
   radial glow reads as elevation, and a flat s1 ground (the generic pad
   hover at (0,2,0), fifty-eight lines earlier) would flatten it exactly
   while hovered — the tie is intent, not neglect. */
.key--main .key__pad { background: radial-gradient(120% 120% at 12% 0%, color-mix(in srgb, var(--blue-lt) 16%, transparent), transparent 55%),
    var(--s0); }

.key--main .key__who h3 { font-size: 1.125rem; }

.roster .shead { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }

/* The facts rail — the register's rhythm: hairline-separated rows that wake
   under the cursor, like the call log. */
.cinfo { display: grid; gap: 0; margin:0; }

.cinfo li { --cinfo-icon:32px; --cinfo-gutter:calc(var(--cinfo-icon) + 1rem); display: flex; align-items: flex-start; gap: 1rem; min-width: 0; padding:clamp(.95rem,1.5vw,1.25rem) 0; border-bottom:1px solid var(--line-d) }
.cinfo li:first-child { padding-top:0 }
.cinfo li:last-child { border-bottom:0 }

/* The facts column never widens its row: the map tile's design width must
   shrink below the measure instead of pushing the rail sideways. The div
   fills its row (flex-grow) so the tile's percentage resolves against the
   ROW — a percentage-width child feeds no max-content, so against a
   content-measured div the tile collapsed to the address text's own
   measure, smaller on desktop than on a phone. */
.cinfo li > div { min-width: 0; flex-grow: 1 }

.cinfo__i { width: var(--cinfo-icon); height: var(--cinfo-icon); }

.cinfo__i svg { width: 19px; height: 19px; }

.cinfo__i--alert { color: var(--warn); }

.cinfo strong { color: var(--fg); font-weight: 600; }

.cinfo h3 { font-size: var(--t-s); margin-bottom: 0.15em; color: var(--fg-3); font-weight: 600; }

.cinfo a, .cinfo p { margin: 0px; font-size: var(--t-s); color: var(--fg); font-weight: 500; }
/* A standalone channel action (the QuickSupport line) is a 44px target,
   not a 17px text run: the row is the hit area. The map tile keeps its
   own block geometry. */
.cinfo div > a:not(.cinfo__map) {
  display: inline-flex; align-items: center; min-height: var(--tap);
}

/* The two address lines are one tight fact, not a spaced-out pair. */
.cinfo__addr { line-height: 1.3; }

/* The map is the row's maps affordance: a square Google Maps preview with
   the pin on it. The whole tile is the link — it opens the place in a new
   tab — so hovering reveals the cue instead of a standing label. One token
   sets its side, so the tile cannot drift from its own size — and the
   div's flex-grow (below) is what lets the side actually bind: against a
   content-measured div the percentage collapsed to the text's own
   measure. */
.cinfo__map {
  /* The one size owner. 382px is the former 450px square less 15%
     (450 × .85 = 382.5, on the whole pixel the tile's imagery reads);
     the phone keeps its own fluid width, so the cap never changes the
     320/390 centering. */
  --map-side: 382px;
  position: relative; display: block;
  /* 100% first, the design size as the CAP: a fixed width feeds the
     grid track's min-content and blew the page out sideways on phones
     (measured: a 353px card in a 350px viewport). Capped, the tile
     keeps its size wherever it fits and shrinks where it cannot. */
  width: 100%; max-width: var(--map-side); margin-top: .85rem;
  text-decoration: none; cursor: pointer;
}
/* The tile's hairline draws the boundary of a large interactive link, so
   it rides the rim policy like every other control (--line-ctrl ≈4:1);
   the map imagery and the cue carry the identification inside it. */
.cinfo__thumb {
  display: block;
  position: relative;
  width: 100%; aspect-ratio: 1;
  border: 0; clip-path: var(--cut-l-fill);
  overflow: hidden; background: var(--ghost-bg);
}
.cinfo__thumb iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; display: block; pointer-events: none; }
/* The shared rim pseudo is painted BEFORE the positioned iframe; without
   a stacking step it disappears under the map. The rim is the tile's
   interactive boundary — keep it above the imagery. */
.cinfo__thumb::before { z-index:1 }
.cinfo__thumb::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0, 5, 30, .55), transparent 62%);
  opacity: 0; transition: opacity .25s var(--ease);
}
.cinfo__cue {
  position: absolute; left: .55rem; bottom: .55rem;
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .4rem .65rem;
  background: rgba(0, 5, 30, .78); border: 0; clip-path: var(--cut-fill);
  color: var(--on-blue); font-family: var(--mono); line-height: 1;
  opacity: 0; transform: translateY(3px);
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
/* The cue's rim is decorative chrome (the tile's own --line-ctrl rim
   carries the interactive boundary), so it keeps the hairline's voice. */
.cinfo__cue::before { background: var(--line-d) }
.cinfo__cue svg { width: 14px; height: 14px }
@media (hover: hover) {
.cinfo a:hover { color: var(--blue-text); }
.cinfo__map:hover .cinfo__thumb::after { opacity: 1 }
.cinfo__map:hover .cinfo__cue { opacity: 1; transform: translateY(0) }
}
/* Keyboard and touch reveal the map cue too: the cue is the only visible
   "open in Google Maps" affordance, so it cannot be hover-only. */
.cinfo__map:focus-visible .cinfo__thumb::after,
.cinfo__map:focus-visible .cinfo__cue { opacity: 1; transform: translateY(0) }
@media (hover: none), (pointer: coarse) {
  .cinfo__cue { opacity: 1; transform: none }
}

/* ONE SURFACE: the form panel is gone — the fields sit on the contact
   ground, GOV.UK-style: the mono eyebrow names the register, every .fld
   keeps its closed border + fill (a field's boundary is its affordance),
   the visible labels stay above, and the submit fill stays the one
   strong action. The container keeps only its identity (the print hide
   and the phone centering reference it). */

/* The form's own two labels carry a readable scale, not the eyebrow
   whisper: the heading speaks the title voice (the owner's veto — the old
   12px mono uppercase was hard to see), the field-set legend speaks the
   field-label voice above its radios. The mono eyebrow keeps the proof,
   the role and the apply verb; the form is a region with a heading, not a
   caption. */
.form__hd {
  margin: 0 0 1.2rem;
  font-size: clamp(1.2rem, 1.7vw, 1.4rem);
  font-weight: 650;
  letter-spacing: -.018em;
  line-height: 1.2;
  color: var(--fg);
}

.fs { border: 0px; padding: 0px; margin: 0px 0px 1.5rem; }

.fs legend {
  display: block;
  padding: 0px; margin: 0 0 .7rem;
  font-size: var(--t-s);
  font-weight: 600;
  letter-spacing: 0;
  color: var(--fg-2);
}

.opt { display: flex; cursor: pointer; }

.opt input { position: absolute; opacity: 0; pointer-events: none; }

.opt span { display: flex; position: relative; align-items: center; justify-content: center; width: 100%; min-height: 2.75rem; height: 100%; padding: 0.5rem 0.25rem; border:0; border-bottom:2px solid transparent; clip-path: var(--cut-s-fill); background:none; font-size: var(--t-xs); font-weight: 600; text-align: center; color: var(--fg-3); transition: color .2s var(--ease), border-color .2s var(--ease); }

@media (hover: hover) {
.opt:hover span { color: var(--fg); }
}

.opt input:checked + span { color: var(--fg); border-bottom-color: var(--blue-lt); }

/* The option's keyboard indication is the focus law's own ring pseudo
   (the focus law section, at the end of the file) — an outline cannot
   travel with a clipped shape. */

.fld { margin: 0px 0px 1.15rem; }

.fld label { display: block; margin-bottom: 0.4rem; max-width: 36ch; font-size: var(--t-xs); font-weight: 600; color: var(--fg-2); }

/* The fields keep their real border: inputs and textareas host no
   pseudo-elements, so the open chamfer corners (the border ends, the
   diagonal bare) are the fields' own rim voice — one pattern for every
   input, and the autofill rules below keep working because the rim is
   still a border there. */
.fld input, .fld textarea, .fld select { width: 100%; padding: 0.8rem 0.95rem; font:inherit; font-size: max(var(--t-s), 1rem); color: var(--fg); background: var(--s0); border: 1px solid var(--line-ctrl); clip-path: var(--cut-s-fill); transition: border-color .22s var(--ease), box-shadow .22s var(--ease), background-color .22s var(--ease); }

.fld textarea { resize: vertical; min-height: 7.5rem; }

/* Native option menus follow color-scheme:dark (the root token); the
   select keeps the same open-chamfer rim as every other field. */
.fld select { cursor: pointer; min-height: var(--tap); }
.fld select:disabled { opacity: .55; cursor: default; }

.fld input::placeholder, .fld textarea::placeholder { color: var(--fg-3); }

.fld input:focus:not(:focus-visible), .fld textarea:focus:not(:focus-visible), .fld select:focus:not(:focus-visible) { outline: none; }

/* The focus twin: the outer halo (0 0 0 3px) is dead paint under the
   clip, and the global outline is clipped away too — the field's own
   indication is this rule alone. The border turns --blue-lt and a 2px
   inset band hugs the padding edge: a continuous 3px blue frame
   (thickness ≥ 2px, contrast ≥ 3:1), offset from the component's
   boundary by the border's own pixel. */
.fld input:focus, .fld textarea:focus, .fld select:focus { border-color: var(--blue-lt); background: var(--s1); box-shadow: inset 0 0 0 2px var(--blue-lt); }

.fld.bad input, .fld.bad textarea, .fld.bad select { border-color: var(--err); }

.fld .msg { display: block; margin-top: 0.35rem; font-size: var(--t-xs); color: var(--err); }

.fld--career { animation: fade .3s var(--ease); }

/* ── the contact form's attachment picker ──
   One field, one native input. Without JS (or after a failed script) the
   native input is the visible control: its own filename readout keeps the
   state truthful. Once main.js is ready, the native input is clipped but
   STILL focusable and labelled (never display:none / visibility:hidden),
   and the cut CTA is the visible action; the selected list is its
   enhancement. The focus ring follows the input onto the CTA, because a
   1px clipped control cannot paint its own outline. */
.fld--attach .att__pick { position: relative; }

.fld--attach .att__input::file-selector-button {
  min-height: var(--tap);
  margin-right: .75rem;
  padding: .45rem .9rem;
  border: 1px solid var(--line-ctrl);
  background: var(--s1);
  color: var(--fg);
  font: inherit;
  font-size: var(--t-xs);
  font-weight: 600;
  cursor: pointer;
  clip-path: var(--cut-s-fill);
}

.fld--attach .att__cta {
  display: none;
  position: relative;
  align-items: center;
  justify-content: center;
  width: max-content;
  max-width: 100%;
  min-height: var(--tap);
  padding: .7em 1.25em;
  border: 1px solid var(--line-ctrl);
  background: var(--s0);
  clip-path: var(--cut-s-fill);
  color: var(--fg);
  font-size: var(--t-s);
  font-weight: 600;
  line-height: 1.1;
  cursor: pointer;
  transition: background-color .22s var(--ease), border-color .22s var(--ease);
}

.att__cta::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: transparent;
  clip-path: var(--cut-ring-s);
}

html.js-ready .fld--attach .att__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

html.js-ready .fld--attach .att__cta { display: inline-flex; }

html.js-ready .fld--attach .att__input:focus-visible + .att__cta { outline: none; }
html.js-ready .fld--attach .att__input:focus-visible + .att__cta::before {
  background: var(--blue-lt);
  clip-path: var(--cut-ring-s-focus);
}

@media (hover: hover) {
  html.js-ready .fld--attach .att__cta:hover { background: var(--wash); }
}

.fld--attach .att__hint {
  margin: .45rem 0 0;
  max-width: 52ch;
  font-size: var(--t-xs);
  color: var(--fg-3);
}

.att__list {
  margin: .7rem 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--line-d);
}

.att__list[hidden] { display: none; }

.att__item {
  display: flex;
  align-items: center;
  gap: .65rem;
  min-height: var(--tap);
  padding: .35rem 0;
  border-bottom: 1px solid var(--line-d);
}

.att__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-s);
  color: var(--fg-2);
  overflow-wrap: anywhere;
}

.att__remove {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  width: var(--tap);
  height: var(--tap);
  padding: 0;
  border: 1px solid var(--line-ctrl);
  background: var(--s0);
  clip-path: var(--cut-s-fill);
  color: var(--fg-2);
  font: inherit;
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color .22s var(--ease), color .22s var(--ease);
}

.att__remove::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: transparent;
  clip-path: var(--cut-ring-s);
}

.att__remove:focus-visible { outline: none; }
.att__remove:focus-visible::before {
  background: var(--blue-lt);
  clip-path: var(--cut-ring-s-focus);
}

@media (hover: hover) {
  .att__remove:hover { background: var(--wash); color: var(--fg); }
}

.fld.bad .att__cta { border-color: var(--err); color: var(--err); }

@media (forced-colors: active) {
  html.js-ready .fld--attach .att__input:focus-visible + .att__cta,
  .att__remove:focus-visible {
    clip-path: none !important;
    outline: 2px solid CanvasText !important;
    outline-offset: 2px !important;
  }
}

@keyframes fade { 
  0% { opacity: 0; transform: translateY(-6px); }
  100% { opacity: 1; transform: none; }
}

/* The visually-hidden pair: .hp (the form's honeypot) and .sr (text only a
   screen reader sees) share one recipe; .sr adds the clip. */
.hp, .sr { position: absolute; left: -9999px; top: auto; width: 1px; height: 1px; overflow: hidden; }
.print-only { display:none }

.form__msg { min-height: 1.6em; margin: 0.9rem 0px 0px; font-size: var(--t-s); font-weight: 600; text-align: center; }

.form__msg.ok { color: var(--ok); }

.form__msg.err { color: var(--err); }

.form__note { margin: 0.75rem 0px 0px; font-size: var(--t-xs); color: var(--fg-3); text-align: center; }

/* Required fields say so visibly, and the disabled submit names the
   missing step through one described line (both copy owners data-owned). */
.form__req { margin: 0.7rem 0 0; font-size: var(--t-xs); color: var(--fg-3); }
.req { color: var(--err); font-weight: 700; margin-left: .18em; }
.form__ready { min-height: 1.25em; margin: .45rem 0 0; font-size: var(--t-xs); color: var(--fg-3); text-align: center; }
.form__note--ts { color: var(--err); font-weight: 600; }
.form__note--ts:empty { display: none; }

/* A script that never reached js-ready cannot wire the form: the submit
   stays hidden and this note names the two channels that still work. It
   appears only after the head watchdog marks js-failed, and never in a
   browser without JS (the <noscript> note owns that state). */
.form__note--fail { display: none; }
html.js-failed:not(.js-ready) .form__note--fail { display: block; }
html:not(.js) .form__note--fail { display: none !important; }

/* Without JS the form cannot be sent (the endpoint speaks JSON only) — and a
   native GET submit would put the visitor's PII in the query string. The
   submit button therefore exists only under html.js; the <noscript> note
   under the form carries the real channels. */
html:not(.js-ready) #form button[type="submit"] { display: none; }

/* The chat launcher opens a JS-only panel — without JS there is nothing to
   open; the support page and the mail links carry the no-JS visitor. The
   socket dies with its key (it would otherwise hang as an empty fixed box
   whose filter paints nothing). */
html:not(.chat-ready) .fab-host { display: none; }

/* The slogan pause is main.js's control. Without a ready script it is a
   dead button; hide it with the ticker's scripted motion. */
:is(html:not(.js), html.js-failed:not(.js-ready)) .hero__pause { display: none; }

.ftr { border-top: 1px solid var(--line-d); }

.ftr__in { position:relative; display: flex; justify-content: space-between; gap: 2.5rem; flex-wrap: wrap; padding-block: clamp(2.25rem, 3.25vw, 3.25rem); }

/* The mark and the legal line are one lockup: logo left, « © … · fait maison »
   right, on the footer's left edge. Wraps to a stack when the measure can't
   hold both. */
.ftr__brand { max-width: 30rem; display: flex; align-items: center; flex-wrap: wrap; gap: 1rem; }

.ftr__brand .logo__i { height: 42px; }

.ftr__brand p { color: var(--fg-3); font-size: var(--t-s); margin: 0px; }

/* Sideways: one row of destinations, not a tall stack — the footer keeps a
   single short band thanks to the © line living under the logo. */
.ftr__nav { display: flex; flex-wrap: wrap; align-items: center; align-self: flex-start; gap: 0.55rem clamp(1.25rem, 2.5vw, 2.25rem); }

.ftr__nav a { color: var(--fg-2); font-size: var(--t-s); display:inline-flex; align-items:center; min-height:var(--tap); }

@media (hover: hover) {
.ftr__nav a:hover { color: var(--fg); }
}

.ftr__brand b { color: var(--fg); font-weight: 600; }

.call { margin: 0px; display: grid; }

.call__row { display: grid; grid-template-columns: auto minmax(0px, 1fr); gap: 0.95rem; padding: 0.72rem 0px; border-bottom: 1px solid var(--line-d); }

.call__row:first-child { padding-top: 0px; }

.call__row:last-child { border-bottom: 0px; padding-bottom: 0px; }

.call__t { font-family: var(--mono); font-size: var(--t-xs); letter-spacing: 0.06em; color: var(--blue-text); font-variant-numeric: tabular-nums lining-nums; padding-top: 0.18rem; }

.call__x { margin: 0px; color: var(--fg-2); font-size: var(--t-s); line-height: 1.55; }

.call__x b { color: var(--fg); font-weight: 600; }

/* ── « le standard » : la carte d'appel qu'une touche répond ──
   Pressing a team key springs this card out from under it on a phone cord
   (drawn between key and card) and asks phone or email. Non-modal: the page
   stays scrollable, Escape hangs up, and the key's own tel: lives inside. */
/* Dim the CONTENT, never the boundary: the dimmed keys stay fully
   interactive while a callcard is open (clicks still route through
   ccOpen, so 1.4.11's inactive-component exemption cannot apply), so
   the pad's rim holds its full 4.03:1 and only the words fade. THE
   DIM RULES WRITE THEIR ONE PROPERTY ONLY — color for the name,
   opacity for the CTA: the role span sits INSIDE the h3 with its own
   --fg-3 (5.63:1) and the blurb its --fg-2 (10.0:1), so a COLOR dim
   on the h3 dims the name alone (6.18:1 at .55 white) and EXEMPTS the
   metadata an opacity dim could not split (the role read 2.49:1 and
   the blurb 3.72:1 under it — below 4.5:1, and 1.4.3 has no transient
   exemption to lean on). .key__more is a SIBLING of the pad (outside
   the <a>) and keeps its own hover law. The fade lives in the owners
   (the verb's through its own opacity transition, the name's through
   the h3's base color transition), so a dimmed key's hover still
   animates its border/background and the verb fades
   .55→1 with no pop. .key__go keeps its richer transition and its
   reveal contract: its rest opacity stays 0 while dimmed — only a
   HOVERED dimmed key's arrow renders .55 (the pre-R7 pad-level
   equivalent; the (0,7,0) gate beats the reveal's (0,3,0)). */
.paddock.has-open .key:not(.is-open) .key__pad .key__who h3 { color: rgba(255,255,255,.55) }
.paddock.has-open .key:not(.is-open) .key__pad .key__verb { color: rgba(255,255,255,.55) }
.paddock.has-open .key:not(.is-open) .key__pad:is(:hover, :focus-visible) .key__go { opacity: .55 }

/* The card is one size everywhere — a team key column and a lone key alike. */
.callcard {
  --cord-h: 62px;
  position: absolute; top: 100%; left: 0; z-index: 60;
  width: min(25rem, calc(100vw - 2rem)); margin-top: .5rem;
  padding: 1.35rem 1.35rem 1.15rem;
  background: var(--s1);
  border: 0; clip-path: var(--cut-l-fill);
  transform-origin: top center;
  animation: cc-in .45s var(--ease-spring) both;
}
/* The card's rim keeps its blue voice (the standard's own edge). 72% blue
   over the card grounds is >=3.1:1 — WCAG 1.4.11's 3:1 boundary (55% was
   2.35:1). */
.callcard::before { background: color-mix(in srgb, var(--blue-lt) 72%, transparent) }
.callcard.is-out { animation: cc-out .22s var(--ease) both }
@keyframes cc-in {
  from { opacity: 0; translate: 0 -10px; scale: .96 }
  to   { opacity: 1; translate: 0 0; scale: 1 }
}
@keyframes cc-out {
  to { opacity: 0; translate: 0 8px; scale: .97 }
}

/* The cord hangs from the trigger's lower right into the card and draws itself. */
.callcard__cord {
  position: absolute; right: 2.15rem; bottom: 100%;
  width: 44px; height: var(--cord-h); overflow: visible;
  pointer-events: none; color: var(--blue-lt);
}
.callcard__cord path {
  fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round;
  stroke-dasharray: 260; stroke-dashoffset: 260;
  animation: draw .7s var(--ease-out) .08s forwards;
}

/* A key has its unfold line to cross and the cord tucks into the key's
   corner; a lone trigger (the nav's text item, the hero's key) has none,
   so on those the card drops a full cord's length and the spiral runs
   under the trigger's bottom edge to the card instead of drawing over it. */
.nav__join .callcard { margin-top: var(--cord-h) }
/* The nav's text item is the trigger at the row's RIGHT end: the generic
   card anchors left and would spring from the header's start while the
   cord hangs from that item's lower right. Right-anchor this one host. */
.nav__join .callcard { left:auto; right:0 }

.callcard__q { margin: 0 0 1rem; color: var(--fg-2); font-size: var(--t-s) }
.callcard__q b { color: var(--fg); font-weight: 600 }
/* The job variant's heading (F20): the poste's title + status replace the
   question — the dialog's name carries the visible text. The status reads
   like the pad's own role line: mono, small, muted. */
.callcard__q small { display: block; margin-top: .25rem; font-family: var(--mono); font-size: var(--t-xs); font-weight: 500; letter-spacing: .08em; text-transform: uppercase; color: var(--fg-3) }

/* The posting detail (F20): three groups, the register's own bullet, and
   the card's scroll surface — the long content scrolls INSIDE this region
   so the card never outgrows the viewport while the apply channels and
   the hang stay reachable below it. The cap rides the vh-then-dvh pair
   (the mobile-chrome pattern the chat budget's max-height documents):
   42vh alone measured the LARGE viewport — the sheet's own documented
   trap — 42dvh tracks the real estate under the browser chrome, with
   vh kept as the fallback.
   The scrollbar rides the global rim law (the * scrollbar-color/width
   rules at the top of the file). */
.callcard__detail {
  display: grid; gap: .95rem; align-content: start;
  max-height: min(26rem, 42vh);
  max-height: min(26rem, 42dvh);
  overflow-y: auto;
  margin: 0 0 1rem; padding-right: .4rem;
}
.callcard__grp h4, .key__detail h4, .common__grp h3 { margin: 0 0 .4rem; font-family: var(--mono); font-size: var(--t-xs); font-weight: 500; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-2) }
.callcard__grp ul, .key__detail ul, .common__grp ul { display: grid; gap: .3rem; margin: 0; padding: 0; list-style: none }
.callcard__grp li, .key__detail li, .common__grp li { position: relative; padding-left: 1.4rem; color: var(--fg-2); font-size: var(--t-s); line-height: 1.45 }

/* The posting detail's no-JS block (F20): the same three groups rendered
   inside the key, visible only when the card mechanism cannot run. One
   voice BY CONSTRUCTION: the group bodies above are ONE owner the card's
   groups and this block share (a voice change to one is a change to both
   — before round 11 these were byte-identical copies and this comment's
   sharing claim was aspirational), the gem dot is the other genuinely
   shared owner (its placement in the shared li::before selector, its
   shape in that rule's width/height), and both
   renderings keep identical computed styles on the screen path and the
   paper path alike — the print block hides .callcard, so this block is
   the posting's one paper path and it reads exactly what the card
   reads. */
.key__detail { display: grid; gap: .95rem; margin: .55rem 0 0; padding-top: .8rem; border-top: 1px solid var(--line-d) }
/* The poste details exist for no-JS (noscript) and for paper. The served
   mirror is screen-hidden; the print block opens it, so an interactive
   card cannot take the posting's specifics away from the sheet. */
.key__detail--print { display: none }

/* ── ce qui est pareil pour les deux postes ──────────────────────────
   The shared lines, said once, below the offers: a press answers with
   only what is SPECIFIC to that job — everything here is what both
   answers share. The ten hook questions are the visitor's SELF-CHECK:
   each row a toggle whose ring fills with the house check, the text
   brightening as it is claimed, the count a live numeral, and the
   payoff line lit exactly while at least one answer stands (the
   punchline answers the visitor's own « oui »). The ring's pop is a
   one-shot beat — the motion doctrine's own class — and the rows
   carry no scroll arrival state at all. Without scripting the plain
   lines render in the twin (the keypad's html:not(.js) precedent),
   and the print block strips the rows to their text: the band is the
   one paper path for everything both postes repeat. */
.common { margin: 2.25rem 0 0 }
.common__quiz { display: grid; margin: .9rem 0 0 }
.common__quiz li + li { border-top: 1px solid var(--line-d) }
.common__quiz button {
  display: flex; align-items: baseline; gap: 1rem; width: 100%;
  min-height: var(--tap);
  padding: .55rem 0; border: 0; background: none; cursor: pointer;
  font: inherit; font-size: var(--t-lead); line-height: 1.4;
  color: var(--fg-2); text-align: left;
  transition: color .25s var(--ease-out);
}
/* « la ponctuation qui tranche »: no checkbox, no ring — the answer IS
   the punctuation. The register numeral rides the call log's own idiom
   (call__t's exact voice), and a press swaps the question's ? for the
   statement's . — two real spans the print path keeps as text, CSS
   reading aria-pressed (the one state owner; main.js never styles).
   The dot's one-shot pop is the press beat, finite as ever. */
.common__n {
  flex-shrink: 0; font-family: var(--mono); font-size: var(--t-xs);
  letter-spacing: .06em; color: var(--blue-text);
  font-variant-numeric: tabular-nums lining-nums;
}
.common__dot { display: none }
.common__quiz button[aria-pressed="true"] { color: var(--fg) }
.common__quiz button[aria-pressed="true"] .common__qm { display: none }
.common__quiz button[aria-pressed="true"] .common__dot {
  display: inline-block; color: var(--blue-text);
  animation: common__pop .24s var(--ease-out);
}
@keyframes common__pop {
  0% { opacity: 0; transform: scale(1) } 55% { opacity: 1; transform: scale(1.22) } 100% { opacity: 1; transform: scale(1) }
}
@media (hover: hover) {
  .common__quiz button:hover { color: var(--fg) }
}
.common__score {
  display: flex; align-items: baseline; gap: 1rem;
  margin: 1rem 0 0; padding-top: .9rem; border-top: 1px solid var(--line-d);
}
.common__count {
  font-family: var(--mono); font-size: var(--t-d3); letter-spacing: .04em;
  line-height: 1; font-variant-numeric: tabular-nums lining-nums;
  color: var(--fg-3); transition: color .35s var(--ease-out);
}
.common__closer {
  font-size: var(--t-lead); color: var(--fg-3);
  transition: color .35s var(--ease-out);
}
.common__score.is-lit .common__count,
.common__score.is-lit .common__closer { color: var(--blue-text) }
/* the no-script twin: the same lines as plain text, the payoff always
   lit (nothing answers back on paper's terms) */
html:not(.js) .common__quiz, html:not(.js) .common__score { display: none }
/* A failed main.js (js-failed, set by the head watchdog after 1.5 s) must
   not erase the ten questions: show the quiz as a readable, non-interactive
   list with every answer punctuation visible, the noscript reading's
   equivalent in a JS-enabled browser. */
html.js-failed:not(.js-ready) .common__quiz button { cursor: default }
html.js-failed:not(.js-ready) .common__qm { display: inline }
html.js-failed:not(.js-ready) .common__dot { display: none }
.common__paper { display: grid; gap: .55rem; margin: 1.3rem 0 0; font-size: var(--t-lead); color: var(--fg) }
.common__paper-closer {
  margin: 1.1rem 0 0; padding-top: 1.1rem; border-top: 1px solid var(--line-d);
  font-size: var(--t-lead); color: var(--blue-text);
}
.common__facts { display: grid; grid-template-columns: 1fr; gap: 1.8rem 2.4rem; margin-top: 1.4rem; padding-top: 1.1rem; border-top: 1px solid var(--line-d) }

/* ── the space-efficient twin columns (the owner's round-12 call) ──
   At phone widths the band and the register read exactly as before:
   one column, every rule below inside the min-width gate only. From
   761px the ten questions become a 2×5 register (01-05 down the left,
   06-10 down the right — grid-auto-flow:column, the same flow the
   numbering already reads in) and the profile's nine rows split 5+4
   the same way, so the section carries its whole pitch in half the
   scroll. The column heads lose their top hairline (the li+li and
   border-bottom patterns cannot know which row starts a column);
   the profile's rows flip to border-top so the two column tails end
   clean and `nope`'s own border-top closes the register. The mechanics
   — the punctuation swap, the numerals, the count — ride the rows,
   untouched. */
@media (min-width: 761px) {
  .common__quiz {
    grid-auto-flow: column; grid-template-rows: repeat(5, auto);
    grid-template-columns: 1fr 1fr; column-gap: clamp(2rem, 4vw, 3.5rem);
  }
  .common__quiz li:nth-child(5n + 1) { border-top: 0 }
  .bar .call {
    grid-auto-flow: column; grid-template-rows: repeat(5, auto);
    grid-template-columns: 1fr 1fr; column-gap: clamp(2rem, 4vw, 3.5rem);
  }
  .bar .call__row { border-bottom: 0; border-top: 1px solid var(--line-d) }
  .bar .call__row:first-child,
  .bar .call__row:nth-child(6) { border-top: 0; padding-top: 0 }
}

/* ── les canaux : the register row ──
   The base row is ONE SURFACE: no box, no rim, no sheen — the bare ink
   mark is the marker, the hairline is the rule, the whole row is the link.
   The callcard's acts keep that flat register inside the dialog (hover
   adds the cut wash and the key__go arrow); the contact directory is the
   one promoted context: four cut tiles in the team key's contour (--s0
   fill, 12px cut, 1px inked rim, the pad's --s1 hover under the pointer
   sheen). Focus materializes the cut ring at the row's scale (the focus
   law, at the sheet's foot). */
.callcard__acts { display: grid; gap: 0 }
.cckey {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center;
  gap: .85rem; padding: .72rem 0; min-height: var(--tap);
  color: inherit; text-decoration: none;
  transition: border-color .35s var(--ease);
}
/* A channel that opens the panel is a BUTTON, not a fake href="#": the
   native button carries Space/Enter and the focus order for free. The
   reset keeps the row's register look — the menu's tile rules below still
   own the contour. */
button.cckey {
  width:100%; margin:0; background:none; border:0;
  font:inherit; text-align:left; cursor:pointer;
}
/* The row's hover light follows the CUT — the same 45° shape every
   surface speaks, never a flat rectangle. The wash rides a pseudo behind
   the content, so the row's hairline stays whole while the light's four
   corners are chamfered; ::before remains the focus ring's owner. The
   pointer sheen deliberately skips these flat register rows (its own
   owner's rule: the glare belongs on glass, not cells). */
.callcard__acts .cckey::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: var(--wash); clip-path: var(--cut-fill);
  opacity: 0; pointer-events: none;
  transition: opacity .25s var(--ease);
}
/* The hover arrow — the team keys' own grammar, drifting in at the row's
   right end (keyboard focus lights it too: the pads' own parity). */
.cckey__go {
  display: inline-flex; color: var(--fg-3);
  opacity: 0; transform: translateX(-8px);
  transition: opacity .35s var(--ease-out), transform .35s var(--ease-spring),
              color .35s var(--ease-out);
}
@media (hover: hover) {
.cckey:is(:hover, :focus-visible) .cckey__go { opacity: 1; transform: none; color: var(--blue-lt) }
}
/* The card's register: the .call grammar wholesale — hairline-top rows
   (the first none), the shared CUT hover wash on these flat rows (they
   never join the glass), the arrow at the row's end. The dialog is the
   only frame here; the rows stay ink inside it. */
.callcard__acts .cckey { border-top: 1px solid var(--line-d) }
.callcard__acts .cckey:first-child { border-top: 0 }
@media (hover: hover) {
.callcard__acts .cckey:hover::after { opacity: 1 }
.callcard__acts .cckey:hover { border-color: var(--hover-edge) }
}
.cckey__ico { color: var(--blue-lt) }
.cckey__ico svg { width: 24px; height: 24px }
.cckey__txt b { display: block; font-size: var(--t-s); font-weight: 600; letter-spacing: -.01em }
/* The sub-line under a bold title — the channel rows and the download box
   share it; each tunes its leading (the channel rows go mono and wrap). */
.cckey__txt small, .dlbox__txt small { display: block; font-size: var(--t-xs); color: var(--fg-3); }
.cckey__txt small {
  margin-top: .1rem;
  font-family: var(--mono);
  overflow-wrap: anywhere;
}
/* The callcard's tel line is three markup-owned slots — the label, the
   · separator (the key__sep pattern, aria-hidden, hidden when there is
   no extension), the extension — one rendered line: the trio flows
   inline beside each other, the label keeps the row's own top spacing,
   and the sep's flanks carry the ' · ' margins the composed string once
   did, so the line reads exactly as before, in both languages. */
.cckey__txt small[data-cc-tel],
.cckey__txt small[data-cc-tel-sep],
.cckey__txt small[data-cc-tel-ext] { display: inline-block; }
.cckey__txt small[data-cc-tel-sep] { margin-inline: .6em; }

.callcard__hang {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: var(--tap);
  margin: 1.1rem 0 0 auto; padding: .35rem 0;
  background: none; border: 0; border-bottom: 1px solid transparent;
  font: inherit; font-size: var(--t-xs); font-weight: 600;
  color: var(--fg-3); cursor: pointer;
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
@media (hover: hover) {
.callcard__hang:is(:hover, :focus-visible) { color: var(--fg); border-bottom-color: var(--wash-hi) }
}

/* The power-on wave animates the RIM pseudo's colour now — the border it
   once pulsed is the pseudo's paint, and the glow half (a box-shadow) is
   dead under the cut's clip: the rim pulse alone carries the beat. */
@keyframes keyon {
  0% { background-color: var(--line-d); }
  40% { background-color: color-mix(in srgb, var(--blue-lt) 80%, transparent); }
  100% { background-color: var(--line-d); }
}
/* The contact directory's tiles wake with the same power-on wave, but
   their rest rim is the interactive boundary --line-ctrl, not the
   decorative keypad --line-d. One parallel keyframe keeps the tile's own
   rest colour at both ends; the beat is the team's own. */
@keyframes keyon-ctrl {
  0% { background-color: var(--line-ctrl); }
  40% { background-color: color-mix(in srgb, var(--blue-lt) 80%, transparent); }
  100% { background-color: var(--line-ctrl); }
}

.js .key.in .key__pad::before { animation: keyon .9s var(--ease-out) calc(var(--d,0) * 70ms); }
/* The power-on wave animates the rim's background-color, and animations
   outrank normal author rules; the menu tile's own fix is the pattern. The
   keyboard band must survive while the wave is in flight. */
.key__pad:focus-visible::before {
  background: var(--blue-lt) !important;
  clip-path: var(--cut-ring-focus);
}
/* Bound to the reveal class, never to hover/focus: the wave fires once as
   the section enters and the four tiles keep their data-d order through
   the li's --d, the same 70ms stagger as the team board. */
.js .menu .r.in .cckey::before { animation: keyon-ctrl .9s var(--ease-out) calc(var(--d,0) * 70ms); }

.mesh::after { content: ""; position: absolute; inset: 0px; opacity: 0.05; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E"); }

.sr { clip-path: inset(50%); }

.tgl { display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem; min-width: var(--tap); height: var(--tap); padding: 0px 0.55rem; background: transparent; color: var(--fg); border: 0; clip-path: var(--cut-s-fill); font-style: inherit; font-variant: inherit; font-stretch: inherit; line-height: inherit; font-family: inherit; font-optical-sizing: inherit; font-size-adjust: inherit; font-kerning: inherit; font-feature-settings: inherit; font-variation-settings: inherit; font-language-override: inherit; font-size: var(--t-s); font-weight: 600; cursor: pointer; text-decoration: none; transition: background-color .25s var(--ease),
             transform var(--dur-out) var(--ease-spring),
             translate var(--dur-out) var(--ease-spring),
             scale var(--dur-out) var(--ease-spring);
  /* Physical object (« la lumière »): the knob leans into the light —
     --rx/--ry écrits par main.js, composés here. Text stays level; the
     knob is the hero of the twist (full ±7°rx/±5°ry). */
  transform: perspective(var(--persp)) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg)); }

@media (hover: hover) {
.tgl:is(:hover, :focus-visible) { background: var(--wash); }
.tgl:is(:hover, :focus-visible)::before { background: var(--wash-hi); }
}

/* The key « answers »: one ring on the pad (the load wave shares keyon).
   Scoped under .js to outrank the wave rule while both apply. The hero has
   no card key any more — the board's pads are the only answering keys. */
.js .key.is-open .key__pad::before { animation: keyon .9s var(--ease-out) }

/* The Microsoft-licensing note belongs with the pricing terms above it:
   one block of explanatory text under the heading, not a stray strip below the
   cards. Wide like the lead, so it doesn't break into many lines. */
.shead__note { margin-top:.85rem; color:var(--fg-2); font-size:var(--t-s); max-width:none }

/* ── « le standard » grandeur nature : les trois lignes ──
   The same rows the menu answers with (the callcard's own channel
   component), promoted to section scale: one line per channel, each going
   straight to it. Labels are the chooser's — one owner. */
.menu {
  display:grid; gap:var(--gap);
  /* The channel directory: four cut tiles on ONE line, the label the only
     visible fact and the promise kept in the link's name (sr-only). The
     count steps 4 → 2 → 1 as the cell narrows; a label never wraps. */
  grid-template-columns:repeat(4,minmax(0,1fr));
  margin:clamp(1.6rem,2.6vw,2.2rem) 0 clamp(2.2rem,4vw,3.2rem);
}
/* The buttons wear the TEAM KEY's contour — the same 12px cut fill, its
   1px inked rim AND the pad's shared pointer field: the four tiles carry
   data-grav, so the one [data-grav] physics in main.js writes the same
   --tx/--ty, --rx/--ry and --sc here that it writes on the team pads.
   The composition is the pad's own, verbatim. A boundary-bearing control
   is the design law's own exception to ONE SURFACE, so the directory may
   carry the frame the team pads wear; the card's rows stay flat register
   lines inside the dialog's frame (boxes never stack). The hover is the
   pad's own: the fill steps --s0 → --s1 under the pointer sheen. */
.menu .cckey {
  padding:.6rem .75rem;
  min-height:var(--tap);
  grid-template-columns:auto minmax(0,1fr);
  background:var(--s0); clip-path:var(--cut-fill);
  transform: perspective(var(--persp))
    rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
    translate3d(var(--tx,0px),var(--ty,0px),0)
    scale(var(--sc,1));
  will-change: transform;
  transition:background-color .35s var(--ease);
}
.menu .cckey::before {
  content:""; position:absolute; inset:0; pointer-events:none;
  background:var(--line-ctrl); clip-path:var(--cut-ring);
  transition:background-color .25s var(--ease), clip-path 160ms var(--ease);
}
@media (hover: hover) {
.menu .cckey:hover { background:var(--s1) }
}
/* The tile's rest rim is (0,2,0); the shared focus law's ring is (0,2,0)
   too and only source order keeps the band on top. Name this host's focus
   explicitly at (0,3,0) so a later rest rule can never win the tie.
   The entry wave animates this pseudo's background-color, and animations
   outrank normal author rules: the focus fill is the one host-local
   important declaration so the keyboard band stays whole while the wave
   is in flight. Do not cancel the animation here — removing and restoring
   it would restart the wave when focus leaves. */
.menu .cckey:focus-visible::before {
  background:var(--blue-lt) !important; clip-path:var(--cut-ring-focus);
}
.menu .cckey__txt b { white-space:nowrap }
/* The promise leaves the line but stays in the link's accessible name:
   the directory shows the channel, the screen reader keeps the context. */
.menu .cckey__txt small {
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap;
}
@media (max-width:900px) {
  .menu { grid-template-columns:repeat(2,minmax(0,1fr)) }
}
/* No JS hides the chat tile, leaving three: four tracks would leave a
   hole at the measure. Below 900 the ordinary 2+1 handles three. */
@media not all and (max-width:900px) {
  html:not(.js) .menu { grid-template-columns:repeat(3,minmax(0,1fr)) }
  /* The closed chat tile leaves the directory with three visible tiles;
     four tracks left an empty quarter. The 2+1 and 1-col phone layouts
     already collapse cleanly. */
  html.chat-closed .menu { grid-template-columns:repeat(3,minmax(0,1fr)) }
}
@media (max-width:420px) {
  .menu .cckey { gap:.6rem; padding-inline:.4rem }
  .menu .cckey__txt b { font-size:.8125rem }
}
@media (max-width:359px) {
  .menu { grid-template-columns:1fr }
}

.contact { display: grid; gap: clamp(2.5rem, 5vw, 4.5rem); grid-template-columns: minmax(0px, 0.9fr) minmax(0px, 1.1fr); align-items: start; }

.form__row { display: grid; gap: 0 var(--gap); grid-template-columns: minmax(0px, 1fr) minmax(0px, 1fr); }

.opts { display: grid; gap: 0.5rem; grid-template-columns: repeat(3, minmax(0px, 1fr)); }

/* The gem's socket — the one unclipped parent « the cut standard » needs:
   a box-shadow is dead paint under a clip-path, so the fab's halo is
   re-cast HERE as a drop-shadow, which follows the octagon's own
   silhouette (the parent-filter pattern). The socket also hosts the live
   dot: the dot hangs past the gem's cut corner, where the key's own clip
   could never paint it — chat.js's state owner (data-live, on the
   button) is read across the boundary by :has(). */
.fab-host { position: fixed; right: calc(1.25rem + var(--sa-r)); bottom: calc(1.25rem + var(--sa-b)); z-index: 80; }
/* The gem yields the bottom-right corner while the contact form or the
   footer is on screen (main.js's observer): those regions carry full-width
   actions and the privacy links, which a fixed 44px+ pill covered at every
   tested width. It moves under the header there and returns to its corner
   when the region leaves. */
html.fab-yield .fab-host {
  top: calc(var(--hdr-h) + 1rem);
  bottom: auto;
}
html.fab-yield .fab-host .callcard {
  top: calc(100% + .5rem);
  bottom: auto;
  transform-origin: top right;
}
.fab-socket { position: relative;
  filter: drop-shadow(0 12px 34px color-mix(in srgb, var(--blue) 44%, transparent)); }
/* The gem's card springs UP: the pill sits at the viewport's bottom edge,
   so a downward card would open below the fold — and a fixed socket
   cannot scroll it into view. Right-aligned like the gem, the entrance
   origin at its bottom-right; the cord has no run to draw (the gem and
   its panel are one floating pair). The height cap keeps the card on
   short viewports, scrolling inside instead of leaving the screen. */
.fab-host .callcard {
  top: auto; bottom: calc(100% + .5rem); left: auto; right: 0;
  margin: 0;
  transform-origin: bottom right;
  max-height: calc(100vh - 5.5rem); overflow-y: auto;
}
.fab-host .callcard__cord { display: none; }
.fab-socket:has(.fab--chat[data-live])::after {
  content: ''; position: absolute; top: -4px; right: -4px;
  width: 10px; height: 10px;
  /* the dot is a tiny gem — the tiles' own octagon at the 30% proportion,
     the one round thing the amendment still owed the cut standard */
  clip-path: polygon(30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 30%);
  background: var(--on-blue); border: 2px solid var(--blue);
}
/* The switchboard's answer key: the gem octagon — leg 14, the brand
   scale — whatever the key's width (the label stays on wide viewports;
   the 44px --tap box is the phone's, below). */
.fab { display: inline-flex; align-items: center; gap: 0.55rem; padding: 0.85rem 1.3rem; background: var(--blue); color: var(--on-blue); border: 0; clip-path: var(--cut-gem-fill); font-size: var(--t-s); font-weight: 600; transition: background-color .25s var(--ease), color .25s var(--ease), transform .25s var(--ease), translate var(--dur-out) var(--ease-spring), scale var(--dur-out) var(--ease-spring); }

/* Physical object + magnet: the drift and the lean share one transform
   (--mx/--my and --rx/--ry écrits par main.js). The hover lift is the shared
   --py (« la pression ») — no raw translateY here, so the two never fight. */
@media (hover: hover) {
.fab:is(:hover, :focus-visible) { background: var(--blue-lt); color: var(--on-blue-lt); }
}
.fab {
  --mag-x:.24; --mag-y:.30;                 /* a floating key answers harder */
  --tilt-y:5deg;                            /* gentle ±5° lean */
  transform: perspective(var(--persp)) rotateX(var(--rx,0deg)) rotateY(var(--ry,0deg))
             translate3d(var(--mx,0px),var(--my,0px),0);
}

.plans__cta {
  position:relative;
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:1rem; margin-top:clamp(1.5rem,2.5vw,2.25rem);
  padding-top:clamp(1.25rem,2vw,1.75rem);
}
.plans__cta p { margin:0; max-width:none; color:var(--fg-2) }

/* Rows wake up under the cursor. The waking row is a rounded chip: the cells
   are square, so an un-radiused wash would paint a hard-edged rectangle of
   light inside the panel's own edge. The 10px is the cut standard's one
   sanctioned transient radius — a hover/focus-only state that never rests,
   decided per the owner's own option (« keep the 10px or take --cut-s —
   one pattern only »); the resting hairline stays full-width and uncut. */
.call__row, .cinfo li {
  transition:background-color .35s var(--ease), translate .35s var(--ease-out),
             border-color .35s var(--ease), border-radius .35s var(--ease);
}
@media (hover: hover) {
.call__row:hover {
  background:var(--wash);
  border-color:var(--hover-edge);
  border-radius:10px;
}
}
.call__row:focus-within {
  background:var(--wash);
  border-color:var(--hover-edge);
  border-radius:10px;
}
@media (hover: hover) {
.cinfo li:hover { translate:0 -4px; border-color:var(--hover-edge) }
}

/* ═══════════════════════════════════════ EMPLOIS : les postes ouverts ══
   The open postes are keys on the same board as the team keypad — same pad,
   same gravity, same power-on wave, same « le standard » answer — so « le
   clavier » continues into the section: two keys are free. The pad is the
   link (to the full posting without JS); with JS a press applies through the
   standard, whose form channel is the contact form. */

/* Two keys stay two keys: the wide-viewport 4-column keypad would strand them
   in quarter-width cells. Pads fill their cell, so the two keys in a row are
   the same height whatever their copy does. */
@media (min-width:1700px) {
  .paddock--postes { grid-template-columns:repeat(2,minmax(0,1fr)) }
}
.paddock--postes .key { grid-template-rows:1fr auto }

/* The poste's one-liner under its title (a team key carries none). */
.key__who p { margin:.2rem 0 0; color:var(--fg-2); font-size:var(--t-s); line-height:1.55 }

/* The apply verb — the standing signifier the dead chip once framed, now
   ink under the blurb: the eyebrow voice (the shared rule above) with the
   pad's own register spacing. Its opacity transition is its own too — the
   ghost surface that once carried it is gone, and the dim below fades
   .55→1 with no pop (the chip's exact contract). */
.key__verb { display:block; margin:.6rem 0 0; letter-spacing:.08em;
  transition:opacity .35s var(--ease) }

/* ── ce qui compte : le profil fusionné des deux offres ──
   The register is the call log's: one numeral, one line. The four values
   flow straight into the five hard facts — values and requirements are
   ONE « who thrives here » artifact, stated once for the section (the
   owner's round-12 surgery: before, the requirements lived twice). The
   honest negative closes the register — who should keep walking. From
   761px the register reads in twin columns (the block above); the
   hero's own call log is untouched — these rules scope to .bar only. */
.bar { margin-top:clamp(1.75rem,3.5vw,2.75rem); max-width:82rem }
.bar__nope {
  margin:.9rem 0 0; padding-top:.9rem;
  border-top:1px solid var(--line-d);
  color:var(--fg-3); font-size:var(--t-s); line-height:1.6;
}

/* ── avantages : six cellules du registre ── */
.perks { margin-top:clamp(1.75rem,3.5vw,2.75rem) }
.perks__grid {
  display:grid;
  gap:clamp(1.25rem,2.4vw,1.75rem) clamp(1.5rem,3vw,3rem);
  grid-template-columns:repeat(3,minmax(0,1fr));
}
.perk {
  display:flex; align-items:center; gap:.75rem;
  padding-top:clamp(.95rem,1.6vw,1.25rem);
  border-top:1px solid var(--line-d);
  font-size:1.0625rem; font-weight:600; letter-spacing:-.015em; color:var(--fg);
  transition:border-color .35s var(--ease);
}
@media (hover: hover) {
.perk:hover { border-top-color:var(--hover-edge) }
}
.perk__ico { width:22px; height:22px }
.perk__ico svg { width:20px; height:20px }

/* ═════════════════════ download pointer (dlptr) ═════════════════════
   Chrome/Edge/Safari all put a download at the TOP-RIGHT of the browser
   window. The page cannot measure browser chrome, so the arrow is pinned to
   the top-right corner of the VIEWPORT and points up-and-right into it.

   --ptr-x / --ptr-y are measured from JS against the live viewport so the
   head lands inside the corner at any window size or zoom level, rather than
   being a fixed guess that misses on some screens. */
.dlptr {
  position:fixed;
  top:var(--ptr-y, 8px);
  right:var(--ptr-x, 238px);
  /* Above the fixed header (100): the head points UP at the download icon in
     the browser toolbar, whose column passes over the header band.
     pointer-events:none, so it never blocks. */
  z-index:110;
  width:186px;
  pointer-events:none;
  color:var(--fg);
  filter:drop-shadow(0 2px 10px color-mix(in srgb, var(--s0) 85%, transparent));
  opacity:0;
  visibility:hidden;
  transition:opacity .4s var(--ease-out), visibility .4s;
}
.dlptr.is-on { opacity:1; visibility:visible }

/* No mirroring: the path already ends with the head at the box's top-right,
   so it points up-and-right into the corner. overflow:visible lets the head
   marker breathe past the box edge. */
.dlptr__svg { display:block; width:100%; height:auto; overflow:visible }
.dlptr__shaft { stroke-dasharray:200; stroke-dashoffset:200 }
.dlptr.is-on .dlptr__shaft { animation:draw .9s var(--ease-out) .1s forwards }
/* One line-draw owner: the download pointer's shaft and the call-card cord. */
@keyframes draw { to { stroke-dashoffset:0 } }

.dlptr__tip {
  display:block; margin-top:.2rem;
  font-size:var(--t-xs); font-weight:600; color:var(--fg);
  text-align:right; line-height:1.3;
}
.dlptr__hint {
  display:block; margin-top:.15rem;
  font-size:.6875rem; color:var(--fg-3);
  text-align:right; line-height:1.35;
  max-width:170px; margin-left:auto;
}

/* Narrower box on small windows so it never crowds the content underneath. */
@media (max-width:900px) {
  .dlptr { width:150px }
  .dlptr__hint { max-width:150px }
}

/* ══════════════════════════════════════════════ TEAMVIEWER PAGE ══ */
.tv-hero { min-height:100svh; padding-block:calc(var(--hdr-h) + 1.5rem) 3.5rem }
.tv-hero__in { max-width:76rem }
.tv-hero h1 { max-width:16ch }
.tv-hero__sub {
  max-width:46rem; margin-bottom:2.25rem;
}

/* Télécharger et les instructions, côte à côte : le fichier à gauche, les
   trois gestes à droite — un seul rang, sans vide entre les deux. La colonne
   de téléchargement suit le regard pendant qu'on lit les étapes. */
.tv-cols {
  display:grid;
  grid-template-columns:minmax(300px,25rem) minmax(0,1fr);
  gap:clamp(1.75rem,3vw,3.25rem);
  align-items:start;
}
.tv-cols__dl { position:sticky; top:calc(var(--hdr-h) + 1.25rem) }
@media (max-width:1120px) {
  .tv-cols { grid-template-columns:1fr }
  .tv-cols__dl { position:static }
}

/* download box */
.dlbox {
  position:relative;
  padding:clamp(1.1rem,1.8vw,1.5rem);
  background:var(--panel);
  border:0; clip-path:var(--cut-l-fill);
  backdrop-filter:blur(16px);
  transition:background-color .5s var(--ease-out);
}
/* position:relative also anchors the sheen pseudo to the box itself —
   before the cut, its ::after drifted to the sticky column's own box and
   only matched by luck of geometry. */
.dlbox.is-done::before { background:color-mix(in srgb, var(--ok) 40%, transparent) }
.dlbox.is-done { background:var(--panel-done) }

.dlbox__row { display:flex; align-items:center; gap:1rem; flex-wrap:wrap }
.dlbox__ico {
  width:32px; height:32px;
}
.dlbox__ico svg { width:23px; height:23px }
.dlbox__txt { flex:1 1 14rem; min-width:0 }
.dlbox__txt b {
  display:block; font-size:1rem; font-weight:600; letter-spacing:-.015em;
  overflow-wrap:anywhere;
}
.dlbox__txt small { margin-top:.15rem }
.dlbox .btn { flex-shrink:0 }

.dlbox__alt {
  display:flex; align-items:center; gap:.75rem; flex-wrap:wrap;
  margin:1rem 0 0; padding-top:.9rem;
  border-top:1px solid var(--line-d);
  font-size:var(--t-xs); color:var(--fg-3);
}
.dlbox__alt a {
  position:relative;
  min-height:var(--tap); min-width:var(--tap); display:inline-flex; align-items:center; justify-content:center;
  padding:.25em 0;
  border:0; border-bottom:1px solid var(--line-ctrl);
  clip-path:var(--cut-s-fill);
  color:var(--fg-2);
  transition:color .3s var(--ease-out), border-color .3s var(--ease-out);
}

@media (hover: hover) {
.dlbox__alt a:hover { color:var(--fg); border-bottom-color:var(--line-ctrl) }
}
/* Confirmation line shown after a download starts (support.js writes into it).
   Green normally, red on the failure path (--err) so a real error is not
   mistaken for success — the amber word named a token the rule never used;
   is-err is the class. */
.dlbox__note {
  margin:.85rem 0 0; font-size:var(--t-xs); line-height:1.5;
  color:var(--ok);
}
.dlbox__note.is-err { color:var(--err) }
.dlbox__note-link { color:var(--blue-text); text-decoration:underline; text-underline-offset:2px }

.dlbox.is-busy .btn { opacity:.7; pointer-events:none }


/* animated walkthrough — ONE SURFACE: the step boxes died; the steps ride
   the .call register (the hero's own « fil d'appel » grammar): hairline
   bottoms, the numeral in the call__t voice, the tick at the row's end.
   The is-next wake recolors the hairlines — the first step's own line,
   numeral and tick carry the ok voice. */
.walk { display:grid; gap:0; max-width:54rem }
.walk__step {
  display:grid; grid-template-columns:auto minmax(0,1fr) auto;
  align-items:start; gap:1.25rem;
  padding:.72rem 0;
  border-bottom:1px solid var(--line-d);
  transition:border-color .5s var(--ease-out);
}
.walk__step:first-child { padding-top:0 }
.walk__step:last-child { border-bottom:0; padding-bottom:0 }
/* Once the file is on its way, the steps read as the active register. */
#etapes.is-next .walk__step { border-bottom-color:var(--hover-edge) }
#etapes.is-next .walk__step:first-child { border-bottom-color:color-mix(in srgb, var(--ok) 42%, transparent) }

.walk__n {
  display:block;
  min-width:2.25rem;
  padding-top:0.18rem;
  font-family: var(--mono); font-size: var(--t-xs);
  letter-spacing: 0.06em;
  color: var(--blue-text);
  font-variant-numeric: tabular-nums lining-nums;
  transition:color .5s var(--ease-out);
}
#etapes.is-next .walk__step:first-child .walk__n { color:var(--ok) }

.walk__body h3 { margin:0 0 .4em; font-size:1.1875rem }
.walk__body p { margin:0; color:var(--fg-2); font-size:var(--t-s) }
.walk__body { min-width:0 }
.walk__acts { display:flex; gap:.6rem; flex-wrap:wrap; margin-top:1.1rem }
/* The CTA diet's one affordance keeps its number-bearing label on ONE
   line (.btn's own nowrap; the old wrap escape died with the buttons it
   saved). The ≤420px band slims the padding so « Appeler le 819 987-1616 »
   clears the step's content column at 320px — the chat card's own
   narrow-band precedent, the label never touched. */
@media (max-width:420px) {
  .walk__acts .btn { padding-inline:1.3em }
}

/* The tick draws itself once the step is on screen. */
.walk__tick {
  display:grid; place-items:center;
  width:28px; height:28px;
  color:var(--fg-3);
  opacity:0; transform:scale(.7);
  transition:opacity .6s var(--ease-out), transform .6s var(--ease-spring),
             color .5s var(--ease-out);
}
.walk__step.in .walk__tick { opacity:1; transform:none }
#etapes.is-next .walk__step.in .walk__tick { color:var(--ok) }

/* The tv-note — ONE SURFACE: the warn box died; the note reads as the
   GOV.UK warning-text idiom: the warn-colored mono uppercase eyebrow
   (the .tag voice, one owner), the existing bold lead, the prose under
   it, and the hairline-top the register uses. The support page keeps
   exactly one box: the dlbox. */
.tv-note {
  margin-top:2rem; padding-top:1.1rem;
  border-top:1px solid var(--line-d);
  color:var(--fg-2); font-size:var(--t-s); max-width:54rem;
}
.tv-note .tag { margin:0 0 .6rem; color:var(--warn) }
.tv-note b { display:block; margin-bottom:.3em; color:var(--fg); font-weight:600 }
.tv-note strong { color:var(--warn) }


/* ════════════════════════════════ POLITIQUE DE CONFIDENTIALITÉ ══
   A document, not a pitch — the register's quiet cousin. The application
   clause leads under the headline, a summary lists the sections, and the
   body is one hairline-topped row per section with prose capped at the
   reading measure. The quietest page on the site stays in the same world. */
.legal-hero {
  gap:clamp(1rem,2vh,1.75rem);
  padding-block:calc(var(--hdr-h) + 1.5rem) clamp(1.25rem,2.2vw,2rem);
}
.legal-hero h1 { margin-bottom:.42em }
.legal__lead {
  color:var(--fg-2);
  font-size:var(--t-lead); line-height:1.6;
  max-width:46rem;
}
.legal__updated {
  margin:.75rem 0 0;
  color:var(--fg-3); font-size:var(--t-xs);
  letter-spacing:.02em;
}

/* Le sommaire : l'index du registre — un filet par section, le titre à
   gauche, la flèche à droite. Deux colonnes sur la mesure, une sur
   téléphone. Les cibles font 44px — les exceptions documentées vivent
   au --tap. */
.legal__toc {
  display:grid; grid-template-columns:repeat(2,minmax(0,1fr));
  column-gap:clamp(1.5rem,3vw,3rem);
  max-width:56rem; margin-top:clamp(1.75rem,2.8vw,2.5rem);
  border-top:1px solid var(--line-d);
}
.legal__toc a {
  display:flex; align-items:center; justify-content:space-between; gap:.75rem;
  min-height: var(--tap); padding: .45rem .15rem;
  border-bottom:1px solid var(--line-ctrl);
  font-size:var(--t-s); color:var(--fg-2);
  /* the row slides on `translate`, the site's lift vocabulary — never on a
     layout property (a padding shift reflows every row below it). */
  translate:0 0;
  transition:color .3s var(--ease-out), border-color .35s var(--ease),
             translate .35s var(--ease-out);
}
.legal__toc-go {
  flex-shrink:0; color:var(--fg-3);
  transition:color .35s var(--ease-out), transform .35s var(--ease-spring);
}
@media (hover: hover) {
  .legal__toc a:hover { color:var(--fg); border-bottom-color:var(--line-ctrl); translate:.4rem 0 }
  .legal__toc a:hover .legal__toc-go { color:var(--blue-lt); transform:translateX(2px) }
}

/* Le registre : une section par filet, comme les offres — sans carte, sans
   boîte. La prose garde la mesure de lecture : un document se lit, il ne
   se survole pas. */
.legal__sec {
  border-top:1px solid var(--line-d);
  padding-block:clamp(1.75rem,3vw,2.75rem);
}
.legal__sec h2 {
  /* a document's own scale step: under the page's d2, above a subsection */
  font-size:clamp(1.5rem,1.2rem + 1.4vw,2.3rem);
  margin-bottom:.55em;
}
.legal__sec h3 { margin:1.15em 0 .35em; font-size:1.0625rem; letter-spacing:-.012em }
.legal__sec p {
  max-width:70ch; margin-bottom:.9em;
  color:var(--fg-2);
  hyphens:auto;             /* html lang is set — the French sets itself */
  overflow-wrap:anywhere;   /* a long URL cannot widen the 320px page */
  text-wrap:pretty;
}
.legal__sec a { overflow-wrap:anywhere; }
.legal__sec p:last-child { margin-bottom:0 }
.legal__sec p b { color:var(--fg); font-weight:600 }

.legal__list { display:grid; gap:.45rem; margin:.25em 0 1em; max-width:70ch }
.legal__list li {
  position:relative; padding-left:1.5rem;
  color:var(--fg-2);
}
.legal__list li::before { left:.1rem; top:.5em; width:7px; height:7px; }


/* ───────────────────────────────────────────────────── reveal ── */
/* The only reveal left is the headline's line mask: lines ride up out of
   their own clip on the transform axis, 1.15s var(--ease-out), a 105%
   line-height offset. Opacity is never reduced — a blocked script means
   fully visible lines, not a blank page (the hidden start state is
   scoped to `html.js`, which an inline <head> script sets). */
.r {
  opacity:1;
  transform:none;
}
/* Scroll reveals are gone on purpose: content must never be caught
   half-hidden mid-scroll. The only arrival moments left are one-shot
   effects (hero line mask, counters, the keypad power-on wave) — all
   finite. The hover lift still transitions `translate` immediately
   (no stagger delay leaks into interaction). */
/* The generic reveal no longer owns a transition: `transition` is a
   shorthand, and a .js .r rule (0-2-0) outranks every component's own
   (0-1-0) — it was silently replacing the .s/.plan/.perk/.walk__step/
   .dlbox hover and press transitions. The h1's line mask is a CSS
   animation now; nothing here needs a transition. */

/* The forfait card is a physical object (« la lumière »): it leans into the
   light. The reveal owns `transform` here (the reset above), so the lean rides
   the independent `rotate` property — main.js writes the axis-angle form of
   --rx/--ry — and `.plans` supplies the perspective. This rule must follow
   `.js .r` (same 0-2-0) to carry the `rotate` transition; the `translate`
   entry keeps the reveal's hover-lift contract. */
.js .plan {
  transition:rotate var(--dur-out) var(--ease-spring), translate .5s var(--ease-out);
}

/* Headline lines ride up out of their own clip, staggered per line.
   The h1 carries `.r.ln`; each line is `.ln` wrapping an inner `.ln__i`, which
   is what actually translates. */

.ln__i { transform:none }
/* The mask is a CSS animation, not a JS-gated state: the inline .js class
   is set before paint, so a blocked main.js would otherwise leave the
   headline translated out of its clip forever. The animation runs with
   the stylesheet; main.js's .in class is now only a harmless marker. */
.js .ln__i { animation:ln-in 1.15s var(--ease-out) both }
@keyframes ln-in {
  from { transform:translate3d(0,105%,0) }
  to   { transform:none }
}
/* Stagger lives on the line index so each word-line lands in sequence. */
.r.ln .ln:nth-child(1) .ln__i { animation-delay:calc(var(--d,0) * 95ms + 60ms) }
.r.ln .ln:nth-child(2) .ln__i { animation-delay:calc(var(--d,0) * 95ms + 160ms) }
.r.ln .ln:nth-child(3) .ln__i { animation-delay:calc(var(--d,0) * 95ms + 260ms) }
/* The print settle owns the last word: beforeprint adds this class, and the
   !important reset beats both the roll's inline styles and the ln-in
   animation, including timers that were already in flight. */
.hero h1.print-settled .ln__i {
  animation:none !important; transition:none !important;
  transform:none !important; opacity:1 !important; filter:none !important;
}

/* Register cells wake via their border (see .s:hover / .perk:hover) and
   the rows via `translate` (the cinfo wake): `transform` is owned by the
   reveal, which resets it to none at specificity 0-2-0 — a transform-based
   lift here would be overridden and jitter against it. */

/* ─────────────────────────────────────────────── responsive ── */
/* The slogan « Quand ça bloque, » must hold ONE line. Below 360px the type
   floor steps down (2.75rem → 2.45rem) — scoped to that band only, so the
   designed scale is untouched everywhere else. */
@media (max-width:359px) {
  :root { --t-d1: clamp(2.45rem, 1.4rem + 4.2vw, 5.5rem) }
}

@media (max-width:1180px) {
  .hero__in { grid-template-columns:1fr; }
  /* minmax(0,…), like the desktop rule: a bare 1fr means minmax(auto,1fr),
     whose content floor once blew the page sideways on phones — the form's
     300px-minimum widget priced this track at 353px in a 350px viewport.
     Zero base: the column takes its share, and content that refuses to
     shrink overflows its own box, never the page. */
  .contact { grid-template-columns:minmax(0,1fr) }
}

@media (max-width:900px) {
  :root { --sec:2.25rem }
  .plan { padding-block:1.25rem }
  .key__pad { padding:1rem }
  .burger { display:flex }
  .hdr__act .btn { display:none }
  /* .nav carried the auto margin on desktop; as a drawer it leaves the flow,
     so the controls take over the push to the right edge. */
  .hdr__act { margin-left:auto }

  /* A content-height cut sheet, not a full-height slab: the menu ends with
     its last control, the veil shows the page behind, and the blank tail of
     the old sheet (which measured 728px of ground for ~480px of links) is
     gone. The max height keeps every destination reachable in landscape; a
     type-enlarged host scrolls the sheet inside itself. */
  .nav {
    position:fixed; inset:var(--hdr-h) 0 auto; z-index:95;
    flex-direction:column; gap:0; margin:0;
    padding:1rem var(--pad) 1.25rem;
    max-height:calc(100vh - var(--hdr-h) - 12px);
    max-height:calc(100dvh - var(--hdr-h) - 12px);
    background:var(--s0);
    border-top:1px solid var(--line-d);
    overflow-y:auto;
    -webkit-overflow-scrolling:touch;
    box-shadow:var(--elev);
    clip-path:polygon(0 0,100% 0,100% calc(100% - 16px),calc(100% - 16px) 100%,16px 100%,0 calc(100% - 16px));
    translate:0 -2%; opacity:0; visibility:hidden;
    transition:
      translate .5s var(--ease-out),
      opacity .35s var(--ease-out),
      visibility .5s;
  }
  .nav.on {
    translate:0 0; opacity:1; visibility:visible;
    /* Opening reveals at once so the first nav link can take focus on the
       next frame; closing keeps the base .5s visibility delay to fade. */
    transition:translate .5s var(--ease-out), opacity .35s var(--ease-out), visibility 0s;
  }

  /* No JS, or main.js failed before it could bind: the burger has nothing
     to open. The header returns to the flow and the nav becomes a wrapped
     row under it, so every destination stays reachable. */
  :is(html:not(.js), html.js-failed:not(.js-ready)) .hdr { position:static }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .hdr__in { flex-wrap:wrap; padding-block:.5rem }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .burger { display:none }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .nav {
    position:static; flex:1 0 100%; order:3;
    flex-direction:row; flex-wrap:wrap; gap:0 .35rem;
    margin:0; padding:.25rem 0 .5rem;
    max-height:none; background:none; border-top:0;
    overflow:visible; visibility:visible; opacity:1; translate:none;
    box-shadow:none; clip-path:none;
  }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .nav a:not(.btn) {
    min-height:var(--tap); padding:.5rem .55rem; font-size:1rem;
    border-top:0;
  }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .nav a:not(.btn) + a:not(.btn) { border-top:0 }
  :is(html:not(.js), html.js-failed:not(.js-ready)) .nav__act { display:none }

  .nav a:not(.btn) {
    display:flex; align-items:center; min-height:52px;
    padding:.7rem .25rem; font-size:1.125rem;
    color:var(--fg-2); border-radius:0;
    transition:color .3s var(--ease-out), padding-left .4s var(--ease-out);
  }
  .nav a:not(.btn)::after { display:none }
  .nav a:not(.btn).on { color:var(--fg) }
  @media (hover: hover) { .nav a:not(.btn):hover { color:var(--fg); padding-left:.85rem } }
  .nav a:not(.btn) + a:not(.btn) { border-top:1px solid var(--line-d) }
  /* The join item's wrapper breaks the sibling chain, so the drawer's
     separator rides the wrapper itself; the row fills the drawer's width
     like every destination (the link stretches inside it). */
  .nav__join { display:block; border-top:1px solid var(--line-d) }
  .nav__join a:not(.btn) { display:flex }

  /* Actions live inside the drawer at this width — a header that hides both
     CTAs leaves the visitor with no visible way to act. The block now
     carries the one channel fact (the phone line, its label the number);
     « Nous joindre » lives in the list above as the nav's own text item,
     so no card anchors here anymore. */
  .nav__act {
    display:grid; gap:.6rem;
    margin-top:1rem; padding-top:1rem;
    border-top:1px solid var(--line-d);
  }
  .nav__act .btn { justify-content:center }
}

@media (max-width:960px) {
  .svc { grid-template-columns:repeat(2,minmax(0,1fr)) }
  .perks__grid { grid-template-columns:repeat(2,minmax(0,1fr)) }
  /* the mark steps down one notch in the narrow band: at 901px the four
     destinations and the two CTAs still share one row (the fifth lives in
     the header as the TV CTA), and the full-size lockup crowds the
     right gutter. */
  .logo__i { height:46px }
}

@media (max-width:760px) {
  /* ── composition : centré ──
     Sur téléphone, le texte se centre et occupe toute la mesure disponible :
     pas de colonne étroite à gauche avec du vide à droite, donc moins de
     lignes, donc moins de défilement. Les exceptions (saisie) restent à
     gauche : on n'écrit pas au milieu. */
  .sec, .hero, .tv-hero, .ftr__in, .dlbox, .tv-note,
  .walk, .walk__step, .call, .key__pad, .plan,
  .form { text-align:center }
  /* the registers are the other exception: the expertise glyph is a left
     rail on the title's own edge, and the channel rows are a table — a
     wrapped promise centered under its label reads as a mistake. Both
     stay left at phone widths (like the fields). */
  .s, .s p, .menu .cckey, .cinfo { text-align:left }

  /* colonnes qui se replient en piles centrées : chaque bloc garde toute la
     largeur pour son texte au lieu d'un rail de temps/d'icône sur le côté */
  .call__row, .walk__step, .key__pad {
    grid-template-columns:1fr; justify-items:center;
  }
  .call__row { gap:.3rem }
  .call__t { padding-top:0 }
  /* The facts rail is a register, not a poster: the icon stays BESIDE its
     title (the owner's law), the address reads left, and the former stacked
     icon/heading pair stops costing four lines per fact. It is the same
     exception the expertise cards and the channel rows already take. */
  /* The map is a visual, not a fact row: its fold spans the contact rail
     and centers the square, so the icon gutter stops pushing it off the
     page's midline. The summary still reads left; the map holds centre. */
  .cinfo__fold { margin-inline:calc(-1 * var(--cinfo-gutter)) 0 }
  .cinfo__fold .cinfo__map { margin-inline:auto }
  .dlbox__row { flex-direction:column; align-items:center; text-align:center }
  .dlbox__alt { justify-content:center }
  .key__who h3 { align-items:center }   /* column heading: the cross axis carries the centering */
  .walk__step { gap:.75rem }
  .key__pad { gap:.65rem }
  .key__more { padding-left:0; justify-content:center }
  .key__go { display:none }          /* affordance de survol : sans objet ici */

  /* les puces restent, mais en ligne devant le texte centré */
  .plan__list li { padding-left:0 }
  .plan__list li::before {
    position:static; display:inline-block;
    margin-right:.55rem; vertical-align:middle;
  }

  .fold > summary.plan__hd { text-align:center }
  .plan__hd h3 { grid-column:1 / -1; padding-inline:var(--chev) }
  .plan__hd .plan__term { grid-column:1 / -1; justify-self:center; align-items:center; text-align:center }
  .plan__for { margin-left:0 }

  /* caps de mesure qui rognaient les lignes : libérés */
  .num span, .key__who p, .fld label { max-width:none }
  .hero__nums .nums__grid { text-align:center }
  .hero__nums .num { align-items:center }   /* padding/border: see 900px block */

  .shead, .tv-hero h1 { margin-inline:auto }
  .walk__acts, .plans__cta { justify-content:center }
  .hero__direct { margin-inline:auto }
  .plans__cta { flex-direction:column; text-align:center }

  .opts { grid-template-columns:minmax(0,1fr) minmax(0,1fr) minmax(0,1fr) }
  /* on n'écrit pas au milieu d'un champ */
  .fld input, .fld textarea, .fld select { text-align:left }

  .svc { grid-template-columns:1fr }
  .perks__grid { grid-template-columns:1fr }
  /* the feature's three answer surfaces read left on a phone: the band
     (.common — the self-check's rows and the facts' dots), the posting
     card (.callcard — the spawned detail lists and their dots) and the
     noscript detail (.key__detail — the paper path). All three carry
     absolutely-anchored list dots, so inherited centering orphans them
     the same way; the section's centering opt-out, the legal page's own
     (« un document se lit à gauche »). */
  .common, .callcard, .key__detail { text-align:left }
  .hero { padding-block:calc(var(--hdr-h) + 1.25rem) var(--sec) }   /* coverage stays: sections fill the view at every size */
  .fab span { display:none }
  /* The phone's gem: exactly the --tap 44px target, leg 14 — the label
     leaves, the octagon stays the brand's. */
  .fab { width:var(--tap); height:var(--tap); padding:0 }
  .ftr__brand { justify-content:center; margin-inline:auto }
  .ftr__nav { justify-content:center; column-gap:1.25rem }

  /* un document se lit à gauche : la politique ne se centre pas, comme on
     n’écrit pas au milieu d’un champ. Le sommaire rend la colonne. */
  .legal-hero, .sec.legal { text-align:left }
  .legal__toc { grid-template-columns:1fr }
}

/* ─────────────────────────────────────────── la pression ──
   ONE socket for every key — readable or physical, the same bottoming-out:
   the key drops 2px into its socket at scale(.98) on :active and springs back
   on release. --py/--psc are the only knobs; each family composes them through
   the independent `translate`/`scale` properties, so the three transform
   ownership regimes stay untouched (the keypad keeps its own --sc .982 press,
   written by main.js). Hover lifts every key the same 2px — one vocabulary:
   text CTAs lift 2px, cards lift 4–5px (.s, .cinfo li). States own their
   timing: arriving (--dur-in) and pressing (--dur-press) are quick, every
   release is the settle language (--dur-out + --ease-spring). */
:is(.btn, .fab, .tgl, .burger, .hero__pause,
    .chat__saybtn, .chat__attach, .chat__close, .chat__unattach) {
  --py:0px; --psc:1;
  translate:0 var(--py,0px);
  scale:var(--psc,1);
}
@media (hover: hover) {
  :is(.btn, .fab, .tgl, .burger, .hero__pause,
      .chat__saybtn, .chat__attach, .chat__close, .chat__unattach):is(:hover, :focus-visible) {
    --py:-2px;
    transition-duration:var(--dur-in); transition-timing-function:var(--ease-spring);
  }
}
:is(.btn, .fab, .tgl, .burger, .hero__pause,
    .chat__saybtn, .chat__attach, .chat__close, .chat__unattach):active {
  --py:2px; --psc:.98;
  transition-duration:var(--dur-press); transition-timing-function:var(--ease-out);
}
/* The raised-glow collapse that lived here (a box-shadow on :active) is
   dead paint under the cut's clip — the press itself (the socket's
   --py/--psc and the fab's drop-shadow, which shrinks with the key it
   follows) carries the beat now. */

/* ── tactile : la page ne doit pas réagir « en trop » au doigt ──
   - :hover ne colle pas après un tap (voir le gating (hover:hover) partout);
   - manipulation supprime le délai et le zoom double-tap sur les cibles
     tactiles; le surlignage natif est remplacé par :active/:focus-visible. */
a, button, label, .opt, .tgl, .key__pad, .dlbox__alt a {
  touch-action:manipulation;
  -webkit-tap-highlight-color:transparent;
}
.fld input, .fld textarea, .fld select { -webkit-tap-highlight-color:transparent }

/* Motion is not gated on the OS preference: the site has one motion language
   and every visitor gets it. The opt-outs are the control the content owns —
   the hero slogan's pause button under the headline (WCAG 2.2.2) — and ONE
   documented exception below: the waiting-room dots under
   `prefers-reduced-motion` (the site's only infinite animation). Do not
   reintroduce further `prefers-reduced-motion` guards without the owner's
   decision. */

/* ── la file d'équipe — the chat key (the page's one floating affordance,
    bottom right — it took the call pill's old berth) and the panel it
    opens above it. The surface is the callcard's idiom (--s1, the blue
    rim, the --cut-l chamfer) and the fields/labels/status reuse the form
    classes wholesale: one idiom per concern. ─────────────────────── */
.fab--chat {
  font: inherit; font-size: var(--t-s); font-weight: 600;
  cursor: pointer;                            /* the rim comes from .fab: one
     fill, one boundary rule — the launcher is one of the surfaces the
     1.4.11 rim policy names */
}
/* A live session says so on the key itself — one quiet dot and a new
   label (chat.js swaps both), so a returning visitor knows the
   conversation is still open before the first click. The dot paints on
   the SOCKET (::after, above): it hangs past the gem's cut corner, where
   the key's own clip could never carry it. */
.chat {
  position: fixed; right: calc(1.25rem + var(--sa-r)); bottom: calc(4.9rem + var(--sa-b));
  z-index: 90;
  /* Two-sided bubbles need a reading measure; the phone keeps the
     viewport cap. The bubble rhythm lives here too, one owner per row. */
  --chat-w: 36rem;
  --bubble-gap: .65rem;
  --bubble-cap: 92%;
  --bubble-pad: .75rem 1rem;
  --media-cap: 96%;
  --img-cap: 24rem;
  --img-cap-only: 30rem;
  width: min(var(--chat-w), calc(100vw - 2.5rem));
  animation: cc-in .45s var(--ease-spring) both;
}
/* the dismissal mirrors the entrance (the callcard's own exit) — a
   short settle, never a blink to nothing; chat.js unhides on re-open */
.chat[data-closing] { animation: cc-out .22s var(--ease) both }
.chat__card {
  position: relative;
  padding: 1.5rem 1.5rem 1.3rem;
  background: var(--s1);
  border: 0; clip-path: var(--cut-l-fill);
}
/* The panel keeps the standard's blue edge (the callcard's own voice);
   the same 72% band reads 3.24:1 on --s1. */
.chat__card::before { background: color-mix(in srgb, var(--blue-lt) 72%, transparent) }
/* The panel grows upward from the pill, so on short screens its top
   would leave the viewport with no way back — the close button first.
   The scroll area caps the growth: the card's top always lands 4px
   BELOW the page header (the header is fixed, z-100, and paints over
   the panel — a budget that ignores it lets the close button disappear
   behind the band), and the rest counts every millimetre the panel
   owes: its offset above the pill plus the card's paddings (7.7rem all
   told — 4.9 + 2.8), the safe-area inset (--sa-b), the header band
   (--hdr-h), and 6px — the card's 2px border plus the 4px the top
   keeps below the band. dvh tracks the real estate under mobile
   browser chrome; vh is the fallback. */
.chat__scroll {
  max-height: calc(100vh - 7.7rem - var(--sa-b) - var(--hdr-h) - 6px);
  max-height: calc(100dvh - 7.7rem - var(--sa-b) - var(--hdr-h) - 6px);
  overflow-y: auto;
  overflow-x: clip;
  overscroll-behavior: contain;
}
.chat__close {
  position: absolute; top: .55rem; right: .55rem;
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; clip-path: var(--cut-s-fill);
  color: var(--fg-3); font: inherit; font-size: 1.3rem; line-height: 1;
  cursor: pointer;
  transition: color .25s var(--ease), background-color .25s var(--ease), translate var(--dur-out) var(--ease-spring), scale var(--dur-out) var(--ease-spring);
}
@media (hover: hover) {
  .chat__close:is(:hover, :focus-visible) { color: var(--fg); background: var(--wash) }
}
/* The heading and lead stop short of the close button's lane — at 320px
   the French heading otherwise reaches within a pixel of the × glyph. */
.chat__hd { margin: 0 0 .4rem; font-size: var(--t-b); font-weight: 600; color: var(--fg); padding-inline-end: calc(var(--tap) + .35rem) }
.chat__lead { margin: 0 0 1.1rem; font-size: var(--t-s); line-height: 1.5; color: var(--fg-2); padding-inline-end: calc(var(--tap) + .35rem) }
/* The closed notice is the panel's whole cold state: no opener form, no
   Turnstile, one data-owned sentence naming the next opening. It is
   focusable programmatically so opening a closed panel lands somewhere
   readable instead of on a hidden field. */
.chat__closed {
  /* A status paragraph inside the card, not a second framed box: the
     notice's own background/border/clip read as a box-in-a-box. One
     hairline under it keeps the register and the focus target. */
  margin: 0 0 1.1rem; padding: 0 0 1.1rem;
  font-size: var(--t-s); line-height: 1.5; color: var(--fg);
  background: none; border: 0; border-bottom: 1px solid var(--line-d);
  clip-path: none;
}
.chat__closed:focus { outline: none; }
.chat__closed:focus-visible { outline: 2px solid var(--blue-lt); outline-offset: 3px; }
.chat__closed-close {
  display: inline-flex; align-items: center; min-height: var(--tap);
  margin: 0 0 1.1rem; padding: 0 .35rem;
  background: none; border: 0; border-bottom: 1px solid transparent;
  color: var(--fg-2); font: inherit; font-size: var(--t-s); font-weight: 600;
  cursor: pointer;
}
@media (hover: hover) {
  .chat__closed-close:hover { color: var(--fg); border-bottom-color: var(--line-d); }
}
.chat__closed-close:focus-visible { outline: 2px solid var(--blue-lt); outline-offset: 3px; }
@media (forced-colors: active) {
  .chat__closed:focus-visible { outline: 2px solid CanvasText; outline-offset: 2px; }
}
/* The two-way log: the team's rows hug the left edge, the visitor's the
   right — a normal conversation, in the cut's idiom. Rows are built by
   chat.js with textContent only — nothing a sender types is ever parsed
   as markup. The grid owns the side lanes; no hairline rules the turns
   (the fill and the gap do). One scroller owns all the scrolling
   (.chat__scroll): a second nested one once ate touch gestures first
   and buried the composer below the fold. */
.chat__log {
  display: grid; gap: var(--bubble-gap); align-content: start;
  justify-items: start;
  margin: 0 0 1rem;
  overflow: clip;
}
/* TWO SIDES, ONE FILL — the bubbles are speech, not chrome: no border,
   no shadow, no radius, no second box. Each row is one fill with ONE 45°
   cut at the speaker-side top corner; the team sits left, the visitor
   right. The author line keeps the you/team split; the seen-receipt,
   queue, thumbnails and composer keep their own contracts. */
.chat__m {
  min-width: 0; max-width: var(--bubble-cap);
  justify-self: start;
  margin: 0; padding: var(--bubble-pad);
  background: var(--bubble); clip-path: var(--cut-speech-team);
  font-size: var(--t-b); line-height: 1.5; color: var(--fg);
  overflow-wrap: anywhere;
}
.chat__m--you {
  justify-self: end;
  background: var(--bubble-you); clip-path: var(--cut-speech-you);
}
.chat__m b { display: block; margin-bottom: .2rem; font-size: var(--t-xs); line-height: 1.35; font-weight: 600; color: var(--fg-2) }
/* who spoke, and when — the clock rides beside the author, the
   visitor's own, in the lightest voice */
.chat__m b time { font-weight: 400; color: var(--fg-3); margin-left: .4rem }
/* the you/team color split — the speaker line owns it, the words stay
   the page's own ink */
.chat__m--you b { color: var(--blue-text) }
/* the honest "seen": a team reaction (👍) in Teams — the channel's only
   read receipt, because Microsoft gives channels none. One small muted
   line under the words it belongs to, the check in the one blue accent. */
.chat__seen {
  display: block; margin-top: .2rem;
  font-size: var(--t-xs); font-weight: 400; text-align: right;
  color: var(--fg-3);
}
.chat__seen::before {
  content: '✓'; margin-right: .25rem;
  color: var(--blue-text); font-weight: 600;
}
/* the one arrival beat: a fresh reply PULSES its bubble — one flash of
   the gem blue where the answer sits, then it settles; the loudest thing
   in the panel is the answer, and it announces itself once, never on
   every rebuild */
.chat__m--new b { animation: chat-new .9s var(--ease-out) both }
@keyframes chat-new { from { color: var(--blue-text) } }
/* the waiting room: one block, two lines — "connecting" while nothing
   has landed in the channel, "in the queue" once the visitor's words
   are in and nobody has engaged. A team reaction (👍) or a reply starts
   the conversation and the block simply leaves. The line is INKED: the
   state is information, not decoration. */
.chat__queue {
  justify-self: center; align-self: center;
  display: inline-flex; align-items: center; gap: .55rem;
  max-width: 92%;
  margin: .25rem 0 0; padding: .4rem .15rem;
  font-size: var(--t-xs); line-height: 1.45; color: var(--fg-3);
}
.chat__queue i {
  display: inline-block; width: 5px; height: 5px;
  border-radius: 50%; background: var(--fg-3);
  animation: chat-dots 1.2s var(--ease) infinite;
}
.chat__queue i:nth-child(2) { animation-delay: .15s }
.chat__queue i:nth-child(3) { animation-delay: .3s }
@keyframes chat-dots { 0%, 60%, 100% { opacity: .25; translate: 0 0 } 30% { opacity: 1; translate: 0 -3px } }
/* The ONE documented `prefers-reduced-motion` exception (the policy above
   names it): the waiting-room dots are the site's only infinite animation —
   WCAG 2.2.2 — and the waiting line's own text carries the state, so under
   the preference the dots may rest. */
@media (prefers-reduced-motion: reduce) {
  .chat__queue i { animation: none; }
}
/* The follow-up composer — the form input's idiom compressed to one
   line, and the send affordance in the pill's blue. The lock beat after
   a send mirrors the server's spacing floor.
   The dock pins the whole composer group to the bottom of the one
   scroller: the history slides under a hairline seam while the words
   stay within reach — the standard chat geometry, in this idiom. */
.chat__dock {
  position: sticky; bottom: 0;
  background: var(--s1);
  border-top: 1px solid var(--line-d);
  padding: .6rem 0 .6rem;
}
.chat__say { display: flex; gap: .5rem; margin: 0; }
.chat__say input {
  flex: 1 1 auto; min-width: 0;
  padding: .8rem .95rem; font: inherit; font-size: max(var(--t-s), 1rem);
  color: var(--fg); background: var(--s0);
  border: 1px solid var(--line-ctrl); clip-path: var(--cut-s-fill);
  transition: border-color .22s var(--ease), box-shadow .22s var(--ease), background-color .22s var(--ease);
}
.chat__say input::placeholder { color: var(--fg-3); }
/* Mirrors .fld exactly: the clip removes the global outline on every
   focus, so the border + inset band is the composer's one indication —
   mouse and keyboard alike. */
.chat__say input:focus:not(:focus-visible) { outline: none; }
.chat__say input:focus { border-color: var(--blue-lt); background: var(--s1);
  box-shadow: inset 0 0 0 2px var(--blue-lt); }
.chat__say input:disabled { opacity: .55; }
.chat__saybtn {
  flex: 0 0 auto; width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--blue); color: var(--on-blue);
  border: 0; clip-path: var(--cut-fill); cursor: pointer;
  transition: background-color .25s var(--ease), color .25s var(--ease), opacity .25s var(--ease), translate var(--dur-out) var(--ease-spring), scale var(--dur-out) var(--ease-spring);
}
.chat__saybtn:disabled { opacity: .55; cursor: default; }
.chat__saybtn[aria-busy="true"] { opacity: .75; }
.chat__saybtn svg { width: 18px; height: 18px; }
@media (hover: hover) {
  .chat__saybtn:not(:disabled):is(:hover, :focus-visible) { background: var(--blue-lt); color: var(--on-blue-lt); }
}
/* The screenshot door: pasted or picked, one at a time. Dashed ghost
   so it reads as an invitation, not a second submit. */
.chat__attach {
  flex: 0 0 auto; width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 1px dashed var(--line-ctrl); clip-path: var(--cut-s-fill);
  color: var(--fg-3); cursor: pointer;
  transition: color .22s var(--ease), border-color .22s var(--ease), translate var(--dur-out) var(--ease-spring), scale var(--dur-out) var(--ease-spring);
}
.chat__attach svg { width: 18px; height: 18px; }
.chat__attach:disabled { opacity: .55; cursor: default; }
@media (hover: hover) {
  .chat__attach:not(:disabled):is(:hover, :focus-visible) { color: var(--fg); border-color: var(--blue-lt); }
}
/* The staged attachment: a thumbnail of what will leave, and its way back out. */
.chat__att { display: flex; align-items: center; gap: .55rem; margin: 0 0 .6rem; }
.chat__att img {
  width: 48px; height: 48px; display: block; object-fit: cover;
  border: 1px solid var(--line-ctrl); clip-path: var(--cut-s-fill);
}
/* the way out is a full --tap target (the mistake being undone is the
   one control that must never be hard to hit) and speaks the site's
   press language through the shared socket */
.chat__unattach {
  width: var(--tap); height: var(--tap);
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; clip-path: var(--cut-fill);
  color: var(--fg-3); font: inherit; font-size: 1.05rem; line-height: 1; cursor: pointer;
  transition: color .22s var(--ease), translate var(--dur-out) var(--ease-spring), scale var(--dur-out) var(--ease-spring);
}
@media (hover: hover) {
  .chat__unattach:is(:hover, :focus-visible) { color: var(--err); }
}
/* The image inside a turn: as wide as the lane allows, never wider, and
   never taller than the viewport ceiling (a long screenshot must leave
   the composer and the thread reachable). The cut frame is the media
   law's one frame; the pixels keep their aspect ratio. The rem value is
   the fallback where dvh is unsupported. */
.chat__mimg {
  display: block; width: auto; height: auto; max-width: 100%;
  max-height: var(--img-cap); max-height: min(var(--img-cap), 55dvh);
  margin-top: .5rem;
  border: 1px solid var(--line-ctrl); clip-path: var(--cut-s-fill);
}
/* An image row takes the wider media lane. An image-ONLY row is media,
   not speech: no fill, no pad, no tail — one cut frame, no box in a
   bubble. */
.chat__m--img { max-width: var(--media-cap) }
.chat__m--imgonly {
  max-width: var(--media-cap); padding: 0;
  background: none; clip-path: none;
}
.chat__m--imgonly .chat__mimg {
  max-height: var(--img-cap-only); max-height: min(var(--img-cap-only), 60dvh);
  margin-top: .35rem;
}
/* The composer's status line IS the form's status voice (form__msg —
   the .ok/.err colours live there, one owner); only its spacing is the
   dock's own: the composer sits tight under the input, not at the
   form's own looser rhythm. */
.chat__dock .form__msg { margin: .5rem 0 0; }
/* The human-check slots reserve the widget's rendered height in the FIRST
   layout: Turnstile injects a host measuring ~73.5px (flexible) or
   ~148px (compact) when it loads, and without the reservation the submit
   button and the whole form below jump (CLS). The 74/149 reserve leaves
   a 1px margin above every measured host. The breakpoints mirror the
   render size choice in main.js/chat.js (clientWidth < 304): the form
   slot is 303px at the 353px viewport; the chat slot's LAYOUT client
   width (not the scaled entrance rect) is 303px at 388px and 304px at
   389px (measured). The heights hang on the pre-paint .js class
   (head.php) so a no-JS reader sees no empty slot: the reservation and
   the widget arrive together. */
.form__ts iframe { clip-path:var(--cut-s-fill) }
.js .form__ts { min-height:74px }
@media (max-width:353px) {
  .js .form__ts { min-height:149px }
}
.chat__ts { margin: 0 0 1rem }
.chat__ts iframe { clip-path: var(--cut-s-fill) }
.js .chat__ts { min-height:74px }
@media (max-width:388px) {
  .js .chat__ts { min-height:149px }
}

/* The narrow card: every millimetre of writable width counts — the
   card's walls and the input's own padding slim down while the --tap
   controls, the type scale and the radius hold their ground. */
@media (max-width: 480px) {
  .chat { --bubble-cap: 94%; --bubble-pad: .65rem .8rem; }
  .chat__card { padding-inline: 1.1rem; }
  .chat__say input { padding: .8rem .6rem; }
}

/* ────────────────────────────────────────────── the focus law ──
   One place for every keyboard indication a clipped surface carries.
   The ringed cut components re-parameterize their rim pseudo into the
   2px --blue-lt band offset 1px inside the edge (thickness ≥ 2px,
   contrast ≥ 3:1, offset from the component's boundary — WCAG 2.4.7 +
   2.4.13), and the global outline dies ONLY on these scoped selectors —
   never anywhere else. The section sits LAST so no component's hover
   recolor can ever out-rank the blue band. The fields keep the global
   outline (inputs host no pseudo; their border + inset band carry the
   indication regardless — see .fld), and the quiet-zone links (prose,
   nav, footer, legal) keep it everywhere. */
:is(.btn, .key__pad, .hero__pause,
    .chat__saybtn, .hero__line):focus-visible::before { clip-path:var(--cut-ring-focus); background:var(--blue-lt); }
:is(.tgl, .burger):focus-visible::before { clip-path:var(--cut-ring-s-focus); background:var(--blue-lt); }
.fab:focus-visible::before { clip-path:var(--cut-ring-gem-focus); background:var(--blue-lt); }
/* The blue fills answer with WHITE bands: the blue band is 1.813:1 on
   --blue and 1.000:1 on the --blue-lt hover/focus fill; white is 6.788:1
   and 3.744:1. Source-order after the shared rule on equal specificity. */
:is(.btn--fill, .fab, .chat__saybtn):focus-visible::before {
  background:var(--on-blue);
}
/* The hero plate's focus band is WHITE, not blue-lt: its top-left corner
   ornament is blue-lt, so a blue band met the corner at 1:1. White is
   6.788:1 on the blue and 17+:1 on the dark plate. */
.hero__line:focus-visible::before { background:var(--on-blue); }
:is(.btn, .cckey, .key__pad, .hero__pause, .hero__line,
    .chat__saybtn, .tgl, .burger):focus-visible,
.fab:focus-visible,
.skip:focus-visible,
:is(.chat__close, .chat__attach, .chat__unattach):focus-visible,
.opt input:focus-visible + span,
.dlbox__alt a:focus-visible { outline:none; }
/* Controls whose rest state carries no rim (the chat's close, attach and
   unattach buttons, the form's option rows, the OS-label links, and the
   channel rows — ONE SURFACE: the row rests on the ground, the ring
   materializes only for the keyboard): the pseudo materializes ONLY on
   keyboard focus — the same ring, zero rest paint, so their design is
   untouched until the keyboard arrives. */
:is(.chat__close, .chat__attach):focus-visible::before { content:""; position:absolute; inset:0; pointer-events:none; background:var(--blue-lt); clip-path:var(--cut-ring-s-focus); }
:is(.chat__unattach, .cckey):focus-visible::before { content:""; position:absolute; inset:0; pointer-events:none; background:var(--blue-lt); clip-path:var(--cut-ring-focus); }
.opt input:focus-visible + span::before,
.dlbox__alt a:focus-visible::before { content:""; position:absolute; inset:0; pointer-events:none; background:var(--blue-lt); clip-path:var(--cut-ring-s-focus); }
/* Bordered hosts: the band's box is inset:0, but the visible boundary
   includes the border — the 2px option underline and the 1px OS-label /
   card-row rules. Pull the ring's relevant edge out by that border so the
   gap to the visual edge stays the law's 1px everywhere. */
.opt input:focus-visible + span::before { bottom:-2px }
.dlbox__alt a:focus-visible::before { bottom:-1px }
.callcard__acts .cckey:focus-visible::before { top:-1px }

/* Windows High Contrast: the site's cut rims and focus bands are paint
   (backgrounds and box-shadows), which forced colors removes. The focus
   ring becomes a real outline, the meter's gradient ink becomes system
   text, and the white logo inverts only on a light system canvas. */
@media (forced-colors: active) {
  :focus-visible { outline:2px solid CanvasText !important; outline-offset:2px !important; }
  /* A clip-path also clips the outline, so every clipped control keeps its
     keyboard ring by dropping the clip for the focused frame and drawing
     a real CanvasText outline. The cut language returns when focus leaves;
     the system's focus contract wins while it is held. */
  :is(.btn, .key__pad, .hero__pause, .hero__line, .chat__saybtn,
      .tgl, .burger, .fab, .skip, .chat__close, .chat__attach,
      .chat__unattach, .cckey, .dlbox__alt a):focus-visible {
    clip-path:none !important;
    outline:2px solid CanvasText !important;
    outline-offset:2px !important;
  }
  .opt input:focus-visible + span {
    clip-path:none !important;
    outline:2px solid CanvasText !important;
    outline-offset:2px !important;
  }
  /* Text fields carry clip-path:var(--cut-s-fill) and their own border +
     inset-shadow focus; forced colors removes the shadow and clips the
     ring, leaving only a 1px border tint. Drop the clip for the focused
     frame and draw a real CanvasText ring. The 1x1 native file input is
     deliberately excluded: its ring belongs to the .att__cta twin below. */
  :is(.fld input:not(.att__input), .fld textarea, .fld select,
      .chat__say input):focus-visible {
    clip-path:none !important;
    outline:2px solid CanvasText !important;
    outline-offset:2px !important;
  }
  /* The fold's plus is background paint; forced colors turns it Canvas
     (white on white). CanvasText keeps the plan/map/careers chevrons
     visible. */
  .fold__chev::before, .fold__chev::after {
    background:CanvasText !important;
  }
  /* The pause control's boundary is its clipped fill; restore a real
     border and drop the clip so the 44px target reads as a control. */
  .hero__pause {
    clip-path:none !important;
    border:1px solid CanvasText !important;
  }
  /* The cut rims are background paint and vanish under forced colors; give
     the surviving surfaces an interior 1px CanvasText contour so the card,
     key and control boundaries do not disappear. */
  :is(.btn, .key__pad, .tgl, .fab, .cckey, .plan, .s, .callcard,
      .chat__card, .dlbox, .cinfo__thumb, .hero__line, .chat__close,
      .chat__attach, .chat__unattach) {
    outline:1px solid CanvasText;
    outline-offset:-1px;
  }
  :is(.btn, .key__pad, .tgl, .fab, .cckey, .plan, .s, .callcard,
      .chat__card, .dlbox, .cinfo__thumb, .hero__line, .chat__close,
      .chat__attach, .chat__unattach):focus-visible {
    outline-width:2px;
    outline-offset:2px;
  }
  /* The switchboard's strips are a paint effect; forced colors removes
     their gradient and exposes every digit on the strip at once. Show the
     final value from the .sr owner instead — the same text no-JS and paper
     read — and hide the visual drums. */
  .num .meter, .num__fig { display:none !important; }
  /* The team cluster is decorative paint around two figures; its full
     sentence rides the .sr span below, so the labels, note and lamp leave
     with the paint and the sentence alone carries both readings. */
  .num .lamp, .num--team .team__lbl, .num--team .num__label--team,
  .num--team .team__fig::before { display:none !important; }
  .num .sr {
    position:static; left:auto; top:auto; width:auto; height:auto;
    overflow:visible; clip-path:none; white-space:normal;
    color:CanvasText !important;
    font-size:clamp(2.5rem,1.7rem + 2.8vw,4.5rem); font-weight:600;
    letter-spacing:-.04em; line-height:1;
    font-variant-numeric:tabular-nums lining-nums;
  }
  .num--team .sr {
    font-size:clamp(1.5rem,1.2rem + 1.6vw,2.25rem);
    line-height:1.25;
  }
}
@media (forced-colors: active) and (prefers-color-scheme: light) {
  .logo__i { filter:invert(1) !important; }
}

@media print {
  /* The canvas is paper, not the dark screen. `color-scheme: dark` makes the
     root canvas #121212 when the user prints backgrounds, and the forced
     black ink would disappear into it; the body's own ground is already
     cleared by the remap below, but the root canvas has no body ancestor. */
  html { background:#fff !important; color-scheme:light !important }
  /* One remap rule instead of a hand-listed selector inventory: anything that
     printed white-on-white came from a token the list missed. The border
     grey is the 3:1 rim law's own constant — rgba(0,0,0,.5) reads ≈3.9:1
     on white paper (the .35 the first draft priced fails at 2.44:1). */
  body, body * {
    background:none !important; color:#000 !important;
    -webkit-text-fill-color:#000 !important; box-shadow:none !important;
    border-color:rgba(0,0,0,.5) !important;
  }
  /* The pseudo-painted marks the remap cannot reach — `body *` matches no
     pseudo-element. Inked to the same print grey. The list markers stay
     out of this on purpose: their brand blue prints at 3.74:1 on white by
     itself, precisely because the remap misses them too. */
  .sec::before, .num::after, .s__more::before, .plan__more::before,
  .hero__line::after {
    background:rgba(0,0,0,.5) !important;
  }
  /* The cut's rim pseudos are background paint the remap cannot reach
    either — inked to the same print grey, so paper prints clean black
    chamfer outlines on every ringed surface that survives the hide list:
    the keypads' pads (team, poste, hero), the hero's direct-line plate
    and the walk's act buttons — ONE SURFACE unboxed everything else that
    once carried a rest rim. Nothing hidden is listed (no surface to ink)
    and no focus-only pseudo either (it never materializes on paper). */
  :is(.btn, .key__pad, .menu .cckey, .hero__line)::before {
    background:rgba(0,0,0,.5) !important;
  }
  /* Chrome and interactive surfaces are meaningless on paper — the block's
     own principle, now complete: the hero's pause control, the plan and
     key triggers, the callcard they spring, and the contact map tile —
     its whole tile is the link, so paper carries no label for it, and a
     print issued before the lazy iframe loads would render an empty
     grey-rimmed box; the address text right above carries the place.
     (The posting callcard's detail is screen-only with it — the old
     posting pages printed, the card never does; the noscript rendering
     inside each poste key is the one paper path, a JS-less print's own.) */
  .hdr, .fab, .fab-host, .veil, .dlptr, .skip, .form, .dlbox, .burger, .chat,
  .hero__pause, .callcard, .cinfo__map, .cinfo__fold, .num .lamp { display:none !important }
  .num--team .team__fig::before { display:none !important }
  /* The contact lead names the form as one way to reach us; paper hides the
     form, so the inline clause that points at it leaves with it. The rest of
     the sentence keeps its phone and email channels. The print-only twin
     carries the instruction a sheet can actually follow (the URL replaces
     the hidden download box). */
  .print-hide { display:none !important }
  .print-only { display:inline !important }
  .num::after { transform:scaleX(1) !important }
  /* The phone's folds never cost the paper: the modern engines that expose
     ::details-content print every closed panel, its summary stays as the
     label, and the chevron (screen state) leaves. The child display remains
     for engines without the pseudo. The hover-only detail lines print too. */
  details[data-fold] > *:not(summary) { display:block !important }
  /* The UA hides a closed details through ::details-content's
     content-visibility; a descendant display cannot override that. The
     paper opens the pseudo explicitly. */
  details[data-fold]::details-content {
    content-visibility: visible !important;
    display: block !important;
  }
  details[data-fold] > summary { cursor:default }
  html:not(.js) .key__detail--print { display:none !important }
  .key__detail--print { display:grid !important }
  .fold__chev { display:none !important }
  .s__more, .plan__more, .key__more { opacity:1 !important; translate:none !important }
  /* The pointer field is screen state: the paper reads every moving
     surface at rest — no transient lean, drift or press survives. */
  [data-grav], .btn, .plan { transform:none !important; translate:none !important; scale:none !important; rotate:none !important }
  /* The contact directory on paper: the chat tile is script-only (its li
     starts hidden and chat.js reveals it) and the form tile would point
     at the form this block hides — print the phone and email channels as
     TWO tiles, with their promises restored in place of the sr-only clip
     the screen uses. (A printed form would need the 3-col grid; the form
     is deliberately paper-hidden like the rest of the interactive chrome.) */
  .menu { grid-template-columns:repeat(2,minmax(0,1fr)) !important }
  .menu .menu__chat, .menu .menu__form { display:none !important }
  .menu .cckey__txt small {
    position:static; width:auto; height:auto; padding:0; margin:.15rem 0 0;
    overflow:visible; clip-path:none; white-space:normal;
  }
  body::before, body::after { display:none !important }
  /* The one lockup is white ink (its strokes carry the brand blue) — paper
     forces black text, so the image would print white-on-white. The filter
     turns every visible pixel black: the mark prints, one asset, no
     light-ground variant. */
  .logo__i { filter:brightness(0) !important }
  /* Paper starts at the content, not under a fixed header that is not
     there, and a single-page product hero must not force a blank sheet. */
  .hero, .tv-hero { padding-block:0 1.5rem !important; min-height:0 !important }
  /* Long sections must be allowed to break: break-inside:avoid on every
     .sec pushed a nearly empty page before each long block (page 2 held
     69 characters). The small atomic blocks keep their own avoid. */
  .sec { padding-block:1.5rem; break-inside:auto }
  .nums__grid, .hero__nums, .ftr__in { break-inside:avoid }
  /* The footer's destination row is screen chrome and its alone-on-paper
     tail made a final 64-character page. The brand/legal lockup stays. */
  .ftr__nav { display:none !important }
  /* The posting pads are grid rows 1fr auto on screen so both keys share
     a height; on paper the printed detail is content, not a stretched
     footer. Content-sized rows align the two postings instead of padding
     one card around a short detail. */
  .paddock--postes .key { grid-template-rows:auto auto }
  .paddock--postes .key__pad { align-self:start }
  .paddock--postes .key__detail--print { display:grid !important }
  /* the policy is the printed page: the register breaks between sections,
     never through one; the summary's anchors mean nothing on paper. */
  .legal__toc { display:none !important }
  .sec.legal { break-inside:auto }
  .legal__sec { break-inside:avoid }
  /* The offers' self-check strips to its text on paper: the rows are
     content (the shared hook questions are the band's reason), only
     the chrome is interactive — the count and the cursor go; the
     numerals and the punctuation marks stay real text, so a pressed
     row prints as its own statement and an unpressed one as its
     question; the payoff line prints as the plain reading it always
     was (the remap's ink owns its color like every other line). */
  .common__quiz button { display:block; padding:0; border:0; background:none }
  .common__count { display:none }
  .common__score { display:block; padding-top:.9rem }
  /* Outlined accents would vanish without -webkit-text-stroke in the print
     pipeline; force solid ink instead. */
  h1 em, h2 em { -webkit-text-stroke:0 }
  /* The hover unfolds carry the extensions and mailboxes: on paper there is no
     hover, so they print as the always-visible lines they are on touch. */
  .key__more, .s__more, .plan__more { opacity:1 !important; translate:none !important }
  .mesh { display:none }
}
