/* ══════════════════════════════════════════════════════════════════════════
   PHASE 2 OVERRIDES
   Loaded AFTER /styles.css in BaseLayout.astro so rules here win on equal
   specificity. /styles.css is a frozen Phase 1 copy — never edit it.
   All Phase 2 CSS adjustments live in this file, grouped by bug.
   ══════════════════════════════════════════════════════════════════════════ */

/* ──────────────────────────────────────────────────────────────────────────
   BUG 1 — About Section 03 "Vision" loses horizontal gutter
   .ap-vision overrides .section's var(--margin) padding with 12px and
   .ap-vision__content { width: 100% } defeats the inherited .section__inner
   max-width: 1440px, so kicker/headline/body reach within 12px of the
   viewport edge at wide viewports. Restore the standard gutter inside the
   rounded video card.
   ────────────────────────────────────────────────────────────────────────── */
.ap-vision__content {
  /* Match Section 04 (`.section__inner`) text alignment at every viewport.
     `.section` adds --margin padding then centers a --maxw inner; here the
     full-bleed wrapper has only 12px padding, so widen the box by the
     missing 2*(--margin - 12) and re-introduce that as inner padding. */
  max-width: calc(var(--maxw) + 2 * (var(--margin) - 12px));
  margin-inline: auto;
  padding-inline: calc(var(--margin) - 12px);
  box-sizing: border-box;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 3 — /en/news Publications italic headline ("announcements") overflows
   the 410px column into the publication rows. Widen column 1 so the long
   EN word fits without overlap.
   BUG 10 (mobile regression) — original rule was unguarded, so the 540px
   column-1 floor overrode the base styles.css mobile collapse
   (`@media (max-width: 768px) { .ac-publications { grid-template-columns:
   1fr } }`) at every viewport. On 390px viewports column 2 was pushed past
   the right edge and clipped by `.section { overflow: hidden }`, leaving the
   publications list invisible on mobile. Gate to desktop (≥1024px) so the
   base mobile single-column collapse runs again below the breakpoint.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 1024px) {
  .ac-publications {
    grid-template-columns: minmax(540px, 1.5fr) 2fr;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 4 — Section 02 (homepage) image shifts vertically between FR and EN.
   Phase 1 anchors .plateforme__media inside the body grid with
   `margin-top: -420px`. That anchor is relative to the grid's top, which
   sits immediately under .section-head. The FR headline wraps to 3 lines
   (~256px tall); the EN headline wraps to 2 lines (~173px). The 83px
   delta in section-head height ripples through to the figure.
   Pin the figure to .plateforme__inner so its top is section-relative,
   not headline-relative — locks the visual top identical on FR + EN.
   Because the figure is now out of flow, .plateforme__body collapses
   to copy-only height and the stats row below it overlaps the figure.
   Reserve 480px of bottom padding on .plateforme__body so the stats
   sibling is pushed below the figure's bottom edge (~25px breathing
   room, matching Phase 1).
   Desktop only — Phase 1 collapses the 2-col grid below 1024 and resets
   margin-top to 0 there.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 1024px) {
  .plateforme__media {
    position: absolute;
    top: -80px;
    right: 0;
    width: calc((100% - 80px) / 2.2);
    margin-top: 0;
  }
  .plateforme__body {
    padding-bottom: 216px;
  }
  /* Lock copy column to FR-measured height so EN reserves the same
     vertical space (EN copy column otherwise renders 28px taller at
     this viewport because the FR copy paragraph wraps wider). */
  .plateforme__copy {
    min-height: 220px;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 5 — "Read more" CTA on Influence (RSE) cards.
   Cards already become whole-card links when articleRef is set; this adds
   a visible textual affordance below the body with an animated amber
   underline. Hover scope is the parent .story--link (the whole card is
   the link target). The card-level -2px lift defined in Influence.astro's
   scoped <style> is preserved.
   ────────────────────────────────────────────────────────────────────────── */
.influence .story__cta {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  /* Amber at rest; SVG arrow inherits via stroke="currentColor". The
     ::after underline (also amber) stays scaleX(0) at rest so the only
     amber affordance in idle is the label + arrow. */
  color: var(--amber);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 24px;
  position: relative;
  padding-bottom: 4px;
}
.influence .story__cta::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  height: 1px;
  width: 100%;
  background: var(--amber);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
.influence .story__cta svg {
  transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) {
  .influence .story--link:hover .story__cta::after {
    transform: scaleX(1);
  }
  .influence .story--link:hover .story__cta svg {
    transform: translateX(2px);
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 6 — Title hover treatment on Influence (RSE) cards.
   Idle title stays near-black with no underline. On parent .story--link
   hover the title fades to amber and an animated 2px amber underline wipes
   in left→right. Pairs with the existing CTA underline (1px), card -2px
   lift, and arrow nudge — all sharing the same 400ms cubic-bezier(0.22,1,
   0.36,1) ease so the four signals read as one coordinated gesture.
   Position: relative on the title so the ::after anchors correctly; the
   underline sits at bottom: -4px to clear descenders rather than crowd
   them. Hover guarded by @media (hover: hover) to avoid sticky touch.
   ────────────────────────────────────────────────────────────────────────── */
.influence .story__title {
  position: relative;
  transition: color 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
.influence .story__title::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  height: 2px;
  width: 100%;
  background: var(--amber);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
}
@media (hover: hover) {
  .influence .story--link:hover .story__title {
    color: var(--amber);
  }
  .influence .story--link:hover .story__title::after {
    transform: scaleX(1);
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 7 — Tight image-to-meta-strip gap on article card components.
   The "ÉVÉNEMENT · SPORT · OCTOBRE 2025" kicker hugs the photo with no
   breathing room. Source of the gap on each card is the top padding of
   the body wrapper:
     • .ac-article-card__body (ArticleGrid, /actualites + /en/news) → 28px
     • .ar-related__body      (ArticleRelated, À lire aussi)       → 16px
   Lift body top padding to 40px so the kicker reads as metadata, not a
   caption. Side/bottom padding on .ac-article-card__body (28px) is
   preserved by only overriding padding-top. Mobile padding on
   .ac-article-card__body (20px at ≤640px) is intentionally left alone —
   stacked single-column rhythm reads correctly with the tighter value.
   ────────────────────────────────────────────────────────────────────────── */
.ac-articles-grid .ac-article-card__body {
  padding-top: 40px;
}
@media (max-width: 640px) {
  .ac-articles-grid .ac-article-card__body {
    padding-top: 20px;
  }
}
/* .ar-related__body lives inside an Astro scoped style block, whose
   compiled selector picks up a [data-astro-cid-…] attribute. That gives
   the scoped rule a (0,2,0) specificity. Match-and-beat it with three
   classes (0,3,0) so this override wins without resorting to !important. */
.ar-related .ar-related__card .ar-related__body {
  padding-top: 40px;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 9 — Mobile hero "scroll-jacking" perception (homepage + entity pages).
   No actual pin or scroll-jacking exists. The zoom/jump perception comes
   from two compounding causes on iOS Safari:
     1. The hero uses `height: 100dvh` (homepage .hero) and `min-height: 92vh`
        (.entity-hero__inner on mobile). When Safari's address bar collapses
        on first scroll, the visual viewport grows ~55px and these heights
        recompute, jolting the hero box.
     2. .hero__video carries `transform: scale(1.15) translate(2%, 3%)` to
        crop the Veo watermark. The GPU compositor layer re-rasterises on
        the resize, which visually reads as the video "zooming in".
   Fix swaps dvh/vh for svh on mobile heights (small viewport unit ignores
   the address bar so heights stay stable), and replaces the scale transform
   with object-position on mobile so no compositor layer needs re-rastering.
   Desktop transform is preserved — Safari address-bar collapse isn't an
   issue at ≥1024px and the original watermark crop logic stays intact.
   ────────────────────────────────────────────────────────────────────────── */
.hero {
  height: 100svh;
  min-height: 100svh;
}
@media (max-width: 1023px) {
  .hero__video {
    transform: none;
    object-fit: cover;
    /* Centre horizontally so the video fills the hero card edge-to-edge
       on mobile (the prior -7.5% x-offset exposed the dark hero card
       background as a ~200px strip on the left). Keep the small vertical
       -3% offset to push the bottom-right Veo watermark out of frame;
       object-fit: cover + portrait viewport already crops most of the
       width-axis frame, so centring loses no useful image content. */
    object-position: center -3%;
  }
}
/* Entity sub-page hero (.entity-hero__inner) uses 92vh at ≤767px, which
   also recomputes on address-bar collapse. Swap to 92svh for the same
   stability. The 600-1023 rule (`max(100svh, 1000px)`) is already svh. */
@media (max-width: 767px) {
  .entity-hero__inner {
    min-height: 92svh;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 11 — Entity scroll section description overlap on mobile.
   On Section 04 (Ecosystem / org-tree), the .org-tree__row uses
   `min-height: 320px` when its descendant .org-tree__entry is .is-active
   (styles.css:1977). That min-height SNAPS instantly (no transition) when
   main.js reassigns .is-active on fast scroll, while the description's
   max-height animates over 700ms (styles.css:1862-1866). The mismatch
   leaves half-collapsed descriptions rendering outside their shrunken
   rows for ~600ms, bleeding into adjacent rows.
   Fix: add the same 700ms transition to min-height on .org-tree__row so
   row resize stays in lockstep with the description expand/collapse.
   Scoped to .org-tree .org-tree__row (specificity 0,2,0) so it beats any
   tied bare-class rule. Property is transition only — the actual
   min-height values stay owned by styles.css.
   ────────────────────────────────────────────────────────────────────────── */
.org-tree .org-tree__row {
  transition: min-height 700ms cubic-bezier(0.16, 1, 0.3, 1);
}
@media (prefers-reduced-motion: reduce) {
  .org-tree .org-tree__row {
    transition: none;
  }
}

/* Hero cities row ("Genève · Conakry · Kinshasa") removed from the brand
   narrative. The string lives inside Sanity-driven heroBody HTML as
   <span class="locus">…</span>, so we suppress it via CSS on every viewport. */
.subline .locus,
.hero .locus,
.locus {
  display: none !important;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 8 — Featured-article hero kicker on Actualités / News.
   The .ac-featured card at the top of /actualites + /en/news shows a
   "CATEGORY · DATE" meta row between the photo and the title that
   duplicates information already conveyed by the page layout (large
   "Lire la suite" CTA + the curated context) and crowds the photo →
   title hierarchy. Hide the meta strip on the featured hero only.
   The .ac-featured__* class namespace is exclusive to NewsHero.astro,
   so this rule does not touch:
     • .ac-article-card (grid below)
     • .ar-related__card (À lire aussi)
     • .ar-split__kicker (article detail page)
   ────────────────────────────────────────────────────────────────────────── */
.page-hero .ac-featured__meta {
  display: none;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 9 — Supply Trading Mission ripple-mark reads too large + wrong colour.
   The base rule at styles.css:5190 sizes `.en-mission__mark img` to 90% of
   the cream circle. That was tuned for the entity brand-lockup marks
   (KP / Manta / Kamino / etc.) — the okapi symbol-only glyph reads much
   heavier at 90%. Scale to 54% (= 90% × 60%, i.e. −40% of current) and
   swap the fill to brand maroon `--color-brand` #5C2E20. Fill can't be
   applied here because .en-mission__mark renders as <img>, so a recoloured
   copy of the SVG (fill #5C2E20 vs original #1a0c06) lives at
   /public/icone-filigramme-maroon.svg and only Supply Trading points at
   it — the attribute selector below therefore auto-scopes to Supply.
   Other entities (Kamino, KP, Manta, Geneva, Hummingbird) continue to
   point at their own /entities-logos/*.png marks and are untouched.
   ────────────────────────────────────────────────────────────────────────── */
.en-mission__mark img[src="/icone-filigramme-maroon.svg"] {
  width: 54%;
  height: 54%;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 12 (mobile audit B1) — header nav does not collapse at 768–900px.
   Phase 1 collapses the header to the hamburger at `@media (max-width: 767px)`
   (styles.css:824-835). Between 768px and 900px the desktop header is still
   painted, and `.nav__actions` / `.nav__cta` ("Nous contacter") run past the
   right viewport edge: +143px FR, +99px EN, inflating documentElement
   .scrollWidth to 911 / 867 on all 24 pages at a 768 viewport.

   The contact drawer already switches to its 90vw mobile geometry at
   `max-width: 900px` (styles.css:3822), so 767 vs 900 is a dead zone where
   the layout is half-mobile, half-desktop. Aligning the nav collapse to 900
   closes that gap in the direction Phase 1 already committed to.

   This block is a verbatim copy of the Phase 1 767px rule set — same
   properties, same values, same mechanism that already works at 390 — with
   the query widened to 900. Nothing new is introduced.

   The second rule (.mobile-drawer) is required: Phase 1 hides the drawer
   outright at `@media (min-width: 768px)` (styles.css:944-946), so without
   it the hamburger would render at 768–900 and open nothing. `.mobile-drawer`
   has no `display` in its base rule, so `block` is the restore value.
   Scoped to max-width 900, so >900px is untouched — desktop is unchanged.

   Residual, out of scope (main.js is not editable here): main.js:1303 binds
   matchMedia('(min-width: 768px)') and force-closes the overlay on `change`.
   A static 768–900 viewport is unaffected; only a live resize across 768
   while the menu is open would close it early.
   ────────────────────────────────────────────────────────────────────────── */
@media (max-width: 900px){
  .nav{
    grid-template-columns: 1fr auto;
    gap: 16px;
    padding: 20px var(--margin) 16px;
    margin-bottom: -76px;
  }
  .nav__pill{ display: none; }
  .nav__actions{ display: none; }
  .nav__hamburger{ display: flex; }
  .nav__logo{ height: 32px; }
  .mobile-drawer{ display: block; }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 13 (mobile audit B3) — `.en-activities__filigree` overhangs the
   viewport on the entity sub-pages. The img is `position: sticky` with
   `right: -30%` / `margin-right: -30%` and a `clamp(600px, 55vw, 900px)`
   width, so its right edge lands past the viewport: +169px at 768,
   +317px at 1440 (scrollWidth 937 / 1757 on /entites/okapi-supply-trading).
   Phase 1 already deletes it below 767px (styles.css:6241-6243); 768px and
   up were never contained.

   Phase 1's comment at styles.css:6239-6240 warns that "containing it via
   overflow on the section would also break the sticky behavior" — true of
   `overflow: hidden`, which establishes a scroll container and re-parents
   the sticky element's scrollport. `overflow-x: clip` does NOT establish a
   scroll container: it clips paint only, and sticky descendants keep sticking
   to the viewport. `clip` is also the one value that may legally pair with
   `visible` on the other axis, so the section's vertical flow is untouched
   and `section.en-activities { overflow: visible }` (styles.css:6206) stays
   effectively intact for the y axis it was written for.

   Deliberately NOT a width change on the img: its width is load-bearing for
   the sticky scroll choreography and is what makes the filigree read as a
   cropped bleed rather than a centred decoration. Visually identical — the
   overhang was already invisible (clipped by the viewport edge and
   body { overflow-x: hidden }); only the phantom scroll width goes away.
   ────────────────────────────────────────────────────────────────────────── */
section.en-activities {
  overflow-x: clip;
  overflow-y: visible;
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 14 (mobile audit B2) — "Retour aux entités" / "Back to entities" was
   invisible across the whole 768–900 tablet band on all 6 entity pages.
   Phase 1 hides it at styles.css:5972-5976 — `@media (max-width: 767px){
   .entity-back{ display: none } }` — because the sticky OKAPI logo and the
   in-hero back link share the top-left zone on phones, with the hamburger
   drawer's "Entités" item standing in for the affordance.

   That Phase 1 rule is CORRECT below 768 and is deliberately left in force:
   at 390 the link's box lands at y=41 while .nav is a full-width
   `position: sticky; z-index: 100` band occupying y 0–68, so the nav's own
   box swallows the pointer over the link's top 27px and the tap never
   reaches the anchor (elementFromPoint returns NAV.nav on 3 of the 4 test
   pages). The clash is real — it is a hit-testing clash rather than a
   visual one. On phones the burger drawer's "Entités" entry stays the
   designed route back.

   What this block fixes is only the band BUG 12 opened up: the nav now
   collapses to the hamburger at ≤900 rather than ≤767, and between 768 and
   900 the hero has ample top clearance (link lands at y=157, nav ends at
   68), so the link is both safe and needed there. Hence the query is
   bounded on BOTH sides — it must not leak below 768.

   Tap height: the link's content box is 224x16 (the 16px chevron drives the
   height; font-size is 14px / line-height 1). 14px of block padding takes it
   to exactly 44px. The padding is then cancelled out — `margin-top: -14px`
   and 14px shaved off the existing `clamp(40px, 6vh, 64px)` bottom margin —
   so the glyphs land on precisely the same baseline and the gap to the
   headline below is unchanged. Typography, colour (--amber), and position
   are byte-for-byte the desktop element; only the hit box grows.

   Padding rather than an ::after hit-area overlay because the ancestor
   .entity-hero__inner is `overflow: hidden`, which would clip the overlay
   and take the hit region with it.

   `align-self: flex-start` guards the case where .entity-hero__copy-col
   resolves to `display: flex; flex-direction: column`, which blockifies
   `inline-flex` to `flex` and stretches the link to the full column width.
   Desktop's parent is a block, so `inline-flex` stays shrink-to-fit there;
   flex-start keeps the hit box hugging the label either way instead of
   spanning dead space to its right.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 768px) and (max-width: 900px){
  .entity-back{
    display: inline-flex;
    align-self: flex-start;
    padding-block: 14px;
    margin-top: -14px;
    margin-bottom: calc(clamp(40px, 6vh, 64px) - 14px);
  }
}

/* ────── BUG 15 — section 03 vision copy renders as one run-on paragraph ──────
   Compass.astro renders the body as `<p class="boussole__copy">{bodyText}</p>`
   — a plain `{expr}`, not `set:html`. The Sanity value stores the client's two
   blocks separated by a literal "\n\n", but HTML's default `white-space:
   normal` collapses every run of whitespace to a single space, so the two
   paragraphs arrive glued: "…et des marchés. Grâce à cette vision, Okapi…".

   `pre-line` collapses spaces and tabs exactly as `normal` does but PRESERVES
   newlines as line breaks — so the stored \n\n becomes the intended break and
   nothing else about the wrapping changes. `pre-wrap` would also honour the
   newlines but would additionally preserve the source indentation Astro emits
   around the expression, indenting the first line.

   Scoped to `.boussole__copy` alone; no other element stores newlines.
   ────────────────────────────────────────────────────────────────────────── */
.boussole__copy{
  white-space: pre-line;
}

/* ────── BUG 16 (rev. 2) — "MANTA MARINE TECHNOLOGIES" single line, FULL size ──────
   Supersedes the earlier font-size reduction (this entity was rendered at
   14px against the other five at 20px). Typography is now UNMODIFIED —
   font-size, weight and letter-spacing all inherit .org-tree__name, so the
   name is byte-identical in style to KAMINO ENERGY, HUMMINGBIRD, KP,
   GENEVA OIL and OKAPI SUPPLY & TRADING. The container is the only lever.

   At 20px the name measures 359.51px (incl. the 34px arrow slot that
   .org-tree__arrow reserves even at opacity 0), vs .org-tree__content's
   320px max-width — hence the original 2-line wrap. Lifting the cap plus
   nowrap puts it on one line at full size.

   THE CAP IS NOT THE ONLY CONSTRAINT. Manta is a row--right, whose content
   box is pinned by styles.css:1712 at
     left: calc(50% + (--ot-icon/2) + --ot-arm + --ot-gap)
   = viewport centre + 112px on desktop. Room to the section's right edge is
   therefore (viewportWidth / 2) - 112, independent of --maxw or the 880px
   diagram width (the diagram is centred, so 50% is always viewport centre).
   Single-line at 359.51px needs (359.51 + 112) x 2 = 943px of viewport.
   Measured: 1440 leaves 608px (fits, 248px spare); 768 leaves 272px
   (88px short); 390 leaves ~133px.

   So the rule is gated at >=944px, where widening genuinely works. Below
   that the name is left at STOCK Phase 1 behaviour — full 20px, wrapped to
   two lines. Widening cannot help there: the overflow is against the
   viewport edge, not the 320px cap, and the only way to gain room would be
   to pull the text left over its own icon and connector arm. Wrapping at
   full size is the sole option that keeps typography identical, and it
   matches OKAPI SUPPLY & TRADING, which already wraps at 390px.

   --ot-arm-extended (hover underline width) is a static per-entity px
   value, NOT derived from rendered text width. All five siblings satisfy
   round(nameWidth + 84) [76px gap-to-text-edge + 8px trailing buffer].
   Applying that formula to the full-size single-line width:
   round(359.51 + 84) = 444px. Scoped to the same >=944px query so the
   stock 279px (desktop) / 155px (<=767px) values still govern the wrapped
   renderings they were originally tuned for.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 944px){
  .org-tree__row[data-entity="manta"] .org-tree__content{
    max-width: none;
  }
  .org-tree__row[data-entity="manta"] .org-tree__name{
    white-space: nowrap;
  }
  .org-tree__row[data-entity="manta"]{
    --ot-arm-extended: 444px;
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 17 — Mobile hero: .subline overlaps the "DÉFILER" cue, and its last
   line is clipped by the hero's rounded bottom edge.

   MECHANISM — a viewport-unit mismatch introduced by BUG 9 above.
     · styles.css:1062  @media (max-width:1023px) .stage{ padding-top: 44vh }
     · BUG 9 (this file, :211) changed .hero to height/min-height: 100svh.
   `vh` resolves to the LARGE viewport (address bar hidden) while `svh`
   resolves to the SMALL one (address bar shown). On desktop they are equal,
   so the bug is invisible in a normal browser — but on iOS Safari they
   diverge, and the copy is positioned with a taller yardstick than the box
   that contains it.

   MEASURED, iPhone 14 (390x844 device, Safari visible height 713):
     hero height          = 100svh = 713px
     .stage padding-top   = 44vh   = 44% x 844 = 371px   ← 58px too far down
     .subline bottom      = 712px
     "DÉFILER" label top  = 591px  → overlaps by 122px
     hero bottom          = 705px  → subline clipped by 7px (overflow:hidden)
   Both reported symptoms reproduce exactly from this one cause. The cue is
   NOT a z-index fault: .scroll is position:absolute, bottom:28px, z-index 6,
   correctly pinned to the hero's bottom edge. It is the TEXT that runs into
   it, so the fix belongs on .stage, not on .scroll. (Lowering .scroll is not
   available — it is already flush at the bottom.)

   FIX — express the offset in svh so it shares the hero's yardstick, then
   cap it so the copy can never reach the cue:
     clamp(96px, 44svh, calc(100svh - 488px))
   488px = 349px content block + 115px cue reserve + 24px breathing gap.
     · 349px = tallest measured content block, at 390px wide:
               headline 171 + 16px margin (styles.css:7407) + subline 162.
               At 428px it is only 261px, so 390 is the binding case.
     · 115px = hero bottom to "DÉFILER" label top (28px bottom + 87px cue).
   The 96px floor keeps the headline clear of the fixed nav on very short
   viewports; below ~560px tall the floor wins and the gap closes, which is
   outside any real phone in portrait.

   44svh is preserved as the preferred value, so on tall viewports the
   original cinematic composition is untouched and only the cases that would
   actually collide are pulled up. Scoped to max-width:1023px to match the
   exact range of the styles.css rule it overrides — no desktop effect.
   ────────────────────────────────────────────────────────────────────────── */
@media (max-width: 1023px){
  .stage{
    padding-top: clamp(96px, 44svh, calc(100svh - 488px));
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 18 — Footer social: LinkedIn/Facebook move out of the text nav row and
   become icon buttons on their own row beneath the centre nav column.

   TWO SURFACES — Phase 1 ships two independent footers with no shared
   partial: .site-footer (Footer.astro, homepage FR+EN only) and
   .closing__footer (Closing.astro, all 19 other pages). Their nav columns are
   structurally identical, so both class families are matched by one rule set
   here rather than duplicating the block.

   PLACEMENT — the social row is the last child of the nav column, which is
   already `inline-flex; align-items:center; flex-wrap:wrap; gap:12px;
   justify-self:center` (styles.css:3729 / :3629). Because that column wraps,
   the row only needs `flex-basis:100%` to be forced onto a line of its own
   below the links — no wrapper element, and the links' own flex behaviour is
   untouched. `justify-content:center` then centres the buttons within the
   column, and the column is the `auto` track of a symmetric `1fr auto 1fr`
   grid (styles.css:3704 / :3606), so its centre IS the page centre.

   At <=900px both grids collapse to a single `1fr` column (styles.css:3711 /
   :3611) and the nav column KEEPS its `justify-self:center` — unlike the
   locus column, which is explicitly reset to `start` (styles.css:3762 /
   :3656). So the buttons stay page-centred when stacked with no extra media
   query needed here.

   The column's own 12px gap already separates the row from the links;
   margin-top adds 12 more for 24px total, so the break reads as a group
   change rather than another 12px list step.

   COLOR — both surfaces resolve to the SAME ground: .site-footer uses
   --color-brand and .closing uses --canvas-d, which is itself defined as
   `var(--color-brand)` (styles.css:79). #5C2E20 either way, so a single set
   of alphas is correct for both and no per-surface adjustment is needed.
   The .closing__portal filigree behind .closing__footer does not change this
   either. It is amber #F29100 at opacity 0.0585 — it LIGHTENS the ground
   rather than darkening it, so it would in principle narrow the gap to the
   cream fill. Measured at 1440 it does not reach the buttons at all: the
   portal box is 495,406 450x441 while the buttons sit at 120,781 40x40, no
   intersection. Even where it does land, 0.0585 amber shifts the ground only
   ~9/6/2 RGB, which is below the fill's own step. Default
   fill is cream at 0.10 alpha: solid enough to read as a filled button, but
   recessive enough to sit alongside the 0.65-alpha nav links rather than
   outshouting them. Two full-opacity cream blocks would become the loudest
   element in the footer, which the text links they replace never were.
   Amber (--color-accent-orange) is reserved for the hover fill only, per the
   interactivity-only rule; the glyph flips to maroon there for contrast.
   The 0.14 border alpha echoes the footer's own border-top (0.08) family.

   GLYPH ALPHA — 0.65, not the 0.75 the outline glyphs used. The marks are now
   the official SOLID brand paths (fill, no stroke), which cover several times
   the pixel area of a 1.75px outline at the same size, so 0.75 made them the
   heaviest ink in the footer. 0.65 is not a new value: it is exactly the
   alpha of the nav links directly above them (styles.css:3741 / :3640), so
   the row now matches the type it sits under instead of leading it. Solid
   coverage still reads slightly stronger than the links at equal alpha, which
   is the correct hierarchy for a control versus a text link.

   RADIUS — --radius-md (10px), the same token as .btn--secondary /
   .btn--tertiary (styles.css:343, 452). Consistent with the existing 8/10/
   14/18 scale, no new value.

   SIZE — 40px square, unchanged. The glyph inside drops 24px -> 20px for the
   same reason as the alpha: the official marks are full-bleed badges with no
   internal margin, where the lucide outlines carried their own optical
   padding. 20px restores a 10px surround and keeps the chip reading as a
   button rather than a cropped logo. Touch target is still 40px + the 8px
   inter-button gap.
   ────────────────────────────────────────────────────────────────────────── */
.site-footer__social,
.closing__footer-social{
  flex: 0 0 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 12px;
}
/* Scoped under the column on purpose. Phase 1's `.site-footer__col--links a`
   / `.closing__footer-col--links a` (styles.css:3736 / :3636) force
   `background:transparent; border:0` on every anchor in this column, and at
   0,1,1 they outrank a bare `.site-footer__social-btn` at 0,1,0 — which
   silently stripped the chip fill and border when the buttons moved in here.
   Adding the column class takes these to 0,2,0 and restores them. */
.site-footer__col--links .site-footer__social-btn,
.closing__footer-col--links .closing__footer-social-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: var(--radius-md);
  background: rgba(245, 240, 234, 0.10);
  border: 1px solid rgba(245, 240, 234, 0.14);
  color: rgba(245, 240, 234, 0.65);
  text-decoration: none;
  transition: background 260ms var(--ease-out, ease),
              color 260ms var(--ease-out, ease),
              border-color 260ms var(--ease-out, ease);
}
.site-footer__social-btn svg,
.closing__footer-social-btn svg{ display: block; }
.site-footer__col--links .site-footer__social-btn:hover,
.site-footer__col--links .site-footer__social-btn:focus-visible,
.closing__footer-col--links .closing__footer-social-btn:hover,
.closing__footer-col--links .closing__footer-social-btn:focus-visible{
  background: var(--color-accent-orange);
  border-color: var(--color-accent-orange);
  color: var(--color-brand);
}
@media (prefers-reduced-motion: reduce){
  .site-footer__col--links .site-footer__social-btn,
  .closing__footer-col--links .closing__footer-social-btn{ transition-duration: 0ms; }
}

/* ──────────────────────────────────────────────────────────────────────────
   BUG 19 — Section 09 DIRECTION: role text clipped at the bottom of the
   pinned viewport on 2-line cards, at >=900px.

   The GSAP pin and the horizontal scroll-jack are UNCHANGED. This is a pure
   vertical-budget fix; main.js is not touched.

   MEASURED BUDGET (1440x900, at pin start). The bottom of the role line is
   the thing that must clear the fold — NOT the section box, whose
   padding-bottom is allowed to overflow harmlessly:

     roleBottom = padTop + header + layoutGap + portraitH + 16 + textBlock

   padTop = clamp(72px, 11vh, 120px) and layoutGap = clamp(40px, 5vh, 64px)
   (styles.css:3071,3074), so together they scale at 0.16vh in the range that
   matters. Measuring two heights pins the constant term:
     vh 900 -> roleBottom 908, portrait 440  =>  fixed 468
     vh 800 -> roleBottom 792, portrait 340  =>  fixed 452
   giving fixed(vh) = 324px + 0.16vh. Constraint portraitH <= 0.84vh - 324px.

   The stock reserve of 460px (styles.css:3117) satisfies that only for a
   1-line name. Measured text-block heights at 1440:
     1-line name + 1-line role   81px   (Mohamed Ndao)
     1-line name + 2-line role   98px   (Simplice Mulumba, Edmund Philips)
     2-line name + 2-line role  137px   (Moustapha Mouhamadou,
                                          Estelle Florence Gbenou)   <- worst
   The 56px difference between 81 and 137 is exactly what overruns: at
   1440x900 those two cards' roles ended 8px past the fold.

   NEW RESERVE 512px. Portraits get shorter, which is the intended trade:
     vh 800: 340 -> 288    vh 900: 440 -> 388    vh 1000: 506 -> 488

   The text block is NOT a constant, so the reserve was not solved
   algebraically — shrinking the portrait also narrows the card (width is
   height x 4/5), which makes names and roles wrap onto more lines, which
   makes the text block taller. Swept the clamp across the range instead and
   measured the worst card at each height. Clearance of the lowest role
   baseline to the fold:

     vh    portrait  cardW  worst lines  clearance
     700     276      221     3n / 3r      -52   clipped (pre-existing)
     750     276      221     3n / 3r      -10   clipped (pre-existing)
     788     276      221     3n / 3r      +22
     800     288      230     3n / 3r      +20
     850     338      270     2n / 2r      +52
     900     388      310     2n / 2r      +44
     950     438      350     2n / 2r      +36
    1000     488      390     1n / 2r      +28
    1018     506      405     1n / 2r      +25
    1200     506      405     1n / 2r     +178

   Positive from about vh 765 upward, with the tightest point at vh 1018
   where the 506px max clamp takes over. Below ~765 the 276px MIN clamp
   governs and is unchanged from the stock rule, so those heights clip
   identically before and after this fix — not a regression, and a <765px
   tall window at >=900px wide is rare. Everything from 788 up is fixed.

   MIN-HEIGHTS — 2.4em is exactly two lines at --leading-tight (1.2), which
   .member__name already uses and .member__role resolves to as well (measured
   1-line 40/17px, 2-line 79/34px). Reserving two lines on both keeps the
   worst case structural rather than dependent on which names happen to wrap
   at the current card width, so a future editor renaming a member cannot
   silently re-introduce the overflow.
   It fully bottom-aligns the row from vh ~860 up, where no card exceeds two
   lines (verified at 900: every role baseline lands on 856). Below that the
   cards are narrow enough that some strings reach a third line and overflow
   the 2-line reserve, so alignment goes ragged again (at vh 800: baselines
   740 / 757 / 780). Raising the reserve to 3.6em would fix the ragging but
   costs ~40px, which would cut the vh 900 clearance from 44px to 4px and
   re-open the clipping this block exists to close. Nothing clips at 800, so
   the alignment is traded for the clearance deliberately.

   Everything is scoped to >=900px, the same gate as the desktop rules it
   overrides. <=899px keeps the stacked .team-grid untouched.
   ────────────────────────────────────────────────────────────────────────── */
@media (min-width: 900px){
  .direction__row .member__portrait{
    height: clamp(276px, calc(100vh - 512px), 506px);
  }
  .direction__row .member__name{
    min-height: 2.4em;
  }
  .direction__row .member__role{
    min-height: 2.4em;
  }
}

/* ────── BUG 20 — contact forms had no send path ────── */
/* Both forms now submit over fetch and swap themselves for a confirmation.
   Phase 1 gives `.contact-form` and `.drawer__form` `display:flex`, which
   outranks the UA's `[hidden]{display:none}` — without the !important below
   the "hidden" form stays on screen underneath the success panel. */
.contact-form[hidden],
.drawer__form[hidden],
.form-success[hidden],
.form-error[hidden]{
  display: none !important;
}

/* Confirmation. Deliberately unboxed: no border, no panel, no background — a
   centred amber disc over open space, so sending reads as a moment rather than
   as a notification. Amber is the site's interactivity colour, so the state
   borrows it rather than introducing a green that exists nowhere else. */
.form-success{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 72px 24px;
  border: 0;
  background: none;
  text-align: center;
  animation: none;
}
.form-success:focus{ outline: none; }
.form-success:focus-visible{
  outline: 2px solid var(--amber);
  outline-offset: 3px;
}
/* Disc matches the footer social chips: amber field, maroon glyph. */
.form-success__mark{
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 73px;
  height: 73px;
  margin: 0;
  border-radius: 50%;
  background: var(--amber);
  color: var(--color-brand);
  animation: form-success-mark-in 520ms var(--ease-out, ease) both;
}
.form-success__mark svg{ width: 34px; height: 34px; display: block; }
.form-success__text{
  max-width: 34ch;
  margin: 0;
  color: var(--ink-d);
  font-size: var(--text-md);
  line-height: 1.6;
  animation: form-status-in 520ms var(--ease-out, ease) 80ms both;
}
@keyframes form-success-mark-in{
  from{ opacity: 0; transform: scale(0.86); }
  to  { opacity: 1; transform: none; }
}

/* Inline send failure. Sits above the submit button; the form stays filled. */
.form-error{
  margin: 0;
  padding: 12px 14px;
  border-left: 2px solid var(--amber);
  border-radius: var(--radius-sm);
  background: rgba(242, 145, 0, 0.09);
  color: var(--ink-d);
  font-size: var(--text-sm);
  line-height: 1.5;
  animation: form-status-in 300ms var(--ease-out, ease) both;
}

@keyframes form-status-in{
  from{ opacity: 0; transform: translateY(8px); }
  to  { opacity: 1; transform: none; }
}

/* Sending — the button is disabled so it cannot double-fire. */
.contact-form__submit:disabled,
.drawer__submit:disabled{
  opacity: 0.55;
  cursor: not-allowed;
}

@media (prefers-reduced-motion: reduce){
  .form-success,
  .form-success__mark,
  .form-success__text,
  .form-error{ animation: none; }
}

/* ────── BUG 21 — Chrome's default blue focus ring in the drawer ────── */
/* Phase 1 already standardises on `2px solid var(--amber)` for .btn, the
   hamburger and the mobile drawer (styles.css:235, :821, :892) but never
   covers the drawer shell or any form control, so those fell back to the UA
   blue. Same treatment, same token — the indicator is restyled, never removed. */
.drawer:focus-visible,
.drawer__close:focus-visible{
  outline: 2px solid var(--amber);
  outline-offset: 3px;
}

/* Form fields opt out of the ring. An outline draws a rectangle around the
   control, and the drawer's fields are underline-only (styles.css:3890 sets
   `border:0` + a single `border-bottom`), so a box is a shape the field
   language never uses. The indicator is not removed — it moves onto the
   underline below, which thickens and turns amber. */
.contact-form input:focus,
.contact-form input:focus-visible,
.contact-form textarea:focus,
.contact-form textarea:focus-visible,
.drawer__field input:focus,
.drawer__field input:focus-visible,
.drawer__field textarea:focus,
.drawer__field textarea:focus-visible{
  outline: none;
  /* Phase 1 turns all four sides amber on the page form (styles.css:3203);
     hold the other three at rest so only the underline reads as the state. */
  border-top-color: var(--hairline-d);
  border-right-color: var(--hairline-d);
  border-left-color: var(--hairline-d);
  border-bottom-color: var(--amber);
  border-bottom-width: 2px;
}
/* The extra border pixel would otherwise push the text up by 1px on focus. */
.contact-form input:focus,
.contact-form textarea:focus{ padding-bottom: 15px; }
.drawer__field input:focus,
.drawer__field textarea:focus{ padding-bottom: 11px; }
/* Phase 1 also lifts the page form's field background on focus; hold it flat
   so the underline is the only thing that changes. */
.contact-form input:focus,
.contact-form textarea:focus{ background: rgba(245, 240, 234, 0.04); }
/* The drawer is a container that only receives focus programmatically on open;
   pull the ring in so it reads as a soft edge rather than a control's ring. */
.drawer:focus-visible{ outline-offset: -3px; }
