@charset "UTF-8";
/* ============================================================================
   VOXYMORE — main.css
   Ordre des sections imposé par docs/08 §2 :
     1. @font-face          <- ce fichier, ci-dessous
     2. Jetons :root + sombre
     3. Reset minimal + base
     4. Layout
     5. Composants
     6. Utilitaires
     7. @media (prefers-reduced-motion: reduce)   <- toujours en fin de fichier

   Règles non négociables :
   - Aucune valeur magique : toute couleur, tout espacement, tout rayon vient
     d'un jeton. Si une valeur manque, c'est le jeton qui manque.
   - Mobile-first : les @media ne contiennent que des min-width (480/760/1080).
   - Zéro style inline dans le HTML (CSP style-src 'self' sans unsafe-inline).
   - Pas de bascule de thème manuelle : prefers-color-scheme seul, zéro stockage.
   ========================================================================= */

/* ===========================================================================
   1. FONTES
   Sous-ensemble unique FR+EN, servi depuis /assets/fonts/ (contrainte 1 :
   aucune requête tierce). unicode-range déclaré pour que le navigateur
   n'aille pas chercher la fonte si la page ne contient aucun caractère couvert.

   NB U+00A0 (espace insécable) est dans la plage U+0000-00FF.
   U+202F (espace FINE insécable) n'est PAS dans le sous-ensemble : ne jamais
   l'employer, il ferait basculer le caractère sur une fonte de repli en plein
   milieu d'un nombre. Séparateur de milliers = U+00A0. Voir docs/13 §10.
   ======================================================================== */
@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/Poppins-SemiBold.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0152-0153, U+2013-2014, U+2018-2019,
                 U+201C-201D, U+2026, U+20AC, U+2212;
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/assets/fonts/NunitoSans-Regular.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0152-0153, U+2013-2014, U+2018-2019,
                 U+201C-201D, U+2026, U+20AC, U+2212;
}
@font-face {
  font-family: 'Nunito Sans';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url('/assets/fonts/NunitoSans-SemiBold.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0152-0153, U+2013-2014, U+2018-2019,
                 U+201C-201D, U+2026, U+20AC, U+2212;
}

/* ===========================================================================
   2. JETONS
   Copie exacte de docs/03. Aucune couleur, aucune taille n'est inventée ici.
   Les ratios en commentaire sont MESURÉS, pas estimés : ils justifient la
   conformité WCAG 2.2 AA et doivent être recalculés si une valeur change.
   ======================================================================== */
:root {
  color-scheme: light dark;

  /* ---- Couleurs : identité --------------------------------------------- */
  /* 2,07:1 sur blanc. CE N'EST PAS UN DÉFAUT et il ne faut jamais « corriger »
     cette valeur : WCAG 1.4.3 exempte explicitement les logotypes du contraste
     de texte. L'exemption ne tient QUE parce que cet orange n'habille jamais de
     texte, jamais un bouton, jamais un lien — uniquement le logo et un décor
     ponctuel. Le vert porte toute l'action.
     Encre mesurée dans le PNG source du logo : #F19C4C — écart imperceptible,
     la palette est construite sur #F9A03F, qui fait foi. */
  --color-logo: #F9A03F;

  /* ---- Couleurs : action ----------------------------------------------- */
  --color-accent: #166534;         /* 6,68:1 sur bg · 7,13:1 sur surface */
  --color-accent-strong: #14532D;  /* footer, hover · ivoire dessus 8,54:1 */

  /* ---- Couleurs : surfaces --------------------------------------------- */
  --color-bg: #F6F8F6;
  --color-surface: #FFFFFF;
  --color-tint: #E8F0E8;

  /* ---- Couleurs : bordures --------------------------------------------- */
  /* DÉCORATIF uniquement — cartes, filets, séparateurs. Hors périmètre de
     WCAG 1.4.11, qui ne s'applique pas à la décoration pure. */
  --color-border: #DCE4DC;
  --color-border-strong: #9FB2A4;
  /* INTERACTIF — limite visuelle d'un composant d'interface, et graphiques
     porteurs de sens. WCAG 1.4.11 exige 3:1 ; aucun jeton d'origine n'y arrivait
     (#DCE4DC = 1,30:1 sur blanc). Un champ blanc sur page ivoire ne diffère que
     de 1,05:1 : la bordure est le seul élément qui définit le composant.
     Valeur unique pour les deux thèmes.
     clair  3,57:1 /surface · 3,34:1 /bg · 3,07:1 /tint (juste — éviter les
            champs sur fond tint)
     sombre 4,39:1 /surface · 5,03:1 /bg · 3,90:1 /tint
     Emploi : input, textarea, select, checkbox, bordure de .btn-ghost, et
     connecteurs de flux des schémas SCH-04/05/07/09. Rien d'autre. */
  --color-border-input: #7A8C80;

  /* ---- Couleurs : texte ------------------------------------------------- */
  --color-ink: #1A2E22;            /* 13,50:1 sur bg · 14,41:1 sur surface */
  --color-ink-2: #44554A;          /*  7,44:1 sur bg ·  7,94:1 sur surface */

  /* ---- Couleurs : états ------------------------------------------------- */
  --color-success: #15803D;        /* 4,70:1 sur bg  (mesuré ; docs/03 disait
                                      5,02 — corrigé, voir CHANGELOG #26) */
  --color-warning: #A16207;        /* 4,61:1 sur bg */
  --color-error:   #B91C1C;        /* 6,06:1 sur bg · 5,86:1 sur error-bg */
  --color-info:    #1D4ED8;        /* 6,28:1 sur bg */
  --color-error-bg:   #FDF1F1;
  --color-success-bg: #EFF7F0;

  /* ---- Boutons ---------------------------------------------------------- */
  --btn-primary-bg: #166534;   --btn-primary-ink: #FFFFFF;   /* 7,13:1 */
  --btn-secondary-bg: #1A2E22; --btn-secondary-ink: #F6F8F6; /* 13,50:1 */

  /* ---- Focus ------------------------------------------------------------ */
  /* L'anneau est doublé d'un outline transparent partout où il est appliqué :
     une box-shadow disparaît en mode contraste élevé de Windows. */
  --focus-ring: 0 0 0 3px rgba(22, 101, 52, .35);

  /* ---- Typographie ------------------------------------------------------ */
  --font-head: 'Poppins', 'Trebuchet MS', Verdana, sans-serif;
  --font-body: 'Nunito Sans', system-ui, -apple-system, 'Segoe UI', Roboto,
               Arial, sans-serif;

  --text-xs:  .8rem;
  --text-base: 1rem;
  --text-md:  1.25rem;
  --text-lg:  1.563rem;
  --text-xl:  1.953rem;
  --text-2xl: 2.441rem;
  --text-3xl: 3.052rem;

  --leading-body: 1.65;
  --leading-head: 1.18;
  --measure: 68ch;
  --tracking-head: -.005em;
  --tracking-label: .14em;

  /* ---- Espacement (échelle 4 px) ---------------------------------------- */
  --space-1: .25rem;  --space-2: .5rem;   --space-3: .75rem;
  --space-4: 1rem;    --space-6: 1.5rem;  --space-8: 2rem;
  --space-12: 3rem;   --space-16: 4rem;   --space-24: 6rem;

  /* ---- Formes ----------------------------------------------------------- */
  --radius-sm: 8px;  --radius-md: 12px;  --radius-lg: 18px;
  --radius-pill: 999px;

  /* ---- Ombres ----------------------------------------------------------- */
  --shadow-card: 0 1px 2px rgba(26, 46, 34, .05),
                 0 4px 14px rgba(26, 46, 34, .06);
  --shadow-card-hover: 0 2px 4px rgba(26, 46, 34, .07),
                       0 10px 30px rgba(26, 46, 34, .10);

  /* ---- Mouvement -------------------------------------------------------- */
  --dur-fast: 120ms;   /* survols */
  --dur-base: 250ms;   /* apparitions */
  --ease: ease-out;

  /* ---- Cibles tactiles --------------------------------------------------- */
  /* WCAG 2.5.8 AA = 24×24. 44×44 (AAA 2.5.5) est appliqué au menu mobile et au
     pied de page seulement, pas partout — voir docs/06 §4. */
  --target-min: 24px;
  --target-comfort: 44px;          /* cible au DOIGT (docs/06 §4) */
  --target-pointer: 32px;          /* cible au POINTEUR, pied de page seulement */

  /* ---- Schémas ------------------------------------------------------------ */
  /* Sarcelle de départ du dégradé de flux. Ce n'est pas une couleur inventée :
     c'est déjà celle du ruban d'ondes (docs/03 §7, rgb(62,124,134)). Elle n'est
     jamais employée pour du texte — uniquement comme départ de dégradé. */
  --schema-flow-from: #3E7C86;

  /* ---- Chrome de page ---------------------------------------------------- */
  --footer-bg: #14532D;
  --footer-ink: #F6F8F6;           /* 8,54:1 */
  --footer-ink-2: #C8D6CC;         /*  6,05:1 sur footer-bg clair · 11,82:1 sur sombre */
  --footer-rule: rgba(246, 248, 246, .2);   /* filet du pied de page = footer-ink à 20 % */
}

/* ---------------------------------------------------------------------------
   Mode sombre. prefers-color-scheme UNIQUEMENT — pas de bascule manuelle, donc
   pas de JS et pas de stockage (contrainte 1). Seules les valeurs qui changent
   sont redéclarées ; l'échelle typographique, les espacements et les rayons
   sont communs aux deux thèmes.
   ------------------------------------------------------------------------ */
@media (prefers-color-scheme: dark) {
  :root {
    --color-accent: #4ADE80;         /* 10,28:1 sur bg · 8,98:1 sur surface */
    --color-accent-strong: #86EFAC;  /* 12,76:1 sur bg */

    --color-bg: #101915;
    --color-surface: #1A2620;
    --color-tint: #203026;

    --color-border: #2A3A30;
    --color-border-strong: #5F7265;  /* décoratif. 3,04:1 sur surface : passe,
                                        mais sans marge — d'où le jeton dédié
                                        ci-dessous pour tout ce qui est interactif */
    --color-border-input: #7A8C80;   /* 4,39:1 sur surface · 5,03:1 sur bg */

    --color-ink: #E4EDE6;            /* 14,98:1 sur bg · 13,08:1 sur surface */
    --color-ink-2: #A3B5A9;          /*  8,31:1 sur bg ·  7,26:1 sur surface */

    --color-success: #4ADE80;        /* 10,28:1 */
    --color-warning: #FCD34D;        /* 12,43:1 */
    --color-error:   #FCA5A5;        /*  9,44:1 */
    --color-info:    #93C5FD;        /*  9,94:1 */
    --color-error-bg:   #2A1A1A;
    --color-success-bg: #16281C;

    --btn-primary-bg: #4ADE80;   --btn-primary-ink: #0B1B12;   /* 10,22:1 */
    --btn-secondary-bg: #E4EDE6; --btn-secondary-ink: #101915;

    --focus-ring: 0 0 0 3px rgba(74, 222, 128, .45);

    --shadow-card: 0 1px 2px rgba(0, 0, 0, .35),
                   0 4px 14px rgba(0, 0, 0, .40);
    --shadow-card-hover: 0 2px 4px rgba(0, 0, 0, .45),
                         0 10px 30px rgba(0, 0, 0, .50);

    --schema-flow-from: #60AAB4;     /* variante sombre du ruban (docs/03 §7) */

    --footer-bg: #0B1B12;
    --footer-ink: #F6F8F6;           /* 16,68:1 */
    --footer-rule: rgba(246, 248, 246, .16);
    /* --color-logo est inchangé : #F9A03F mesure 8,64:1 sur bg et 8,59:1 sur
       le footer sombre. Le logo garde sa couleur dans les deux thèmes. */
  }
}

/* ===========================================================================
   3. RESET MINIMAL + BASE
   ======================================================================== */
*, *::before, *::after { box-sizing: border-box; }

/* Pas de reset agressif : on remet à zéro ce qui gêne, rien de plus. */
body, h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
/* Les listes de navigation n'ont pas toutes une classe : viser aussi celles
   qui vivent dans un <nav>, sinon le pied de page affiche des puces. */
ul[class], ol[class], nav ul, nav ol { list-style: none; padding: 0; }

html {
  font-size: 100%;                 /* respecte la taille choisie par l'utilisateur */
  -webkit-text-size-adjust: 100%;
  /* Le header est collant : sans marge de défilement, une ancre place sa cible
     SOUS le header et le focus devient invisible (WCAG 2.4.11). */
  scroll-padding-top: 6rem;
  scroll-behavior: smooth;         /* neutralisé en §7 si reduced-motion */
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3 {
  font-family: var(--font-head);
  font-weight: 600;
  line-height: var(--leading-head);
  letter-spacing: var(--tracking-head);
  text-wrap: balance;              /* ignoré si non supporté, aucun repli requis */
}
h4 { font-family: var(--font-body); font-weight: 600; font-size: var(--text-md); line-height: var(--leading-head); }

h1 { font-size: clamp(var(--text-2xl), 5vw, var(--text-3xl)); }
h2 { font-size: clamp(var(--text-xl), 4vw, var(--text-2xl)); }
h3 { font-size: var(--text-lg); }

/* Mesure de lecture : appliquée au texte courant, jamais aux conteneurs de
   mise en page (sinon la grille se retrouve bridée à 68ch). */
p, li, dd, blockquote { max-width: var(--measure); }

a {
  color: var(--color-accent);
  text-decoration-thickness: 1px;
  text-underline-offset: .18em;
}
a:hover { color: var(--color-accent-strong); }

/* --- Focus -----------------------------------------------------------------
   L'outline transparent n'est pas décoratif : en mode contraste élevé de
   Windows, les box-shadow sont supprimées et l'anneau disparaîtrait. Le
   navigateur remplace alors l'outline transparent par une couleur système. */
:focus-visible {
  outline: 2px solid transparent;
  outline-offset: 2px;
  box-shadow: var(--focus-ring);
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

img, svg, video { max-width: 100%; height: auto; }
/* PAS de `svg { fill: currentColor }` global : une règle CSS l'emporte sur
   l'attribut de présentation fill="none", ce qui remplissait toutes les icônes
   Tabler au trait. La couleur vient des classes (schémas) ou de l'attribut
   stroke="currentColor" (icônes). */

::selection { background: var(--color-tint); color: var(--color-ink); }

hr { border: 0; border-top: 1px solid var(--color-border); margin: var(--space-12) 0; }

code, kbd, samp { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; }

/* --- Skip-link -------------------------------------------------------------
   Premier élément du DOM. Hors écran jusqu'au focus — jamais display:none,
   qui le retirerait de l'ordre de tabulation. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: var(--space-3) var(--space-4);
  background: var(--color-accent);
  color: var(--btn-primary-ink);   /* encre sur fond accent — 7,13:1 */
  font-weight: 600;
  text-decoration: none;
  border-radius: 0 0 var(--radius-sm) 0;
}
.skip-link:focus { left: 0; }

/* ===========================================================================
   4. LAYOUT
   ======================================================================== */
.wrap { max-width: 1080px; margin-inline: auto; padding-inline: var(--space-4); }

/* ── L'ALIGNEMENT DE LA BOÎTE SUIT CELUI DU TEXTE ───────────────────────────
   La version précédente centrait par défaut toute boîte bornée par un
   max-width, en posant que « centrer la BOÎTE n'est pas centrer le TEXTE ».
   Cette distinction FABRIQUAIT le défaut au lieu de le corriger.

   Pourquoi : un paragraphe est borné (max-width: 65ch) et se déplace donc vers
   le centre ; un titre ne l'est pas (max-width: none) et ne peut pas suivre. Le
   titre reste à 116 px, le paragraphe part à 300 px — 184 px d'écart visible
   entre un titre et le paragraphe juste dessous, à l'intérieur du même bloc.
   Mesuré sur 22 conteneurs et 11 pages avant correction.

   Et le contrôle géométrique ne pouvait pas le voir : chaque bloc est
   individuellement centré (16/16 et 200/200). Il demandait « ce bloc est-il
   centré ? », jamais « ces deux blocs partagent-ils un bord ? ».

   Règle : une boîte ne se centre QUE si son texte se centre. Hors `.text-center`
   tout partage le bord gauche de la colonne, titres et corps ensemble ; dans
   `.text-center` tout est centré, encre comprise. Le panachage devient
   impossible à écrire, au lieu d'être une erreur à ne pas commettre.
   Spécificité (0,1,1) : bat le `margin: 0` du reset (0,0,1). */
.wrap h1, .wrap h2, .wrap h3, .wrap h4,
.wrap p, .wrap ul, .wrap ol, .wrap dl,
.wrap figure, .wrap address, .wrap blockquote { margin-inline: 0; }

/* ── `.align-start` — CONDITION D'EMPLOI, UNIQUE ET VÉRIFIABLE ──────────────
   `.align-start` ne s'emploie QUE sur une section DEUX COLONNES dont une
   colonne porte un schéma. Aujourd'hui : la section « Capacités » des quatre
   pages produits, et rien d'autre. Un schéma pleine largeur ne suffit pas :
   l'axe gauche commun n'a de sens que face à une colonne voisine. Le titre, le sous-titre, la phrase d'accroche et le `<svg>` y
   descendent sur un seul bord ; centrer le texte le décrocherait du dessin.

   Partout ailleurs, la classe n'a pas de justification et ne doit pas figurer.
   Elle avait essaimé par recopie de gabarit sur 7 pages sans schéma (hub
   produits, secteurs, sur-mesure, contact, merci et les deux pages légales) :
   une classe dont la condition d'emploi n'est pas écrite se propage au
   copier-coller. La condition est donc écrite ici, dans docs/03 §4, et
   surtout CONTRÔLÉE — `check-layout` refuse tout `.align-start` dans un
   conteneur qui ne contient pas de `svg.schema`.

   Sa valeur coïncide avec le défaut, et c'est délibéré, pas redondant : le
   défaut exprime « rien ne demande le centrage ici », `.align-start` exprime
   « le centrage est INTERDIT ici, un dessin en dépend ». Si le défaut changeait,
   ces sections devraient rester à gauche.

   `.split` : mêmes raisons, deux colonnes au lieu d'un schéma. */
.align-start h1, .align-start h2, .align-start h3, .align-start h4,
.align-start p, .align-start ul, .align-start ol, .align-start dl,
.align-start figure, .align-start address, .align-start blockquote,
.split h1, .split h2, .split h3, .split p, .split ul, .split ol, .split figure {
  margin-inline: 0;
}

/* docs/03 §4 « Sections : 96 px desktop / 64 px mobile » = l'espace ENTRE deux
   sections. Un padding de 96 de chaque côté en produisait 192 entre deux blocs
   de contenu — d'où le vide sous le héros. Moitié de chaque côté. */
.section { padding-block: var(--space-8); }
@media (min-width: 760px) { .section { padding-block: var(--space-12); } }

.section--tint { background: var(--color-tint); }
.section--surface { background: var(--color-surface); }

/* Grille fluide : auto-fit plutôt qu'un nombre de colonnes figé, pour que le
   reflow à 320 px (WCAG 1.4.10) se fasse sans media query dédiée. */
.grid { display: grid; gap: var(--space-6); }
/* 2x2 sur desktop, une colonne en mobile. auto-fit donnait 3+1 à 1080 px,
   déséquilibré pour quatre produits de même rang. */
.grid--cards { grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid--cards { grid-template-columns: 1fr 1fr; } }
/* Cinq cartes de même rang : 3 puis 2. La grille les place ainsi d'elle-même ;
   un auto-fit aurait donné 3+2 à 1080 px mais 2+2+1 à 900, en orphelinant la
   dernière — exactement ce qu'on cherche à éviter. */
/* Grille de cartes sur trois colonnes. Nommée `--3-2` jusqu'au 23/08, d'après
   la rangée orpheline des cinq cartes secteur ; elles sont six depuis, donc
   3 + 3, et le nom ne décrivait plus que l'ancien contenu. Un nom qui survit à
   ce qu'il décrit finit par vouloir dire autre chose — c'est ce qui était
   arrivé à `.align-start` (docs/03 §6). */
.grid--cards-3 { grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid--cards-3 { grid-template-columns: repeat(3, 1fr); } }

/* Carte secteur : le lien produit s'aligne sur le bord bas quelle que soit la
   longueur du texte. `margin-block-start: auto` dans un conteneur flex en
   colonne — les cartes s'étirent déjà à la hauteur de leur rangée (comportement
   par défaut de la grille), il ne reste qu'à pousser le lien vers le bas. */
.card--sector { display: flex; flex-direction: column; }
.card--sector > * + * { margin-block-start: var(--space-3); }
.card--sector .card__link { margin-block-start: auto; padding-block-start: var(--space-4); }

/* Liens croisés : trois cartes de même rang, donc trois colonnes. */
.grid--also { grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid--also { grid-template-columns: repeat(3, 1fr); } }
.grid--2 { grid-template-columns: 1fr; }
@media (min-width: 760px) { .grid--2 { grid-template-columns: 1fr 1fr; } }

/* Texte + illustration en alternance (accueil, pages produits). */
.split { display: grid; gap: var(--space-8); align-items: center; }
@media (min-width: 760px) {
  .split { grid-template-columns: 7fr 5fr; }
  .split--reverse > :first-child { order: 2; }
}
/* Les deux colonnes démarrent au même bord haut ; le schéma peut courir plus
   bas que la liste sans la recentrer. `center` conviendrait à deux colonnes de
   hauteurs voisines, pas à une liste courte face à un dessin haut. */
.split--top { align-items: start; }
/* Colonnes égales pour la section schéma des pages produits : le 7fr/5fr par
   défaut ne laissait au dessin que ~400 px, et un schéma dessiné pour 496
   s'y affichait à 0,8 — ses libellés de 9 px passaient sous 8. */
@media (min-width: 760px) { .split--even { grid-template-columns: 1fr 1fr; } }

/* --- Liste de capacités ------------------------------------------------------
   Se parcourt du regard, ne se lit pas : une ligne par capacité, interligne
   confortable, coche verte en tête. Pas de puce native, dont la position et la
   couleur ne sont pas maîtrisables de façon fiable.
   La coche est un SVG INLINE et non une image de fond en data: URI : la CSP est
   `img-src 'self'`, sans `data:` — une icône en background-image serait bloquée
   à l'affichage (docs/08). Un SVG inline n'est pas une requête. */
.caps { list-style: none; padding-inline-start: 0; display: grid; gap: var(--space-3); }
.caps li {
  display: grid; grid-template-columns: auto 1fr;
  gap: var(--space-3); align-items: start; line-height: 1.5;
}
.caps__ic {
  inline-size: 1.15rem; block-size: 1.15rem; margin-block-start: .18em;
  fill: none; stroke: var(--color-accent); stroke-width: 2.5;
  stroke-linecap: round; stroke-linejoin: round;
}

.stack > * + * { margin-block-start: var(--space-4); }
.stack--lg > * + * { margin-block-start: var(--space-8); }

/* --- Héros -------------------------------------------------------------------
   Contraintes typographiques reprises telles quelles de la préversion validée
   (docs/preview-design-valide.html). Elles ne sont pas cosmétiques : elles
   donnent au héros sa hauteur, donc au ruban son cadrage. Sans le h1 borné à
   18ch, le héros est trop court, le ruban est vu en entier et sa bande se
   pince à droite au lieu de couler. */
.hero { position: relative; overflow: hidden; }
.hero .eyebrow { color: var(--color-accent); }
/* La préversion posait 18ch, mais son h1 fait 41 caractères. Appliqué tel quel
   à un titre de 78 caractères, cela donne sept lignes et un héros en colonne
   étroite avec le ruban qui occupe le vide à droite. 22ch tient les titres
   courts et n'écrase plus les longs ; scripts/check-layout.py signale tout h1
   qui dépasserait trois lignes. */
.hero h1 { max-width: 22ch; }
.hero .lede { font-size: var(--text-md); color: var(--color-ink-2); max-width: 50ch; }
.hero .actions { margin-block-start: var(--space-6); }
/* Sous-titre de héros : porte la définition courte quand le h1 est réduit au
   nom du produit. Ce n'est PAS un titre — pas de h2, qui casserait la
   hiérarchie et ferait deux entrées de même rang dans le plan du document. */
.hero__subtitle {
  font-family: var(--font-head);
  font-size: var(--text-lg);
  font-weight: 600;
  line-height: var(--leading-head);
  color: var(--color-accent);
  max-width: 30ch;
}
/* Titre de BLOC dans un héros (Voxybox) : un h2 qui nomme un contenu porté par
   le héros lui-même, avec son paragraphe et son lien. À la taille normale d'un
   h2 il arrive presque à hauteur du h1 et le héros se lit comme deux titres
   empilés, ce que la restructuration cherchait justement à éviter. On lui donne
   l'échelle du sous-titre : le héros garde sa typographie, seul son contenu
   change. Ne pas confondre avec .hero__subtitle ci-dessus, qui n'est PAS un
   titre et ne doit jamais devenir un h2. */
.hero h2 { font-size: var(--text-lg); }

.hero .wrap { position: relative; }

/* --- Ruban d'ondes ---------------------------------------------------------
   En background-image, PAS en SVG inline ni en <img> : le fichier est mis en
   cache une seule fois pour tout le site au lieu d'être réinjecté dans chaque
   page (≈ 9 Ko × 2 thèmes × 30 pages), la bascule de thème reste du CSS pur, et
   il n'y a aucun élément supplémentaire dans le DOM à masquer.
   Décoratif : aucun rôle ARIA, invisible pour les lecteurs d'écran par nature.
   Fichiers produits en Phase 2. */
.ribbon {
  position: relative;
  background-image: url('/assets/svg/ribbon-hero-light.svg');
  background-repeat: no-repeat;
  background-position: 30% bottom;
  /* « cover » montrait la bande entière, pincement de droite compris. On
     agrandit pour n'en garder que la partie qui coule. */
  background-size: 165% auto;
}
/* « Discret » = plus pâle, PAS plus petit. Réduire la taille dézoome et fait
   entrer dans le cadre le nœud de croisement de la bande, donc rend le ruban
   PLUS visible — l'inverse de l'intention. On garde le même cadrage partout et
   on lave la couleur d'un voile au fond de page. */
.ribbon--soft::before {
  content: '';
  position: absolute;
  inset: 0;
  background: var(--color-bg);
  opacity: .5;
  pointer-events: none;
}
.ribbon--soft { position: relative; }
.ribbon--soft > * { position: relative; }
@media (prefers-color-scheme: dark) {
  .ribbon { background-image: url('/assets/svg/ribbon-hero-dark.svg'); }
}

/* ===========================================================================
   5. COMPOSANTS
   Un bloc par entrée de l'inventaire docs/03 §5.
   ======================================================================== */

/* --- Boutons ---------------------------------------------------------------
   Cible minimale 24×24 (WCAG 2.5.8 AA) garantie par le padding + min-height.
   Pas de « loading spinner » : sur un envoi de formulaire natif sans JS, rien
   ne peut déclencher un état de chargement (retiré de l'inventaire, CHANGELOG #30c). */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: var(--target-comfort);
  padding: var(--space-3) var(--space-6);
  border: 2px solid transparent;
  border-radius: var(--radius-pill);
  font-family: var(--font-body);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.2;
  text-decoration: none;
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}
.btn:hover { transform: translateY(-1px); box-shadow: var(--shadow-card); }
.btn:active { transform: translateY(0); }
.btn[disabled], .btn[aria-disabled='true'] {
  opacity: .55; cursor: not-allowed; transform: none; box-shadow: none;
}

.btn-primary { background: var(--btn-primary-bg); color: var(--btn-primary-ink); }
.btn-primary:hover { background: var(--color-accent-strong); color: var(--btn-primary-ink); }

.btn-secondary { background: var(--btn-secondary-bg); color: var(--btn-secondary-ink); }
.btn-secondary:hover { background: var(--color-ink); color: var(--btn-secondary-ink); }

/* Bordure d'un composant interactif -> jeton à 3:1, pas la bordure décorative. */
.btn-ghost {
  background: transparent;
  color: var(--color-accent);
  border-color: var(--color-border-input);
}
.btn-ghost:hover { background: var(--color-tint); border-color: var(--color-accent); }

/* --- Champs de formulaire --------------------------------------------------
   Le label est TOUJOURS visible : jamais de placeholder en guise de label
   (docs/06 §4). La bordure porte le contraste du composant — un champ blanc sur
   fond ivoire ne diffère que de 1,05:1. */
.field { margin-block-end: var(--space-6); }
.field label {
  display: block;
  margin-block-end: var(--space-2);
  font-weight: 600;
  font-size: var(--text-base);
}

.field input[type='text'],
.field input[type='email'],
.field input[type='tel'],
.field textarea,
.field select {
  width: 100%;
  min-height: var(--target-comfort);
  padding: var(--space-3) var(--space-4);
  background: var(--color-surface);
  color: var(--color-ink);
  border: 1px solid var(--color-border-input);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--text-base);
  line-height: var(--leading-body);
}
.field textarea { min-height: 9rem; resize: vertical; }

.field input:hover, .field textarea:hover, .field select:hover {
  border-color: var(--color-accent);
}
.field input[disabled], .field textarea[disabled], .field select[disabled] {
  background: var(--color-tint); opacity: .7; cursor: not-allowed;
}

/* Erreur : jamais portée par la seule couleur — bordure + icône + texte lié
   par aria-describedby (WCAG 1.4.1 et 3.3.3). */
.field.is-error input,
.field.is-error textarea,
.field.is-error select { border-color: var(--color-error); border-width: 2px; }

.msg-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
  color: var(--color-error);
  font-size: var(--text-base);
  font-weight: 600;
}
.msg-error svg { flex: 0 0 auto; width: 20px; height: 20px; }

/* Case à cocher RGPD : la cible reste 24×24 minimum, le label est cliquable. */
.field--check { display: flex; align-items: flex-start; gap: var(--space-3); }
.field--check input[type='checkbox'] {
  flex: 0 0 auto;
  width: var(--target-min);
  height: var(--target-min);
  margin-block-start: .15rem;
  accent-color: var(--color-accent);
}
.field--check label { margin: 0; font-weight: 400; }

/* --- Cartes ---------------------------------------------------------------- */
.card {
  position: relative;              /* ancre le lien étendu ci-dessous */
  padding: var(--space-6);
  background: var(--color-surface);
  border: 1px solid var(--color-border);   /* décoratif : pas de seuil 3:1 */
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.card:hover { transform: translateY(-2px); box-shadow: var(--shadow-card-hover); }
.card__icon { width: 40px; height: 40px; color: var(--color-accent); }
.card__title { margin-block: var(--space-4) var(--space-2); font-size: var(--text-md); }
/* Une carte sans icône commence par son titre : la marge haute de .card__title
   n'a plus rien à espacer et creuse le haut de la carte. */
.card > .card__title:first-child { margin-block-start: 0; }
.card__title a { text-decoration: none; }
/* Toute la carte devient cliquable sans imbriquer un lien dans un lien : un
   seul lien réel, dont la zone de clic est étendue. L'ordre de tabulation et
   l'annonce au lecteur d'écran restent ceux d'un lien unique. */
.card__title a::after { content: ''; position: absolute; inset: 0; }

.stat { text-align: left; }
.stat__value {
  display: block;
  font-family: var(--font-head);
  font-weight: 600;
  font-size: var(--text-2xl);
  line-height: 1.1;
  color: var(--color-accent);
}
.stat__label { color: var(--color-ink-2); }

/* --- Badges ---------------------------------------------------------------- */
.badge {
  display: inline-block;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
/* La bordure n'est pas décorative : sans elle, .badge-tint posé sur une
   .section--tint a exactement le fond de la section et disparaît. */
.badge-tint {
  background: var(--color-tint); color: var(--color-accent-strong);
  border: 1px solid var(--color-border-input);
}
.badge-accent { background: var(--color-accent); color: var(--btn-primary-ink); }

/* Un chiffre n'est pas une étiquette : la capitalisation et l'interlettrage de
   .badge rendent illisible une pastille de six mots.
   Placée APRÈS .badge, et c'est la seule chose qui la fait gagner : même
   spécificité (0,1,0), donc l'ordre du fichier tranche. Écrite plus haut, elle
   était appliquée et sans effet — le pire des cas, puisque rien ne le signale.
   Cinquième occurrence du piège, cf. docs/03 §6. */
/* La pastille chiffrée écrasait le titre de sa carte : mesurée le 23/08 à
   16 px/600 sous un titre de 20 px/600, sur presque toute la largeur.
   CAUSE : `var(--text-sm)`, un jeton qui N'EXISTE PAS. C'était le seul
   `var()` orphelin de la feuille. Un jeton absent ne se comporte pas comme une
   déclaration ignorée : la déclaration devient invalide au calcul, la propriété
   passe donc en `inherit` et la pastille prenait le 1 rem du paragraphe — plus
   grande que le `--text-xs` de `.badge` qu'elle croyait affiner. Un défaut qui
   AGRANDIT est plus difficile à voir qu'un défaut qui casse : le rendu reste
   plausible.
   Elle revient donc au `--text-xs` de `.badge` (12,8 px), avec un rembourrage
   horizontal resserré. Le poids 600 est CONSERVÉ : c'est un chiffre, sa raison
   d'être est d'être lu d'un coup d'œil, et l'écart de taille avec le titre
   (12,8 contre 20) suffit à le ranger dessous. En 400, essayé et mesuré, la
   pastille se lit comme une légende encadrée et le chiffre perd sa fonction. */
.badge--figure {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
}

/* --- Alertes --------------------------------------------------------------- */
.alert {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-6);
  border-radius: var(--radius-md);
  border-inline-start: 4px solid;
}
.alert svg { flex: 0 0 auto; width: 24px; height: 24px; }
.alert-success { background: var(--color-success-bg); border-color: var(--color-success); color: var(--color-ink); }
.alert-error { background: var(--color-error-bg); border-color: var(--color-error); color: var(--color-ink); }

/* --- Eyebrow / surtitre ---------------------------------------------------- */
.eyebrow {
  display: block;
  margin-block-end: var(--space-3);
  color: var(--color-ink-2);
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.lede { font-size: var(--text-md); color: var(--color-ink-2); }

/* --- En-tête et navigation -------------------------------------------------
   Le déroulant « Produits » est un <details> À TOUS LES BREAKPOINTS, pas un
   survol : entre 760 et 1080 px (iPad portrait = 768) la nav de bureau
   s'affiche, mais il n'y a ni survol ni focus avant un appui — et « Produits »
   n'est pas un lien, donc rien n'est appuyable. Un :hover/:focus-within
   rendrait les quatre pages produits inatteignables sur tablette.
   <details> fonctionne au clavier, au toucher et au lecteur d'écran, sans JS. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--color-surface);
  border-block-end: 1px solid var(--color-border);
}
.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  min-height: 5rem;
}
.site-header__logo { display: inline-flex; color: var(--color-logo); }
.site-header__logo svg { height: 2.25rem; width: auto; }

.nav { display: none; }
/* La nav de bureau mesure 643 px et démarre après le logo (195 px) : il lui
   faut ~894 px. Elle apparaissait à 760 px et débordait de 111 px sur toute
   page — le défilement horizontal que WCAG 1.4.10 interdit. Bascule au
   breakpoint 1080 déjà défini (docs/03 §4) plutôt que d'inventer une valeur.
   Effet de bord souhaitable : les tablettes reçoivent le menu tactile. */
@media (min-width: 1080px) { .nav { display: flex; align-items: center; gap: var(--space-6); margin-inline-start: auto; } }

/* `>` et non un descendant quelconque : la règle ne doit toucher QUE les liens
   de premier niveau de la nav.
   Historique : `:not(.btn)` a été ajouté pour empêcher .nav a de repeindre le
   libellé des boutons. Mais `:not(X)` ajoute la spécificité de X — la règle est
   passée de (0,1,1) à (0,2,1) et s'est mise à battre `.nav-drop__panel a`
   (0,1,1). Les liens du panneau sont alors devenus `inline-flex` : ils se sont
   écoulés en ligne et sont passés à la ligne deux par deux.
   Le combinateur enfant règle la cause plutôt que la conséquence : le panneau
   n'est plus dans le périmètre du tout. */
.nav > a:not(.btn) {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
  color: var(--color-ink);
  text-decoration: none;
  border-block-end: 2px solid transparent;
}
.nav > a:not(.btn):hover { border-block-end-color: var(--color-border-strong); }
.nav > a:not(.btn)[aria-current='page'] { color: var(--color-accent); border-block-end-color: var(--color-accent); }

.nav-drop { position: relative; }
.nav-drop > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--target-min);
  cursor: pointer;
  list-style: none;                /* masque le triangle par défaut */
}
.nav-drop > summary::-webkit-details-marker { display: none; }
.nav-drop > summary::after {
  content: '';
  width: .5rem; height: .5rem;
  border-inline-end: 2px solid currentColor;
  border-block-end: 2px solid currentColor;
  transform: rotate(45deg) translate(-2px, -2px);
  transition: transform var(--dur-fast) var(--ease);
}
.nav-drop[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }

.nav-drop__panel {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 100%;
  min-width: 14rem;
  padding: var(--space-3);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card-hover);
}
/* Liste verticale de cinq entrées, largeur juste. */
.nav-drop__panel { min-width: 11rem; }
/* Séparateur du déroulant : distingue le lien de tête et le sur-mesure des
   quatre produits, sans en faire des entrées de même rang. */
.nav-drop__sep {
  margin-block: var(--space-2);
  border: 0;
  border-block-start: 1px solid var(--color-border);
}
.nav-drop__panel a {
  display: block;
  min-height: var(--target-min);
  padding: var(--space-2) var(--space-3);
  border: 0;
  color: var(--color-ink);
  text-decoration: none;
  white-space: nowrap;
}
.nav-drop__panel a:hover { background: var(--color-tint); border-radius: var(--radius-sm); }

/* Sélecteur de langue : même <details> que le déroulant Produits (D9), donc
   même comportement au clavier et au toucher, sans JS et sans cookie. */
.lang-drop > summary { font-weight: 600; }
.lang-drop .nav-drop__panel { inset-inline-start: auto; inset-inline-end: 0; min-width: 9rem; }
.lang-drop a[aria-current='true'] { color: var(--color-accent); font-weight: 600; }
.lang-drop a[aria-current='true']::after { content: ' ·'; }

/* --- Menu mobile ----------------------------------------------------------- */
.nav-mobile { margin-inline-start: auto; }
@media (min-width: 1080px) { .nav-mobile { display: none; } }
.nav-mobile > summary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-width: var(--target-comfort);
  min-height: var(--target-comfort);
  cursor: pointer;
  list-style: none;
  font-weight: 600;
}
.nav-mobile > summary::-webkit-details-marker { display: none; }
.nav-mobile__panel {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  padding: var(--space-4);
  background: var(--color-surface);
  border-block-end: 1px solid var(--color-border);
  box-shadow: var(--shadow-card);
}
/* 44 px dans le menu mobile et le pied de page (docs/06 §4), 24 px ailleurs. */
.nav-mobile__panel a:not(.btn) {
  display: flex;
  align-items: center;
  min-height: var(--target-comfort);
  color: var(--color-ink);
  text-decoration: none;
}
.nav-mobile__panel a[aria-current='page'] { color: var(--color-accent); font-weight: 600; }
.nav-mobile__sub { padding-inline-start: var(--space-6); }

/* --- Fil d'Ariane (pages produits uniquement) ------------------------------ */
.breadcrumb { font-size: var(--text-xs); color: var(--color-ink-2); }
.breadcrumb ol { display: flex; flex-wrap: wrap; gap: var(--space-2); list-style: none; padding: 0; }
.breadcrumb li + li::before { content: '›'; margin-inline-end: var(--space-2); color: var(--color-border-strong); }

/* --- Sélecteur de langue ---------------------------------------------------- */
.lang-switch {
  display: inline-flex;
  align-items: center;
  min-width: var(--target-min);
  min-height: var(--target-min);
  font-weight: 600;
  text-decoration: none;
}

/* --- Bandeau d'information zéro-traceur ------------------------------------
   Accueil uniquement : sur les 30 pages il coûterait du LCP et du CLS, il
   reviendrait au clic suivant faute de stockage, et la notice permanente du
   pied de page est déjà partout — c'est elle qui est indexée et lue par les
   assistants IA (docs/12 §3). */
.notice-bar {
  background: var(--color-tint);
  color: var(--color-ink);
  padding-block: var(--space-3);
}
.notice-bar__inner { display: flex; align-items: flex-start; gap: var(--space-4); }
.notice-bar p { max-width: none; margin: 0; }
/* La croix n'existe que si le script tourne : sans JS elle serait un bouton
   mort. C'est le script qui l'insère, pas le HTML. */
.notice-bar__close {
  flex: 0 0 auto;
  min-width: var(--target-min);
  min-height: var(--target-min);
  margin-inline-start: auto;
  background: none;
  border: 0;
  color: inherit;
  font-size: var(--text-md);
  line-height: 1;
  cursor: pointer;
}

/* --- Bannière de suggestion de langue --------------------------------------
   Injectée par le seul script du site. Placée APRÈS le skip-link, jamais avant :
   le skip-link doit rester le premier élément atteignable. */
.lang-suggest {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--color-tint);
  color: var(--color-ink);
  border-block-end: 1px solid var(--color-border);
}
.lang-suggest button {
  min-width: var(--target-min);
  min-height: var(--target-min);
  margin-inline-start: auto;
  background: none;
  border: 0;
  color: inherit;
  font-size: var(--text-md);
  cursor: pointer;
}

/* --- Tableau des offres ----------------------------------------------------
   Sous 760 px : défilement horizontal, avec une ombre portée par un dégradé de
   fond attaché au conteneur — elle disparaît d'elle-même en fin de course. */
/* tabindex="0" sur le conteneur : sans lui, un utilisateur au clavier ne peut
   pas faire défiler le tableau horizontalement (WCAG 2.1.1). D'où aussi le
   focus visible ci-dessous. */
.table-scroll {
  overflow-x: auto;
  background:
    linear-gradient(to right, var(--color-surface) 30%, transparent),
    linear-gradient(to left, var(--color-surface) 30%, transparent) 100% 0,
    linear-gradient(to right, rgba(26, 46, 34, .12), transparent 12px),
    linear-gradient(to left, rgba(26, 46, 34, .12), transparent 12px) 100% 0;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}
.table-offres { width: 100%; border-collapse: collapse; min-width: 44rem; }
.table-offres th, .table-offres td {
  padding: var(--space-3) var(--space-4);
  border-block-end: 1px solid var(--color-border);
  text-align: start;
  vertical-align: top;
}
.table-offres thead th { font-family: var(--font-head); color: var(--color-accent-strong); }
/* En-têtes de ligne collants : le nom du critère reste lisible pendant le
   défilement horizontal. */
.table-offres tbody th {
  position: sticky;
  inset-inline-start: 0;
  background: var(--color-surface);
  font-weight: 600;
}

/* --- Pied de page ----------------------------------------------------------- */
.site-footer {
  background: var(--footer-bg);
  color: var(--footer-ink);
  padding-block: var(--space-16);
}
/* ⚠ CORRECTIF WCAG 1.4.3 du 23/08 — les titres de colonne étaient ILLISIBLES.
   `.eyebrow` pose `color: var(--color-ink-2)`, une encre de CORPS pensée pour
   un fond clair. Posée telle quelle sur le vert du pied de page, elle mesurait
   **1,15:1** — pas « discrète », indéchiffrable, sur les 32 pages.
   Pourquoi cela a survécu : `--color-ink-2` est redéfini clair en mode sombre,
   où le même titre mesure 8,26:1. Un pied de page relu en thème sombre paraît
   parfaitement normal. Le défaut n'existait que dans un thème sur deux, et
   c'est le thème par défaut.
   Jeton dédié plutôt que `--footer-ink` : les titres doivent rester en retrait
   des liens. Valeur unique pour les deux thèmes, comme --color-border-input. */
.site-footer .eyebrow { color: var(--footer-ink-2); }
.site-footer a:not(.btn) { color: var(--footer-ink); text-decoration: none; }
.site-footer a:not(.btn):hover { text-decoration: underline; }
.site-footer__cols { display: grid; gap: var(--space-8); grid-template-columns: 1fr; }
@media (min-width: 760px) { .site-footer__cols { grid-template-columns: repeat(4, 1fr); } }
.site-footer li a {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-comfort);   /* 44 px au doigt : docs/06 §4 */
}
/* docs/06 §4 AMENDÉ le 23/08 : 44 px est une mesure d'ergonomie du POUCE. Sur
   un poste à la souris elle n'a plus d'objet, et six liens de 44 px faisaient
   à eux seuls 264 px de pied de page. 32 px reste très au-dessus du minimum
   AA de WCAG 2.5.8 (24×24).
   La condition `pointer: fine` n'est PAS décorative, et elle ne figurait pas
   dans l'arbitrage : la largeur seule aurait donné 32 px à l'iPad portrait,
   qui fait exactement 768 px et qui se touche avec un doigt. Le raisonnement
   qui autorise 32 px porte sur le PÉRIPHÉRIQUE, pas sur la largeur ; on
   interroge donc le périphérique. Une tablette tactile garde 44 px à toute
   largeur. Sans support de `pointer` (navigateur ancien), la requête ne
   s'applique pas et l'on retombe sur 44 px : le repli va vers l'accessible. */
@media (min-width: 768px) and (pointer: fine) {
  .site-footer li a { min-height: var(--target-pointer); }
}
.site-footer__legal {
  margin-block-start: var(--space-12);
  padding-block-start: var(--space-6);
  border-block-start: 1px solid var(--footer-rule);
  font-size: var(--text-xs);
}
.site-footer__logo { color: var(--footer-ink); }
/* Horizontal ici aussi : en empilé à 3 rem, le mot n'est plus lisible. */
.site-footer__logo .logo-full-h { width: 10rem; height: 2.05rem; }

/* --- FAQ (<details> stylés) -------------------------------------------------- */
.faq-item { border-block-end: 1px solid var(--color-border); }
.faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--target-comfort);
  padding-block: var(--space-4);
  cursor: pointer;
  font-family: var(--font-head);
  font-weight: 600;
  list-style: none;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after {
  content: '';
  flex: 0 0 auto;
  width: .55rem; height: .55rem;
  border-inline-end: 2px solid var(--color-accent);
  border-block-end: 2px solid var(--color-accent);
  transform: rotate(45deg);
  transition: transform var(--dur-fast) var(--ease);
}
.faq-item[open] > summary::after { transform: rotate(-135deg); }
.faq-item__body { padding-block-end: var(--space-4); }

/* --- Schémas SVG inline -----------------------------------------------------
   Les schémas héritent des jetons : une seule géométrie par langue sert les deux
   thèmes, au lieu d'un second jeu de fichiers en couleurs sombres.
   Les traits porteurs de sens (flux, connecteurs) utilisent la bordure
   interactive à 3:1, pas la bordure décorative. */
/* Les fiches donnent des viewBox « mobile » (380 unités de large). Sans
   max-width, .wrap les étire à 1080 px, soit x2,84 : SCH-08 sortait à 1472 px
   de haut. On les borne à leur échelle de conception. */
.schema {
  display: block;
  width: 100%;
  max-width: 32rem;
  height: auto;
  margin-inline: auto;
  margin-block: var(--space-8);
}
/* Dans une colonne de .split, le schéma occupe la colonne entière. */
.split .schema { max-width: 100%; }
.schema__ink { fill: var(--color-ink); }
.schema__muted { fill: var(--color-ink-2); }
.schema__stroke { stroke: var(--color-accent); fill: none; }
/* Glyphe plein dans un schéma (le « Aa » de SCH-01) : il lui faut un fill,
   donc surtout PAS .schema__stroke qui impose fill:none. */
.schema__glyph { fill: var(--color-accent); stroke: none; }
.schema__arrowhead { fill: var(--color-border-input); stroke: none; }
/* Boîtes de schéma : le contour porte le sens, il prend donc le jeton
   interactif à 3:1 et non la bordure décorative (docs/03 §1bis). */
.schema__box { fill: var(--color-surface); stroke: var(--color-accent); }
.schema__box-neutral { fill: var(--color-surface); stroke: var(--color-border-input); }
.schema__box-accent { fill: var(--color-tint); stroke: var(--color-accent); }
.schema__cartouche { fill: var(--color-bg); stroke: none; }
.schema__solid { fill: var(--color-accent-strong); stroke: none; }
.schema__on-solid { fill: var(--btn-primary-ink); }
.schema__accent-stroke { stroke: var(--color-logo); fill: none; }
.schema__dot { fill: var(--color-accent); }
.schema__pill-solid { fill: var(--color-accent); }
/* Boucle de retour de SCH-07 : orange = retour, vert = progression. Le jeton
   --color-logo sert ici de trait décoratif, jamais de texte (docs/03 §1).
   L'étiquette prend --color-warning, seul jeton orangé lisible en texte
   (4,61:1) — la fiche indiquait #B45309, qui n'est pas un jeton. */
.schema__loop { stroke: var(--color-logo); fill: none; }
.schema__loop-ink { fill: var(--color-warning); }
.schema__rule-soft { stroke: var(--color-border-input); fill: none; }
.schema__flow { stroke: var(--color-border-input); fill: none; }
.schema__surface { fill: var(--color-surface); }
.schema__tint { fill: var(--color-tint); }
.schema__accent { fill: var(--color-logo); }
.schema-caption {
  max-width: 32rem;
  margin-inline: auto;
  color: var(--color-ink-2);
  font-size: var(--text-xs);
  text-align: center;
}
.split .schema-caption { max-width: 100%; }

/* --- Animation des schémas --------------------------------------------------
   REMPLACE INTÉGRALEMENT LE SMIL. <animate>, <animateMotion> et
   <animateTransform> sont interdits dans ce projet : aucune media query ne peut
   les arrêter, donc un schéma en SMIL ignorerait prefers-reduced-motion — et le
   corriger en JS est exclu. Trois équivalents CSS, tous neutralisés en §7 :
     · particules le long d'un tracé -> offset-path + offset-distance
     · pointillés qui défilent       -> stroke-dashoffset
     · anneau tournant / halo pulsé  -> transform, r, opacity
   Si un effet ne peut pas s'exprimer ainsi, le schéma est livré FIGÉ. Statique
   est une réponse acceptable ; JS ne l'est pas.

   offset-path prend un tracé propre à chaque schéma : les règles
   .sch-XX__particle--N sont ajoutées en Phase 2, à côté de chaque schéma. */
@keyframes vx-travel { from { offset-distance: 0%; } to { offset-distance: 100%; } }
@keyframes vx-dash   { to { stroke-dashoffset: -24; } }
@keyframes vx-spin   { to { transform: rotate(360deg); } }
@keyframes vx-pulse  { 50% { opacity: .32; transform: scale(1.06); } }

.vx-travel { animation: vx-travel 3.2s linear infinite; offset-rotate: 0deg; }
.vx-dash   { animation: vx-dash 1.8s linear infinite; }
.vx-spin   { animation: vx-spin 26s linear infinite; transform-box: fill-box; transform-origin: center; }
.vx-pulse  { animation: vx-pulse 5s var(--ease) infinite; transform-box: fill-box; transform-origin: center; }

/* --- Logo --------------------------------------------------------------------
   En masque CSS, pas en <svg> inline ni en <img>.
   · inline : 5 Ko dupliqués deux fois (header + pied) sur chacune des 32 pages,
     et jamais mis en cache ;
   · <img>  : mis en cache, mais fill="currentColor" ne traverse pas la frontière
     du document — le logo resterait orange sur le vert du pied de page ;
   · <use href="fichier.svg#id"> : référence externe, bloquée ou incohérente
     selon les navigateurs sous CSP.
   Le masque cumule les trois avantages : un seul fichier mis en cache, la
   couleur vient de currentColor, et il tombe sous img-src 'self'. */
.logo-full, .logo-full-h, .logo-mark {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
/* Deux lockups, deux contenants.
   EMPILÉ (1000 x 899,6 = 1,112:1) : pied de page, où la hauteur est disponible.
   HORIZONTAL (3490,3 x 717,1 = 4,867:1) : en-tête. À 2,5 rem de haut sa
   capitale fait 16,8 px, contre 8,7 px pour l'empilé à 3,75 rem.
   Les boîtes respectent le rapport exact : avec mask-size:contain, une boîte au
   mauvais rapport laisse du vide et rapetisse le logo d'autant. */
.logo-full-h {
  width: 12.17rem; height: 2.5rem;
  -webkit-mask-image: url('/assets/svg/logo-full-h.svg');
  mask-image: url('/assets/svg/logo-full-h.svg');
}
.logo-full {
  width: 3.34rem; height: 3rem;
  -webkit-mask-image: url('/assets/svg/logo-full.svg');
  mask-image: url('/assets/svg/logo-full.svg');
}
.logo-mark {
  width: 2.5rem; height: 1.8rem;
  -webkit-mask-image: url('/assets/svg/logo-mark.svg');
  mask-image: url('/assets/svg/logo-mark.svg');
}
/* inline-flex : sans cela le <a> est inline et sa boîte suit l'interligne
   (53x22) au lieu d'épouser le logo (53x48). La zone cliquable ne couvrait pas
   la marque. */
.site-header__logo, .site-footer__logo { display: inline-flex; }
.site-header__logo { color: var(--color-logo); }
.site-footer__logo { color: var(--footer-ink); }
/* Horizontal ici aussi : en empilé à 3 rem, le mot n'est plus lisible. */
.site-footer__logo .logo-full-h { width: 10rem; height: 2.05rem; }

/* --- Liste des secteurs (accueil) ------------------------------------------ */
.sector-list {
  display: flex; flex-wrap: wrap; gap: var(--space-3);
  list-style: none; padding: 0; max-width: none;
}
/* Les pilules ne sont plus des liens : les ancres de la page Secteurs ont été
   supprimées avec la mise en grille. Le lien « Voir les cas d'usage » qui suit
   porte la navigation ; cinq liens vers une même destination auraient été cinq
   fois la même annonce pour un lecteur d'écran. */
.sector-list a,
.sector-list span {
  display: inline-flex; align-items: center;
  min-height: var(--target-min);
  padding: var(--space-2) var(--space-4);
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-pill);
  text-decoration: none;
}
.sector-list a:hover { border-color: var(--color-accent); }

/* --- Bloc d'introduction des pages produits ----------------------------------
   Définition, encadré et schéma dans UNE seule section, sur le fond de page.
   Trois raisons de ne pas les séparer :
   · une frontière de section crée une coupure horizontale visible, et deux
     paddings de section mis bout à bout font un vide plus grand que tous les
     autres écarts de la page ;
   · le schéma doit se lire comme l'illustration du paragraphe qui précède, pas
     comme un bloc autonome ;
   · un seul .wrap garantit que titre, texte et schéma partagent le même axe.
   Pas de ruban ici : docs/05 §3 le réserve à l'accueil, la plateforme, Voxybox
   et à-propos — « aucune sur les autres ». */
.product-intro .lede { max-width: 60ch; }
/* L'intro produit aligne titre, texte et schéma sur un même bord gauche. */
.product-figure { margin-block-start: var(--space-6); }
/* En section deux colonnes, cette marge décalait le schéma de 24 px sous la
   liste : les deux colonnes doivent démarrer au même bord haut. La marge n'a de
   sens que lorsque la figure suit du texte dans le même flux. */
.split .product-figure { margin-block-start: 0; }
/* Aligné à gauche, comme le h1 et le paragraphe — pas centré, ce qui mettrait
   le texte et le schéma sur deux axes. Bridé à 560 px : au-delà, un schéma
   vertical dépasse ses proportions de conception. */
.product-figure .schema {
  max-width: 35rem;
  margin-inline: 0;
  margin-block: 0 var(--space-3);
}
.product-figure .schema-caption {
  max-width: 35rem;
  margin-inline: 0;
  text-align: start;
}

/* --- Pages légales -----------------------------------------------------------
   Une colonne de texte à la mesure de lecture, sommaire ancré (docs/05). */
.legal-body { max-width: var(--measure); }
.legal-body h2 { font-size: var(--text-md); margin-block-start: var(--space-8); }
.legal-body h2:first-child { margin-block-start: 0; }
.toc { border-inline-start: 3px solid var(--color-border-input); padding-inline-start: var(--space-4); }
.toc .bullets { list-style: none; padding-inline-start: 0; }
.toc .bullets li { margin-block-end: var(--space-1); }

/* --- Page de contact ---------------------------------------------------------
   Formulaire sur 8 colonnes, coordonnées sur 4 (docs/05). Aucune carte : ni
   iframe ni image statique — l'adresse suffit pour du B2B logiciel, et cela
   économise 50 à 150 Ko (docs/12 §1). */
.contact-grid { display: grid; gap: var(--space-12); }
@media (min-width: 760px) { .contact-grid { grid-template-columns: 2fr 1fr; } }
.contact-aside h2 { font-size: var(--text-md); }
.contact-grid form { max-width: 34rem; }

/* --- Encadré de positionnement ----------------------------------------------
   Lève la confusion Voxyspeech / Voxytel (docs/06 §2). Bordure latérale plutôt
   qu'un fond plein : l'encadré doit se distinguer du corps sans se lire comme
   une alerte. */
.callout {
  padding: var(--space-4) var(--space-6);
  background: var(--color-tint);
  border-inline-start: 4px solid var(--color-accent);
  border-radius: 0 var(--radius-md) var(--radius-md) 0;
}
.callout p { margin: 0; }

/* --- Listes à puces du corps ------------------------------------------------- */
/* ul[class] retire les puces pour les listes de navigation : il faut les
   rendre explicitement aux listes de contenu, sinon les cas d'usage se lisent
   comme des paragraphes collés. */
/* `ul.bullets` et non `.bullets` : le reset `ul[class]` vaut (0,1,1) et
   battrait une simple classe (0,1,0). Même piège que .nav a vs .btn-primary —
   troisième fois, d'où la note en docs/03 §6. */
/* Processus en 4 étapes : liste ORDONNÉE, c'est une vraie séquence (docs/05). */
ol.steps { list-style: decimal; padding-inline-start: var(--space-6); }
ol.steps li { margin-block-end: var(--space-3); }
.contact-block { font-style: normal; line-height: 2; }
/* Le numéro est une vraie cible tactile — on appelle depuis un téléphone —
   pas un lien noyé dans une phrase : il ne relève pas de l'exemption « inline »
   de WCAG 2.5.8 et doit tenir 24x24. */
.contact-block a {
  display: inline-flex;
  align-items: center;
  min-height: var(--target-min);
}
ul.bullets { list-style: disc; padding-inline-start: var(--space-6); }
.bullets li { margin-block-end: var(--space-2); }

/* --- Cartes d'offre Voxybox -------------------------------------------------
   Cinq cartes de même rang : auto-fit plutôt que 2x2, la cinquième n'ayant pas
   de jumelle. En dessous de 760 px elles s'empilent. */
.grid--offers {
  display: grid; gap: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
}
.offer { text-align: center; }
.offer__figure { max-width: 9rem; margin-inline: auto; }
.offer__figure .schema { margin-block: 0; }
.offer .card__title { margin-block-start: var(--space-3); }
.offer__usage, .offer__users, .offer__price { max-width: none; }
.offer__usage { color: var(--color-ink-2); font-size: var(--text-xs); }
.offer__users { font-weight: 600; }
.offer__price { color: var(--color-accent); font-weight: 600; }

.note { color: var(--color-ink-2); font-size: var(--text-xs); }

/* --- Groupe de boutons ------------------------------------------------------ */
.actions { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.text-center .actions { justify-content: center; }

/* --- SCH-01 : entrées / sorties du VNE --------------------------------------
   Les stops de dégradé ne peuvent pas porter var() en attribut de présentation
   (stop-color="var(--x)" est ignoré) : il faut passer par le CSS. */
.sch-grad-from { stop-color: var(--schema-flow-from); }
.sch-grad-to   { stop-color: var(--color-accent); }

/* Trajectoires des particules et des pastilles en orbite. offset-path prend un
   tracé littéral, propre à chaque schéma : une classe par trajectoire, ici,
   puisque style= est interdit par la CSP. Convention de nommage à garder
   strictement parallèle entre FR et EN — les fichiers ne diffèrent que par le
   texte, jamais par la géométrie animée. */
.sch01-orbit { offset-path: path('M190 107 A 58 58 0 1 1 189.9 107'); }
.sch03-orbit { offset-path: path('M190 130 A 58 58 0 1 1 189.9 130'); }
.sch01-in-1  { offset-path: path('M64 60 C120 60 140 140 144 161'); }
.sch01-in-2  { offset-path: path('M64 130 C110 130 128 152 144 161'); }
.sch01-in-3  { offset-path: path('M64 200 C110 200 128 178 144 169'); }
.sch01-in-4  { offset-path: path('M64 270 C120 270 140 190 144 169'); }
.sch01-out-1 { offset-path: path('M236 161 C240 140 260 60 316 60'); }
.sch01-out-2 { offset-path: path('M236 161 C252 152 270 130 316 130'); }
.sch01-out-3 { offset-path: path('M236 169 C252 178 270 200 316 200'); }
.sch01-out-4 { offset-path: path('M236 169 C240 190 260 270 316 270'); }

/* Départs décalés : sans cela les six particules partent ensemble et le schéma
   clignote au lieu de couler. */
.sch-d1 { animation-delay: -0.4s; }
.sch-d2 { animation-delay: -1.1s; }
.sch-d3 { animation-delay: -1.9s; }
.sch-d4 { animation-delay: -2.6s; }
.sch-d5 { animation-delay: -3.2s; }

/* Durées : >= 1,6 s, discrètes, jamais de clignotement (schemas §Règles communes). */
.vx-travel--slow { animation-duration: 3.6s; }
.vx-travel--orbit { animation-duration: 18s; }

/* ===========================================================================
   6. UTILITAIRES
   Rares et nommés explicitement. Toute classe ajoutée ici doit être justifiée :
   une utilité qui ne sert qu'une fois est un composant mal découpé.
   ======================================================================== */
.visually-hidden {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
/* Champ piège du formulaire. En classe et non en style= : la CSP interdit
   style-src 'unsafe-inline', et check-external.py contrôle 5 échoue sur
   l'attribut style. */
.hp-field { position: absolute; left: -9999px; }

/* Centrer une section, ce n'est PAS seulement centrer le texte dans sa boîte.
   Les éléments de contenu portent un max-width (mesure 68ch, lede 50ch, h1
   22ch) avec une marge de départ nulle : ils restent collés au bord gauche du
   .wrap pendant que le fond de section court sur toute la largeur. C'est ce
   décalage qu'on voit, pas l'alignement du texte. Il faut centrer LA BOÎTE.
   Surtout pas `:where()` : sa spécificité nulle perd contre le `margin: 0` du
   reset sur h1/h2/p (0,0,1). Sélecteurs réels, donc (0,1,1). */
/* Titre centré AU-DESSUS d'un contenu qui doit rester à gauche (FAQ, listes
   pleine largeur). Distinct de .text-center, qui centrerait aussi les questions
   et les réponses. Nommé et documenté parce que le cas se reproduira : sans
   classe dédiée, la tentation est de poser .text-center et de casser la FAQ. */
.heading-center { text-align: center; margin-inline: auto; }

.text-center { text-align: center; }
.text-center p,
.text-center h1,
.text-center h2,
.text-center h3,
.text-center ul,
.text-center ol,
.text-center address,
.text-center figure,
.text-center .eyebrow,
.text-center .lede { margin-inline: auto; }
.text-center .sector-list { justify-content: center; }
.measure-none { max-width: none; }
.no-wrap { white-space: nowrap; }

/* ===========================================================================
   7. MOUVEMENT RÉDUIT
   Dernière section du fichier : elle doit l'emporter sur tout ce qui précède.
   Couvre les transitions, les animations CSS des schémas et le défilement doux.
   ======================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  html { scroll-behavior: auto; }
  .btn:hover, .card:hover { transform: none; }
}
