/* ---------- Polices, servies par le site (P-054) ----------
   Fichiers variables de Google Fonts (licence SIL OFL 1.1, assets/fonts/OFL-*.txt),
   sous-ensembles latin et latin etendu, memes unicode-range que Google. Plus de
   feuille tierce bloquante ni de connexion a deux domaines avant le premier rendu.
   Les fichiers latins sont precharges dans chaque page. */
@font-face{font-family:'Fraunces'; font-style:normal; font-weight:300 700; font-display:swap; src:url(assets/fonts/fraunces-latin.woff2) format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Fraunces'; font-style:normal; font-weight:300 700; font-display:swap; src:url(assets/fonts/fraunces-latin-ext.woff2) format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}
@font-face{font-family:'Alan Sans'; font-style:normal; font-weight:400 700; font-display:swap; src:url(assets/fonts/alan-sans-latin.woff2) format('woff2'); unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD}
@font-face{font-family:'Alan Sans'; font-style:normal; font-weight:400 700; font-display:swap; src:url(assets/fonts/alan-sans-latin-ext.woff2) format('woff2'); unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF}

/* ---------- Cross-document view transitions (homepage <-> case studies) ----------
   Chromium only (@view-transition is unsupported elsewhere): other browsers keep
   today's instant navigation, no fallback needed. Direction ("forward" into a case
   study, "back" to the homepage) is set in JS via the pageswap/pagereveal events,
   inlined in <head> on every page -- see the comment there for why it must be inline
   rather than in script.js/cursors.js. */
@view-transition{
  navigation:auto;
}
::view-transition-old(root), ::view-transition-new(root){
  animation-duration:.36s; animation-timing-function:cubic-bezier(.2, .8, .2, 1);
}
::view-transition-old(root){animation-name:vt-fade-out}
::view-transition-new(root){animation-name:vt-fade-in}
:active-view-transition-type(forward)::view-transition-old(root){animation-name:vt-fade-out, vt-slide-out-left}
:active-view-transition-type(forward)::view-transition-new(root){animation-name:vt-fade-in, vt-slide-in-right}
:active-view-transition-type(back)::view-transition-old(root){animation-name:vt-fade-out, vt-slide-out-right}
:active-view-transition-type(back)::view-transition-new(root){animation-name:vt-fade-in, vt-slide-in-left}
@keyframes vt-fade-out{to{opacity:0}}
@keyframes vt-fade-in{from{opacity:0}}
@keyframes vt-slide-out-left{to{transform:translateX(-32px)}}
@keyframes vt-slide-in-right{from{transform:translateX(32px)}}
@keyframes vt-slide-out-right{to{transform:translateX(32px)}}
@keyframes vt-slide-in-left{from{transform:translateX(-32px)}}
@media (prefers-reduced-motion: reduce){
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*){animation:none !important}
}
/* Elements presents a l'identique sur toutes les pages : nommes pour que la
   view transition les garde en place (leger morph) au lieu de les fondre avec
   le reste. Un seul exemplaire par page, sinon le nom entre en collision.
   (fab-grid est declare plus bas, apres le all:unset de .fab qui l'ecraserait.) */
.yk-wordmark{view-transition-name:wordmark}
.theme-picker{view-transition-name:fab-theme}
::view-transition-group(wordmark), ::view-transition-group(fab-grid), ::view-transition-group(fab-theme){
  animation-duration:.36s; animation-timing-function:cubic-bezier(.2, .8, .2, 1);
}

/* --theme-t enregistre comme nombre : il devient animable,
   et tout ce qui en derive (fonds, textes, overlays, textures, rotation
   d'icone) suit la meme courbe avec cette seule transition. Pas de fondu au
   chargement : la valeur est posee inline par le script du <head> avant le
   premier calcul de style. prefers-reduced-motion est couvert par la regle
   globale sur * plus bas. */
@property --theme-t{syntax:'<number>'; inherits:true; initial-value:0}
/* Seuil separe pour le texte des cartes de travail (cf. --card-text plus bas) :
   leur fond suit sa propre trajectoire Figma Dark->Light, distincte de celle
   du fond de page, donc le point ou l'encre doit basculer n'est pas le meme
   (mesure : cran 2 pour les cartes contre cran 3 pour le reste du site). */
@property --card-text-t{syntax:'<number>'; inherits:true; initial-value:0}
/* Ouverture du selecteur de theme, de 0 (fermee) a 1 : animable, elle porte le
   trajet de la poignee entre le bouton et son cran (P-048). */
@property --open-t{syntax:'<number>'; inherits:true; initial-value:0}
/* Palette par cran (P-023, collection Figma Theme/Cran) : couleurs enregistrees
   pour que le passage d'un cran a l'autre reste un fondu, comme avec --theme-t. */
@property --yk-bg{syntax:'<color>'; inherits:true; initial-value:#141620}
@property --yk-menu-bg{syntax:'<color>'; inherits:true; initial-value:#141620}
@property --yk-text{syntax:'<color>'; inherits:true; initial-value:#dce4ff}
@property --yk-text-muted{syntax:'<color>'; inherits:true; initial-value:#afb6cd}
@property --yk-accent-text{syntax:'<color>'; inherits:true; initial-value:#6b9bff}
@property --grid-line-color{syntax:'<color>'; inherits:true; initial-value:#23252f}
@property --yk-border{syntax:'<color>'; inherits:true; initial-value:#686b78}
/* Le texte (--yk-text, --card-text-t) part en retard et va vite (0,15 s puis 0,2 s) : sa bascule
   tombe ainsi au milieu du fondu du fond, la ou le contraste s'inverse, et
   traverse la zone de faible contraste le moins longtemps possible. Meme
   courbe que les view transitions du site, pour un seul vocabulaire d'anim. */
html{
  transition:--theme-t .5s cubic-bezier(.2, .8, .2, 1),
             --card-text-t .2s cubic-bezier(.2, .8, .2, 1) .15s,
             --yk-bg .5s cubic-bezier(.2, .8, .2, 1),
             --yk-menu-bg .5s cubic-bezier(.2, .8, .2, 1),
             --yk-text .2s cubic-bezier(.2, .8, .2, 1) .15s,
             --yk-text-muted .2s cubic-bezier(.2, .8, .2, 1) .15s,
             --yk-accent-text .2s cubic-bezier(.2, .8, .2, 1) .15s,
             --grid-line-color .5s cubic-bezier(.2, .8, .2, 1),
             --yk-border .5s cubic-bezier(.2, .8, .2, 1);
}

:root{
  /* Couleurs — surfaces */
  --surface-page:#e8edf5;
  --surface-accent-subtle:#eef6ff;
  --surface-raised:#ffffff;

  /* Couleurs — texte */
  --text-primary:#141620;
  --text-secondary:#404c68;
  --text-on-info:#1652c4;

  /* Couleurs — bordures */
  --border-default:#727e98;

  /* Couleurs — accents */
  --color-white:#ffffff;

  /* Rayons */
  --radius-sm:8px;
  --radius-md:12px;
  --radius-tag:999px;

  /* Espacements — échelle 4px */
  --sp-4:4px;
  --sp-8:8px;
  --sp-12:12px;
  --sp-16:16px;
  --sp-20:20px;
  --sp-24:24px;
  --sp-32:32px;
  --sp-48:48px;

  /* Typographie */
  --fs-xs:12px;
  --fs-sm:14px;
  --fs-md:16px;
  --fs-xl:28px;

  --font-display:'Fraunces', Georgia, serif;
  --font-body:'Alan Sans', system-ui, sans-serif; /* Outfit etait declaree sans etre chargee : texte en police systeme */

  /* Layout */
  --topbar-h:68px;

  /* Grille 12 colonnes — miroir des tokens Figma (canvas 1440, marge 48, colonne 90, gouttière 24).
     Source unique pour tout alignement de fond sur la grille : cf. .yk-bg-grid et
     .case-dark-texture plus bas.
     Repère : https://css-tricks.com/building-css-grid-overlay/ */
  --grid-margin:48px;
  --grid-col:90px;
  --grid-gutter:24px;

  /* Theme sombre "YoKo" -- page d'accueil long-scroll (Intro/Work/Values/About).
     Prefixe yk- pour ne jamais entrer en collision avec les tokens ci-dessus,
     herites de l'ancien theme clair des case studies (sombres depuis). */
  /* Palette par cran : 9 crans (0 a 8), fonds sombres de 0 a 3, clairs de 4
     a 8. L'echelle saute la mi-luminance, ou aucun texte n'atteint 4,5:1
     (audit WCAG 1.4.3 du 14/10/2026). Depuis P-027, --yk-bg, --yk-menu-bg,
     --yk-text, --yk-text-muted, --yk-accent-text, --grid-line-color et
     --yk-border sont calcules depuis theme/crans.json et poses dans le bloc
     @crans plus bas (cran 0 sur :root, utilise aussi tant que l'attribut
     n'est pas pose). Le texte bascule au cran 4 avec le fond, sans degrade :
     au milieu d'un mix lineaire, fond et texte convergeraient vers le meme
     gris et le contraste s'effondrerait. */
  /* Sature plutot que neutre, comme le fond : lavande pale (bout nuit) vers
     terracotta fonce (bout chaud), au lieu d'un blanc et d'un navy sans lien
     avec la teinte du fond. Le navy restait la seule couleur "hors famille"
     du degrade -- froid sur un fond devenu chaud. Marge large sur les deux
     bouts (8,4 a 16,5), le choix est esthetique, pas contraint. */
  /* Texte attenue (rail, legendes, sous-titre) : melange du texte vers le fond,
     au moins 80 % de texte, releve jusqu'a 5,2:1 au minimum sur chaque cran.
     Remplace les opacites .8 et .7, qui tombaient a 2,94-4,48:1 aux crans 2 a 5. */
  --yk-text-dark:#141620;     /* texte sur carte claire (prescription-renewal) */
  --yk-accent:#1b5bd7;        /* var(--text-accent) -- fixe, inchange entre modes (cf. Figma) */
  /* Accent du texte pose sur le fond de page : #1b5bd7 tombe sous 3:1 sur les
     crans sombres (2,43 a 3,02:1). --yk-accent-text : teinte de #6b9bff,
     luminosite ajustee par cran pour 4,6:1 au moins (bloc @crans). */
  --yk-white:#ffffff;
  /* Texte des cartes de travail : delibrement neutre (blanc/navy), pas la
     paire sature ci-dessus. Verifie : lavande/terracotta poses sur les
     couleurs de carte (elles-memes deja saturees) tombent a 2,4-2,9 de
     contraste au pire cran -- deux teintes fortes qui se concurrencent. Le
     neutre reste a 4,4-4,66 sur les 4 cartes qui en ont besoin (la 5e,
     prescription-renewal, garde son navy fixe via .yk-card--dark : son fond
     Dark echoue deja avec du blanc, cf. table de contraste). */
  --card-text:color-mix(in oklab, #ffffff, #141620 calc(var(--card-text-t, 0) * 100%));

  /* Slider de theme (9 crans, cf. .theme-picker) : --theme-t va de 0 (cran 0,
     sombre, defaut) a 1 (cran 8). --overlay-* habille les boutons flottants et
     le calque de grille, qui reposent sur le fond de page et suivent donc le
     cran. */
  /* Depuis le 14/10/2026 (P-024) : voiles et traits tires de l'encre du cran,
     donc teintes par la surface au lieu d'un blanc ou d'un navy fixes. Calcul
     (build-theme.mjs) : icone >= 3:1 sur le voile renforce, WCAG 1.4.11 ; un
     cran qui echouerait recoit son propre --overlay-strong dans le bloc @crans. */
  --overlay-soft:color-mix(in srgb, var(--yk-text) 10%, transparent);
  --overlay-strong:color-mix(in srgb, var(--yk-text) 22%, transparent);
  /* Trait du quadrillage calcule depuis la surface (P-025) : meme teinte et
     meme chroma que --yk-bg en oklch, luminosite decalee (+ sur les crans
     sombres, - sur les clairs) jusqu'a 1,18:1 contre le fond, plafonnee pour que
     texte et texte attenue gardent 4,6:1 au moins sur le trait (crans 2 a 6 :
     1,13:1 environ). Mesure en pixels : texte sur trait opaque, pas de marge.
     Valeurs dans le bloc @crans plus bas. */

  --yk-font-display:'Fraunces', Georgia, serif;
  --yk-font-body:'Alan Sans', system-ui, sans-serif;

  --yk-fs-sm:14px;
  --yk-fs-md:16px;
  --yk-fs-xs:12px;
  --yk-fs-xl:28px;       /* font-size/xl -- titres de carte */

  --yk-header-h:64px;

  /* Couleurs de carte Work -- la collection Figma Portfolio/Card accents a
     deja un mode Dark et un mode Light par carte (le code n'utilisait que
     Dark). Chaque carte bascule de son Dark a son Light au cran 4, avec le
     texte (--card-text-t) : un fondu continu faisait passer les cartes par des
     tons moyens ou le texte des tags tombait sous 4,5:1. */
  --yk-card-payment-guarantee:color-mix(in oklab, #1b5bd7, #eef6ff calc(var(--card-text-t, 0) * 100%));
  --yk-card-tele-expertise:color-mix(in oklab, #006b8f, #d9e9ee calc(var(--card-text-t, 0) * 100%));
  --yk-card-prescription-renewal:color-mix(in oklab, #f2ff00, #fdffd9 calc(var(--card-text-t, 0) * 100%));
  --yk-card-coverage-capture:color-mix(in oklab, #037747, #d9ebe3 calc(var(--card-text-t, 0) * 100%));
  --yk-card-directory-formatting:color-mix(in oklab, #92400e, #efe2db calc(var(--card-text-t, 0) * 100%));
}

/* Palette des 9 crans (P-021 a P-029). Seule source : theme/crans.json (surface
   et texte) ; tout le bloc ci-dessous est reecrit par
   `node scripts/build-theme.mjs` et verifie par `--check`. */
/* @crans:debut -- genere par scripts/build-theme.mjs depuis theme/crans.json, ne pas editer a la main.
   Regles (P-027, P-029) : emphase teinte de #6b9bff a 4,6:1 ; texte attenue = texte + au plus 20 % de
   surface, 5,2:1 ; trait de grille 1,18:1 plafonne pour garder 4,6:1 au texte ; bordure sur le fond
   3,4:1 ; voiles = encre du cran a 10 et 22 % (cf. --overlay-* dans :root), icone >= 3:1. */
:root, html[data-theme-step="0"]{--yk-bg:#141620; --yk-menu-bg:#141620; --yk-text:#dce4ff; --yk-text-muted:#afb6cd; --yk-accent-text:#6b9bff; --grid-line-color:#23252f; --yk-border:#686b78}
html[data-theme-step="1"]{--yk-bg:#122a39; --yk-menu-bg:#122a39; --yk-text:#b1d5f8; --yk-text-muted:#8eb0ce; --yk-accent-text:#6b9bff; --grid-line-color:#1e3646; --yk-border:#627d8e}
html[data-theme-step="2"]{--yk-bg:#423651; --yk-menu-bg:#423651; --yk-text:#ffa1d5; --yk-text-muted:#ec97c8; --yk-accent-text:#7aa5ff; --grid-line-color:#4b3e5a; --yk-border:#9689a8}
html[data-theme-step="3"]{--yk-bg:#803632; --yk-menu-bg:#803632; --yk-text:#fbc19f; --yk-text-muted:#f9bf9d; --yk-accent-text:#a0c0fe; --grid-line-color:#893e3a; --yk-border:#e38f87}
html[data-theme-step="4"]{--yk-bg:#ffd0af; --yk-menu-bg:#ffd0af; --yk-text:#a52401; --yk-text-muted:#a52401; --yk-accent-text:#2f59b7; --grid-line-color:#f2c4a3; --yk-border:#93694b}
html[data-theme-step="5"]{--yk-bg:#f6e5cc; --yk-menu-bg:#f6e5cc; --yk-text:#7e4c2f; --yk-text-muted:#835236; --yk-accent-text:#3762c1; --grid-line-color:#e7d7be; --yk-border:#897963}
html[data-theme-step="6"]{--yk-bg:#cfdaca; --yk-menu-bg:#cfdaca; --yk-text:#394235; --yk-text-muted:#4e574a; --yk-accent-text:#2e57b5; --grid-line-color:#c3cebe; --yk-border:#6a7365}
html[data-theme-step="7"]{--yk-bg:#f0fbfd; --yk-menu-bg:#f0fbfd; --yk-text:#141620; --yk-text-muted:#393d46; --yk-accent-text:#416dcd; --grid-line-color:#dee8ea; --yk-border:#7f898b}
html[data-theme-step="8"]{--yk-bg:#f9f9f9; --yk-menu-bg:#f9f9f9; --yk-text:#141620; --yk-text-muted:#3a3c46; --yk-accent-text:#416dcd; --grid-line-color:#e6e6e6; --yk-border:#878787}
/* @crans:fin */

*{margin:0;padding:0;box-sizing:border-box}
@media (prefers-reduced-motion: reduce){ *{scroll-behavior:auto !important; animation-duration:.01ms !important; transition-duration:.01ms !important} }

html, body{height:100%}
body{
  font-family:var(--font-body);
  font-size:var(--fs-sm);
  line-height:1.5;
  display:flex;
  flex-direction:column;
}

/* ---------- Lien d'évitement ---------- */
.skip-link{
  position:fixed; top:-100%; left:var(--sp-16); z-index:200;
  background:var(--color-white); color:var(--text-primary);
  padding:var(--sp-12) var(--sp-20); border-radius:var(--radius-sm);
  font-family:var(--font-body); font-weight:600; font-size:var(--fs-sm);
  text-decoration:none; transition:top .15s ease;
}
.skip-link:focus{top:var(--sp-16)}

/* ---------- Topbar ---------- */
/* Fond transparent : le topbar est en flux normal (rien ne defile dessous), la
   texture et le calque de grille -- tous deux en position fixe, donc peints
   par-dessus -- restent ainsi continus jusqu'en haut de page. */
.topbar{
  background:transparent; color:var(--yk-text);
  display:flex; align-items:center; justify-content:space-between;
  padding:var(--sp-12) 0; height:var(--topbar-h); flex:none;
}
/* Bornee a 1440 et centree comme .yk-header-inner (accueil). L'echelle vient
   de 100cqw (boite de contenu de .topbar-left, sans padding) et non d'un
   pourcentage : dans gap/margin les pourcentages se resolvent sur la boite de
   contenu du conteneur flex, pas sur les 1440. --u est pose sur les enfants :
   cqw se resout contre le conteneur ancetre, jamais contre l'element qui porte
   container-type. */
.topbar-left{
  width:min(1440px, 100%); margin:0 auto; box-sizing:border-box;
  container-type:inline-size;
  display:flex; align-items:center;
}
.topbar-left > *{--u:calc(100cqw / 1440)}
/* Un seul lien « <- Back » (P-037), a la place de la fleche et du wordmark :
   32px de haut, donc au-dessus du minimum de 24px (WCAG 2.5.8). Le retrait
   gauche de 6px est repris par la marge negative : la boite de 20px de la fleche
   tombe sur la colonne 1 (48u en desktop, 24px sous 961px comme le contenu). */
.topbar-back{
  all:unset; cursor:pointer; box-sizing:border-box; display:flex; align-items:center; gap:var(--sp-8);
  height:32px; padding:0 var(--sp-12) 0 6px; color:var(--yk-text); border-radius:var(--radius-sm);
  font-family:var(--yk-font-body); font-weight:500; font-size:var(--yk-fs-wordmark); line-height:1.3; white-space:nowrap;
  margin-left:calc(48 * var(--u) - 6px);
}
@media (max-width:960px){
  .topbar-back{margin-left:calc(var(--sp-24) - 6px)}
  /* Barre collante sous 961 px (P-039) : la page est longue et le rail local
     defile avec elle, la sortie reste donc a portee. Une fois la page defilee
     (.is-stuck, pose par script.js), meme traitement que .yk-header.is-solid
     de l'accueil : fond du cran (96 %, voir plus bas) et flou de 8px, la texture reste lisible
     a travers. Au repos la barre est transparente. En desktop elle reste en
     flux normal : le rail fixe suffit. */
  .topbar{position:sticky; top:0; z-index:20; transition:background-color .2s ease}
  /* html, body{height:100%} bornerait le bloc conteneur du sticky au premier
     ecran : le corps grandit avec la page. */
  body.case-dark{height:auto; min-height:100%}
  /* 96 % (P-054) : a 75 %, une capture blanche passant sous la bande faisait
     tomber les intitules attenues a 2,5:1 (cas 02, 390 px, cran 2) ; a 92 %,
     encore 4,1:1. A 96 %, 4,6:1 au pire. */
  .topbar.is-stuck, .case-local-rail.is-stuck{
    background:color-mix(in oklch, var(--yk-bg) 96%, transparent);
    backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  }
  /* P-042 : « Back » se cache quand on descend et revient quand on remonte ;
     la bande des sections reste collee en haut. Les deux se deplacent par
     transform ; la transition n'existe que pendant le basculement (.bar-anim),
     pas pendant le suivi du defilement sous 68px. */
  .topbar.is-hidden{transform:translateY(-100%)}
  .bar-anim .topbar, .bar-anim .case-local-rail{transition:transform .2s ease, background-color .2s ease}
}
.topbar-back svg{width:20px; height:20px; flex:none}
/* Largeur fixe : le plus large des quatre libelles est Retour (3,13em en
   Alan Sans). Le passage Retour -> Back -> Volver -> 戻る, et les signes du
   brouillage, ne font pas bouger la page. */
.topbar-back-label{display:inline-block; width:3.2em}
.topbar-back-label .dud{opacity:.55}
.topbar-back:hover{background:var(--overlay-soft)}
.topbar-back:focus-visible, .skip-link:focus-visible{
  outline:2px solid var(--yk-text); outline-offset:2px; /* bleu : 2,28 a 2,43:1 sur les crans sombres (WCAG 1.4.11) */
}
.yk-wordmark:focus-visible{outline:2px solid var(--yk-text); outline-offset:4px; border-radius:var(--radius-sm)}

/* ---------- Calque de grille (bouton flex bas gauche, toutes pages) ----------
   Meme canevas 1440 / marge 48 / colonnes 90 / gouttieres 24 que .yk-grid
   (accueil) et .case-dark-layout (case studies, cf. .case-dark-texture) :
   un seul calque reutilisable partout, pas de variante par gabarit.
   --u recalcule localement en min(100cqw,1440px) plutot qu'herite : ce calque
   est position:fixed (plein viewport), un containment cqw ordinaire ne
   traverserait pas cette frontiere (meme raison que .case-dark-texture). */
.grid-overlay{
  position:fixed; top:0; bottom:0; left:0; right:0; margin:0 auto;
  width:min(1440px, 100%); z-index:1; pointer-events:none; /* sous le texte (contenu a z 1 et plus, place apres dans le DOM) : a z 30, les lignes coupaient les lettres */
  opacity:0; transition:opacity .2s ease;
  container-type:inline-size;
}
body.show-grid .grid-overlay{opacity:1}
/* Le calque passe sous le chrome de la home (rail 35, header 40) et sous les
   cartes Work (31) : les cartes masquent les lignes mais restent sous le header
   au scroll -- les monter au-dessus d'un calque a 90 les ferait recouvrir le header. */
#work .yk-grid{z-index:31}
/* --u est declare sur l'enfant, pas sur le conteneur : cqw se resout contre le
   conteneur *ancetre* le plus proche, jamais contre l'element lui-meme. Declare
   sur le conteneur, il retombait sur le viewport (colonnes de 80 + 32 au lieu
   de 90 + 24 a 1920px). */
.grid-overlay-track{
  height:100%;
  --u:calc(100cqw / 1440);
  padding:0 calc(48 * var(--u));
  box-sizing:border-box;
  display:grid; grid-template-columns:repeat(12, 1fr); column-gap:calc(24 * var(--u));
}
/* Les colonnes se dessinent du haut vers le bas, en cascade, a l'ouverture ;
   a la fermeture le fondu du calque (.2s) prend le dessus, sans decalage. */
.grid-overlay-track span{
  height:100%;
  border-left:1px solid var(--overlay-strong); border-right:1px solid var(--overlay-strong);
  transform:scaleY(0); transform-origin:top; transition:transform .35s ease;
}
body.show-grid .grid-overlay-track span{transform:none}
body.show-grid .grid-overlay-track span:nth-child(2){transition-delay:20ms}
body.show-grid .grid-overlay-track span:nth-child(3){transition-delay:40ms}
body.show-grid .grid-overlay-track span:nth-child(4){transition-delay:60ms}
body.show-grid .grid-overlay-track span:nth-child(5){transition-delay:80ms}
body.show-grid .grid-overlay-track span:nth-child(6){transition-delay:100ms}
body.show-grid .grid-overlay-track span:nth-child(7){transition-delay:120ms}
body.show-grid .grid-overlay-track span:nth-child(8){transition-delay:140ms}
body.show-grid .grid-overlay-track span:nth-child(9){transition-delay:160ms}
body.show-grid .grid-overlay-track span:nth-child(10){transition-delay:180ms}
body.show-grid .grid-overlay-track span:nth-child(11){transition-delay:200ms}
body.show-grid .grid-overlay-track span:nth-child(12){transition-delay:220ms}
/* P-032 : la ou le contenu passe a 3 colonnes (accueil sous 768px) ou a une
   colonne de lecture (cas sous 961px), le calque montre cette grille-la :
   3 colonnes, marge et gouttiere 24px. 12 colonnes de 24px a 390px ne
   decrivaient aucune mise en page. */
@media (max-width:767px){
  body.yk-page .grid-overlay-track{padding:0 var(--sp-24); column-gap:var(--sp-24); grid-template-columns:repeat(3, 1fr)}
  body.yk-page .grid-overlay-track span:nth-child(n+4){display:none}
}
@media (max-width:960px){
  body.case-dark .grid-overlay-track{padding:0 var(--sp-24); column-gap:var(--sp-24); grid-template-columns:repeat(3, 1fr)}
  body.case-dark .grid-overlay-track span:nth-child(n+4){display:none}
}

/* ---------- Boutons flottants : socle commun .fab (position : P-028 plus bas) ---------- */
.fab{
  all:unset; width:40px; height:40px; border-radius:50%; box-sizing:border-box;
  display:flex; align-items:center; justify-content:center; cursor:pointer;
  background:var(--overlay-soft); border:1px solid var(--overlay-strong);
  color:var(--yk-text);
  transition:background-color .15s ease, transform .15s ease;
}
.fab svg{width:20px; height:20px; display:block}
.fab:hover{background:var(--overlay-strong); transform:scale(1.05)}
.fab:active{transform:scale(.95)}
/* Halo du fond de page sous le contour (P-054) : sur mobile le contenu defile sous
   les boutons, le contour garde ainsi le contraste texte / fond (3:1 au moins)
   quelle que soit la carte ou l'image qui passe dessous. */
.fab:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px; box-shadow:0 0 0 6px var(--yk-bg)}

.grid-toggle-btn{position:fixed; right:var(--sp-16); bottom:var(--sp-16); z-index:150; view-transition-name:fab-grid;
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px)} /* meme flou que la capsule de theme : lisible au-dessus du contenu */
.grid-toggle-btn[aria-pressed="true"]{background:var(--overlay-strong)}

/* ---------- Slider de theme (9 crans, capsule unique au-dessus du bouton grille)
   Motif ARIA (spec theme-picker.md) : un bouton d'ouverture (aria-expanded) et,
   capsule ouverte, une poignee role="slider" verticale qui prend le focus
   (fleches, Debut, Fin). Les pastilles ne sont plus que des reperes cliquables,
   hors tabulation et masques aux technologies d'assistance : la poignee porte
   seule la valeur. Glissement par Pointer Events (script.js) ; touch-action:none
   sur toute la capsule, bouton ferme compris, pour que la page ne defile pas.
   --theme-t (0..1) reste le seul point de pilotage, pose sur <html> par
   script.js et par le script inline du <head> (evite le flash avant peinture).
   La capsule porte seule la matiere (fond, rayon). Chaque pastille garde une
   rangee cliquable pleine largeur de 24px et n'y dessine qu'un point de 6px --
   l'aspect des petits reperes de la reference sans descendre sous 24px. */
.theme-picker{
  --slot:24px; --slots:9; --pad:5px;
  touch-action:none; user-select:none; -webkit-user-select:none; -webkit-touch-callout:none;
  position:fixed; right:var(--sp-16); bottom:calc(var(--sp-16) + 48px); z-index:150; /* a droite : a gauche, les boutons recouvraient la colonne de lecture sur mobile */
  width:40px; padding:var(--pad); box-sizing:border-box;
  border-radius:999px; background:var(--overlay-soft);
  /* Contour du socle .fab, trace a l'interieur : ne decale ni la poignee ni les crans. */
  box-shadow:inset 0 0 0 1px var(--overlay-strong);
  /* Flou d'arriere-plan : sans lui le contenu de la page traverse la capsule et
     concurrence les points (emoji et texte de l'intro passaient au travers). */
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  /* border-box : la hauteur inclut le padding, il ne faut donc pas le soustraire
     au repli (40 = la taille du bouton grille) ni l'oublier a l'ouverture, sinon
     la piste perd 10px. */
  height:40px;
  /* Exception assumee a la regle « ni width ni height animees » : la capsule est
     fixe, hors flux, et ne relaie sa taille a aucun voisin ; l'ouverture dure
     250ms. Un scaleY deformerait le rayon, les points et le flou ; un clip-path
     garderait une boite de 226px qui capterait les clics et fausserait la pile
     des boutons flottants (P-028).
     Fermeture en deux temps (P-048) : la poignee redescend au bouton en 0,4 s
     et efface les crans au passage, puis la capsule se replie (0,22 s apres
     0,56 s). Ces transitions sont celles de l'etat ferme ; l'etat ouvert a les
     siennes, sans delai. */
  --open-t:0;
  transition:height .22s ease-out .56s, --open-t .4s ease-in-out, background-color .15s ease, transform .15s ease;
}
/* Capsule fermee : survol et appui du socle .fab, portes par la capsule entiere
   et non par l'icone ; pas pendant la fermeture, ou elle est encore haute. */
.theme-picker[data-open="false"]:not(.is-closing):hover{background:var(--overlay-strong); transform:scale(1.05)}
.theme-picker[data-open="false"]:not(.is-closing):active{transform:scale(.95)}
/* Ouverture : la capsule s'allonge et la poignee monte a son cran sur la meme
   courbe ; au cran 8, elle accompagne le haut de la capsule. */
.theme-picker[data-open="true"]{
  --open-t:1; height:calc(var(--slots) * var(--slot) + 2 * var(--pad));
  transition:height .24s ease-out, --open-t .24s ease-out, background-color .15s ease, transform .15s ease;
}
.theme-picker-track{
  display:flex; flex-direction:column; align-items:center;
  overflow:hidden; pointer-events:none;
}
.theme-picker[data-open="true"] .theme-picker-track{pointer-events:auto}
/* Les crans se deplient en cascade depuis le bas (le plus proche du curseur). */
.theme-dot{
  box-sizing:border-box; cursor:pointer;
  width:100%; height:var(--slot);
  display:flex; align-items:center; justify-content:center;
  opacity:0; transform:translateY(6px);
  transition:opacity .2s ease, transform .2s ease;
}
/* Le point dessine : 6px, 9px aux deux extremites. Sur 8 crans, seuls les bouts
   peuvent etre accentues symetriquement -- tout autre pas casse la symetrie
   (le rythme d'un majeur tous les 4 de la reference marche parce qu'elle a
   17 crans). Ce sont de toute facon les reperes utiles : le plus clair, le plus
   sombre. */
.theme-dot::before{
  content:''; width:6px; height:6px; border-radius:50%;
  background:var(--yk-text); opacity:.35;
  transition:opacity .15s ease; /* la taille des points ne change jamais : seule l'opacite s'anime */
}
.theme-dot:first-child::before, .theme-dot:last-child::before{width:9px; height:9px; opacity:.7}
.theme-picker[data-open="true"] .theme-dot{opacity:1; transform:none}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(2){transition-delay:25ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(3){transition-delay:50ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(4){transition-delay:75ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(5){transition-delay:100ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(6){transition-delay:125ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(7){transition-delay:150ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(8){transition-delay:175ms}
.theme-picker[data-open="true"] .theme-dot:nth-last-child(9){transition-delay:200ms}
/* A la fermeture, ils s'effacent depuis le haut, au rythme de la poignee qui descend. */
.theme-picker.is-closing .theme-dot{transition-duration:.15s}
.theme-picker.is-closing .theme-dot:nth-child(2){transition-delay:50ms}
.theme-picker.is-closing .theme-dot:nth-child(3){transition-delay:100ms}
.theme-picker.is-closing .theme-dot:nth-child(4){transition-delay:150ms}
.theme-picker.is-closing .theme-dot:nth-child(5){transition-delay:200ms}
.theme-picker.is-closing .theme-dot:nth-child(6){transition-delay:250ms}
.theme-picker.is-closing .theme-dot:nth-child(7){transition-delay:300ms}
.theme-picker.is-closing .theme-dot:nth-child(8){transition-delay:350ms}
.theme-picker.is-closing .theme-dot:nth-child(9){transition-delay:400ms}
.theme-dot:hover::before{opacity:1}
/* Le point du cran actif s'efface : la poignee le recouvre. */
.theme-dot[data-active]::before{opacity:0}

/* L'icone : bouton d'ouverture capsule fermee, poignee capsule ouverte ou en
   fermeture. Les deux portent la meme icone ; un seul est visible a la fois.
   Tous deux sont ancres par le bas, qui ne bouge pas pendant que la hauteur
   s'anime. La poignee part du centre du bouton (3 px au-dessus du cran 0) et
   rejoint son cran avec --open-t (P-048) ; --theme-t etant anime par
   @property, elle glisse au changement de cran sans transition dediee (en
   ajouter une appliquait la courbe deux fois). Le trajet passe par translate,
   pas par bottom : pas de recalcul de mise en page a chaque image. Pendant un
   glissement, elle suit le doigt (--drag-pos, pose par script.js). */
.theme-toggle-btn, .theme-handle{
  position:absolute; left:var(--pad); width:30px; height:var(--slot);
  background:none; border:none;
  transition:transform .15s ease;
}
.theme-toggle-btn{bottom:calc((40px - var(--slot)) / 2)}
.theme-picker[data-open="true"] .theme-toggle-btn{visibility:hidden}
/* En fermeture, le bouton reste focalisable (Echap y rend le focus) mais
   invisible : la poignee qui redescend le represente jusqu'au repli. */
.theme-picker.is-closing .theme-toggle-btn{opacity:0}
.theme-handle{
  display:flex; visibility:hidden; cursor:grab;
  bottom:var(--pad);
  translate:0 calc(-1 * (var(--open-t) * var(--theme-t, 0) * (var(--slots) - 1) * var(--slot) + (1 - var(--open-t)) * ((40px - var(--slot)) / 2 - var(--pad))));
}
.theme-picker[data-open="true"] .theme-handle, .theme-picker.is-closing .theme-handle{visibility:visible}
/* En fermeture, la poignee n'est qu'une image : l'appui va au bouton dessous, qui rouvre. */
.theme-picker.is-closing .theme-handle{pointer-events:none}
.theme-picker.is-dragging, .theme-picker.is-dragging .theme-handle, .theme-picker.is-dragging .theme-dot{cursor:grabbing}
.theme-picker.is-dragging .theme-handle{translate:0 calc(-1 * var(--drag-pos, 0px)); transform:scale(1.1)}
.theme-picker[data-open="true"]{background:var(--overlay-strong)}
.theme-toggle-btn:hover{background:none; transform:none} /* la capsule grossit, pas l'icone */
.theme-handle:hover{background:none; transform:scale(1.1)}
/* Capsule horizontale (fenetres basses, cf. P-028 plus bas) : data-orient="h"
   est pose par script.js d'apres --orient, qui seul depend des media queries.
   Elle s'ouvre vers la droite, cran 0 a gauche (row-reverse : la pastille du
   cran 0 est la derniere du DOM). */
.theme-picker[data-orient="h"]{transition:width .22s ease-out .56s, --open-t .4s ease-in-out, background-color .15s ease, transform .15s ease} /* meme exception que la hauteur, en horizontal */
.theme-picker[data-orient="h"][data-open="true"]{height:40px; width:calc(var(--slots) * var(--slot) + 2 * var(--pad));
  transition:width .24s ease-out, --open-t .24s ease-out, background-color .15s ease, transform .15s ease}
.theme-picker[data-orient="h"] .theme-picker-track{flex-direction:row-reverse; height:100%}
.theme-picker[data-orient="h"] .theme-dot{width:var(--slot); height:100%; flex:none; transform:translateX(-6px)}
.theme-picker[data-orient="h"][data-open="true"] .theme-dot{transform:none}
.theme-picker[data-orient="h"] .theme-handle{
  bottom:auto; top:var(--pad); width:var(--slot); height:30px;
  translate:calc(var(--open-t) * var(--theme-t, 0) * (var(--slots) - 1) * var(--slot) + (1 - var(--open-t)) * ((40px - var(--slot)) / 2 - var(--pad))) 0;
}
.theme-picker[data-orient="h"].is-dragging .theme-handle{translate:var(--drag-pos, 0px) 0}

/* ---------- Position des boutons flottants (P-028, spec fab.md) ----------
   Par defaut (mobile, et pages de cas sous 961 px ou le rail devient un
   bandeau) : en bas a droite. Desktop : pile en bas de la colonne du rail,
   meme left que .yk-rail et .case-local-rail (24 px au moins, hors zone du
   geste de retour d'iOS), 48 px du bas. Seuils de hauteur mesures a 1440 px
   (bas du rail : 242 px accueil, 320 px cas a 6 entrees ; 16 px d'air) :
   - capsule ouverte verticale (322 px depuis le bas) : 580 px accueil, 658 px cas ;
   - en dessous, capsule horizontale, si la largeur laisse 226 px avant le texte
     (colonne 4 = 390 u, soit 1024 px de large au moins) ;
   - capsule fermee (136 px depuis le bas) : 394 px accueil, 472 px cas ; en
     dessous, ou trop etroit pour l'horizontale, retour en bas a droite. */
:root{--fab-rail-left:max(24px, calc(max(0px, (100% - 1440px) / 2) + min(100%, 1440px) * 48 / 1440))}
@media (min-width:1024px) and (min-height:394px), (min-width:768px) and (min-height:580px){
  body.yk-page .grid-toggle-btn, body.yk-page .theme-picker{right:auto; left:var(--fab-rail-left)}
  body.yk-page .grid-toggle-btn{bottom:var(--sp-48)}
  body.yk-page .theme-picker{bottom:calc(var(--sp-48) + 48px)}
}
@media (min-width:1024px) and (min-height:472px), (min-width:961px) and (min-height:658px){
  body.case-dark .grid-toggle-btn, body.case-dark .theme-picker{right:auto; left:var(--fab-rail-left)}
  body.case-dark .grid-toggle-btn{bottom:var(--sp-48)}
  body.case-dark .theme-picker{bottom:calc(var(--sp-48) + 48px)}
}
@media (min-width:1024px) and (min-height:394px) and (max-height:579px){ body.yk-page .theme-picker{--orient:h} }
@media (min-width:1024px) and (min-height:472px) and (max-height:657px){ body.case-dark .theme-picker{--orient:h} }

@media (prefers-reduced-motion: reduce){
  /* La regle globale ramene les durees a 0,01 ms mais laisse les delais : la
     cascade des pastilles et le retard du texte disparaissent aussi. */
  html, .theme-picker, .theme-picker .theme-dot{transition-delay:0s !important}
  .theme-toggle-btn:hover, .theme-handle:hover, .theme-picker.is-dragging .theme-handle{transform:none}
}

/* ---------- Quadrillage de fond -- page d'accueil "YoKo" ----------
   Calque par section (.yk-section), contenu dans le canvas de la section.
   Pas = 24px a 1440, mis a l'echelle par --u (cf. .yk-section) et arrondi
   au pixel entier par round() : un pas fractionnaire anti-aliaserait le
   trait de 1px en bavure de 2px. Contrepartie assumee : une derive de phase
   de quelques px cumulee sur la largeur aux echelles intermediaires.
   Motif : --grid-texture-x / --grid-texture-y (cf. plus bas), cale sur les
   memes --grid-col / --grid-gutter / --grid-margin que .yk-grid, herites de
   .yk-section. Les positions X ne sont volontairement pas arrondies au pixel :
   l'arrondi cumule jusqu'a 6px de derive sur la derniere colonne et decalerait
   la texture de l'overlay -- on prefere un trait parfois anti-aliase a un
   trait faux. Seul le rythme Y (sans contrepartie a aligner) est arrondi.
   Sur mobile (<=767px) la mise en page passe a 3 colonnes : la texture
   suit ces 3 colonnes (cf. media query plus bas). */
/* Motif partage par les deux textures. Declare ici et non dans :root : les
   var() d'une propriete personnalisee se resolvent sur l'element qui la
   *declare*, donc depuis :root les valeurs locales mises a l'echelle par --u
   seraient ignorees (et --grid-sub, absent de :root, invalidait tout le
   background-image). Chaque texture fournit ses --grid-col / --grid-gutter /
   --grid-sub / --grid-line.
   Axe X : chaque colonne recoit ses deux bords et ses quarts (0/22,5/45/67,5/90
   a l'echelle 1440), rien dans la gouttiere -- toutes les lignes tombent sur la
   grille, marge droite comprise. Le quart est le decoupage le plus proche de la
   gouttiere (22,5 contre 24) : une cellule strictement egale a la gouttiere
   supposerait de diviser la colonne en 3,75, incompatible avec un pas uniforme
   cale sur la grille. Axe Y : meme pas, pour des carres.
   Les deux traits de bord sont *dans* la colonne ([0, 1] et [col - 1, col]),
   comme les bordures de .grid-overlay-track span : sinon le trait droit
   tombait 1px a droite de celui du calque (P-032). */
.yk-bg-grid, .case-dark-texture::before{
  --grid-texture-x:repeating-linear-gradient(to right,
    var(--grid-line-color) 0 1px, transparent 1px var(--grid-sub),
    var(--grid-line-color) var(--grid-sub) calc(var(--grid-sub) + 1px), transparent calc(var(--grid-sub) + 1px) calc(2 * var(--grid-sub)),
    var(--grid-line-color) calc(2 * var(--grid-sub)) calc(2 * var(--grid-sub) + 1px), transparent calc(2 * var(--grid-sub) + 1px) calc(3 * var(--grid-sub)),
    var(--grid-line-color) calc(3 * var(--grid-sub)) calc(3 * var(--grid-sub) + 1px), transparent calc(3 * var(--grid-sub) + 1px) calc(var(--grid-col) - 1px),
    var(--grid-line-color) calc(var(--grid-col) - 1px) var(--grid-col), transparent var(--grid-col) calc(var(--grid-col) + var(--grid-gutter)));
  --grid-texture-y:repeating-linear-gradient(to bottom, var(--grid-line-color) 0 1px, transparent 1px var(--grid-line));
}
.yk-bg-grid{
  position:absolute; inset:0; z-index:1; pointer-events:none;
  --grid-sub:calc(var(--grid-col) / 4);
  --grid-line:var(--grid-sub);
  --grid-span:calc(12 * var(--grid-col) + 11 * var(--grid-gutter));
  background-image:var(--grid-texture-x), var(--grid-texture-y);
  background-size:var(--grid-span) 100%, auto;
  background-position:var(--grid-margin) 0, 0 0;
  background-repeat:no-repeat, repeat;
  /* Estompage vers le fond sombre de la section sur tous les bords. */
  -webkit-mask-image: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 90%);
  mask-image: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 90%);
}
/* Arrondi au pixel entier la ou round() existe (Chrome 125+, Safari 15.4+,
   Firefox 118+) : un pas fractionnaire anti-aliase le trait de 1px en bavure
   de 2px. En dehors de ce support, le pas reste fractionnaire mais le motif
   s'affiche — une declaration round() non gardee, elle, le ferait disparaitre. */
@supports (width: round(1px, 1px)){
  .yk-bg-grid{--grid-line:round(calc(var(--grid-col) / 4), 1px)}
}
/* =========================================================================
   Page d'accueil long-scroll "YoKo" (Intro / Work / Values / About)
   Miroir du fichier Figma PORTFOLIO : desktop section 882:19566 (canvas
   1440), mobile section 912:19567 "Portfolio/Mobile" (canvas 390, grille
   3 colonnes). Une seule page continue, 4 sections empilees verticalement.

   Trois plages :
   - >= 1440px : desktop tel que la maquette (unite --u = 1px).
   - 768-1439px : desktop FLUIDE -- aucune maquette n'existe sur cette plage
     (arbitrage Yoh, 2026-09-03). Toutes les dimensions de grille derivent
     d'une unite unique --u = 100cqw / 1440, donc les proportions de la
     maquette 1440 sont conservees a l'identique ; la typographie interpole
     lineairement entre les bornes des deux maquettes (clamp()).
   - <= 767px : mobile, fidele a la maquette 390 (marge 24, 3 colonnes).
   ========================================================================= */

/* Defilement doux natif : les items du rail sont de simples liens d'ancre,
   le navigateur fait le reste. Neutralise pour qui demande moins d'animation. */
@media (prefers-reduced-motion:no-preference){
  html{scroll-behavior:smooth}
}
body.yk-page{background:var(--yk-bg); color:var(--yk-text); font-size:var(--yk-fs-md)}
body.yk-page.yk-menu-open{overflow:hidden} /* menu mobile ouvert : le fond ne defile pas */

/* Echelle typographique fluide -- bornes = maquette mobile (390) -> desktop (1440),
   interpolation lineaire sur la largeur de viewport entre les deux. */
:root{
  --yk-fs-headline:clamp(40px, 31.09px + 2.286vw, 64px);   /* accroche Intro : 40 -> 64 */
  --yk-fs-values-fluid:clamp(32px, 17.14px + 3.81vw, 72px); /* mots-cles Values : 32 -> 72 */
  --yk-fs-role:clamp(32px, 14.17px + 4.571vw, 80px);        /* titres About : 32 -> 80 */
  --yk-fs-wordmark:clamp(15px, 13.14px + 0.476vw, 20px);    /* wordmark : 15 -> 20 */
}

/* ---------- Header ---------- */
.yk-header{
  position:fixed; top:0; left:0; right:0; height:var(--yk-header-h);
  background:transparent; z-index:40; display:flex; align-items:center;
  transition:background-color .2s ease;
}
/* Transparent tant que l'Intro occupe l'espace sous le header ; fond plein
   des que le scroll la depasse (IntersectionObserver, script.js). */
/* Translucide plutot qu'opaque : la grille reste lisible a travers le header
   une fois l'Intro depassee, sans sacrifier le contraste du wordmark par-dessus
   les cartes Work. Le flou porte sur ce qui defile dessous, pas sur le texte. */
.yk-header.is-solid{
  background:color-mix(in oklch, var(--yk-bg) 75%, transparent);
  backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
}
.yk-header-inner{
  width:min(1440px, 100%); margin:0 auto; box-sizing:border-box;
  display:flex; align-items:center; justify-content:space-between;
  padding:0 calc(min(100%, 1440px) * 48 / 1440); /* = --grid-margin, mis a l'echelle */
}

/* Wordmark -- "Yohann Kouma" au repos, se decrypte/scramble vers "YoKo" au
   survol/focus puis revient au blur/mouseleave (arbitrage Yoh, 2026-09-19,
   remplace l'ancien balayage clip-path). Anime en JS (cf. script.js,
   textScramble) : un seul span de texte reel, dont le nombre de caracteres
   affiches se reduit progressivement de 12 a 4 -- pas de second calque a
   positionner/masquer, donc pas de risque de lettres qui se melangent. */
.yk-wordmark{
  position:relative; display:inline-block; font-family:var(--yk-font-body);
  font-weight:500; font-size:var(--yk-fs-wordmark); line-height:1.3; white-space:nowrap;
  text-decoration:none; color:var(--yk-text);
}
.yk-wordmark-text{color:var(--yk-text)}
.yk-wordmark-text .dud{opacity:.55}

/* Bouton menu -- mobile uniquement (Header/Mobile, 912:19596). */
.yk-menu-btn{
  all:unset; display:none; cursor:pointer; box-sizing:border-box;
  width:32px; height:32px; color:var(--yk-text);
}
.yk-menu-btn svg{display:block; width:32px; height:32px}
/* Remix menu-5-line ferme, close-line ouvert (etat Open du composant Figma). */
.yk-menu-btn .yk-menu-icon-close{display:none}
.yk-menu-btn[aria-expanded="true"] .yk-menu-icon-open{display:none}
.yk-menu-btn[aria-expanded="true"] .yk-menu-icon-close{display:block}
.yk-menu-btn:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px}

/* ---------- Rail de navigation ----------
   Desktop : colonne 1 de la grille (marge + 90px, debord assume de 10px),
   fixe au viewport. Toutes les cotes sont mises a l'echelle du canvas :
   min(100%, 1440px) / 1440 (les % d'un element fixe se referent au bloc
   conteneur initial, donc hors barre de defilement -- contrairement a vw). */
.yk-rail{
  position:fixed; z-index:35; display:flex; flex-direction:column; gap:0;
  /* top en % se refere a la hauteur du viewport (30 au lieu de 48 a 1440 x 900) :
     vw ici, ecart d'une barre de defilement classique sous 1440 (0,5 px au plus). */
  top:calc(var(--yk-header-h) + min(100vw, 1440px) * 48 / 1440);
  left:calc(max(0px, (100% - 1440px) / 2) + min(100%, 1440px) * 48 / 1440);
  width:calc(min(100%, 1440px) * 100 / 1440);
}
.yk-rail-item{
  all:unset; cursor:pointer; box-sizing:border-box;
  display:flex; align-items:center; gap:var(--sp-8); min-height:34px; padding:var(--sp-8) 0;
  font-family:var(--yk-font-body); font-weight:600; font-size:var(--yk-fs-sm);
  color:var(--yk-text-muted); /* P-023 : couleur attenuee par cran, plus d'opacite */
  /* Une seule ligne (P-034). "Case studies" mesure 81px a 14px, + trait actif
     12 + ecart 8 = 101px pour une colonne de 100px (53px a 768) : le libelle
     deborde a droite dans les colonnes 2-3, vides a hauteur du rail (verifie
     de 768 a 1920 : aucun texte recouvert). min-height et non height : le
     trait reste centre si une entree venait a occuper deux lignes. */
  white-space:nowrap;
}
/* Le trait se dessine par transform (scaleX), non par sa largeur : seul le
   transform est anime, la largeur bascule de 0 a 12px d'un coup (le libelle
   saute de 20px au lieu de glisser). Etats au repos identiques a avant. */
.yk-rail-item .yk-rail-rule{background:var(--yk-text); height:1px; width:0; flex:none; transform:scaleX(0); transform-origin:left; transition:transform .15s ease}
.yk-rail-item.is-active{color:var(--yk-text)}
.yk-rail-item.is-active .yk-rail-rule{width:12px; transform:scaleX(1)}
.yk-rail-item:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px; color:var(--yk-text)}

.yk-content{padding-top:var(--yk-header-h)}

/* ---------- Sections et grille 12 colonnes (fluide) ----------
   .yk-section est un query container : --u vaut 1px a 1440 et decroit
   proportionnellement en dessous. Marge, colonne, gouttiere et pas du
   quadrillage en derivent tous -- une seule source, aucune constante
   decimale tapee a la main (pas de derive de la 12e colonne). */
.yk-section{
  position:relative; width:min(1440px, 100%); margin:0 auto;
  container-type:inline-size;
  --u:calc(100cqw / 1440);
  --grid-margin:calc(48 * var(--u));
  --grid-col:calc(90 * var(--u));
  --grid-gutter:calc(24 * var(--u));
  /* Pas var(--u) ici : cqw declare sur le conteneur se resout contre le viewport
     (64px au lieu de 48 a 1920px). min(100%, 1440px) = la largeur de la section. */
  /* 48 en haut et en bas : 96 entre deux sections, comme les cas (P-050). */
  padding-block:calc(min(100%, 1440px) * 48 / 1440); box-sizing:border-box; overflow:hidden;
  scroll-margin-top:var(--yk-header-h); /* header fixe : un #hash ou un clic rail cale sinon 64px trop haut */
}
/* Fin de page a 96, comme .case-dark-main. */
.yk-content > .yk-section:last-child{padding-bottom:calc(min(100%, 1440px) * 96 / 1440)}
.yk-section::before{content:''; position:absolute; inset:0; background:var(--yk-bg); z-index:0}
.yk-grid{
  position:relative; z-index:2;
  display:grid; grid-template-columns:repeat(12, var(--grid-col)); column-gap:var(--grid-gutter);
  padding:0 var(--grid-margin); box-sizing:border-box;
}

/* ---------- Intro ---------- */
.yk-intro-copy{grid-column:4 / span 7}
.yk-intro-headline{
  font-family:var(--yk-font-display); font-weight:300; font-size:var(--yk-fs-headline);
  line-height:1.15; letter-spacing:-0.01em; margin:0;
}
.yk-intro-headline em{font-style:normal; color:var(--yk-accent-text)}
/* Sous-titre : Fraunces 400 en 32px, couleur attenuee (--yk-text-muted) et
   non opacity, qui aurait aussi dilue l'accent du <em> qu'il contient. */
/* 300 : graisse rendue depuis la mise en ligne, l'accueil ne chargeait que celle-ci
   (le 400 ecrit ici n'etait pas servi). Ecrite telle quelle avec les polices
   auto-hebergees (P-054) ; passer a 400 est un choix de rendu laisse a Yoh. */
.yk-intro-subhead{
  font-family:var(--yk-font-display); font-weight:300; font-size:32px;
  line-height:1.4; margin:var(--sp-16) 0 0;
  color:var(--yk-text-muted); /* P-023 : remplace le melange a 70 % */
}
.yk-intro-subhead em{font-style:normal; color:var(--yk-accent-text)}
.yk-intro-meta{list-style:none; margin:var(--sp-24) 0 0; padding:0; display:flex; flex-direction:column; gap:var(--sp-12); font-size:18px}
.yk-intro-meta li{display:flex; gap:var(--sp-8); align-items:flex-start}
.yk-intro-meta-icon{flex:none; margin-top:3px} /* calage optique : Remix Icon, 20 px, aligne sur la 1re ligne de 18 px */

/* ---------- Work : carrousel (P-045) ----------
   Memes regles que le carrousel des cas (P-031, P-032) : cartes de 6 colonnes
   (660 u), ecart = gouttiere, fenetre ouverte 72 u avant la colonne 4 (390 -
   72 = 318) jusqu'au bord droit du canevas, ruban au meme retrait : au repos
   la 1re carte reste sur la colonne 4 ; apres une avance, la precedente laisse
   une amorce de 48 u. Fondu des deux bords : masque commun .carousel-track (P-046). */
.yk-work-wrap{grid-column:4 / span 9; justify-self:start; margin-left:calc(-72 * var(--u)); width:calc(1122 * var(--u))}
.yk-work-grid{
  display:flex; gap:var(--grid-gutter); overflow-x:auto; scroll-snap-type:x mandatory; scrollbar-width:none;
  padding-inline-start:calc(72 * var(--u)); scroll-padding-inline-start:calc(72 * var(--u));
  /* overflow rogne aussi en hauteur : reserve pour la carte survolee (montee de
     4px), compensee par la marge, la mise en page ne bouge pas. Elle date de
     l'ombre de survol (retiree, P-055) et reste telle quelle : la reduire
     deplacerait le ruban et les 8px gardes dessous en mobile. */
  padding-block:16px 28px; margin-block:-16px -28px;
  --fade-start:calc(48 * var(--u));
}
.yk-work-grid::-webkit-scrollbar{display:none}
.yk-work-controls{grid-column:4 / span 9; position:relative} /* au-dessus de la reserve basse du ruban, qui est positionne */
/* Le point d'arret est sur la diapositive, jamais sur la carte : Chromium le
   cale sur la boite transformee, et la carte reduite a 0,94 decalait chaque
   retour de 40 u (meme montage que .carousel-slide > .carousel-zoom). */
.yk-work-slide{display:flex; flex:none; scroll-snap-align:start}
.yk-card{
  display:flex; flex-direction:column; gap:var(--sp-16); padding:var(--sp-32); box-sizing:border-box;
  flex:none; width:calc(660 * var(--u)); min-height:calc(470 * var(--u));
  text-decoration:none; color:var(--card-text); min-width:0;
  /* Contour du cran (P-055) a la place de l'ombre de survol : --yk-border est a
     3,4:1 du fond de page sur les 9 crans (build-theme.mjs), le bord de la carte
     se lit au repos, au doigt comme a la souris. Trace en ombre interne : aucune
     incidence sur la mise en page, et l'anneau de focus (outline) reste libre. */
  box-shadow:inset 0 0 0 1px var(--yk-border);
  transform:translateY(0); transition:transform .15s ease;
}
@media (hover:hover) and (pointer:fine){
  .yk-card:hover{transform:translateY(-4px)}
}
.yk-card:active{transform:translateY(-1px)}
/* Carte active et inactives (P-045, regles de P-031), a partir de 768 px :
   classes posees par script.js. Une carte inactive se reduit a 0,94 depuis son
   bord tourne vers la carte active, SANS baisse d'opacite : le texte est dans
   la carte et passerait sous 4,5:1. */
.yk-work-grid.has-active-state .yk-card{transform:scale(.94); transform-origin:right center; transition:transform .3s ease-out}
.yk-work-grid.has-active-state .is-active ~ .yk-work-slide .yk-card{transform-origin:left center}
.yk-work-grid.has-active-state .is-active .yk-card{transform:none}
@media (hover:hover) and (pointer:fine){
  .yk-work-grid.has-active-state .is-active .yk-card:hover{transform:translateY(-4px)}
}
@media (prefers-reduced-motion:reduce){
  .yk-work-grid.has-active-state .yk-card{transform:none; transition:none}
}
/* Aucune couleur d'anneau ne passe le seuil UI (3:1) sur les cinq fonds de
   carte a la fois (mesure : blanc 1,10 sur le jaune, marine 2,5-3,2 sur les
   quatre autres) -- meme modificateur .yk-card--dark que pour le texte, pas un
   nouveau systeme.
   outline-offset NEGATIF, pas positif : un offset positif dessine l'anneau
   hors de la carte, sur le fond de PAGE (theme-reactif), pas sur le fond fixe
   de la carte -- verifie au clavier, l'anneau marine devenait invisible sur
   le fond sombre par defaut (navy sur quasi-noir), et le blanc des 4 autres
   cartes ne tenait que par coincidence (il aurait pareillement disparu au
   cran clair). Decale vers l'interieur, l'anneau reste sur la couleur de
   carte mesuree ci-dessus, quel que soit le theme du site. */
/* outline suit --card-text, plus #fff fixe : les fonds de carte glissent
   maintenant eux-memes vers leur teinte Light (cf. --yk-card-*), le meme
   piege que celui deja corrige ci-dessus (outline invisible une fois le
   fond devenu pale) reapparaitrait sinon a l'autre bout du curseur. */
.yk-card:focus-visible{outline:2px solid var(--card-text); outline-offset:-4px}
.yk-card--dark:focus-visible{outline-color:var(--yk-text-dark)}
.yk-card-eyebrow{font-family:var(--yk-font-body); font-size:var(--yk-fs-xs); line-height:1.3; margin:0}
.yk-card-title{font-family:var(--yk-font-display); font-weight:300; font-size:var(--yk-fs-xl); line-height:1.15; margin:var(--sp-8) 0 0}
.yk-card-tags{display:flex; flex-wrap:wrap; gap:var(--sp-8) var(--sp-12); margin-top:auto; padding-top:var(--sp-16)}
.yk-tag{
  display:inline-flex; align-items:center; border:1px solid currentColor;
  border-radius:999px; padding:var(--sp-4) var(--sp-8); font-family:var(--yk-font-body); font-size:var(--yk-fs-xs); /* pastille 4 x 8 partout (P-050) */
}
.yk-card--dark{color:var(--yk-text-dark)}
/* Fonds de carte -- variables Figma card/* (collection Portfolio/Card accents). */
.yk-card--payment-guarantee{background:var(--yk-card-payment-guarantee)}
.yk-card--tele-expertise{background:var(--yk-card-tele-expertise)}
.yk-card--prescription-renewal{background:var(--yk-card-prescription-renewal)}
.yk-card--coverage-capture{background:var(--yk-card-coverage-capture)}
.yk-card--directory-formatting{background:var(--yk-card-directory-formatting)}

/* ---------- Values ---------- */
.yk-values-heading{
  grid-column:5 / span 6; font-family:var(--yk-font-display); font-weight:300;
  font-size:var(--yk-fs-values-fluid); line-height:1.15; letter-spacing:-0.01em; margin:0;
}
.yk-values-copy{grid-column:8 / span 4; font-size:var(--yk-fs-md); line-height:1.5; margin:calc(48 * var(--u)) 0 0}

/* ---------- About ---------- */
.yk-about-intro{grid-column:5 / span 4; font-size:var(--yk-fs-md); line-height:1.5; margin:0}
.yk-about-roles{grid-column:5 / span 6; display:flex; flex-direction:column; gap:var(--sp-24); margin-top:calc(48 * var(--u)); list-style:none; padding:0}
.yk-role{display:flex; flex-direction:column; gap:var(--sp-12)}
.yk-role-org{
  font-family:var(--yk-font-body); font-size:var(--yk-fs-xs); letter-spacing:.08em; text-transform:uppercase;
  margin:0;
}
.yk-role-title{font-family:var(--yk-font-display); font-weight:300; font-size:var(--yk-fs-role); letter-spacing:-0.01em; line-height:1.05; margin:0}
.yk-role-meta{display:flex; flex-direction:column; gap:var(--sp-8)}
.yk-role-dates{font-family:var(--yk-font-body); font-weight:700; font-size:var(--yk-fs-xs); line-height:1.65; margin:0}
.yk-role-desc{font-family:var(--yk-font-body); font-size:var(--yk-fs-md); line-height:1.5; margin:0}

/* =========================================================================
   Mobile <= 767px -- maquette Figma "Portfolio/Mobile" (912:19567), canvas 390.
   Grille : marge 24, 3 colonnes de 98, gouttieres 24 ; en fr pour rester
   exacte a 390 et s'etirer jusqu'a 767 sans rien inventer.
   Non specifie dans la maquette (spec vide), defauts poses ici :
   fermeture du menu au tap sur un item, a Echap et sur le bouton ;
   defilement du fond bloque tant que le menu est ouvert ; cartes 4 et 5
   conservees dans le carrousel (arbitrage Yoh, 2026-09-03).
   ========================================================================= */
@media (max-width:767px){
  .yk-header-inner{padding:0 var(--sp-24)}
  .yk-menu-btn{display:inline-flex; align-items:center; justify-content:center}
  /* Rail -> panneau plein ecran sous le header (912:19679) : degrade noir
     -> 20% vers la droite, items 18px alignes a droite. Colonne de 148px
     (P-034) : marge 24 + trait actif 12 + ecart 8 + "Case studies" 104,
     pour que chaque entree tienne sur une ligne (la colonne Figma de 84px
     le coupait en deux). */
  .yk-rail{
    top:var(--yk-header-h); left:0; right:0; bottom:0; width:auto;
    padding:var(--sp-32) calc(100% - 148px) 0 0; box-sizing:border-box;
    /* Meme traitement que .yk-header.is-solid et la barre des cas (P-043) :
       fond du cran a 75 % et flou de 8px a la place du degrade. */
    background:color-mix(in oklch, var(--yk-bg) 75%, transparent);
    backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
    opacity:0; visibility:hidden; transition:opacity .2s ease, visibility 0s linear .2s;
  }
  .yk-rail.is-open{opacity:1; visibility:visible; transition:opacity .2s ease}
  /* Menu ouvert : le header prend le meme fond que le panneau, sans rupture a 64px. */
  body.yk-menu-open .yk-header{
    background:color-mix(in oklch, var(--yk-bg) 75%, transparent);
    backdrop-filter:blur(8px); -webkit-backdrop-filter:blur(8px);
  }
  .yk-rail-item{padding:var(--sp-12) 0; font-size:18px; justify-content:flex-end; width:100%}

  .yk-section{
    --grid-margin:24px; --grid-gutter:24px; --u:1px;
    padding-block:var(--sp-24); /* 48 entre deux sections, comme les cas (P-050) */
  }
  .yk-content > .yk-section:last-child{padding-bottom:var(--sp-48)}
  /* 3 colonnes ici, pas 12 : meme motif que le desktop, recale sur ces
     3 colonnes (P-032), donc sur le calque. Quart de colonne ~24,5px a 390px ;
     le rythme Y reste a 24px entiers (rien a y aligner). */
  .yk-bg-grid{
    --grid-col:calc((100cqw - 4 * 24px) / 3);
    --grid-span:calc(100cqw - 2 * 24px);
    --grid-line:24px;
  }
  .yk-grid{grid-template-columns:repeat(3, minmax(0, 1fr))}

  .yk-intro-copy{grid-column:1 / -1}

  /* Work : ruban pleine largeur, cartes de 318, 24px de la carte suivante
     visibles ; ni amorce ni etat actif sous 768 px (P-045). Le compteur
     reste, les boutons sont masques (regle commune des controles). */
  .yk-work-wrap{grid-column:1 / -1; justify-self:stretch; width:auto; margin:0 calc(-1 * var(--sp-24))}
  .yk-work-grid{
    gap:var(--sp-24); padding-inline:var(--sp-24); margin-block-end:-20px; /* 8px d'ecart garde sous le ruban */
    scroll-padding-inline-start:var(--sp-24); -webkit-overflow-scrolling:touch;
  }
  .yk-work-wrap .yk-work-grid{-webkit-mask-image:none; mask-image:none} /* le fondu masquerait les 24px de la carte suivante, seule affordance du ruban */
  .yk-work-controls{grid-column:1 / -1}
  .yk-card{width:318px; min-height:269px; padding:var(--sp-24)}

  .yk-values-heading{grid-column:1 / -1}
  .yk-values-copy{grid-column:2 / span 2; margin-top:var(--sp-24)}

  .yk-about-intro{grid-column:1 / span 2}
  .yk-about-roles{grid-column:1 / span 2; margin-top:var(--sp-24)}
}

@media (prefers-reduced-motion:reduce){
  .yk-rail, .yk-header, .yk-rail-item .yk-rail-rule, .case-local-rail .rail-rule{transition:none}
  .bar-anim .topbar, .bar-anim .case-local-rail{transition:none}
}

.sr-only{
  position:absolute; width:1px; height:1px; padding:0; margin:-1px;
  overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0;
}

/* =========================================================================
   Case study — patron sombre "grille 13" (Figma 921:20599), Payment guarantee.
   Reutilise les tokens --yk-* deja definis pour l'accueil (meme langage
   visuel : fond slate/900, Alan Sans, accent bleu) plutot que d'en creer un
   second jeu — seule la mise en page differe (colonne unique + rail local,
   pas la grille 12 colonnes fluide de l'accueil, hors-scope pour une seule
   page de case study).
   ========================================================================= */
body.case-dark{background:var(--yk-bg); color:var(--yk-text)}

/* Meme motif que .yk-bg-grid (accueil), en une seule instance fixe plein
   viewport : cette page n'a qu'une colonne de lecture continue. L'element est
   centre et borne a 1440px comme .case-dark-layout et .grid-overlay-track
   (auparavant il partait du bord du viewport : ~232px de decalage a 1920px).
   Il est son propre conteneur cqw et peint sur ::before -- un descendant de
   position:fixed ne peut pas heriter le --u de .case-dark-layout (le
   containment changerait son bloc conteneur, donc plus fige au scroll). */
.case-dark-texture{
  position:fixed; top:0; bottom:0; left:0; right:0; margin:0 auto;
  width:min(1440px, 100%); z-index:0; pointer-events:none;
  container-type:inline-size;
}
.case-dark-texture::before{
  content:''; position:absolute; inset:0;
  --u:calc(100cqw / 1440);
  --grid-col:calc(90 * var(--u));
  --grid-gutter:calc(24 * var(--u));
  --grid-margin:calc(48 * var(--u));
  --grid-sub:calc(var(--grid-col) / 4);
  --grid-line:var(--grid-sub);
  --grid-span:calc(12 * var(--grid-col) + 11 * var(--grid-gutter));
  background-image:var(--grid-texture-x), var(--grid-texture-y);
  background-size:var(--grid-span) 100%, auto;
  background-position:var(--grid-margin) 0, 0 0;
  background-repeat:no-repeat, repeat;
  -webkit-mask-image: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 90%);
  mask-image: radial-gradient(ellipse 70% 65% at 50% 50%, #000 35%, transparent 90%);
}
@supports (width: round(1px, 1px)){
  .case-dark-texture::before{--grid-line:round(calc(var(--grid-col) / 4), 1px)}
}
/* Sous 961px le cas est une colonne de lecture a 24px des bords : le motif
   suit la grille de 3 colonnes du calque (P-032). Place apres le bloc
   @supports pour que --grid-line reste en pixels entiers. */
@media (max-width:960px){
  .case-dark-texture::before{
    --grid-margin:24px; --grid-gutter:24px;
    --grid-col:calc((100cqw - 4 * 24px) / 3);
    --grid-span:calc(100cqw - 2 * 24px);
    --grid-line:24px;
  }
}

/* Positionnement fluide, miroir exact de la grille Figma (canvas 1440,
   marge 48, colonnes 90, gouttieres 24) : meme technique --u que .yk-section
   (cf. accueil) — une seule unite, aucune valeur decimale tapee a la main.
   Le rail est en colonne 1 (48px). "Overview"/"Constraint"/"Reality check"/
   "Outcome" demarrent en colonne 4 (390px = marge + 3 x (colonne+gouttiere)).
   "Blueprint" seul demarre en colonne 8 (846px) : decalage assume du Figma,
   pas une erreur — son carrousel deborde ensuite d'une unite de grille vers
   la gauche (114px = colonne+gouttiere) pour revenir visuellement vers la
   colonne de lecture principale. */
.case-dark-layout{
  position:relative; z-index:1;
  width:min(1440px, 100%); margin:0 auto;
  container-type:inline-size;
  --u:calc(100cqw / 1440);
  /* Pas var(--u) sur le conteneur lui-meme (cqw y retombe sur le viewport). */
  padding-top:calc(min(100%, 1440px) * 48 / 1440);
}

.case-local-rail{
  position:fixed; z-index:5;
  top:calc(var(--topbar-h) + 48 * var(--u));
  /* Fixe, donc positionne par rapport au viewport : on ajoute le retrait de la
     colonne 1440 centree (nul sous 1440px) pour rester en colonne 1. */
  left:calc((100% - min(100%, 1440px)) / 2 + 48 * var(--u));
  width:calc(90 * var(--u));
  display:flex; flex-direction:column;
}
.case-local-rail a{
  all:unset; cursor:pointer; display:flex; align-items:center; gap:var(--sp-8);
  box-sizing:border-box; height:34px; white-space:nowrap;
  font-family:var(--yk-font-body); font-weight:600; font-size:var(--fs-sm);
  color:var(--yk-text-muted); /* P-023 : couleur attenuee par cran (WCAG 1.4.3) */
}
.case-local-rail .rail-rule{background:var(--yk-text); height:1px; width:0; flex:none; transform:scaleX(0); transform-origin:left; transition:transform .15s ease}
.case-local-rail a.is-active{color:var(--yk-text)}
.case-local-rail a.is-active .rail-rule{width:12px; transform:scaleX(1)}
.case-local-rail a:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px; color:var(--yk-text)}

.case-dark-main{padding-bottom:calc(96 * var(--u))}
.case-dark-main article{display:flex; flex-direction:column}
.case-dark-section{
  display:flex; flex-direction:column; gap:var(--sp-16);
  scroll-margin-top:calc(var(--topbar-h) + var(--sp-24));
  margin-left:calc(390 * var(--u)); width:calc(546 * var(--u));
  margin-top:calc(96 * var(--u));
}
@media (max-width:960px){
  /* P-042 : sous la barre « Back » (68px) et la bande des sections (46px, P-044) */
  .case-dark-section{scroll-margin-top:calc(var(--topbar-h) + 46px + var(--sp-24))}
}
.case-dark-section:first-child{margin-top:0; width:calc(774 * var(--u))}
/* #blueprint suit la colonne commune (x 390) : condition d'entree du carrousel,
   qui deborde a droite depuis la colonne de texte (spec carousel.md, § 3). */
#outcome{width:calc(660 * var(--u))}
/* Fenetre anchored : 72 avant la colonne de texte (390 - 72 = 318) jusqu'au bord
   droit du canvas (1440). Le ruban porte un retrait egal : au repos le 1er media
   reste sur la colonne et rien ne depasse a gauche ; apres une avance, le media
   precedent laisse une amorce de 48 (72 - gouttiere 24) (P-031, P-032). Sous
   961 px : fenetre pleine largeur, sans amorce (bloc mobile plus bas). */
#blueprint .carousel-wrap{margin-left:calc(-72 * var(--u)); width:calc(1122 * var(--u))}
#blueprint .carousel-track{padding-inline-start:calc(72 * var(--u)); scroll-padding-inline-start:calc(72 * var(--u)); --fade-start:calc(48 * var(--u))}

.case-dark-headline{
  font-family:var(--font-display); font-weight:300; font-size:clamp(32px, 4vw, 64px);
  line-height:1.15; letter-spacing:-.01em; margin:0;
}
.case-dark-section h2{
  font-family:var(--font-display); font-weight:400; font-size:var(--fs-xl); line-height:1.5; margin:0;
}
/* Corps de texte des sections. :where() ramene la specificite a celle d'un type :
   les composants (.kpi-value, .carousel-counter, .mermaid-legend...) gardent leurs
   propres tailles et marges au lieu d'etre ecrases en 16 px (audit du 15/10, P-053). */
:where(.case-dark-section) p{font-family:var(--font-body); font-size:var(--fs-md); line-height:1.5; margin:0}
:where(.case-dark-section) ul{margin:0; padding-left:var(--sp-20); display:flex; flex-direction:column; gap:var(--sp-4)}
.case-dark-lead{font-family:var(--font-body); font-weight:700; font-size:var(--fs-md); margin:0} /* ecart porte par le gap (P-050) */
/* Rythme d'une section (P-050) : texte -> texte 16 (le gap) ; un bloc (diagramme,
   contrainte, KPI, carrousel, verbatim) a 24 avant et apres (gap + 8). Les controles
   du carrousel lui restent colles a 16, comme sur l'accueil. */
.case-dark-section > :is(.mermaid-wrap, .challenge-block-dark, .kpi-card, .carousel-wrap, .quote-block-light),
.case-dark-section > :is(.mermaid-wrap, .challenge-block-dark, .kpi-card, .carousel-controls, .quote-block-light) + *{margin-top:var(--sp-8)}
.case-dark-section > .carousel-controls{margin-top:0}

/* ---------- Diagrammes Mermaid (case studies) ----------
   Meme principe que .quote-block-light/.kpi-card : une carte
   claire posee sur le fond sombre, plutot qu'un theme sombre dedie -- le
   theme Mermaid du SVG pre-rendu dans la page est deja pense pour un fond
   clair. overflow-x:auto : un flowchart a mise en
   page fixe ne se redimensionne pas comme le reste du site (fluide), donc
   il defile plutot que de deborder ou de retrecir illisible sur mobile. */
.mermaid-wrap{
  background:var(--surface-page); border-radius:var(--radius-md);
  padding:var(--sp-24); overflow-x:auto;
}
.mermaid-wrap .mermaid{display:flex; justify-content:center}
.mermaid-legend{
  list-style:none; margin:var(--sp-16) 0 0; padding:0;
  display:flex; flex-direction:row; flex-wrap:wrap; gap:var(--sp-16) var(--sp-24); /* en ligne : la regle des listes de section est en colonne */
}
.mermaid-legend li{
  display:flex; align-items:center; gap:var(--sp-8);
  font-family:var(--font-body); font-size:var(--fs-xs); color:var(--text-secondary);
}
.mermaid-legend-dot{width:10px; height:10px; border-radius:50%; flex:none}

.challenge-block-dark{
  border-left:1px solid var(--yk-text); padding-left:var(--sp-16);
  display:flex; flex-direction:column; gap:var(--sp-24);
}
.challenge-block-dark .challenge-question{
  font-family:var(--font-display); font-style:normal; font-weight:400; font-size:var(--fs-xl); color:var(--yk-text);
}

/* ---------- Carrousel des 4 etats de facturation ---------- */
.carousel-wrap{position:relative}
/* Fondu des deux bords (P-046, remplace le voile opaque) : masque transparent
   sur le ruban, le quadrillage reste visible au travers. A gauche sur l'amorce
   (--fade-start, 0 sans amorce), a droite sur 96px. Le masque rognerait le
   contour de focus du ruban, dessine hors de sa boite : il passe sur la fenetre. */
.carousel-track, .yk-work-grid{
  --fade-mask:linear-gradient(to right, transparent, #000 var(--fade-start, 0px), #000 calc(100% - 96px), transparent);
  -webkit-mask-image:var(--fade-mask); mask-image:var(--fade-mask);
}
.carousel-wrap:has(> :focus-visible){outline:2px solid var(--yk-text); outline-offset:4px}
.carousel-track:focus-visible, .yk-work-grid:focus-visible{outline:none} /* sinon l'anneau par defaut du navigateur transparait, rogne */
.carousel-track{
  display:flex; gap:calc(24 * var(--u)); overflow-x:auto; /* gouttiere de la grille (P-032) */ scroll-snap-type:x mandatory; scrollbar-width:none;
  /* overflow rogne aussi en hauteur : 4px (contour 2 + decalage 2) pour que le
     contour de focus des medias reste entier, compenses par la marge. */
  padding-block:4px; margin-block:-4px;
}
.carousel-track::-webkit-scrollbar{display:none}
.carousel-slide{flex:none; width:calc(660 * var(--u)); scroll-snap-align:start} /* 6 colonnes : le 2e media tombe sur la colonne 10 (P-032) */
.carousel-zoom{
  all:unset; box-sizing:border-box; /* bordure comprise : 660 et non 662, ecart de 24 exact (P-031) */
  cursor:zoom-in; display:block; width:100%; position:relative;
  border:1px solid var(--yk-border); border-radius:var(--radius-md); overflow:hidden; background:#fff;
}
.carousel-zoom img{display:block; width:100%; height:auto; aspect-ratio:660/470; object-fit:cover}
.carousel-zoom::after{
  content:'Click to enlarge'; position:absolute; right:var(--sp-12); bottom:var(--sp-12);
  padding:var(--sp-4) var(--sp-8); border-radius:var(--radius-tag); background:rgba(20,22,32,.7); color:var(--yk-white);
  font-family:var(--font-body); font-size:var(--fs-xs); white-space:nowrap; opacity:0; transition:opacity .15s ease;
}
.carousel-zoom:focus-visible::after{opacity:1}
@media (hover:hover) and (pointer:fine){
  .carousel-zoom:hover::after{opacity:1}
}
.carousel-zoom:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px} /* blanc : 1,07:1 sur les crans clairs */
/* Media actif et inactifs (P-031), a partir de 768 px : .has-active-state et
   .is-active sont poses par script.js (sans JS, tous les medias restent pleins).
   Seuls transform et opacity s'animent. Un media inactif se reduit depuis son
   bord tourne vers le media actif : l'ecart de 24 et l'amorce de 48 restent
   exacts. Legende et contour de focus ne sont jamais attenues (l'opacite ne
   porte que sur l'image). */
.carousel-track.has-active-state .carousel-zoom{transform:scale(.94); transform-origin:right center; transition:transform .3s ease-out}
.carousel-track.has-active-state .is-active ~ .carousel-slide .carousel-zoom{transform-origin:left center}
.carousel-track.has-active-state .carousel-zoom img{opacity:.6; transition:opacity .3s ease-out}
.carousel-track.has-active-state .is-active .carousel-zoom{transform:none}
.carousel-track.has-active-state .is-active .carousel-zoom img{opacity:1}
@media (prefers-reduced-motion:reduce){
  .carousel-track.has-active-state .carousel-zoom{transform:none; transition:none}
  .carousel-track.has-active-state .carousel-zoom img{transition:none}
}
.carousel-caption{
  margin-top:var(--sp-12); font-family:var(--font-body); font-size:var(--fs-sm); color:var(--yk-text-muted);
  text-align:center;
}

/* ---------- Controles du carrousel (spec carousel.md, § 6 et 13) ----------
   Precedent / suivant : 44 x 44, a partir de 768 px et seulement si le ruban
   deborde (classe .is-scrollable posee par script.js). Butee : aria-disabled,
   pas disabled, pour ne pas ejecter le focus clavier. Le compteur reste sur
   mobile ; seul le texte cache (sr-only) est annonce, une fois par arret. */
.carousel-controls{display:flex; align-items:center; gap:var(--sp-16); margin-top:var(--sp-16)}
.carousel-btn{
  all:unset; box-sizing:border-box; display:none; align-items:center; justify-content:center;
  width:44px; height:44px; border:1px solid var(--yk-border); border-radius:999px; color:var(--yk-text); cursor:pointer;
}
.carousel-controls.is-scrollable .carousel-btn{display:inline-flex}
.carousel-btn[aria-disabled="true"]{opacity:.4; cursor:default}
.carousel-btn:focus-visible{outline:2px solid var(--yk-text); outline-offset:2px}
.carousel-counter{margin:0; font-family:var(--font-body); font-size:var(--fs-sm); color:var(--yk-text-muted)}
@media (max-width:767px){ .carousel-controls.is-scrollable .carousel-btn{display:none} }

/* ---------- Lightbox (agrandissement d'une capture du carrousel) ---------- */
/* <dialog> natif (P-053) : marges, bordure et tailles maximales du navigateur
   remises a zero pour couvrir tout l'ecran ; le fond est porte par le dialogue. */
.lightbox{
  position:fixed; inset:0; width:100%; height:100%; max-width:none; max-height:none;
  margin:0; border:0; box-sizing:border-box;
  background:rgba(10,11,16,.92);
  align-items:center; justify-content:center; padding:var(--sp-16);
}
.lightbox[open]{display:flex}
.lightbox::backdrop{background:transparent}
.lightbox-img{max-width:100%; max-height:100%; width:auto; height:auto; object-fit:contain}
.lightbox-close{
  all:unset; cursor:pointer; position:absolute; top:var(--sp-24); right:var(--sp-24);
  width:44px; height:44px; display:flex; align-items:center; justify-content:center;
  color:var(--yk-white); border-radius:var(--radius-sm);
}
.lightbox-close svg{width:24px; height:24px}
.lightbox-close:hover{background:rgba(255,255,255,.1)}
.lightbox-close:focus-visible{outline:2px solid var(--yk-white); outline-offset:2px}

/* ---------- Live cursors (realtime visitor presence, Liveblocks) ---------- */
.live-cursor-layer{position:fixed; inset:0; z-index:9999; pointer-events:none; overflow:hidden}
.live-cursor{position:absolute; top:0; left:0; will-change:transform; transition:transform .08s linear}
.live-cursor-arrow{display:block; filter:drop-shadow(0 1px 2px rgba(0,0,0,.35)); transform:translate(-1px,6px)} /* Remix cursor-fill : pointe recalee sur l'ancienne fleche */
.live-cursor-label{
  position:absolute; left:22px; top:22px; white-space:nowrap;
  padding:var(--sp-4) var(--sp-8); border-radius:var(--radius-tag);
  font-family:var(--font-body); font-size:var(--fs-xs); font-weight:600; color:var(--color-white);
  box-shadow:0 1px 3px rgba(0,0,0,.3);
}
@media (prefers-reduced-motion: reduce){
  .live-cursor{transition:none}
}

.quote-block-light{
  background:var(--surface-page); color:var(--text-secondary); border-radius:var(--radius-sm);
  padding:var(--sp-16); font-family:var(--font-body); font-size:var(--fs-md); line-height:1.5;
  margin:0;
}
.quote-block-light blockquote,.quote-block-light p{margin:0}
.quote-block-light figcaption{margin-top:var(--sp-8); font-size:var(--fs-sm); color:var(--text-secondary)}
.mermaid-legend-dot.is-neutral{box-shadow:inset 0 0 0 1px var(--text-secondary)}

/* ---------- Carte KPI (Outcome) ---------- */
.kpi-card{
  background:var(--surface-raised); color:var(--text-primary); border-radius:20px;
  padding:var(--sp-32); display:flex; flex-direction:column; gap:var(--sp-24);
}
.kpi-card-eyebrow{font-family:var(--font-body); font-size:var(--fs-xs); letter-spacing:.08em; text-transform:uppercase; color:var(--yk-accent); margin:0}
.kpi-card-desc{font-family:var(--font-body); font-size:var(--fs-sm); color:var(--text-secondary); margin:var(--sp-8) 0 0}
.kpi-card-rule{background:var(--border-default); height:1px}
.kpi-columns{display:flex; gap:var(--sp-16); align-items:center}
.kpi-col{flex:1; min-width:0; display:flex; flex-direction:column; gap:var(--sp-8)}
.kpi-col-label{font-family:var(--font-body); font-weight:600; font-size:var(--fs-sm); color:var(--text-secondary); margin:0}
.kpi-value-card{background:var(--surface-page); border-radius:var(--radius-sm); padding:var(--sp-16); display:flex; flex-direction:column; gap:var(--sp-8)}
.kpi-value-card.is-highlight{background:var(--surface-accent-subtle); color:var(--text-on-info)}
.kpi-value{font-family:var(--font-display); font-weight:700; font-size:44px; margin:0}
.kpi-value-desc{font-family:var(--font-body); font-size:var(--fs-sm); color:var(--text-secondary); margin:0}
.kpi-value-card.is-highlight .kpi-value-desc{color:inherit}
.kpi-connector{display:flex; flex-direction:column; align-items:center; gap:var(--sp-4); flex:none}
.kpi-time-pill{
  background:var(--surface-raised); border:1px solid var(--border-default); border-radius:999px;
  padding:var(--sp-4) var(--sp-8); font-family:var(--font-body); font-size:var(--fs-xs); letter-spacing:.08em;
  text-transform:uppercase; color:var(--text-secondary);
}
.kpi-attribution{background:var(--surface-accent-subtle); border-radius:var(--radius-sm); padding:var(--sp-16); display:flex; gap:var(--sp-8)}
.kpi-attribution-label{font-family:var(--font-body); font-weight:600; font-size:var(--fs-sm); color:var(--text-secondary); flex:none; width:105px; margin:0}
.kpi-attribution-text{font-family:var(--font-body); font-size:var(--fs-sm); color:var(--text-secondary); margin:0}
/* Mobile : label au-dessus du texte ; a cote, le texte tombait a ~120px de large. */
@media (max-width:767px){
  .kpi-card{padding:var(--sp-24)} /* comme .yk-card en mobile */
  .kpi-attribution{flex-direction:column; gap:var(--sp-4)}
  .kpi-attribution-label{width:auto}
}

@media (max-width:960px){
  /* Pas de maquette Figma sous 960px pour ce patron (grille 13 = desktop
     uniquement) : repli en colonne unique, meme logique que le repli mobile
     de l'accueil — rail en bandeau au-dessus du contenu plutot que fixe. */
  .case-dark-layout{padding-top:0}
  /* P-033 : une seule ligne, glissement au doigt pour les entrees suivantes.
     padding-block 4px = place du contour de focus, que overflow rognerait ;
     compense par 20px en haut au lieu de 24 (hauteur totale inchangee). */
  .case-local-rail{
    /* Collee en haut (P-042) ; --bar-shift (script.js) la descend sous la barre
       « Back » tant que celle-ci est visible, par transform et non par top. */
    position:sticky; top:0; z-index:19; transform:translateY(var(--bar-shift, 0px));
    width:auto; flex-direction:row; flex-wrap:nowrap; gap:var(--sp-16);
    padding:var(--sp-8) var(--sp-24) var(--sp-4); /* marge du calque mobile (P-032) ; 8px en haut sous la barre « Back » (P-044) */
    scroll-padding-inline:var(--sp-24);
    overflow-x:auto; overscroll-behavior-x:contain; scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  .case-local-rail::-webkit-scrollbar{display:none}
  .case-local-rail a{flex:none}
  .case-dark-main{padding:0 var(--sp-24) var(--sp-48)} /* idem : contenu sur les colonnes 1 a 3 */
  .case-dark-section, .case-dark-section:first-child, #outcome{
    margin-left:0; width:100%; margin-top:var(--sp-48);
  }
  .case-dark-main article > .case-dark-section:first-child{margin-top:var(--sp-24)}
  #blueprint .carousel-wrap{width:100%; margin-left:0}
  /* Sans amorce, le 1er media touche le bord du ruban : meme reserve de 4px
     pour son contour de focus, compensee par la marge. */
  #blueprint .carousel-track{padding-inline-start:4px; margin-inline-start:-4px; scroll-padding-inline-start:4px; --fade-start:0px} /* pas d'amorce, pas de fondu a gauche */
  .carousel-track{gap:var(--sp-24)}
  .carousel-slide{width:min(660px, 85vw)} /* ruban défilant : seul le 1er bord est sur la colonne 1 */
  .kpi-columns{flex-direction:column; align-items:stretch}
  .kpi-connector{flex-direction:row}
  .lightbox-close{top:var(--sp-12); right:var(--sp-12)}
}

@media (max-width:767px){
  /* Sous 768 px (spec carousel.md, § 11, P-057) : ruban pleine largeur de fenêtre,
     retrait de 24 à gauche, médias de 100vw - 72 (318 à 390) : le 2e commence à
     366 et laisse 24 px visibles, comme les cartes Work de l'accueil. Le fondu
     droit est retiré : il masquerait ces 24 px. */
  #blueprint .carousel-wrap{margin-inline:calc(-1 * var(--sp-24)); width:auto}
  #blueprint .carousel-track{padding-inline:var(--sp-24); margin-inline-start:0; scroll-padding-inline:var(--sp-24); -webkit-mask-image:none; mask-image:none}
  #blueprint .carousel-slide{width:min(660px, calc(100vw - 72px))}
}

/* ---------- Bloc de méta d'une page de cas (spec : wiki/specs/case-meta.md) ----------
   Liste de définitions sur la grille (P-032) : libellé sur une colonne, valeur
   à partir de la colonne suivante, gouttière de la grille ; rythme 8px, 24px au-dessus
   (P-055) : 16 de gap de la section + 8 de marge.
   Remplace la liste à émojis .case-dark-meta (P-012, 14/10/2026). */
.case-dark-section .case-meta{
  display:grid; grid-template-columns:calc(90 * var(--u)) 1fr; column-gap:calc(24 * var(--u)); row-gap:var(--sp-8); margin:var(--sp-8) 0 0;
}
.case-meta > div{display:contents}
.case-meta dt{font-family:var(--yk-font-body); font-weight:600; font-size:14px; line-height:1.3; padding-top:3px} /* calage optique sur la 1re ligne de 16 px */
.case-meta dd{margin:0; font-family:var(--yk-font-body); font-weight:400; font-size:16px; line-height:1.5}
/* Sous 961 px la colonne 1 ne porte plus qu'une icone de 20px : colonne de 24px et
   ecart de 16px, donc valeur a 40px (P-044, revise P-032 pour la meta sous 961px). */
@media (max-width:960px){ .case-dark-section .case-meta{grid-template-columns:24px 1fr; column-gap:var(--sp-16)} }
/* Sous 961 px, l'icone Remix remplace le mot (P-035) ; le mot reste dans le
   DOM, hors ecran, pour le lecteur d'ecran. Desktop : le mot seul. */
.case-meta-icon{display:none}
@media (max-width:960px){
  .case-meta dt{padding-top:2px} /* calage optique de l'icone de 20 px */
  .case-meta-icon{display:block; width:20px; height:20px}
  .case-meta-word{position:absolute; width:1px; height:1px; margin:-1px; padding:0; overflow:hidden; clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0}
}

/* ---------- Encadré « format court » dans une page de cas (cas 03 dans le 02) ---------- */
.case-aside{border-left:1px solid var(--yk-text); padding-left:var(--sp-20)}
.case-aside-eyebrow{font-size:var(--fs-xs) !important; letter-spacing:.08em; text-transform:uppercase; color:var(--yk-text-muted)} /* P-024 : couleur attenuee par cran, plus d'opacite .85 (3,77:1 au cran 4) */
