/* ── LE CHANGEMENT DE LANGUE SANS L'ÉCLAIR BLANC ──────────────────────
   Owner 2026-08-19 : « no reload when language is changed ». Le brouillage
   du texte répondait déjà à la moitié du symptôme ; il restait l'échange de
   document, visible comme un flash.

   ⚠ ET L'ÉCHANGE EN PLACE RESTE INTERDIT — ce n'est PAS ce qu'on fait ici.
   ADR-0038 §Forbidden le nomme : « Lang toggles re-navigate (?lang=…) —
   they do not mutate DOM in place. » Il a été essayé (3d5c5b18, retiré le
   2026-08-17) et re-proposé le 2026-08-19 sous condition de couverture ;
   verdict `extend_existing`, consultation …-dd7c08 : REFUSÉ, et pour une
   raison qui ne se répare pas avec un seuil. Peindre la locale en JS fait
   un SECOND rendeur pour une notion qui en a déjà un — le serveur. Mesurer
   qu'il ne reste que deux chaînes en dur retire la PREUVE du défaut, pas le
   défaut.

   La transition de vue inter-documents donne le même confort sans rien de
   tout ça : le navigateur garde l'ancienne image, va chercher le document
   rendu PAR LE SERVEUR dans la nouvelle langue, et fond de l'un à l'autre.
   Le serveur reste la seule chose qui peint la locale. Aucun catalogue,
   aucun appel, aucun ADR à changer.

   ⚠ PROGRESSIF PAR CONSTRUCTION, donc pas de repli à écrire : un
   navigateur qui ignore cette règle at-rule navigue exactement comme
   aujourd'hui. C'est la même polarité que le reste du fichier — le CSS
   ajoute, il ne conditionne pas.

   ⚠ Portée GLOBALE et non réservée au changement de langue : la règle
   s'applique à toute navigation same-origin. C'est voulu et c'est plus
   sûr qu'un ciblage — un `@view-transition` limité demanderait de nommer
   les navigations concernées, donc une liste à tenir. */
@view-transition { navigation: auto; }

  :root { --nav-h: 68px; }
  /* Promote <body> to a stacking context so the fixed .gk-nav (and its
     auth dropdown) paint above ANY descendant that creates a mid-page
     stacking context — mix-blend-mode, filter, transform, will-change,
     contain, opacity<1 in some browsers. Without this, Chromium bug
     753075 lets an isolated mid-page context paint over a fixed root-
     level positioned element, and the auth dropdown disappears under
     a hero with `mix-blend-mode: luminosity`. Architect verdict
     2026-05-07-recurring-bug-sphaigne-index-html-has-2289f9 (also
     hit on elen 2026-05-05-migrate-beta-elen-gateka-fr-canonical). */
  body { isolation: isolate; }
  /* Override _beta_banner.html's defensive rule that forces every
     header (.header / .gk-nav / .topbar / etc.) to position:static
     on beta. That defensive rule was meant for legacy product headers
     that didn't know how to clear the banner; the canonical .gk-nav
     IS banner-aware (banner is fixed at top:0, .gk-nav fixed below
     it via top: var(--gk-beta-banner-h)). Without this re-fixing,
     the auth dropdown opens at document position and gets covered
     by any hero/section that follows in flow — same root cause as
     2026-05-07-recurring-bug-sphaigne-index-html-has-2289f9. */
  body.gk-env-beta .gk-nav { position: fixed !important; top: var(--gk-beta-banner-h, 36px) !important; }
  .gk-nav{position:fixed;top:0;left:0;right:0;height:var(--nav-h);z-index:100;
    display:flex;align-items:center;justify-content:space-between;padding:0 24px;
    background:var(--overlay-bg,rgba(255,255,255,.92));backdrop-filter:blur(18px);
    border-bottom:1px solid var(--border,rgba(0,0,0,.08));
    transition:height var(--gk-duration, 180ms) ease, box-shadow var(--gk-duration, 180ms) ease}
  /* Shrink-on-scroll (Guillaume 2026-05-18, lifted from Elen pilot).
     Toggled by the rAF-throttled listener inlined at the bottom of
     _gk_nav.html. 68→52 desktop / 60→48 mobile. Body padding-top
     stays at --nav-h so content doesn't reflow when the nav collapses.

     HARD RULE — horizontal positions never shift during shrink
     (Guillaume 2026-05-18). The .gk-nav-brand block is locked: its
     contents (img / svg) get `transition: none` so a per-product
     brand_html that adds its own .is-scrolled logo-shrink rule can't
     animate the logo width and reflow the center/right clusters.
     If a product genuinely needs a smaller logo on scroll, swap the
     image src (not its size) so width stays constant. */
  .gk-nav.is-scrolled{height:52px;box-shadow:0 2px 10px rgba(0,0,0,.06)}
  .gk-nav .gk-nav-brand img,
  .gk-nav .gk-nav-brand svg{transition:none}
  .gk-nav-brand{display:flex;align-items:center;gap:10px;font-weight:800;font-size:20px;color:var(--text);text-decoration:none}
  .gk-nav-center{display:flex;align-items:stretch;gap:2px;height:100%}
  /* Right cluster — every button (lang / theme / connect / crank /
     signed-in auth trigger) shares the same .gk-nav-btn baseline now,
     so the cluster just lays them out with a tight gap. The auth
     trigger's filled-pill carve-out was retired 2026-05-04 (ADR 0018
     addendum #4). */
  .gk-nav-actions{display:flex;align-items:center;gap:0;height:100%}

  /* Hamburger toggle — hidden by default, only surfaces on phones via the
     ≤460px media query below. Pure CSS via the sibling :checked selector;
     no JS needed. Threshold lowered from 640 → 520 → 460 (2026-05-09):
     the full nav (brand + 2-item center + 4-icon right) genuinely fits
     down to ~450px, so collapsing into a burger any earlier wastes
     space and forces an extra tap on tablets / large phones in
     landscape / portrait phones at 480-520px. */
  .gk-nav-burger-btn { display: none; }

  /* Le Monde-style stacked button: icon on top, tiny label below.
     Applies uniformly to every link in the center cluster AND every
     action button on the right (lang, theme, pinned features…). */
  .gk-nav-link, .gk-nav-btn {
    position: relative;  /* anchor the ::before hover/active pill */
    display: inline-flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: 3px;
    /* min-height = 36px = touch-target floor. Center buttons with
       icon+label stacked render at ~39px; right-cluster icon-only
       buttons hit exactly 36px. The visible hover/active background
       is decoupled from this via ::before below — rendered at a fixed
       36px regardless of button height — so the painted rectangle is
       uniform across both clusters (ADR 0018 navigation contract,
       addendum 2026-05-04 #3). */
    min-width: 44px; min-height: 36px; padding: 4px 6px;
    background: transparent; border: none; cursor: pointer;
    color: var(--text2, #64748b);
    text-decoration: none; font-family: inherit;
    font-size: 10px; font-weight: 600;
    letter-spacing: .2px; line-height: 1.1;
    border-radius: var(--radius-sm, 8px);
    transition: color var(--gk-duration, 180ms);
  }
  /* Background pill — fixed 36px tall, centered vertically inside the
     button regardless of the button's actual content height. Drives
     hover (transient) and is-active (persistent) painting. Sits behind
     the icon/label via z-index so transparent gear strokes don't smear. */
  .gk-nav-link::before, .gk-nav-btn::before {
    content: ''; position: absolute;
    top: 50%; left: 0; right: 0; height: 36px;
    transform: translateY(-50%);
    border-radius: var(--radius-sm, 8px);
    background: transparent;
    transition: background var(--gk-duration, 180ms);
    z-index: 0;
  }
  .gk-nav-link > *, .gk-nav-btn > * { position: relative; z-index: 1; }

  /* ── Wide viewports (≥900px) — center cluster goes row-style ──
     When there's room, the center cluster's icon + label sit
     side-by-side with a readable label (14px, was 10px stacked) —
     the "Produits | Utilisateurs" pattern from the desktop mock.
     Right cluster stays icon-only (column / centered) since those
     buttons carry no visible label by design (lang's "EN" hint is
     absolute and hugs the bottom). 2026-05-09 user feedback "when
     enough space, display icons and text close to each other using
     large text sizes". */
  @media (min-width: 900px) {
    .gk-nav-link {
      flex-direction: row; gap: 8px;
      padding: 6px 12px;
      font-size: 14px; font-weight: 600;
      min-height: 44px;
    }
    .gk-nav-link i { font-size: 16px; }
    .gk-nav-link .tr,
    .gk-nav-link.gk-nav-feat-header > span { font-size: 14px; }
  }
  /* Hover (transient), focus-visible (keyboard navigation, same paint
     as hover so Tab users see the same affordance), and active
     (persistent) all paint the same pseudo-pill — sticky-hover idiom
     (= "you are here"). Active additionally tints the text/icon with
     --accent. Selector list covers both clusters: .gk-nav-link (center)
     AND .gk-nav-btn (right — including the features crank when on
     /features). */
  .gk-nav-link:hover::before, .gk-nav-btn:hover::before,
  .gk-nav-link:focus-visible::before, .gk-nav-btn:focus-visible::before,
  .gk-nav-link.is-active::before, .gk-nav-link[aria-current="page"]::before,
  .gk-nav-btn.is-active::before, .gk-nav-btn[aria-current="page"]::before {
    background: var(--surface-hover, rgba(0,0,0,.05));
  }
  .gk-nav-link:hover, .gk-nav-btn:hover {
    color: var(--text, #1a1a2e);
    text-decoration: none;  /* see `<a>:hover` UA-underline note */
  }
  /* The nav does not take the element-box ring, and that part of the
     original reasoning holds: the pill is a 36px-tall ::before inset
     inside a 68px-tall nav item, so a ring on the element paints a
     second rectangle well outside the pill — the "double bg" this rule
     was written to avoid.

     What did NOT hold was the sentence that followed it: « the focused
     button gets the same surface-hover tint via :hover-equivalent state
     in all modern browsers ». There is no such state. What actually
     tints the pill is the EXPLICIT `:focus-visible::before` selector
     eleven lines above — verified 2026-08-14 by pixel comparison on
     beta, because three cheaper probes each lied about it: reading
     `outline`/`box-shadow` off the element cannot see a pseudo-element,
     `getComputedStyle(el, '::before')` does not reflect :focus-visible
     (it reports the pill transparent while the pixels show it painted),
     and a viewport pixel-clip is invalidated the moment focus scrolls
     the page.

     So the nav was never keyboard-blind — it was OUTSIDE THE TOKEN. Its
     affordance was a 5 %-opacity veil, identical to hover, where every
     other control in the system carries the 2px+2px --gk-focus-ring.
     Identical to hover is the weaker half: it says "something is under
     the pointer", not "the keyboard is here".

     The ring therefore moves onto the pill, which is where this nav's
     geometry lives. Same token as everywhere else (§D3 wants ONE ring,
     not one per component), correct shape, no second rectangle. The
     element-box shadow is explicitly cleared: gateka.css now paints
     every focusable element, and (0,2,0) here beats its (0,1,1). */
  .gk-nav-link:focus, .gk-nav-btn:focus,
  .gk-nav-link:focus-visible, .gk-nav-btn:focus-visible {
    outline: 2px solid transparent; outline-offset: 2px; box-shadow: none; }
  .gk-nav-link:focus-visible::before, .gk-nav-btn:focus-visible::before {
    box-shadow: var(--gk-focus-ring, 0 0 0 2px #fff, 0 0 0 4px #3b82f6); }
  .gk-nav-link i, .gk-nav-btn i {
    font-size: 16px; line-height: 1;
    margin: 0 !important;  /* override any inline margin-right shipped from older markup */
  }
  /* Anon connect CTA — ADR 0018 Addendum #7 (supersedes #6). Three-
     state ladder for every right-cluster sibling:
       rest:   transparent pseudo-pill (geometry only, ink-color icon)
       hover:  --surface-hover fill (Addendum #3)
       active/CTA: full --accent fill, on-accent ink
     Anon connect is permanently in the active/CTA state — it's the
     row's primary action. The full fill makes it the unmissable
     hot spot, identical visual weight to FR/EN's active pill. */
  .gk-nav-btn--cta-anon { color: #fff; }
  .gk-nav-btn--cta-anon::before {
    background: var(--accent, #e07a5f);
    opacity: 1;
  }
  .gk-nav-btn--cta-anon:hover { color: #fff; }
  .gk-nav-btn--cta-anon:hover::before {
    background: var(--accent-hover, var(--accent, #c96a50));
  }

  /* .gk-nav-btn--cta-anon-labelled retired 2026-06-30 — the labelled
     variant moved to a standalone .gk-cta-signin class with INLINE
     styles inside _header_auth_menu.html, so it works on products
     (Verlavnir, Stg, other gk_base-only surfaces) that don't ship
     gk-nav.css. Kept the base .gk-nav-btn--cta-anon rules above for
     sites still on the icon-only original (lorvert nav still uses
     them). */

  /* Features crank — gear rotates continuously while hovered, returns
     to 0deg smoothly on hover-out. Scoped via .fa-gear so the lang
     globe / theme moon / connect door icons in the same cluster don't
     pick up the animation.

     §D5 kept the 2s spin and did NOT fold it onto a duration token.
     §C's 200ms ceiling governs a *state change* — the settle-back below
     is one, and it moved .35s → 180ms. An infinite hover ornament is not
     a state change: it announces nothing, so there is nothing to make
     the user wait for. What it MUST be is escapable, and it is —
     `_motion_reset.html` sets animation-iteration-count:1, which stops
     it dead for a reduced-motion user. */
  .gk-nav-btn .fa-gear { transition: transform var(--gk-duration, 180ms) ease; }
  .gk-nav-btn:hover .fa-gear { animation: gk-crank-spin 2s linear infinite; }
  @keyframes gk-crank-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
  }

  /* Lang "next-locale" hint — absolute-positioned so the icon stays at
     the button's centroid instead of being pushed up by the label
     beneath it. Keeps icon Y-baseline identical across lang/theme/
     connect even though only lang carries a hint. Pointer-events:none
     so the hint never steals click focus from the button. */
  .gk-nav-btn--has-hint { position: relative; }
  .gk-lang-hint {
    position: absolute; left: 0; right: 0; bottom: 1px;
    text-align: center; font-size: 8.5px; font-weight: 700;
    letter-spacing: .4px; line-height: 1;
    color: var(--text3, #94a3b8);
    pointer-events: none;
  }
  /* Active state — accent ink on text + icon, complementing the
     persistent pill painted by ::before above. `i` is targeted
     explicitly because FontAwesome icons usually inherit `color` but
     some products override it on `i`. Both clusters covered. */
  .gk-nav-link.is-active, .gk-nav-link[aria-current="page"],
  .gk-nav-link.is-active i, .gk-nav-link[aria-current="page"] i,
  .gk-nav-btn.is-active, .gk-nav-btn[aria-current="page"],
  .gk-nav-btn.is-active i, .gk-nav-btn[aria-current="page"] i {
    color: var(--accent, #3b82f6);
  }

  /* Lang switch — show the <span data-lang="X"> that matches <html lang>. */
  .tr > [data-lang] { display: none; }
  html[lang="fr"] .tr > [data-lang="fr"],
  html[lang="en"] .tr > [data-lang="en"] { display: inline; }
  /* Fallback when no lang set — default to French. */
  html:not([lang="fr"]):not([lang="en"]) .tr > [data-lang="fr"] { display: inline; }

  /* Per-feature header shortcut — feat-color ink on text + icon only.
     The pill background is the SAME shared --surface-hover that every
     other nav-link gets (via the base .gk-nav-link::before rule
     above). One pill style for every center button kills the
     cascade-conflict that produced the "double bg" complaint. */
  .gk-nav-feat-header { color: var(--feat-color, var(--text2, #64748b)); }
  .gk-nav-feat-header:hover { color: var(--feat-color, var(--text)); }
  .gk-nav-feat-header i { color: var(--feat-color, inherit); }
  /* Selected pinned shortcut — feat-color ink on text + icon. Pill
     comes from the base .is-active rule (--surface-hover); per-feat
     differentiation is the text/icon colour, not the pill tint. */
  .gk-nav-feat-header.is-active,
  .gk-nav-feat-header.is-active i {
    color: var(--feat-color, var(--accent, #3b82f6));
  }

  /* Features entry — accent-coloured. */
  .gk-nav-link--features { color: var(--accent, #3b82f6); }
  .gk-nav-link--features:hover { color: var(--accent-hover, var(--accent, #3b82f6)); }
  .gk-nav-link--features i { color: var(--accent, #3b82f6); }

  /* Long nav labels — let them render fully at desktop widths (Guillaume's
     "as long as you show text, don't ellipsis it" rule). Truncation only
     kicks in if a label is genuinely too wide to coexist; we cap at 140px
     to keep one runaway label from squeezing every sibling. The narrow-
     viewport @media block below tightens this further. */
  .gk-nav-link > span,
  .gk-nav-btn > span {
    max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  /* Right cluster — these are pure icon buttons (gear / globe / moon /
     avatar). Tighten their footprint so the center cluster has room to
     breathe its full text labels. */
  .gk-nav-actions .gk-nav-btn { min-width: 32px; padding: 4px 4px; }

  /* ── Tablets ── Slim down padding only. Both nav_pages links AND
     per-product feature chips share the same .gk-nav-link rules now —
     they shrink together; neither loses its label. Right cluster
     stays at gap:0 (default) and gets tighter per-button padding so
     the icons sit shoulder-to-shoulder without any visual breathing
     room — these are pure icons, no label, no need for breathing
     room beyond the 36px touch-target floor. */
  @media (max-width: 900px) {
    .gk-nav-link, .gk-nav-btn { min-width: 48px; padding: 4px 6px; }
    .gk-nav-actions .gk-nav-btn { min-width: 30px; padding: 4px 0; }
  }

  /* ── Phones ── Tighten the bar and the auth pill, but keep icons +
     labels for every action button. Right-cluster buttons drop their
     min-width to 26 and shave padding to 0 so the icons physically
     touch each other. The center cluster collapses into a hamburger
     dropdown — too many feature chips would overflow at this width.
     Threshold lowered from 640 → 520 → 460 so only genuine portrait
     phones see the burger (2026-05-09 user feedback "present the
     hamburger a bit later"). */
  @media (max-width: 460px) {
    :root { --nav-h: 60px; }
    .gk-nav { padding: 0 12px; gap: 4px; }
    .gk-nav.is-scrolled { height: 48px; }
    /* Brand stays at full size — SVG + "Gateka" gradient text both
       readable. The earlier `display:none` on the gradient span hid the
       wordmark on phones, which the user explicitly rejected
       2026-05-09 ("stop compressing logo"). */
    .gk-nav-brand { flex-shrink: 0; }
    .gk-nav-link, .gk-nav-btn { min-width: 44px; padding: 3px 5px; font-size: 9.5px; }
    .gk-nav-link i, .gk-nav-btn i { font-size: 14px; }
    /* Right cluster: 10px gap between buttons so they read as four
       distinct icons. NO margin-left:auto — let the parent's
       `justify-content: space-between` (line 23) distribute the three
       flex children (brand / burger / actions) evenly: brand at the
       left edge, burger CENTRALIZED in the middle, actions at the
       right edge (2026-05-09 user feedback "burger should be
       centralized"). flex-shrink:0 keeps each button at its 36px
       floor. */
    .gk-nav-actions { gap: 10px; flex-shrink: 0; }
    .gk-nav-actions .gk-nav-btn { min-width: 36px; padding: 3px 4px; flex-shrink: 0; }
    /* Auth pill: keep avatar + logout, drop firstname + role text. */
    .gk-auth--out .gk-auth-text { display: none; }
    .gk-auth--out .gk-auth-main { padding: 0; }
    .gk-auth-av { width: 28px; height: 28px; font-size: 12px; }
    .gk-auth-out { width: 26px; height: 26px; font-size: 13px; }

    /* Hamburger surfaces; center cluster collapses unless the burger is
       checked. When checked, .gk-nav-center becomes a vertical dropdown
       below the nav, with each item rendered as a full-width row. */
    .gk-nav-burger-btn {
      display: inline-flex; align-items: center; justify-content: center;
      width: 36px; height: 36px;
      cursor: pointer; color: var(--text2, #64748b);
      font-size: 18px; border-radius: var(--radius-sm, 8px);
      /* Opaque background so the button reads as a solid surface above
         the translucent .gk-nav blur — page content scrolling beneath
         doesn't show through. Matches the dropdown panel's surface
         (--bg2 → --gk-bg-surface, light/dark-aware). */
      background: var(--bg2, #fff);
      /* Centralized in the slack between brand and the right cluster
         via the parent's `justify-content: space-between` (line 23) +
         `margin: 0 auto`. The auto margins claim equal share of the
         remaining width, so the burger sits at the visual midpoint
         between brand-end and actions-start regardless of brand width
         (2026-05-09 user feedback "hamburger should be centralized").
         Note: this is the gap-midpoint, not the viewport-50% midpoint —
         the latter would overlap the right cluster on a 390px phone
         once the "Gateka" wordmark is kept visible. */
      margin: 0 auto;
    }
    .gk-nav-burger-btn:hover { background: var(--surface-hover, rgba(0,0,0,.05)); }
    .gk-nav-center { display: none; }
    .gk-nav-burger-toggle:checked ~ .gk-nav-center {
      display: flex; flex-direction: column;
      position: absolute; top: var(--nav-h); left: 0; right: 0;
      /* Opaque background — content underneath must NOT show through.
         --bg2 → --gk-bg-surface ( #ffffff light / #1a1e27 dark ) per
         _theme.html, so dark-mode users get an opaque dark panel too. */
      background: var(--bg2, #fff);
      border-bottom: 1px solid var(--border, rgba(0,0,0,.08));
      box-shadow: 0 8px 24px rgba(0,0,0,.12);
      padding: 8px;
      gap: 4px;
      align-items: stretch;
      height: auto;
      z-index: 99;
    }
    .gk-nav-burger-toggle:checked ~ .gk-nav-center .gk-nav-link {
      flex-direction: row;
      justify-content: flex-start;
      gap: 16px;
      padding: 16px 18px;
      min-height: 56px;
      width: 100%;
      font-size: 16px;
      font-weight: 500;
    }
    /* Bigger icons in the burger dropdown — products like Gateka with
       only 2 nav items leave lots of vertical space; phones don't have
       hover affordance, so the icon needs to read as a tappable
       primary action, not a tiny hint (2026-05-09 user feedback). */
    .gk-nav-burger-toggle:checked ~ .gk-nav-center .gk-nav-link i {
      font-size: 22px; min-width: 32px; text-align: center;
    }
  }

  /* ── Very small phones ── extra-tight tracking. Feature chips finally
     hide here because there's truly no room left after the brand SVG +
     auth pill + lang/theme. Same rule for nav_pages would over-collapse
     primary navigation, so feat-header is the only one that bows out. */
  @media (max-width: 380px) {
    .gk-nav { padding: 0 8px; }
    /* Brand stays at native size — user pref. */
    .gk-nav-link, .gk-nav-btn { min-width: 40px; padding: 3px 4px; }
    /* Right cluster: still keep 32px (down from the 36px phone default)
       on the smallest phones to fit comfortably, but never collapse to
       the previous 24px which read as "icons missing" (2026-05-09). */
    .gk-nav-actions .gk-nav-btn { min-width: 32px; padding: 3px 2px; }
    .gk-nav-feat-header { display: none; }
  }

  /* ── Center-cluster spacious mode (architect 2026-05-06 / ADR 0018 amendment) ──
     Below 1280px the .gk-nav-link items in .gk-nav-center stack the icon
     above the label (default flex-direction: column from the base rule).
     At ≥1280px we have room to lay icon + label SIDE-BY-SIDE — bigger
     touch target, no vertical stacking, more readable. The right-cluster
     (lang/theme/auth) keeps its compact icon-only shape regardless of
     viewport, since those don't carry labels.
     Pure CSS — feedback_single_source_render: layout decisions belong
     server-side or in CSS, never JS. */
  @media (min-width: 1280px) {
    /* Wider gap between center buttons — the default gap: 2px makes
       Services/Expertise/Ressources feel cramped when there's plenty
       of horizontal room. */
    .gk-nav-center { gap: 18px; }
    .gk-nav-center .gk-nav-link {
      flex-direction: row;
      gap: 8px;
      padding: 6px 12px;
      min-height: 40px;
      font-size: 14px;
    }
    .gk-nav-center .gk-nav-link i {
      font-size: 16px;
    }
    /* Lift the 80px max-width cap inherited from .gk-nav-link > span —
       in spacious mode there's room for full labels ("Ressources",
       "Expertise"…) without truncation. The cap still applies to the
       right cluster (.gk-nav-btn > span) where it remains useful. */
    .gk-nav-center .gk-nav-link > span,
    .gk-nav-center .gk-nav-link > span.tr {
      max-width: none;
      overflow: visible;
    }
  }

  /* Even more spacious at very wide viewports (≥1600px) — extra breathing
     room between center buttons. The cluster sits in a wide bar with the
     brand left and right cluster tight on the right; bumping the inter-
     button gap soaks up the empty middle without making any single
     button look stretched. */
  @media (min-width: 1600px) {
    .gk-nav-center { gap: 28px; }
  }


/* ── Sélecteur de langue — la pilule qui s'ouvre ─────────────────────────
   La forme vient du démo envoyé par l'owner (`files.zip`) : repliée elle
   montre le globe et le code courant ; ouverte elle révèle les AUTRES
   langues en décalé, et le globe fait un demi-tour.

   ⚠ Owner 2026-08-15 : « the animation does not work ». C'était vrai —
   j'avais rendu la pilule toujours ouverte, ce qui supprimait le dépliage
   ET tout ce qui bougeait avec. Le dépliage est de retour, à 3+ langues
   seulement : à 2, une pilule qui s'ouvre pour révéler UN code ferait
   passer de un clic à deux.

   ⚠ Zéro GSAP. Le décalé se fait par `transition-delay` sur `nth-child`,
   le demi-tour par une transition de `transform`. L'ordre du §C de
   l'épopée UX est « CSS d'abord », et ADR-0105 réserve GSAP aux surfaces
   marketing — « jamais sur les surfaces opérationnelles ». ~70 ko pour
   deux codes qui glissent n'est pas un échange raisonnable.

   ⚠ Rien au-dessus de 200 ms (§C). Au-delà, une animation
   d'administration cesse d'informer et commence à faire attendre.

   Jetons de `_theme.html` avec repli littéral. `--accent` est
   per-produit : la pastille est rose sur Artsiderz sans une ligne de plus. */
.gk-langpill {
  position: relative; display: inline-flex; align-items: center;
  margin: 0 2px;
  /* ⚠ `--surface2` N'EXISTE PAS. Mesuré le 2026-08-15 : le jeton
     n'apparaissait qu'ICI dans tout l'arbre — jamais déclaré, ni dans
     `_theme.html` ni ailleurs. La pilule peignait donc TOUJOURS le
     littéral de repli `#f1f5f9`, qui passe pour un plateau neutre en clair
     et devient une dalle blanche en sombre.

     Relevé en navigateur, `data-theme="dark"`, page authentifiée : fond de
     page `#0f141c` (Gateka) / `#14182a` (Artsiderz), plateau de la pilule
     `#f1f5f9`, et les codes au repos en `--text3` par-dessus — 2,44:1 sur
     Gateka, 3,46:1 sur Artsiderz. En dessous des 4,5:1 d'AA pour un texte
     de 10 px, c'est-à-dire illisible, et invisible à tout grep puisque la
     règle CSS était « juste ».

     `--surface-hover` est le jeton de CETTE notion, et `_theme.html` le dit
     mot pour mot : « a translucent veil that composes over whatever sits
     beneath it, so it is the one token that must inverse in dark ». Il est
     déclaré dans les deux modes, il se compose sur le fond réel, et il
     reste distinct de `--surface` que les segments prennent au survol.
     Un plateau translucide, pas un second jeton inventé. */
  background: var(--surface-hover, rgba(0, 0, 0, .05));
  border: 1px solid transparent;
  border-radius: 999px; padding: 3px;
  transition: border-color var(--gk-duration-fast, 120ms) var(--gk-ease, ease);
}
.gk-langpill:hover { border-color: var(--accent, #cbd5e1); }

/* Le déclencheur — pastille pleine, c'est la langue courante. */
.gk-langpill-trigger {
  list-style: none;
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 24px; padding: 0 11px;
  background: var(--accent, #3b82f6); color: #fff;
  border-radius: 999px; cursor: pointer;
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
}
.gk-langpill-trigger::-webkit-details-marker { display: none; }
.gk-langpill-trigger:focus-visible {
  outline: 2px solid var(--accent, #3b82f6); outline-offset: 2px;
}
.gk-langpill-globe { font-size: 11px; flex: none; }
/* `.gk-langpill-code` se déclare PLUS BAS, avec le rouleau — c'est le même
   élément et il n'a qu'un bloc. Il en avait deux pendant dix minutes le
   2026-08-15 (un `display: inline-block` ici, la boîte du rouleau là-bas),
   ce qui est exactement la faute que cette base traque partout ailleurs :
   une notion, deux déclarations, et la seconde gagne en silence. */

/* LE DÉPLIAGE. `<details>` bascule `display` sur son contenu, donc on ne
   peut pas animer une hauteur — on anime la LARGEUR et l'opacité des
   segments, ce qui donne le même effet de glissement que le démo. */
/* ⚠ LA CAUSE DE « the animation is not smooth ».
   `<details>` MASQUE ses enfants non-`<summary>` quand il est ferme — le
   navigateur leur applique `content-visibility: hidden` (autrefois
   `display: none`). Une transition CSS a besoin d'une image de DEPART :
   un element qui passe de « pas rendu » a « rendu » n'en a pas, donc le
   fondu et le glissement ne s'executaient jamais. Ils SAUTAIENT.

   On garde `<details>` pour sa semantique et son clavier gratuits, et on
   lui retire sa facon de cacher : le conteneur reste RENDU en
   permanence, et c'est sa LARGEUR qui s'anime. L'element existe toujours,
   donc la transition a ses deux images.

   `content-visibility: visible` est le pendant moderne du `display: flex`
   ci-dessous ; les deux sont necessaires selon le navigateur. */
/* ⚠ LA DISTANCE PARCOURUE DOIT EGALER LA LARGEUR DU CONTENU.
   Owner 2026-08-15, capture a l'appui : « the transition of the language
   switch is not smooth ».

   Ce bloc animait `max-width: 0 -> 40ch`. La borne large etait un choix
   assume — « `max-width` s'anime, `width: auto` non » — et c'est ce choix
   qui produisait le claquement. Mesure a deux langues : le contenu fait
   ~36 px, la borne ~320 px. La revelation est donc FINIE a 11 % du trajet,
   soit ~18 ms des 180 ms declarees ; les 160 ms restantes animent du vide.
   A la fermeture c'est l'inverse — on attend, puis ca claque. Le
   `cubic-bezier` etait juste, il s'appliquait a une distance fausse.

   La grille resout exactement ca : `0fr -> 1fr` fait parcourir la LARGEUR
   REELLE du contenu, sans nombre magique et sans mesure JS. La duree
   declaree redevient la duree vue.

   ⚠ Il faut les DEUX elements — la grille qui s'anime et une piste
   interieure qui porte `overflow: hidden`. Poser `overflow` sur la grille
   elle-meme ne coupe rien : c'est la PISTE qui retrecit, et son contenu
   deborderait en clair. C'est pourquoi `_lang_switch.html` a gagne un
   `<div class="gk-langpill-track">` le meme jour ; les deux ne se separent
   pas. */
/* ⚠ L'ETAT OUVERT EST LE DEFAUT, ET LE REPLI EST L'EXCEPTION.
   Cette polarite n'est pas un gout : elle est ce qui rend la pilule
   utilisable SANS JavaScript. `<details>` porte `open` en permanence
   (voir `_lang_switch.html`), donc sans JS les langues sont toutes
   visibles en ligne — le comportement d'avant la pilule, qui marchait.
   C'est le JS qui pose `aria-expanded="false"` et REPLIE.

   Le repli se lit sur le declencheur, la ou ARIA veut l'etat d'un
   controle de divulgation, et `:has()` le remonte au conteneur. Une
   SEULE source d'etat, lue par tout le monde. */
.gk-langpill-opts {
  display: grid !important;
  grid-template-columns: 1fr;
  content-visibility: visible !important;
  padding-left: 2px;
  transition: grid-template-columns var(--gk-duration, 180ms) var(--gk-ease, cubic-bezier(.4, 0, .2, 1)),
              padding-left var(--gk-duration, 180ms) var(--gk-ease, ease);
}
.gk-langpill:has(> .gk-langpill-trigger[aria-expanded="false"]) .gk-langpill-opts {
  grid-template-columns: 0fr; padding-left: 0;
}
/* La piste : c'est elle qu'on coupe pendant que la colonne grandit.
   `min-width: 0` est obligatoire — sans lui la piste refuse de descendre
   sous sa largeur min-content et la pilule reste ouverte a l'arret. */
.gk-langpill-track {
  display: flex; align-items: center; gap: 2px;
  overflow: hidden; min-width: 0;
}
.gk-langpill-seg {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; min-height: 24px; padding: 0 8px;
  border-radius: 999px; text-decoration: none; white-space: nowrap;
  font-size: 10px; font-weight: 700; letter-spacing: .06em;
  color: var(--text3, #64748b);
  /* État DÉPLIÉ — le défaut, même polarité que le conteneur au-dessus et
     pour la même raison : sans JS, les segments doivent être lisibles. */
  opacity: 1; transform: translateX(0);
  transition: opacity var(--gk-duration-fast, 120ms) var(--gk-ease, ease-out),
              transform var(--gk-duration-fast, 120ms) var(--gk-ease, ease-out),
              background var(--gk-duration-fast, 120ms) ease-out,
              color var(--gk-duration-fast, 120ms) ease-out;
}
.gk-langpill:has(> .gk-langpill-trigger[aria-expanded="false"]) .gk-langpill-seg {
  opacity: 0; transform: translateX(-6px);
}
/* LE DÉCALÉ vit maintenant dans le VOCABULAIRE — `.gk-stagger` +
   `.gk-stagger-item`, `gateka.css` §C — et plus ici.
   (Étape 3 de la consultation …-eb82be, qui en fait son test
   d'acceptation : « si le vocabulaire ne sait pas exprimer le SEUL
   mouvement que le chrome expédie aujourd'hui, le vocabulaire est mal
   conçu ». Il le sait ; c'est cette suppression qui le prouve.)

   Ce qui a été déplacé sans changer d'un millième : le rang `--i` posé par
   le gabarit (une règle valable pour DEUX langues comme pour six — owner
   2026-08-15 : « i want to have the same animation »), et sa BORNE `min()`
   à deux pas, sans laquelle le coût du dépliage est celui du DERNIER
   segment et grandit avec le nombre de langues (250 ms à trois, 440 à six,
   et aucun nombre de la règle au-dessus de 200 — §D5).

   La remise à zéro sous `prefers-reduced-motion` a suivi le décalé : le
   reset universel annule les DURÉES, pas les DÉLAIS, donc elle doit vivre
   là où le délai est déclaré. `_lang_switch.html` lie `gateka.css` pour
   que ce partiel emporte son vocabulaire chez les en-têtes sur mesure —
   `artsiderz.fr` liait `gk-nav.css` et PAS `gateka.css`, mesuré le
   2026-08-15. */
.gk-langpill-seg:hover,
.gk-langpill-seg:focus-visible {
  background: var(--surface, #fff); color: var(--text, #0f172a);
}
.gk-langpill-seg:focus-visible {
  outline: 2px solid var(--accent, #3b82f6); outline-offset: 1px;
}

/* LE DEMI-TOUR du globe à l'ouverture — l'accusé de réception du clic. */
.gk-langpill-globe { transition: transform var(--gk-duration, 180ms) var(--gk-ease, ease-out); }
.gk-langpill:has(> .gk-langpill-trigger[aria-expanded="true"]) .gk-langpill-globe {
  transform: rotate(180deg);
}

/* ── La moitié « sortie » ────────────────────────────────────────────────
   Le changement de langue est une NAVIGATION (cookie + 303, i18n serveur
   — `gk_base.html:255`, ADR-0038 §3). Rien ne survit au chargement, donc
   une timeline unique est impossible : le mouvement se joue en deux
   moitiés.

   ⚠ CE BLOC A ÉTÉ ÉCRIT SUR UNE HYPOTHÈSE FAUSSE, et il n'a jamais rien
   montré. Elle était écrite ici même : « les ~150 ms de latence réseau
   suffisent à voir le geste », donc on ne retardait pas le lien. Mais
   cette latence n'est pas du temps de RENDU : un clic sur un lien démarre
   la navigation immédiatement et le navigateur cesse de peindre. Les
   transitions posées au clic n'avaient donc pas une seule image.

   Owner 2026-08-15 : « when the language is selected […] no effect
   happen, a lag then a full reload of the page happen ». Le retard qu'on
   refusait d'ajouter, l'utilisateur le subissait déjà — et sans rien voir.
   `gk-nav.js` tient maintenant la navigation ~170 ms et la relance
   lui-même. C'est un coût RÉEL et assumé : on dépense du temps mort pour
   en faire un accusé de réception. Sous `prefers-reduced-motion`, on ne
   le dépense pas — le lien part tout de suite.

   ⚠ `.is-switching` doit BATTRE la règle d'ouverture du globe. Celle-ci
   est passée à `:has()` le même jour et pèse (0,4,0) ; sans le
   `.gk-langpill` en tête, ce sélecteur-ci pesait (0,3,0) et le globe
   restait bloqué à 180°. Une régression que rien n'aurait signalée. */
/* ⚠ PENDANT LA BASCULE, LES SEGMENTS S'EFFACENT. Owner 2026-08-15,
   capture a l'appui : « i expect a more subtile effect and not this
   double english stuff ».

   Le doublon etait le mien : au clic, le rouleau ecrit deja le nouveau
   code dans la pastille, PENDANT que le segment cliqué reste affiche —
   et je le peignais meme en accent avec un agrandissement, donc deux
   « EN » cote a cote pendant les 170 ms de retenue. On annoncait deux
   fois la meme chose, plus fort a chaque fois.

   Le choix est deja dit par le rouleau, qui le nomme dans la pastille.
   Les segments n'ont donc plus rien a dire : ils se retirent. Un seul
   message a la fois.

   ⚠ `max-width: 0` en plus de l'opacite : sans lui, la pilule garderait
   la largeur de ses segments effaces et se contracterait d'un coup au
   chargement suivant. */
/* ⚠ Les deux durées lisent le JETON, pas un nombre. Elles ont été écrites
   `150ms` et `110ms` le 2026-08-15 et faisaient rougir
   `test_operational_stylesheets_use_the_tokens_not_literals` — une porte
   partagée, donc `cs done` bloqué pour TOUTE molécule touchant gateka-core,
   pas seulement pour celle qui a écrit le nombre.

   Elles retombent sur `--gk-duration-fast` (120 ms) et non sur
   `--gk-duration` (180 ms) : la retenue de `gk-nav.js` est de ~170 ms, et
   une sortie de 180 ms ne finirait jamais avant que la page parte. Aucun
   raisonnement n'était attaché au 150/110 — c'était « un peu moins que 170 ».
   Le jeton dit la même chose, en un seul endroit. */
.gk-langpill.is-switching .gk-langpill-opts {
  grid-template-columns: 0fr; padding-left: 0;
  transition-duration: var(--gk-duration-fast, 120ms);
}
.gk-langpill.is-switching .gk-langpill-seg {
  opacity: 0; transition-duration: var(--gk-duration-fast, 120ms);
}
/* Le globe fait un demi-tour de plus, pas un tour complet : il PROLONGE
   celui de l'ouverture au lieu de repartir de zero. Un tour entier en
   170 ms se lisait comme un sursaut. */
.gk-langpill.is-switching:has(> .gk-langpill-trigger) .gk-langpill-globe {
  transform: rotate(270deg);
}

/* ── LE ROULEMENT DU CODE — l'effet que l'owner demande sur le texte ─────
   Le code courant part vers le haut pendant que le code choisi arrive par
   le bas. C'est l'accusé de réception : il dit CE QUI a été choisi, pas
   seulement « quelque chose s'est passé ».

   Deux enfants empilés dans un rouleau, le second posé par `gk-nav.js` au
   clic. `-50%` et non une hauteur en pixels : le rouleau fait exactement
   deux lignes, donc la moitié est toujours la bonne, quelle que soit la
   taille de police héritée. */
.gk-langpill-code {
  display: inline-block; overflow: hidden;
  height: 1em; line-height: 1em; vertical-align: bottom;
}
.gk-langpill-roll {
  display: block;
  transition: transform var(--gk-duration, 180ms) var(--gk-ease, cubic-bezier(.4, 0, .2, 1));
}
.gk-langpill-roll > span { display: block; height: 1em; line-height: 1em; }
.gk-langpill.is-switching .gk-langpill-roll { transform: translateY(-50%); }

/* ── La moitié « entrée » ────────────────────────────────────────────────
   Jouée au chargement de la nouvelle page : la pastille arrive. Les deux
   demi-tours se rejoignent, donc un tour complet perçu. */
@keyframes gk-langpill-arrive {
  from { transform: scale(.86); opacity: .5; }
  60%  { transform: scale(1.05); opacity: 1; }
  to   { transform: scale(1); opacity: 1; }
}
.gk-langpill-trigger {
  animation: gk-langpill-arrive var(--gk-duration, 180ms)
             var(--gk-ease, cubic-bezier(.34, 1.56, .64, 1)) both;
}

/* Une préférence système, pas une option esthétique. Tout tombe — et le
   dépliage reste FONCTIONNEL, juste instantané. */
@media (prefers-reduced-motion: reduce) {
  .gk-langpill-seg,
  .gk-langpill-globe,
  .gk-langpill-opts,
  .gk-langpill-track,
  .gk-langpill-roll,
  .gk-langpill { transition: none !important; }
  /* Le `transition-delay: 0ms !important` du décalé est parti avec lui,
     dans le bloc `prefers-reduced-motion` de `gateka.css` §C — une seule
     adresse pour une seule notion. */
  .gk-langpill-trigger { animation: none; }
}

@media (max-width: 640px) {
  /* ⚠ `.gk-langpill .gk-langpill-globe` (0,2,0) et non `.gk-langpill-globe`
     (0,1,0). FontAwesome déclare `.fa-solid { display: var(--fa-display,
     inline-block) }` à la MÊME spécificité : à égalité, c'est l'ordre source
     qui tranche, et une media-query n'ajoute rien au calcul.

     Mesuré le 2026-08-15 par CDP getMatchedStylesForNode, à 390 px, page
     authentifiée : sur `beta.gateka.fr` cette règle gagne et le globe
     disparaît comme prévu ; sur `beta.artsiderz.fr` le globe RESTE peint,
     parce que `features.html:14` lie FontAwesome une SECONDE fois, après
     `gk-nav.css` — l'en-tête sur mesure d'Artsiderz l'avait déjà lié
     ligne 21. La règle était identique, la cascade non.

     Monter la spécificité rend la déclaration robuste à l'ordre de
     chargement, ce qu'aucun produit ne contrôle. Chasser les doublons de
     <link> produit par produit corrigerait CETTE page et laisserait la
     suivante casser : `gateka.style.css_link()` existe, son équivalent
     FontAwesome n'existe pas — c'est une tâche à part, pas ce lot. */
  .gk-langpill .gk-langpill-globe { display: none; }
  .gk-langpill-seg { min-width: 26px; padding: 0 6px; }
}

/* ── LE MOT DE LA MARQUE EN PARTICULES (Gateka uniquement) ────────────
   Owner 2026-08-18 : « please do something better here » (capture de
   l'en-tête). Le mot-symbole de l'accroche est fait de briques depuis ce
   matin ; celui de la barre restait un `<span>` en dégradé. Deux langages
   visuels pour une même marque, à 200 px l'un de l'autre.

   ⚠ CETTE CLASSE N'EXISTE QUE DANS LE `brand_html` DE GATEKA
   (packages.yaml). Les autres produits gardent leur marque : rien ici ne
   s'applique à eux, et ce fichier est partagé par les dix-huit.

   ⚠ LE TEXTE DE REPLI EST LA RÈGLE, LE CANVAS EST LE BONUS. Tant que le
   script n'a pas construit de particules, c'est « Gateka » qui s'affiche —
   sans JS, sans canvas, police non chargée. La classe `gk-mot-pret` est
   posée par le script APRÈS échantillonnage. Une marque de navigation ne
   peut pas dépendre d'un effet.

   Une fois prêt, le repli passe en masquage ACCESSIBLE et non en
   `display:none` : le canvas est `aria-hidden`, donc c'est ce texte qui
   porte le nom du lien pour un lecteur d'écran. */
/* ⚠ LE MOT EST PLUS GRAND ICI QUE LE TEXTE DE LA BARRE, et c'est ce qui
   rend la brique lisible. Owner 2026-08-18 : « remove the G. you can pick
   a lower definition bricks here, otherwise it is not readable ».

   Le raisonnement tient en une division. À 20 px, la hauteur de capitale
   fait ~14 px ; un pas de 2 px donne SEPT rangées pour dessiner une
   lettre, et sept rangées d'un échantillonnage (pas d'une fonte pensée
   pour ça) rendent une bouillie. Grossir la brique SANS grossir le mot
   aggrave : on passe à cinq rangées.

   Retirer le « G » libère ~44 px : le mot les prend. À 30 px la capitale
   fait ~21 px, et un pas de .105em (≈3 px) donne toujours sept rangées —
   mais des rangées de 3 px, franches, au lieu de 2 px empâtés. Même
   nombre de briques, trois fois la surface : c'est la « lower definition »
   demandée, et elle se lit.

   ⚠ LA BARRE NE GRANDIT PAS POUR AUTANT : `--nav-h` vaut 68 px et le mot
   en occupe ~30. Si tu montes encore la taille, vérifie la hauteur rendue
   de `.gk-nav-brand` avant de conclure — c'est elle qui pousse. */
.gk-nav-mot{position:relative;display:inline-flex;align-items:center;line-height:1;
  font-size:30px;letter-spacing:0}
.gk-nav-mot canvas{display:none}
.gk-nav-mot.gk-mot-pret canvas{display:block}
.gk-nav-mot.gk-mot-pret .gk-nav-mot-repli{
  position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

/* ⚠ CETTE RÈGLE EST ICI, APRÈS LA BASE, ET PAS DANS LE BLOC 460 px PLUS
   HAUT — c'est de l'ORDRE SOURCE, pas de la spécificité. `.gk-nav-mot` de
   base est déclarée en fin de fichier ; posée dans la media-query de la
   ligne 305, la version mobile passait AVANT et se faisait écraser par
   elle. Mesuré : la marque rendait 55 px dans une barre de 60 sur
   téléphone, exactement comme sur bureau — la règle existait et ne
   s'appliquait jamais.

   ⚠ LA BARRE MAIGRIT, LE MOT AUSSI : 68 px de haut sur bureau, 60 sur
   téléphone. Un mot de 30 px y laisse 2,5 px de jeu de chaque côté,
   c'est-à-dire qu'il touche presque le bord. */
@media (max-width: 460px) {
  .gk-nav-mot { font-size: 23px; }
}

/* ⚠ LA BARRE RÉTRÉCIT AU DÉFILEMENT, LE MOT DOIT SUIVRE. Mesuré après
   avoir fait défiler la page : la barre passe à 52 px (48 sur téléphone)
   et la marque en faisait 55 — elle DÉBORDAIT. Le défaut ne se voit qu'une
   fois la page défilée, donc pas sur une capture du haut de page, et pas
   davantage au chargement : c'est pour ça qu'il a survécu à trois passes
   de vérification.

   `.gk-nav-brand` est verrouillée (voir l'en-tête du fichier) : on ne
   touche pas à sa règle, on agit sur le mot, qui est à nous. */
/* ⚠ ON MET À L'ÉCHELLE, ON NE CHANGE PAS LA POLICE — et c'est une leçon
   sur ce canvas. J'ai d'abord écrit `font-size: 24px` ici : mesuré, la
   marque restait à 55 px. La raison n'est pas la cascade mais le fait que
   les dimensions du canvas sont FIGÉES À LA CONSTRUCTION (le script lit la
   police une fois, pose `width`/`height` en pixels, et ne recalcule qu'au
   redimensionnement de la fenêtre — pas au défilement). Une règle de
   police sur un canvas déjà peint ne fait rien.

   `transform: scale()` agit sur le rendu, sans rebâtir : 0.88 ramène les
   55 px à 48, ce qui tient dans les 52 px de la barre repliée. Et
   `transform-origin: left` pour que le mot reste calé à gauche au lieu de
   glisser vers le centre pendant la transition. */
.gk-nav.is-scrolled .gk-nav-mot canvas{
  transform:scale(.88);transform-origin:left center;
  transition:transform .25s var(--gk-ease, ease)}
@media (prefers-reduced-motion:reduce){
  .gk-nav.is-scrolled .gk-nav-mot canvas{transition:none}
}

/* ⚠ LA LIGNE DE MARQUE S'EFFACE AVANT DE DÉBORDER. Elle est la chose la
   moins importante de la barre : devant elle il y a la marque (identité)
   et derrière les commandes (langue, thème, connexion). Elle prend donc
   la place qui reste et disparaît sous 900 px plutôt que de comprimer le
   reste ou de passer à la ligne. Une barre qui se réorganise pour loger
   une baseline a inversé ses priorités. */
/* ⚠ CENTRÉE SUR LA BARRE, PAS ENTRE SES VOISINS. Owner 2026-08-18 :
   « bigger please and centered it ». Posée dans le flux après la marque,
   elle se centrait dans la place RESTANTE — donc jamais au milieu de
   l'écran, et le milieu bougeait avec la largeur de la marque et du groupe
   de commandes. En absolu à 50 %, elle est centrée sur la barre quoi qu'il
   arrive à ses voisins.

   ⚠ `pointer-events:none` — elle couvre le centre de la barre. Sans ça,
   c'est une plaque invisible posée sur tout ce qui s'y trouverait (les
   liens de navigation, quand on est connecté). Elle est décorative : elle
   ne doit intercepter aucun clic.

   Elle disparaît sous 900 px : il n'y a plus de milieu disponible entre la
   marque et les commandes. */
.gk-nav-baseline{
  /* ⚠ ÉLÉMENT DE FLUX, PAS CENTRÉ EN ABSOLU. Centrée sur la BARRE, la
     phrase ignorait où commencent les boutons : une fois grossie et mise
     en gras elle s'est étendue jusque SOUS « Nous contacter » — mesuré,
     elle finissait à x=1014 alors que les commandes commencent à x=946,
     68 px de chevauchement, illisible pour les deux.

     `flex:1` avec `min-width:0` lui donne exactement l'espace laissé
     libre entre la marque et les commandes, et pas un pixel de plus : le
     chevauchement devient structurellement impossible, à toute largeur.
     Elle est centrée dans CET espace plutôt que sur la barre — un très
     léger décalage, contre une collision garantie. */
  flex:1 1 auto;min-width:0;margin:0 20px;
  pointer-events:none;text-align:center;
  @keyframes gkReflet{from{background-position:140% 0}to{background-position:-40% 0}}
@media (prefers-reduced-motion:reduce){ .gk-nav-baseline{animation:none} }
/* ⚠ C'EST LE MESSAGE CLÉ DE LA PAGE, il se peint comme tel. Owner
     2026-08-18 : « please put that in bold or something. maybe lovable
     effect ? this is the key message ». En 15 px / 500 / `--text2` il
     avait le poids d'une légende, entre une marque et deux boutons — donc
     la phrase la plus importante du site était la moins visible de la
     barre.

     Le dégradé accent → or est celui de la marque (`.gk-gradient`), pas un
     ornement de plus : la promesse porte les couleurs de qui la fait.

     ⚠ `-webkit-text-fill-color: transparent` EXIGE que le dégradé peigne
     vraiment. Si `--gold` venait à manquer sur une surface, le texte
     deviendrait INVISIBLE plutôt que moche — c'est le piège classique de
     `background-clip: text`. Les deux teintes sont déclarées dans le thème
     de cette page ; si tu portes cette règle ailleurs, vérifie-les. */
  font-size:17px;font-weight:800;letter-spacing:-.25px;white-space:nowrap;
  /* ⚠ LE TEXTE RESTE À `--text` ; SEUL UN REFLET PASSE. Owner
     2026-08-18, capture Lovable d'une phrase pleine et très contrastée :
     « on lovable, please try this effect for the header sentence ».

     Les deux versions précédentes peignaient la phrase ENTIÈRE en couleur
     de marque — l'or pur d'abord (illisible en thème clair, signalé :
     « difficult to read »), puis un or assombri : lisible, mais toujours
     une phrase colorée là où le modèle montre du texte franc. Le
     contraste ne doit pas être la variable d'ajustement d'un effet.

     Ici la base est la couleur de texte du thème — donc le contraste
     maximal, en permanence — et l'accent ne vit que dans une bande étroite
     traversant la phrase toutes les six secondes. Au repos on lit du texte
     plein ; le reflet ne fait que ramener l'œil, et aucune position de
     l'animation ne descend sous la lisibilité.

     ⚠ `-webkit-text-fill-color:transparent` exige que le dégradé peigne
     vraiment : ses trois arrêts sont `--text` et `--accent`, deux jetons
     que tout thème Gateka déclare. Sans eux le texte serait INVISIBLE, et
     non simplement terne — le piège classique de `background-clip:text`. */
  /* Cinq arrêts plutôt que trois, et une bande PLUS ÉTROITE sur une piste
     plus longue (400 %) : la première version faisait passer un large
     dégradé mou, qui se lisait comme un changement de couleur du texte.
     Un éclat se reconnaît à son bord net — d'où le cœur accent encadré de
     deux demi-teintes sur quatre points de pourcentage.

     L'accélération vient de la courbe, pas d'une durée plus courte : le
     reflet traverse vite puis la phrase se repose plusieurs secondes. Une
     brillance qui balaie sans arrêt cesse d'être un événement, et le sens
     de la phrase passe derrière son animation. */
  background:linear-gradient(100deg,
    var(--text) 44%,
    color-mix(in srgb, var(--accent) 62%, var(--text)) 48%,
    var(--accent) 50%,
    color-mix(in srgb, var(--accent) 62%, var(--text)) 52%,
    var(--text) 56%);
  background-size:400% 100%;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:var(--text);
  animation:gkReflet 5.5s cubic-bezier(.75,0,.25,1) infinite;
  /* Un halo d'accent très bas, posé sous le texte : il donne du relief à
     une phrase qui, sans lui, était du gras noir sur fond crème. Il passe
     par `drop-shadow` et non `text-shadow` — avec un remplissage rendu
     transparent par `background-clip:text`, `text-shadow` peindrait
     l'ombre de la BOÎTE et non celle des lettres. Le filtre, lui, suit la
     forme réellement peinte. Assez faible pour ne rien flouter : c'est un
     rehaut, pas une lueur. */
  filter:drop-shadow(0 1px 5px color-mix(in srgb, var(--accent) 34%, transparent));
  /* ⚠ PAS DE `max-width` EN `ch` : la phrase fait ~68 signes et un plafond
     de 48ch la coupait à TOUTES les largeurs, 1600 px compris. Un plafond
     arbitraire tronque une promesse dont on ne connaît pas la longueur
     d'avance — elle change avec la langue et avec la copie. */
  max-width:min(60vw, 780px);overflow:hidden;text-overflow:ellipsis;
  transition:opacity .25s var(--gk-ease, ease)}
.gk-nav.is-scrolled .gk-nav-baseline{opacity:.75}
/* ⚠ SEUIL À 1360 px, ET IL EST MESURÉ, PAS CHOISI. La phrase réclame
   566 px ; l'espace libre entre la marque et les commandes vaut 607 px à
   1366 et seulement 521 px à 1280. En dessous de ~1360 elle se coupait
   donc à l'ellipse — et une promesse tronquée dit moins que pas de
   promesse : le lecteur en retient une moitié de phrase.

   Le seuil à 900 px datait de la version longue, qui ne tenait de toute
   façon nulle part sous 1500 px ; il laissait passer précisément les
   largeurs où le texte est coupé. À refaire si la phrase change : c'est
   `besoin` qu'on mesure, jamais un nombre rond qui fait joli. */
@media (max-width: 1360px){ .gk-nav-baseline{display:none} }

/* ⚠ L'APPEL À L'ACTION DE LA BARRE. Il porte la couleur pleine : c'est le
   seul de la page depuis que la rangée du héros a disparu, et un bouton
   d'action qui ressemble aux réglages autour de lui n'est plus un bouton
   d'action.

   ⚠ LE LIBELLÉ TOMBE AVANT LE BOUTON. Sous 1100 px il ne reste que
   l'icône — la barre garde son geste sans écraser la marque ni les
   commandes. Le disparaître entièrement priverait le téléphone du seul
   appel à l'action ; c'est le texte qui cède, jamais la cible. */
.gk-nav-cta{
  display:inline-flex;align-items:center;gap:8px;
  padding:9px 16px;margin-right:6px;border-radius:var(--radius, 14px);
  background:var(--accent);color:#fff;text-decoration:none;
  font-size:13.5px;font-weight:600;white-space:nowrap;
  box-shadow:0 3px 14px var(--accent-glow);
  transition:transform .2s var(--gk-ease, ease),box-shadow .2s var(--gk-ease, ease),background .2s}
.gk-nav-cta:hover{background:var(--accent-hover);transform:translateY(-1px);
  box-shadow:0 6px 20px var(--accent-glow)}
.gk-nav-cta:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
@media (max-width: 1100px){
  .gk-nav-cta span{display:none}
  .gk-nav-cta{padding:9px 11px}
}
@media (prefers-reduced-motion:reduce){ .gk-nav-cta:hover{transform:none} }

/* « Nous contacter » — le SECOND geste de la barre, donc en retrait du
   premier : contour plutôt que fond plein. Deux boutons pleins côte à côte
   ne se hiérarchisent plus, et c'est le rendez-vous qui doit gagner. */
.gk-nav-contact{
  display:inline-flex;align-items:center;gap:7px;
  padding:8px 14px;margin-right:8px;border-radius:var(--radius, 14px);
  background:transparent;color:var(--text2);cursor:pointer;
  border:1px solid var(--border2, var(--border));
  font-size:13.5px;font-weight:600;white-space:nowrap;font-family:inherit;
  transition:border-color .2s var(--gk-ease, ease),color .2s var(--gk-ease, ease)}
.gk-nav-contact:hover{border-color:var(--accent);color:var(--accent)}
.gk-nav-contact:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
@media (max-width: 1240px){ .gk-nav-contact span{display:none} .gk-nav-contact{padding:8px 11px} }

/* ⚠ HAUTEUR IMPOSÉE, PAS DÉDUITE DU REMPLISSAGE. Owner 2026-08-18 : « can
   you harmonize the buttons sizes please ».

   Les deux boutons avaient un remplissage vertical équivalent au pixel
   près (9 px contre 8 px + 1 px de bordure) — et pourtant 39,6 px contre
   34 px à la mesure. L'écart vient de la hauteur de ligne des glyphes :
   l'un porte une icône vidéo, l'autre une enveloppe, et leurs boîtes
   n'ont pas la même hauteur intrinsèque. Calculer la hauteur à partir du
   contenu, c'est la laisser dépendre de l'icône qu'on y met.

   Une hauteur déclarée rend les deux boutons égaux quel que soit leur
   contenu — et le prochain bouton ajouté ici sera juste par défaut. Les
   commandes en icône seule partagent un second palier, plus discret. */
.gk-nav-cta,.gk-nav-contact{height:38px;padding-top:0;padding-bottom:0;
  box-sizing:border-box;justify-content:center}
.gk-nav-btn{height:34px;box-sizing:border-box;
  display:inline-flex;align-items:center;justify-content:center}
