/* ============================================================================
   Portfolio Alexandre Lebrun — feuille de style unique, les 14 pages.
   Spec en vigueur : .agentic/design-v9.md

   CYCLE 9 — LE VERRE CANNELÉ, LES TROIS BANDEAUX, LES QUATRE PAGES.

   Le site n'a plus qu'un seul sujet visuel : LA MATIÈRE. Un verre cannelé
   horizontal derrière lequel un spectre défile, identique sur les quatorze
   pages. Devant cette matière on ne pose que ce qui compte — trois mots en
   STIX Two Text gras, pleine largeur. Le reste est du mobilier discret.

   DEUX POLICES, ET LEUR PARTAGE EST STRICT : STIX Two Text, une romaine, pour
   les titres — les trois mots de l'accueil, le titre des pages, le nom du
   client. Archivo, une grotesque, pour absolument tout le reste, y compris le
   nom des plaques et l'écran d'ouverture. Le contraste romaine / grotesque est
   le seul effet typographique du site : ne pas le diluer en mélangeant les deux
   dans un même bloc.

   QUATRE COULEURS, et chacune a un droit d'emploi strict (voir § 1) :
     · encre de nuit #1E223D — les fonds pleins, les ombres, les creux
     · feu          #F54F1B — L'ACCENT, et rien d'autre
     · craie        #F2EDE6 — le texte
     · bleu signal  #3554F0 — le pôle froid du spectre, et rien d'autre

   LE NOIR EST BANNI, ombres comprises. Une ombre noire dans une palette
   colorée salit tout ce qu'elle touche — le problème s'est posé au cycle 8.

   LE BLANC PUR EST DE LA LUMIÈRE, pas une couleur : il n'existe que dans le
   spectre, dans la crête du galbe et dans la lame de survol. Jamais en fond
   de surface, jamais en couleur de texte.
   ========================================================================= */

:root {
  /* ---------- § 1. LA PALETTE — quatre valeurs ----------
     Ratios MESURÉS sur l'encre de nuit, jamais estimés :
       craie 13,34:1 · feu 4,45:1 · bleu signal 2,72:1 */
  --encre-nuit:  #1E223D;
  --feu:         #F54F1B;
  --craie:       #F2EDE6;
  --bleu-signal: #3554F0;

  /* Le feu éclairci pour les survols. Il remplace le #ff5f2e de la maquette,
     qui était une CINQUIÈME couleur en dur. Dérivé du feu : le relevé
     exhaustif du critère 2 reste vrai. */
  --feu-vif: color-mix(in oklab, var(--feu) 88%, #FFFFFF);

  /* Les opacités autorisées, mesurées sur l'encre de nuit. */
  --doux:        rgba(242, 237, 230, 0.62);   /* 5,99:1 — texte secondaire */
  --filet:       rgba(242, 237, 230, 0.42);   /* 3,53:1 — bordures d'interface */
  --separateur:  rgba(242, 237, 230, 0.16);   /* 1,59:1 — DÉCORATIF seulement */
  --voile-rive:  rgba(242, 237, 230, 0.10);   /* le survol des pilules */

  /* Les voiles d'assombrissement. C'est eux qui rendent le texte lisible
     quand le spectre passe par le blanc pur — mesuré : craie sur le voile
     0,72 posé sur du blanc pur donne 5,33:1.
     Celui des bandeaux n'est pas ici : il se règle au curseur, plus bas, par
     `--assombrissement`. Une seconde variable qui aurait dit la même chose
     aurait fini par la contredire. */
  --voile-plaque:  rgba(30, 34, 61, 0.82);
  --voile-lecteur: rgba(30, 34, 61, 0.80);

  /* ---------- § 2. LE FOND — réglages validés au curseur par Alexandre ----
     Ils se reprennent tels quels. Ne pas les « nettoyer ». */
  /* Le nombre de lamelles n'est PAS ici : c'est `outils/generer.mjs` qui écrit
     les 22 éléments dans le HTML. Une variable CSS l'aurait fait croire
     réglable depuis cette feuille, ce qui est faux. */
  --compression:   0.38;
  --galbe:         1.15;
  --ombre:         0.55;
  --joint:         0.35;
  --defile:        46s;
  --desalignement: 3vw;

  /* LES DEUX PILES. Les titres sont en romaine, le texte en grotesque : le
     contraste entre les deux est le seul effet typographique du site, il ne
     faut pas le diluer. Les recours restent dans la même famille de dessin,
     pour qu'un chargement raté ne change pas la nature de la page. */
  --police-titre: 'STIX Two Text', ui-serif, Georgia, 'Times New Roman', serif;
  --police-texte: 'Archivo', ui-sans-serif, system-ui, -apple-system, sans-serif;

  /* ---------- Les bandeaux ---------- */
  --flou-bandeau:      22px;
  --assombrissement:   0.72;
  --voile:             0;      /* validé à 0 — conservé car c'est un réglage */
  --largeur-bandeau:   100%;
  --decalage-bandeau:  0%;     /* l'escalier a été essayé et écarté */
  --hauteur-bandeau:   2.4vh;
  /* Le rang du bandeau. Seul l'accueil le pose ; les panneaux de texte des
     pages de contenu héritent de 0. Sans cette valeur par défaut, le calc du
     margin-left serait invalide sur trois pages. */
  --n: 0;

  /* ---------- Le cadre ---------- */
  --rive-h:        96px;                        /* 76px sous 760px */
  --marge-rive:    clamp(16px, 3.4vw, 34px);
  --marge-contenu: clamp(20px, 5vw, 72px);

  --rayon-pilule: 1440px;
  --rayon-plaque: 22px;
  --rayon-carte:  24px;

  --ease: cubic-bezier(.16, 1, .3, 1);
  --vite: .2s;

  /* ---------- La grille Bento des pages client — CONSERVÉE ----------
     Les dix pages client en dépendent. Elle n'est pas remise en cause : elle
     est repeinte, pas réécrite. */
  --bento-cols: 8;
  --bento-max:  1760px;
  --bento-gap:  32px;
  --tuile-pad:  24px;

  --ratio-16-9: 16 / 9;
  --ratio-1-1:  1 / 1;
  --ratio-9-16: 9 / 16;

  /* ---------- Le lecteur — CONSERVÉ ---------- */
  --lecteur-max:       1280px;
  --lecteur-max-carre: 960px;
  --lecteur-max-vert:  420px;

  --tiroir-largeur: min(420px, 88vw);

  --page-padding: 16px;
  --focus-offset: 4px;
}

@media (min-width: 768px)  { :root { --page-padding: 32px; } }
@media (min-width: 1600px) { :root { --page-padding: 48px; } }

/* Les pages client gardent leurs 8 colonnes au-delà de 2400px : leur motif
   est calculé rangée par rangée sur 8, seize le trouerait en silence. Elles
   s'élargissent seulement, pour ne pas flotter au milieu d'un écran de 3440. */
@media (min-width: 2400px) { .bento { --bento-max: 2400px; } }

@media (max-width: 1199px) { :root { --bento-cols: 2; --bento-gap: 24px; } }
@media (max-width: 767px)  { :root { --bento-cols: 1; } }

/* ---------- LES DEUX POLICES ----------
   Demande d'Alexandre du 31 août : STIX Two Text pour les titres, Archivo pour
   le corps de texte. Elles remplacent Modak et Inter.

   DEUX FICHIERS VARIABLES, tous deux locaux et réduits au sous-ensemble latin.
   Aucune requête vers Google au chargement — une vérification bloquante du
   générateur le contrôle sur les quatorze pages.

   LE PIÈGE DÉJÀ PAYÉ : la feuille Google Fonts d'origine ne demandait que 200
   à 700. Les graisses supérieures n'étaient donc jamais téléchargées, et sur
   une machine sans la police installée les mots retombaient sur du 700 épaissi
   par le navigateur. Les plages déclarées ici sont celles des fichiers, pas des
   souhaits : elles ont été relevées dans les tables `fvar`. Le test
   d'acceptation est de couper le réseau et de vérifier que les gras restent
   des gras.

   Pas d'italique : le site n'en emploie aucune. */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/Archivo-latin.woff2') format('woff2');
}

/* STIX TWO TEXT PLAFONNE À 700, et comme elle porte désormais les TITRES,
   c'est là que la contrainte s'applique : les trois mots de l'accueil, le
   titre des pages et le nom du client sont en 700, pas en 800. Au-delà, le
   navigateur fabriquerait un faux gras, plus épais et plus sale que le dessin.
   NE PAS LES REMONTER À 800 : la police ne l'a pas.

   Le mobilier, lui, est en Archivo, qui monte à 900 : ses quinze déclarations
   à 800 sont de vraies graisses dessinées. */
@font-face {
  font-family: 'STIX Two Text';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('../fonts/StixTwoText-latin.woff2') format('woff2');
}

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

html, body { height: 100%; }

/* LE FOND DE PAGE EST SUR html. La vitre et le grain sont des éléments fixes
   en z-index négatif : si body portait une couleur, il les recouvrirait.
   AUCUN transform sur html ni body : un ancêtre transformé décrocherait tous
   ses descendants position:fixed — les rives, la vitre, le grain. */
html { background: var(--encre-nuit); }

body {
  margin: 0;
  background: transparent;
  color: var(--craie);
  /* Archivo est une grotesque de proportions courantes, comme l'Inter du
     cycle 9 : le corps et l'interligne d'origine lui vont, et le léger serrage
     aussi. Les 17px/1,6 de l'essai en romaine sont annulés — ils compensaient
     la petite hauteur d'x de STIX. */
  font: 400 16px/1.5 var(--police-texte);
  letter-spacing: -0.014em;
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

img, video { max-width: 100%; height: auto; display: block; }

a { color: inherit; text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 4px; }

:focus-visible { outline: 2px solid var(--craie); outline-offset: var(--focus-offset); }
/* ...sauf sur les deux surfaces en feu, où la craie serait invisible. */
.pilule--fort:focus-visible, .cta:focus-visible { outline-color: var(--encre-nuit); }

ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3 { margin: 0; font-weight: 800; }
p { margin: 0; }

/* ============================================================================
   0. LE FOND — le verre cannelé, sur les quatorze pages

   Ce n'est pas une image : c'est une LENTILLE. Trois couches empilées.
     1. le calage  — un aplat qui bouche les fractions de pixel
     2. la vitre   — 22 lamelles, chacune portant le décor ÉCRASÉ
     3. le grain   — un bruit figé, par-dessus tout
   ========================================================================= */

/* LE CALAGE. Il ne porte AUCUN décor et AUCUNE animation.
   La maquette y mettait un 23e décor animé plein écran — que la vitre,
   opaque, masquait entièrement. Un calque animé pour rien : supprimé. */
.fond {
  position: fixed; inset: 0; z-index: -3;
  background: var(--encre-nuit);
  pointer-events: none;
}

.vitre {
  position: fixed; inset: 0; z-index: -2;
  display: flex; flex-direction: column;
  pointer-events: none;
}

/* `contain: paint` borne le rendu à la lamelle : le navigateur n'a plus à
   rastériser la part du décor qui tombe hors d'elle. On passe d'une surface
   déclarée de 250vw × 100vh à la surface utile d'UNE lamelle.
   C'est le gain le plus sûr du cycle, et il ne change rien de visible. */
.lam { flex: 1 1 0; position: relative; overflow: hidden; contain: paint; }

/* LE DÉSALIGNEMENT. Chaque lamelle décale le décor d'un cran de plus que sa
   voisine : les couleurs ne se superposent plus en colonnes, elles descendent
   en escalier. C'est ce décalage qui empêche le fond de ressembler à une
   image simplement rayée.
   L'indice va de −11 à +10 : le décalage se répartit des deux côtés au lieu
   de pousser tout le fond vers la droite.

   L'ÉCRASEMENT VERTICAL (scaleY 0,38) est ce qui fabrique le cylindre de
   verre. Sans lui, il n'y a que des bandes. */
.lam .lentille {
  position: absolute; left: 0; right: 0; top: 50%;
  height: 100vh; margin-top: -50vh;
  transform: translateX(calc(var(--i) * var(--desalignement)))
             scaleY(var(--compression));
}

/* LE SPECTRE. Un motif qui SE BOUCLE SUR LUI-MÊME : il part de l'encre de
   nuit, traverse le bleu signal, le feu, la craie, le blanc, et y revient.
   Le raccord tombe donc sur la même couleur et ne se voit pas.

   LE DÉBORDEMENT N'EST PAS SYMÉTRIQUE, et c'est le point le plus fragile du
   fond. Corrigé le 31 août : il l'était (−75vw / −75vw), et une bande vide
   traversait l'écran au bout de 35 s. Le décor part vers la DROITE, donc il
   doit déborder BEAUCOUP plus à gauche qu'à droite.

   À gauche, il faut couvrir le déplacement de l'animation (59,3vw) PLUS le
   désalignement positif maximal (+10 × 3vw = 30vw) = 89,3vw. On prend 100vw.
   À droite, seul le désalignement négatif compte (−11 × 3vw = 33vw). On
   prend 45vw. L'indice va de −11 à +10, pas de −11 à +11.

   NE PAS RÉÉQUILIBRER CES DEUX VALEURS « pour faire propre » : c'est
   exactement ce qui fabriquait la couture. */
.decor {
  position: absolute; top: 0; bottom: 0; left: -100vw; right: -45vw;
  background: repeating-linear-gradient(112deg,
    var(--encre-nuit) 0vw, var(--encre-nuit) 4vw,
    color-mix(in oklab, var(--encre-nuit) 55%, var(--bleu-signal)) 10vw,
    var(--bleu-signal) 16vw,
    color-mix(in oklab, var(--bleu-signal) 40%, var(--feu)) 21vw,
    var(--feu) 26vw,
    color-mix(in oklab, var(--feu) 45%, var(--craie)) 31vw,
    #FFFFFF 36vw, var(--craie) 40vw,
    color-mix(in oklab, var(--feu) 60%, var(--craie)) 44vw,
    var(--feu) 47vw,
    color-mix(in oklab, var(--encre-nuit) 60%, var(--feu)) 51vw,
    var(--encre-nuit) 55vw);
  animation: defile var(--defile) linear infinite;
}

/* LE DÉPLACEMENT VAUT 59,3 vw = 55 / sin(112°).
   C'est la distance EXACTE pour avancer d'un motif. À un pour cent près, une
   couture apparaît. Il ne s'arrondit pas, il ne se « nettoie » pas à 59 ni
   à 60. NE PAS Y TOUCHER SANS RECALCULER. */
@keyframes defile { to { transform: translate3d(59.3vw, 0, 0); } }

/* LE GALBE — sombre aux deux bords, lumineux au milieu. C'est lui qui donne
   son épaisseur au verre. Le blanc y est de la LUMIÈRE, pas une couleur. */
.lam::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(180deg,
    rgba(30, 34, 61, calc(.92 * var(--ombre)))     0%,
    rgba(30, 34, 61, calc(.34 * var(--ombre)))    16%,
    rgba(255, 255, 255, calc(.10 * var(--galbe))) 42%,
    rgba(255, 255, 255, calc(.22 * var(--galbe))) 50%,
    rgba(255, 255, 255, calc(.06 * var(--galbe))) 60%,
    rgba(30, 34, 61, calc(.40 * var(--ombre)))    86%,
    rgba(30, 34, 61, calc(.94 * var(--ombre)))   100%);
}

/* LE JOINT entre deux lamelles. Absent sur la première : il n'y a rien
   au-dessus d'elle. */
.lam::before {
  content: ""; position: absolute; inset: -1px 0 auto 0; height: 2px; z-index: 2;
  background: rgba(30, 34, 61, calc(var(--joint) * var(--ombre) * 2));
}
.lam:first-child::before { display: none; }

/* LE GRAIN. Il est FIGÉ : rastérisé une fois au décodage, jamais animé,
   jamais recalculé. Sur un spectre saturé, un dégradé produit des bandes
   visibles — le grain répond à ça autant qu'il texture.

   IL NE COUVRE QUE LE FOND. Il était en z-index 8, donc par-dessus les rives,
   les textes et les plaques. Demande d'Alexandre du 31 août : le grain
   appartient au fond, à rien d'autre. Il passe donc entre la vitre (−2) et le
   contenu, qui n'est pas positionné et se peint au-dessus. */
.grain {
  position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: .34;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' fill-opacity='0.42'/%3E%3C/svg%3E");
}

/* ============================================================================
   1. LES RIVES — le haut et le bas, sur les quatorze pages
   ========================================================================= */

/* Les rives sont en FOND PLEIN, pas en verre : c'est ce qui les détache de la
   matière. Le filet qui les borde est décoratif (1,59:1) — aucune information
   n'en dépend, il est exempté du seuil de 3:1. */
.rive {
  position: fixed; left: 0; right: 0; z-index: 6;
  background: var(--encre-nuit);
  display: flex; align-items: center;
  gap: clamp(12px, 2vw, 26px);
  padding: 0 var(--marge-rive);
  height: var(--rive-h);
}
.rive--haut { top: 0; border-bottom: 1px solid var(--separateur); }
.rive--bas  {
  bottom: 0; border-top: 1px solid var(--separateur);
  height: auto; min-height: var(--rive-h);
  padding-top: 14px; padding-bottom: 14px;
}

/* SUR LES PAGES DE CONTENU, LE PIED N'EST PAS FIXE : il vient après le
   contenu. Fixe, il recouvrait le texte dès que la page dépassait la hauteur
   de l'écran — mesuré : 1 144 px de contenu pour 844 px d'écran.

   MAIS IL DOIT RESTER EN BAS QUAND LA PAGE EST COURTE. Sans ce qui suit, il
   remontait au milieu de l'écran et laissait un grand vide de fond sous lui —
   constaté par Alexandre sur la page Contact le 31 août. Le corps devient une
   colonne dont le contenu prend toute la place disponible : le pied se pose
   donc en bas de la fenêtre sur une page courte, et se laisse pousser vers le
   bas sur une page longue. Il n'est toujours pas fixe. */
.page-contenu,
.page-client { height: auto; min-height: 100%; display: flex; flex-direction: column; }

.page-contenu > main,
.page-client  > main { flex: 1 0 auto; }

.page-contenu .rive--bas,
.page-client .rive--bas { position: static; flex: 0 0 auto; }

/* GARDE-FOU : sur une fenêtre très basse, trois bandeaux à clamp(30px,7.4vw,96px)
   peuvent dépasser. Le pied revient au flux, pour la même raison. */
@media (max-height: 640px) { .rive--bas { position: static; } }

.pilule {
  flex: none;
  font-size: 13.5px; font-weight: 800; letter-spacing: .04em;
  color: var(--craie); text-decoration: none;
  padding: 11px 22px; border-radius: var(--rayon-pilule);
  border: 1px solid var(--filet); white-space: nowrap;
  transition: background var(--vite);
}
.pilule:hover { background: var(--voile-rive); text-decoration: none; }

/* L'IDENTITÉ — ce que le visiteur doit savoir avant tout le reste. */
.identite { display: flex; align-items: center; gap: 12px; min-width: 0; }
.portrait {
  flex: none; width: 44px; height: 44px; border-radius: 50%;
  object-fit: cover; border: 1px solid var(--filet);
}
.identite div { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.identite b {
  font-size: 14px; font-weight: 800; letter-spacing: -.015em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* LE MÉTIER RESTE EN GRAISSE NORMALE : c'est le contraste avec le nom qui
   fait comprendre lequel des deux on lit en premier. Ne jamais le passer en
   500 ou 600 pour « équilibrer » — ça détruit la hiérarchie. */
.identite span {
  font-size: 12.5px; font-weight: 400; color: var(--doux);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* UN HAMBURGER N'A PAS DE GRAISSE — il n'a pas de texte. Son équivalent,
   c'est l'épaisseur de ses traits : 2,5 px répond à l'ExtraBold du reste. */
.burger {
  margin-left: auto; flex: none;
  width: 52px; height: 52px; padding: 0;
  border-radius: var(--rayon-pilule); border: 1px solid var(--filet);
  background: transparent; cursor: pointer;
  display: grid; place-content: center; gap: 5px;
  transition: background var(--vite);
}
.burger:hover { background: var(--voile-rive); }
.burger span {
  display: block; width: 21px; height: 2.5px;
  background: var(--craie); border-radius: 2px;
  transition: transform 200ms var(--ease), opacity 200ms var(--ease);
}
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7.5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7.5px) rotate(-45deg); }

/* ---------- La rive basse, en trois temps ---------- */
.accroche { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.accroche b {
  font-size: clamp(15px, 2.4vw, 26px); font-weight: 800; letter-spacing: -.025em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.accroche a { font-size: clamp(12px, 1.5vw, 15px); color: var(--doux); }
.accroche a:hover { color: var(--craie); }

/* LINKEDIN A EXACTEMENT LES MÊMES MESURES QUE LE BOUTON EN FEU qu'il côtoie —
   même corps, même chasse, même rembourrage, donc même hauteur.
   Demande d'Alexandre du 31 août : les deux boutons faisaient 43 px et 57 px.

   C'EST LINKEDIN QUI MONTE, ET PAS L'INVERSE, et ce n'est pas un choix de
   goût. Le libellé de la case CONTACT est en encre de nuit sur feu : 4,4522:1.
   Le seuil du texte courant est 4,5 — il échoue de trois millièmes. Il ne
   passe que parce qu'il est du TEXTE LARGE, dont le seuil tombe à 3:1, ce qui
   exige au moins 18,66 px en gras. Rétrécir CONTACT à 12,5 px pour l'aligner
   sur LinkedIn l'aurait rendu non conforme, et aucune couleur de la palette ne
   le sauverait : la craie sur feu ne donne que 2,9969:1.
   LinkedIn, lui, est en craie sur encre de nuit — 13,34:1. Il peut grandir
   sans aucun risque. NE PAS INVERSER CE RAISONNEMENT.

   `.pilule--li` n'apparaît qu'à deux endroits, et toujours aux côtés d'un
   bouton en feu : le pied des quatorze pages, et le bloc d'actions d'À propos
   et de Contact. La règle vaut donc partout où elle existe. */
.pilule--li {
  display: inline-flex; align-items: center; gap: 8px; flex: none;
  font-weight: 800; letter-spacing: .08em; font-size: 19px; padding: 13px 26px;
}
.pilule--li svg { opacity: .9; }
.rive--bas .pilule--li { margin-left: auto; }

/* LA CASE CONTACT EST CENTRÉE EN ABSOLU SUR LA PAGE, pas entre ses voisines.
   Un centrage en flex la placerait au milieu de l'espace restant — donc
   jamais au milieu de l'écran — et elle bougerait selon la longueur de
   l'adresse e-mail.

   SON LIBELLÉ EST EN ENCRE DE NUIT, PAS EN CRAIE, et voici pourquoi :
   craie sur feu = 2,997:1. Le seuil des textes larges est 3:1 — elle échoue
   donc DE JUSTESSE, même portée à 19 px. Aucune autre couleur de la palette
   ne passe en petit corps sur du feu. L'encre de nuit sur feu donne 4,45:1 :
   elle passe avec une vraie marge, et surtout elle laisse le #F54F1B
   d'Alexandre absolument intact. C'est l'encre qui change, pas sa couleur. */
.pilule--fort {
  position: absolute; left: 50%; transform: translateX(-50%);
  background: var(--feu); color: var(--encre-nuit); border-color: var(--feu);
  font-weight: 800; letter-spacing: .08em;
  font-size: 19px; padding: 13px 26px;    /* 19px : le seuil tombe à 3:1 */
}
/* Le survol éclaircit, et le contraste MONTE : encre de nuit sur feu-vif
   donne 5,03:1. La maquette éclaircissait déjà ; ici ça ne coûte rien. */
.pilule--fort:hover { background: var(--feu-vif); border-color: var(--feu-vif); }

/* ---------- Le tiroir — mécanisme CONSERVÉ, peau et contenu changés ------- */
.tiroir {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 70;
  width: var(--tiroir-largeur);
  padding: 96px 32px 32px;
  background: var(--encre-nuit);
  border-left: 1px solid var(--filet);
  overflow-y: auto;
  transform: translateX(100%);
  transition: transform 420ms var(--ease);
}
.menu-ouvert .tiroir { transform: translateX(0); }

.tiroir-liste { display: grid; gap: 20px; }
.tiroir-liste a {
  display: block;
  font-size: clamp(24px, 6vw, 34px); font-weight: 800; letter-spacing: -.03em;
  line-height: 1.25;
}
.tiroir-liste a:hover { color: var(--feu); text-decoration: none; }
.tiroir-filet { height: 1px; background: var(--filet); margin-block: 12px; }
/* L'ADRESSE N'EST PAS UNE ENTRÉE DE MENU. Elle vit pourtant dans la même
   liste, donc `.tiroir-liste a` (34 px, gras) la rattrapait et la coupait au
   bord du tiroir : on lisait « …@gmail.co ». Il faut donc gagner en
   spécificité, pas seulement redéclarer la taille. Et comme l'adresse est un
   seul mot sans espace, `anywhere` l'autorise à passer à la ligne plutôt que
   de déborder sur les écrans les plus étroits. */
.tiroir-liste a.tiroir-mail {
  font-size: 15px; font-weight: 400; letter-spacing: 0;
  color: var(--doux); overflow-wrap: anywhere;
}

/* La cascade : les lignes arrivent depuis la droite, derrière le tiroir. */
.tiroir-liste > li { opacity: 0; transform: translateX(12px); }
.menu-ouvert .tiroir-liste > li {
  opacity: 1; transform: none;
  transition: opacity 260ms var(--ease), transform 260ms var(--ease);
  transition-delay: calc(120ms + var(--i, 0) * 40ms);
}

/* ============================================================================
   2. L'ACCUEIL — trois mots, et rien d'autre
   ========================================================================= */

/* L'accueil tient en un écran et centre ses trois bandeaux. Les rives sont
   réservées en haut et en bas, donc les bandeaux ne passent jamais dessous. */
.page-accueil main {
  min-height: 100vh;
  padding: var(--rive-h) 0;
  display: flex; flex-direction: column; justify-content: center;
}

.bandeau {
  position: relative; display: block; text-decoration: none;
  width: var(--largeur-bandeau);
  margin-left: calc(var(--n) * var(--decalage-bandeau));
  padding: var(--hauteur-bandeau) var(--marge-contenu);
  border-top: 1px solid rgba(30, 34, 61, .30);
  /* `isolation: isolate` crée un contexte d'empilement pour que les deux
     pseudo-éléments en z-index:-1 restent DERRIÈRE le texte du bandeau et
     non derrière la page. Ce n'est PAS un transform : les éléments fixes ne
     décrochent pas. */
  isolation: isolate;
}
.bandeau:hover { text-decoration: none; }
.bandeau:last-of-type { border-bottom: 1px solid rgba(30, 34, 61, .30); }

/* LA MATIÈRE DU BANDEAU, en deux couches derrière le texte.
   L'assombrissement n'est pas décoratif : SANS LUI, LES MOTS SERAIENT
   ILLISIBLES au passage du blanc du spectre. Mesuré : craie sur ce voile
   posé sur du blanc pur donne 5,33:1. */
.bandeau::before {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(242, 237, 230, var(--voile));
}
.bandeau::after {
  content: ""; position: absolute; inset: 0; z-index: -1;
  background: rgba(30, 34, 61, var(--assombrissement));
  backdrop-filter: blur(var(--flou-bandeau)) saturate(1.35);
  -webkit-backdrop-filter: blur(var(--flou-bandeau)) saturate(1.35);
}

/* STIX TWO TEXT, EN 700 — SA GRAISSE MAXIMALE. Il n'y a pas de 800 dans le
   fichier ; en demander un ferait fabriquer un faux gras au navigateur.

   AUCUN SERRAGE. Le −0,04em d'origine était calé sur une grotesque, dont les
   lettres droites gagnent à se rapprocher. Sur une romaine, il ferait mordre
   les empattements les uns dans les autres : ils débordent déjà de la lettre,
   c'est leur fonction.

   L'INTERLIGNE N'EST PAS UN GOÛT, C'EST UNE MESURE. Chaque lettre est une
   boîte en `overflow: hidden` — c'est ce qui fait glisser son double sous la
   lame de survol. Si la boîte est plus courte que le glyphe, elle le rogne.
   STIX Two Text demande 1,25 em pour contenir un glyphe entier (relevé dans sa
   table hhea) ; 1,35 laisse une vraie marge. NE PAS DESCENDRE SOUS 1,25. */
.mot {
  position: relative; display: inline-flex; flex-wrap: wrap;
  font-family: var(--police-titre);
  font-weight: 700; line-height: 1.35;
  font-size: clamp(30px, 7.4vw, 96px); letter-spacing: 0;
  color: var(--craie);
}
.lettre {
  position: relative; display: inline-block; white-space: pre;
  overflow: hidden; vertical-align: top; line-height: inherit;
  padding: 0 .09em; margin: 0 -.09em;
}
.lettre i {
  font-style: normal; display: block; line-height: inherit;
  transform: translateY(0);
}
/* Le double de la lettre, posé juste dessous : c'est lui qui monte prendre
   la place quand la lame passe. */
.lettre i::after {
  content: attr(data-c); position: absolute; left: 0; top: 100%;
  line-height: inherit;
}
.num {
  position: absolute; top: 10px; right: clamp(16px, 3vw, 32px);
  font-size: 12px; letter-spacing: .14em; color: var(--craie); opacity: .5;
}

/* ---------- Le survol : « la lumière pousse » ----------
   Chaque lettre bascule à l'instant où la lame l'atteint, et s'éclaire en
   basculant. Dosage retenu sur les huit essayés.

   LA LAME EST ENFERMÉE DANS LA BOÎTE DU MOT : elle ne traverse jamais tout
   le bandeau. C'est exactement la différence entre le dosage retenu et les
   sept écartés. */
.boite-lame {
  position: absolute; inset: 8% -2% 12%; overflow: hidden;
  z-index: -1; pointer-events: none;
}
.lame {
  position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 34%,
    color-mix(in oklab, var(--feu) 80%, transparent) 46%,
    rgba(255, 255, 255, .85) 52%, transparent 68%);
  /* LE FLOU N'EST JAMAIS ANIMÉ, seule la position l'est. Un flou posé sur
     l'élément qu'on anime est recalculé à chaque image — mesuré au cycle 8 :
     21,8 images par seconde fusionnés contre 67 séparés. */
  filter: blur(14px);
  transform: translateX(-130%);
  mix-blend-mode: screen;
}
.joue .lame { animation: lame 1.05s var(--ease) forwards; }
@keyframes lame { to { transform: translateX(130%); } }

.joue .lettre i {
  animation: roule .62s var(--ease) forwards;
  animation-delay: calc(120ms + var(--i) * 42ms);
}
@keyframes roule { to { transform: translateY(-100%); } }

.joue .lettre {
  animation: eclat .62s ease forwards;
  animation-delay: calc(100ms + var(--i) * 42ms);
}
@keyframes eclat {
  0%   { text-shadow: none; }
  30%  { text-shadow: 0 0 30px rgba(242, 237, 230, .9); }
  100% { text-shadow: none; }
}

/* ============================================================================
   3. LE STORE — l'écran d'ouverture, accueil seulement

   Sur une page client ou une page de contenu on arrive par le menu : rejouer
   le nom à chaque navigation ferait patienter pour rien.
   ========================================================================= */

.store {
  position: fixed; inset: 0; z-index: 40;
  display: flex; align-items: center; justify-content: center;
  padding: 0 var(--marge-contenu);
}
.store[data-etat="parti"] { pointer-events: none; }

/* LE FOND DU STORE EST FAIT DE LAMELLES, au même pas que le verre du fond.
   Elles sont là dès le départ, opaques, indiscernables d'un aplat — c'est en
   partant qu'elles se révèlent. */
.store-lames { position: absolute; inset: 0; display: flex; flex-direction: column; z-index: -1; }
.store-lame {
  flex: 1 1 0; background: var(--encre-nuit);
  transition: transform .62s var(--ease);
}

/* LA SORTIE — « le store s'ouvre ».
   Le rideau ne s'en va pas : chaque lamelle se referme sur sa propre ligne
   médiane, de haut en bas, décalée de 26 ms. C'est la seule des trois
   transitions qui relie l'ouverture à la matière du site. */
.store[data-etat="parti"] .store-lame {
  transform: scaleY(0);
  transition-delay: calc(var(--j) * 26ms);
}
.store[data-etat="parti"] .store-nom {
  opacity: 0; transform: translateY(-14px);
  transition: opacity .3s ease, transform .5s var(--ease);
}
.store[data-etat="parti"]::after { opacity: 0; }

.store-nom {
  display: flex; flex-wrap: wrap; justify-content: center;
  font-weight: 800; font-size: clamp(30px, 7vw, 92px);
  letter-spacing: -.04em; line-height: 1.22; color: var(--craie);
  transition: opacity .4s ease, transform .5s var(--ease);
}
.store-lettre {
  position: relative; display: inline-block; white-space: pre;
  padding: 0 .06em; margin: 0 -.06em;
}

/* LE BLOC SE CALE SUR LA HAUTEUR DES CAPITALES, pas sur la boîte de ligne.
   À 0,1 em du haut il dépassait au-dessus des lettres et laissait un liseré
   visible. En Inter, avec une interligne de 1,22, le haut des capitales tombe
   à 0,17 em et la ligne de base à 0,32 em du bas. */
.store-bloc {
  position: absolute; left: .04em; right: .04em; top: .17em; bottom: .32em;
  background: var(--craie);
  transform: scaleY(0); transform-origin: bottom;
  animation: bloc .65s var(--ease) forwards;
  animation-delay: calc(var(--i) * 45ms);
}
.store-lettre i {
  font-style: normal; position: relative; opacity: 0;
  animation: signe .34s ease forwards;
  animation-delay: calc(var(--i) * 45ms + 312ms);
}
@keyframes bloc {
  0%   { transform: scaleY(0); transform-origin: bottom; }
  26%  { transform: scaleY(1); transform-origin: bottom; }
  52%  { transform: scaleY(1); transform-origin: top; }
  100% { transform: scaleY(0); transform-origin: top; }
}
@keyframes signe { to { opacity: 1; } }

/* Le store porte SON PROPRE GRAIN : sans lui, le premier écran du site serait
   le seul aplat lisse de toute la page. */
.store::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  opacity: .34; transition: opacity .5s ease;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.62' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23g)' fill-opacity='0.42'/%3E%3C/svg%3E");
}

/* ============================================================================
   4. LES PAGES DE CONTENU — À propos, Contact, Productions
   ========================================================================= */

/* LE CONTENU EST CENTRÉ VERTICALEMENT QUAND IL TIENT DANS L'ÉCRAN, et posé
   normalement quand il déborde. Demande d'Alexandre du 31 août.
   Le centrage se fait par des marges automatiques et NON par
   `justify-content: center` : un contenu plus haut que son conteneur déborde
   alors des deux côtés, et le haut devient inatteignable au défilement. Des
   marges `auto` ne prennent que l'espace qui reste — s'il n'en reste pas,
   elles ne font rien. */
.page-contenu main {
  padding: calc(var(--rive-h) + clamp(24px, 5vh, 64px)) 0 clamp(40px, 6vh, 80px);
  display: flex; flex-direction: column;
}
.page-contenu main > :first-child { margin-top: auto; }
.page-contenu main > :last-child  { margin-bottom: auto; }

/* Le panneau de texte : la même matière que les bandeaux de l'accueil.
   C'est ce qui rend le texte lisible quand le spectre passe par le blanc. */
.bandeau--texte {
  padding: clamp(30px, 4.6vh, 54px) var(--marge-contenu);
  text-align: center;
}

.colonne { max-width: 58ch; margin: 0 auto; }

.titre-page {
  font-family: var(--police-titre);
  font-weight: 700; font-size: clamp(28px, 4.8vw, 60px);
  letter-spacing: 0; line-height: 1.3;
  margin: 0 0 clamp(16px, 2.2vh, 26px); color: var(--craie);
}

/* UN TEXTE CENTRÉ QUI COURT SUR TOUTE LA LARGEUR DEVIENT ILLISIBLE : l'œil
   ne retrouve plus le début de la ligne suivante. 54ch est la limite. */
.texte {
  max-width: 54ch; margin: 0 auto;
  font-size: clamp(17px, 1.6vw, 23px); line-height: 1.58; color: var(--craie);
}
.texte p { margin: 0 0 .85em; }
.texte p:last-child { margin-bottom: 0; }
.texte b { font-weight: 800; }

/* L'ACCENT. La maquette mettait cette phrase EN FEU, dans un paragraphe —
   or le feu donne 4,45:1 et le seuil du texte courant est 4,5:1. Le critère 3
   l'interdit nommément.
   La phrase passe donc en craie, en gras, SOULIGNÉE D'UN FILET FEU : c'est
   exactement le dispositif déjà validé pour l'adresse de la page Contact.
   Le feu reste présent, il reste l'accent, il cesse d'être de l'encre. */
.accent {
  font-weight: 800; color: var(--craie);
  border-bottom: 2px solid var(--feu);
  padding-bottom: 1px;
}

.actions {
  display: flex; flex-wrap: wrap; gap: 10px;
  justify-content: center; align-items: center;
  margin-top: clamp(18px, 2.4vh, 28px);
}

/* Le bouton d'appel : mêmes couleurs et même règle que la case CONTACT.
   Encre de nuit sur feu, 19 px — 4,45:1, admis parce que c'est du texte large.

   LA BORDURE EN FEU N'EST PAS DÉCORATIVE, elle est là pour la mesure : sans
   elle, le bouton faisait 55 px quand la pilule LinkedIn à côté en faisait 57,
   celle-ci ayant un filet d'un pixel. Deux boutons voisins hauts de 55 et 57
   ne se lisent pas comme un écart de dessin, mais comme un défaut. Elle est de
   la couleur du fond : invisible, et elle rétablit l'égalité. */
.cta {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--feu); color: var(--encre-nuit);
  border: 1px solid var(--feu);
  font-weight: 800; letter-spacing: .08em; font-size: 19px;
  padding: 13px 26px; border-radius: var(--rayon-pilule);
  transition: background var(--vite), border-color var(--vite);
}
.cta:hover { background: var(--feu-vif); border-color: var(--feu-vif); text-decoration: none; }

/* ---------- Contact — l'adresse et les quatre amorces ---------- */

.adresse {
  display: inline-block;
  font-weight: 800; letter-spacing: -.03em;
  font-size: clamp(20px, 3.4vw, 44px); color: var(--craie);
  border-bottom: 2px solid var(--feu);    /* feu sur encre : 4,45:1, seuil 3 */
  padding-bottom: 4px; transition: color var(--vite);
}
.adresse:hover { color: var(--feu); text-decoration: none; }

/* QUATRE AMORCES, PAS UNE DE PLUS. Au-delà, ce n'est plus un choix mais une
   liste. Chacune ouvre la messagerie avec l'objet déjà rempli : le message
   arrive qualifié, et le site reste un dossier de fichiers statiques —
   aucun serveur, aucun service tiers, aucune donnée personnelle traitée. */
.amorces {
  display: flex; flex-wrap: wrap; gap: 10px; justify-content: center;
  margin-top: clamp(18px, 2.4vh, 28px);
}
.amorces a {
  font-size: 13.5px; font-weight: 800; letter-spacing: .04em;
  color: var(--craie);
  padding: 12px 22px; border-radius: var(--rayon-pilule);
  border: 1px solid var(--filet);
  transition: background var(--vite), border-color var(--vite);
}
/* LE SURVOL NE TOUCHE QUE LE FILET — ni remplissage de feu, ni voile clair.
   À 13,5 px, ni la craie (2,997:1) ni l'encre de nuit (4,45:1) ne passent le
   seuil de 4,5:1 sur du feu : le feu signale donc le survol PAR LE FILET.
   Et le fond ne bouge pas non plus. Un voile clair posé ici éclaircissait le
   panneau et FAISAIT BAISSER le contraste au lieu de le tenir — mesuré par la
   revue à 4,322:1 contre le pire cas du spectre (le blanc pur), sous le seuil.
   Fond inchangé, le texte garde les 5,33:1 de l'état normal. */
.amorces a:hover {
  border-color: var(--feu);
  text-decoration: none;
}

/* ============================================================================
   5. PRODUCTIONS — la grille des dix clients

   LA CLASSE S'APPELLE .plaque, PAS .tuile, ET C'EST DÉLIBÉRÉ.
   `.tuile` désigne déjà une tuile DE FILM, conservée sur les dix pages
   client. Les animations T1 et T2 d'anim.js visent `.tuile` hors
   `.bento--fixe` : si la grille Productions s'appelait `.tuile`, elles s'y
   appliqueraient par accident et se cumuleraient avec l'apparition
   « C · Verre ». Les plaques bougeraient deux fois, et AUCUNE ERREUR NE
   SERAIT LEVÉE.
   ========================================================================= */

.entete { text-align: center; margin: 0 auto clamp(22px, 3vh, 38px); }
.sous { margin: 8px 0 0; color: var(--craie); font-size: 15px; }

.grille {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: clamp(12px, 1.4vw, 22px);
  max-width: 1600px; margin: 0 auto;
  padding-inline: var(--marge-contenu);
}

/* LES PROPORTIONS ALTERNENT PAR RANGÉE — 16/9, puis 1/1, puis 16/9.
   Demande d'Alexandre du 31 août : aucune plaque verticale. Même la seule
   vidéo verticale du fonds — Chloé Nabédian — tombe dans une rangée carrée,
   qui la cadre bien mieux qu'un 16/9 ne le ferait.
   La classe est posée par le générateur, qui compte les colonnes occupées :
   deux proportions dans une même rangée laisseraient un trou sous la plus
   courte. */
.plaque {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--rayon-plaque); aspect-ratio: 16/9;
  text-decoration: none; isolation: isolate;
  /* Le filet est DÉCORATIF, et c'est mesuré : contrairement aux tuiles du
     cycle 8 — qui étaient translucides et n'avaient que leur filet pour
     bord —, une plaque est remplie par une image opaque en `cover`. C'est
     l'image qui fait le bord, plus le nom du client posé dessus. Le filet
     ne porte aucune information : il est exempté du seuil de 3:1. */
  border: 1px solid rgba(242, 237, 230, .18);
  background: rgba(30, 34, 61, .55);
}
.plaque:hover { text-decoration: none; }
.plaque--large { grid-column: span 2; }
.plaque--carre { aspect-ratio: 1/1; }

.plaque img, .plaque video {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
.plaque img { transition: transform .7s var(--ease); }
.plaque:hover img { transform: scale(1.04); }

/* LA VIDÉO AU SURVOL — décision d'Alexandre, option A.
   Au repos la plaque est une image : coût nul, la page s'ouvre
   instantanément et le verre reste fluide. Une seule vidéo décode à la fois,
   jamais dix. Sur téléphone il n'y a pas de survol : la plaque reste une
   image et le doigt ouvre la page — ça n'engloutit pas le forfait du
   visiteur.
   Le mécanisme est celui des pages client (`.preview`), pas un nouveau. */
.plaque .preview {
  position: absolute; inset: 0; opacity: 0;
  transition: opacity var(--vite) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  .plaque:hover .preview, .plaque:focus-within .preview { opacity: 1; }
}

/* Le voile du bas. Mesuré DANS LE PIRE CAS — une image entièrement blanche
   dessous : le nom tient 7,49:1 et le compte 4,63:1. Il tient donc sans
   retouche quelle que soit la vignette. C'est calculé, pas estimé. */
.plaque-voile {
  position: absolute; inset: 0; pointer-events: none; z-index: 1;
  background: linear-gradient(180deg, rgba(30, 34, 61, 0) 42%, var(--voile-plaque) 100%);
}
.plaque-nom {
  position: absolute; left: 18px; bottom: 16px; z-index: 2;
  font-weight: 800; font-size: clamp(15px, 1.5vw, 21px);
  letter-spacing: -.02em; color: var(--craie);
}
/* .plaque-compte affichait « 13 films » en bas à droite de chaque plaque.
   Supprimé le 31 août : Alexandre ne veut aucun compte de films ni de clients
   dans les textes du site. */

/* Sous 1100 px la grille perd une colonne, puis deux : les rangées ne
   contiennent plus les mêmes plaques et l'alternance calculée pour quatre
   colonnes n'a plus de sens. Toutes les plaques reprennent alors la même
   proportion — mieux vaut un rythme perdu qu'un rythme faux. */
@media (max-width: 1100px) {
  .grille { grid-template-columns: repeat(3, 1fr); }
  .plaque--carre { aspect-ratio: 16/9; }
}
@media (max-width: 760px)  { .grille { grid-template-columns: repeat(2, 1fr); } }

/* ---------- L'apparition « C · Verre », décalage 70 ms ----------
   La plaque arrive floue et translucide, puis se fige nette. Le décalage de
   70 ms fait lire la grille comme une VAGUE, pas comme un bloc qui s'allume
   d'un coup.

   Le flou n'est ici pas un coût permanent : c'est une transition de `filter`
   sur dix éléments, jouée UNE SEULE FOIS au premier passage à l'écran. */
/* LE MASQUAGE EST CONDITIONNÉ À LA MARQUE `js`, posée par le script inline du
   <head>. Une page dont le script ne parle pas — fichier ouvert en double-clic
   sous certains navigateurs, script bloqué, erreur en amont — affiche donc ses
   plaques normalement, au lieu d'une grille entièrement vide. */
.js .plaque { opacity: 0; filter: blur(16px); transform: scale(1.06); }
.plaque.entree {
  opacity: 1; filter: blur(0); transform: scale(1);
  transition: opacity .7s ease, filter .8s var(--ease), transform .9s var(--ease);
  transition-delay: calc(var(--i) * 70ms);
}

/* Sans JavaScript le guetteur ne tourne pas : une règle <noscript>, dans le
   <head> de chaque page, remet les plaques à opacity 1. Sans elle, la page
   Productions serait vide. */

/* ============================================================================
   6. LA GRILLE BENTO — les dix pages client
   Conservée telle quelle, repeinte. Elle n'est pas remise en cause.
   ========================================================================= */

.page-client main { padding-top: var(--rive-h); padding-bottom: clamp(40px, 6vh, 80px); }

/* LE NOM DU CLIENT EST SUR UN BANDEAU, comme les trois mots de l'accueil.
   Demande d'Alexandre du 31 août, et ce n'est pas qu'une question de style :
   posé nu sur le verre, il devenait illisible chaque fois que le spectre
   passait par le blanc sous lui. Le bandeau apporte le même voile mesuré à
   5,33:1 que partout ailleurs.

   IL GARDE L'ALIGNEMENT À GAUCHE de l'accueil — pas le centrage de
   `.bandeau--texte`, qui est le dispositif des pages de contenu. C'est bien
   « comme la homepage », pas « comme Productions ».

   Seul le rythme vertical est repris en main : les 2,4vh de l'accueil sont
   calés sur un mot de 96 px, le nom du client en fait 54 au plus. */
.tete-client { padding-block: clamp(22px, 3.6vh, 44px); }
.tete-client h1 {
  font-family: var(--police-titre);
  font-weight: 700; font-size: clamp(28px, 7vw, 54px);
  letter-spacing: 0; line-height: 1.3;
}

.bento {
  display: grid;
  grid-template-columns: repeat(var(--bento-cols), 1fr);
  grid-auto-flow: row;              /* jamais "dense" : l'ordre lu = l'ordre vu */
  gap: var(--bento-gap);
  align-items: stretch;
  max-width: var(--bento-max);
  margin-inline: auto;
  padding-inline: var(--page-padding);
}

.tuile--l  { grid-column: span 4; }
.tuile--m  { grid-column: span 3; }
.tuile--s  { grid-column: span 2; }
.tuile--xl { grid-column: 1 / -1; }
/* Il n'y a pas de `.tuile--v` : le générateur ne produit que xl, l, m et s
   (voir la table COLONNES). Les trois films verticaux portent leur format
   dans `data-ratio`, pas dans une taille de grille. */

@media (max-width: 1199px) {
  .tuile--l, .tuile--m, .tuile--s { grid-column: span 1; }
}

/* .tuile porte la révélation, .tuile-inner porte la dérive ET la forme :
   deux animations qui écriraient la même propriété sur le même élément se
   marcheraient dessus. Ne pas les fusionner. */
.tuile { display: flex; }

.tuile-inner {
  flex: 1;
  display: flex; flex-direction: column;
  border-radius: var(--rayon-carte);
  overflow: hidden;
  background: rgba(242, 237, 230, .08);
  color: var(--craie);
  /* LE VERRE. Un filet, un liseré de lumière en haut, une ombre EN ENCRE DE
     NUIT — jamais en noir : une ombre noire dans une palette colorée salit
     tout ce qu'elle touche.
     Le filet est à 0,42 et non 0,18 : c'est la correction imposée par la
     revue du cycle 8. Une tuile translucide sur un fond clair par endroits
     perd son bord, et le filet est alors le SEUL bord réel. */
  border: 1px solid var(--filet);
  box-shadow: inset 0 1px 0 rgba(242, 237, 230, .28),
              0 24px 60px rgba(30, 34, 61, .45);
  backdrop-filter: blur(20px) saturate(130%);
  -webkit-backdrop-filter: blur(20px) saturate(130%);
}
a.tuile-inner:hover { text-decoration: none; }

/* L'ENCART PORTE SON PROPRE VOILE, et c'est une correction, pas un ornement.
   La tuile est en verre CLAIR — craie à 8 % — ce qui laisse monter le spectre
   derrière le titre. Quand il passe par le blanc, la craie sur craie tombait
   à **1,7:1**. Mesuré le 31 août sur les pixels d'une capture réelle, pas
   estimé ; le seuil est 4,5. Le défaut existait aussi sur grand écran.

   Le voile employé est celui des plaques, `--voile-plaque`, déjà validé
   ailleurs : il porte le pire cas à **7,5:1**. Une cinquième valeur inventée
   pour l'occasion aurait fini par diverger des quatre autres.

   IL NE COUVRE QUE L'ENCART, jamais la vidéo : le verre clair autour de
   l'image fait la matière des pages client, et il est conservé tel quel. */
.tuile-texte {
  margin-top: auto; padding: var(--tuile-pad);
  background: var(--voile-plaque);
}

/* Sur une page client, l'encart porte le titre du film : casse normale,
   plusieurs lignes autorisées. */
.client {
  font-size: clamp(17px, 1.5vw, 21px); font-weight: 800;
  letter-spacing: -.02em; line-height: 1.3;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
  overflow: hidden;
}
.plateforme {
  margin-top: 8px;
  font-size: 14px; font-weight: 400; letter-spacing: -.018em;
  color: var(--doux);
}
.tuile:hover .client { text-decoration: underline; text-underline-offset: 4px; }

/* ---------- Le visuel : poster, aperçu, tête de lecture ---------- */
.visuel { position: relative; margin: 0; flex: none; overflow: hidden; background: transparent; }
.visuel[data-ratio="16-9"] { aspect-ratio: var(--ratio-16-9); }
.visuel[data-ratio="1-1"]  { aspect-ratio: var(--ratio-1-1); }
.visuel[data-ratio="9-16"] { aspect-ratio: var(--ratio-9-16); }

/* Le plan EST recadré dans la grille : décision d'Alexandre, prise en
   connaissance de cause. Le LECTEUR, lui, garde `contain` — c'est là qu'on
   regarde le film, et le cadre y reste entier. */
.visuel img { width: 100%; height: 100%; object-fit: cover; }

.preview {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; opacity: 0;
  transition: opacity var(--vite) var(--ease);
}

.playhead {
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; background: var(--craie);
  transform: scaleX(0); transform-origin: left; opacity: 0;
}

@media (hover: hover) and (pointer: fine) {
  .tuile:hover .preview { opacity: 1; }
  .tuile:hover .playhead, .tuile:focus-within .playhead {
    opacity: 1; animation: balayage 6s linear infinite;
  }
}
@keyframes balayage { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* ============================================================================
   7. LE LECTEUR — un pop-up posé là où on regarde
   Conservé, repeint. Le voile passe du noir à l'encre de nuit.
   ========================================================================= */

.lecteur-voile {
  position: fixed; inset: 0; z-index: 90;
  background: var(--voile-lecteur);
  animation: fondu 220ms var(--ease);
}
@keyframes fondu { from { opacity: 0; } to { opacity: 1; } }

.lecteur {
  position: fixed; z-index: 91;
  left: 50%; top: 54%; transform: translate(-50%, -50%);
  width: min(92vw, var(--lecteur-max));
  animation: pose 220ms var(--ease);
}
.lecteur[data-ratio="1-1"]  { width: min(92vw, var(--lecteur-max-carre)); }
.lecteur[data-ratio="9-16"] { width: min(92vw, var(--lecteur-max-vert)); }
@keyframes pose {
  from { opacity: 0; transform: translate(-50%, -50%) scale(0.96); }
  to   { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.lecteur video {
  width: 100%; max-height: 78vh;
  border-radius: var(--rayon-carte);
  background: var(--encre-nuit);
  object-fit: contain;
}

.lecteur-client {
  position: absolute; left: 16px; bottom: 16px;
  font-size: 12px; font-weight: 400; text-transform: uppercase;
  line-height: 1; color: var(--craie); opacity: .6; pointer-events: none;
}

.lecteur-commandes {
  position: absolute; right: 16px; bottom: 16px;
  display: flex; gap: 8px;
}
.lecteur-commandes .pilule {
  background: transparent; color: var(--craie);
  border: 1px solid var(--craie);
  padding: 12px 16px; font-size: 14px; letter-spacing: 0;
  min-height: 44px; cursor: pointer; font-family: inherit;
}

@media (max-width: 767px) {
  .lecteur video { max-height: 70vh; }
  .lecteur { width: min(94vw, var(--lecteur-max)); }
  .lecteur-commandes { position: static; margin-top: 16px; justify-content: center; }
  .lecteur-client { bottom: auto; top: 16px; }
}

/* ============================================================================
   8. LES REPLIS
   ========================================================================= */

@media (max-width: 860px) {
  /* LE PIED REPASSE AU FLUX. À trois éléments sur une largeur de téléphone,
     une case centrée en absolu recouvrirait ses voisines.
     LE SEUIL EST LE MÊME SUR LES QUATRE PAGES — il ne l'était pas, et le pied
     ne se comportait pas pareil selon l'endroit du site. */
  .rive--bas { flex-wrap: wrap; row-gap: 14px; }
  .pilule--fort { position: static; transform: none; }

  /* LE PIED S'EMPILE EN DEUX TEMPS, PAS EN ESCALIER. Avec un simple
     `margin-left: auto` sur la case CONTACT, elle passait à la ligne mais
     restait plaquée à droite, sous LinkedIn lui-même plaqué à droite : deux
     boutons décalés l'un sous l'autre, et un grand vide à gauche. Constaté
     sur capture le 31 août.
     Le bloc de texte prend maintenant toute la largeur, et les deux pilules
     partagent la ligne suivante à parts égales. */
  .rive--bas .accroche { flex: 1 0 100%; }
  .rive--bas .pilule {
    flex: 1 1 0; margin-left: 0;
    display: inline-flex; align-items: center; justify-content: center;
    /* LE REMBOURRAGE HORIZONTAL SE RESSERRE, JAMAIS LE VERTICAL NI LE CORPS.
       Les deux boutons sont passés à 19 px pour que CONTACT reste conforme ;
       avec les 26 px de rembourrage du grand écran, ils ne tenaient plus sur
       une ligne de téléphone et s'empilaient. Le pied fixe de l'accueil
       montait alors à 210 px — le quart de l'écran, pris sur les trois
       bandeaux. Seule la marge latérale cède : la hauteur reste 57 px et le
       texte reste large. */
    padding-left: 10px; padding-right: 10px;
  }
}

@media (max-width: 760px) {
  :root { --rive-h: 76px; }
  /* SUR MOBILE, L'IDENTITÉ PASSE AVANT LA MARQUE. La pilule et le nom ne
     tiennent pas ensemble sur 390 px : le nom se faisait tronquer en
     « Alexand… ». On garde ce qui informe — le visage, le nom, le métier —
     et on retire la pilule, dont le contenu est déjà dans le pied. */
  .rive--haut .pilule { display: none; }

  /* LE CORPS DE TEXTE S'ALIGNE À GAUCHE — SEULEMENT ICI, ET SEULEMENT LUI.
     Le centrage de la page À propos est une décision d'Alexandre et elle
     tient : sur un écran large, la colonne fait 54 caractères et le retour à
     la ligne reste facile à suivre.
     Sur 390 px, la même colonne tombe à une quarantaine de caractères sur
     cinq paragraphes : chaque ligne commence à un endroit différent, et l'œil
     perd le début de la suivante. Un texte long centré est fait pour être
     court.
     LES TITRES ET LES BOUTONS RESTENT CENTRÉS : c'est le paragraphe qui
     s'aligne, pas la page. */
  .bandeau--texte .texte { text-align: left; }
  .identite span { font-size: 11.5px; }
  .accroche b { font-size: 14px; }
  .accroche a { font-size: 11.5px; }
}

/* ============================================================================
   9. ANIMATIONS RÉDUITES
   Le verre reste, la couleur reste, seule la dérive cesse.
   ========================================================================= */

@media (prefers-reduced-motion: reduce) {
  /* Le spectre s'arrête, mais le fond garde toute sa matière. */
  .decor { animation: none; }

  /* Le survol des bandeaux ne joue plus : les lettres restent en place. */
  .joue .lame, .joue .lettre, .joue .lettre i { animation: none; }

  /* L'apparition des plaques est neutralisée — jamais suspendue. */
  .plaque { opacity: 1 !important; filter: none !important; transform: none !important; }

  /* L'ÉCRAN D'OUVERTURE NE JOUE PAS DU TOUT — il n'est pas ralenti, il est
     retiré. C'est un défaut réel de la maquette qu'on corrige ici : avec
     `animation: none`, les blocs restaient à scaleY(0) et les signes à
     opacity 0. Le nom aurait été INVISIBLE et le rideau serait resté collé
     à l'écran, puisque rien n'aurait posé data-etat="parti".
     Le script inline du <head> l'écarte avant tout affichage ; cette règle
     est le second filet. */
  .store { display: none; }

  .preview { display: none; }
  .tuile:hover .playhead, .tuile:focus-within .playhead {
    opacity: 1; transform: scaleX(1); animation: none;
  }
  .lecteur, .lecteur-voile { animation: none; }
  * { transition-duration: 0.01ms !important; }
}
