/* === Fonts auto-hébergées === */
@font-face { font-family: 'Nunito'; src: url("../fonts/nunito-v32-latin-regular-sGPAjRX.woff2") format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Nunito'; src: url("../fonts/nunito-v32-latin-600-KlN4mwr.woff2") format('woff2'); font-weight: 600; font-display: swap; }
@font-face { font-family: 'Nunito'; src: url("../fonts/nunito-v32-latin-700-9DwS6xD.woff2") format('woff2'); font-weight: 700; font-display: swap; }
@font-face { font-family: 'Lato'; src: url("../fonts/lato-v25-latin-300-kgbxK91.woff2") format('woff2'); font-weight: 300; font-display: swap; }
@font-face { font-family: 'Lato'; src: url("../fonts/lato-v25-latin-regular-CzNc_Ij.woff2") format('woff2'); font-weight: 400; font-display: swap; }
@font-face { font-family: 'Lato'; src: url("../fonts/lato-v25-latin-700-5CmM-eX.woff2") format('woff2'); font-weight: 700; font-display: swap; }

/* === Charte === */
:root {
    --nuit: #2C3869;
    --nuit-10: #3C4E8D;
    --nuit-20: #4C63B0;
    --sauge: #A8C69F;
    --sauge-10: #BDD3B6;
    --sauge-14: #D2E1CD;
    --sauge-21: #E8EFE5;
    --sable: #DCCCA3;
    --prune: #654F6F;
    --rose: #F7F0F5;

    /* Relevées dans le CV d'Aurore (échantillonnage du PDF) */
    --charbon: #2F3032;
    --gris: #6D6E71;
    --filet: #DADBDB;
    /* Le rouge que « .alert-error » codait déjà en dur ; nommé pour que les erreurs de
       champ et les messages d'alerte parlent d'une seule voix.
       Contraste mesuré : 8,25:1 sur blanc, 7,04:1 sur le fond des alertes — AAA des deux
       côtés. Le chiffre est calculé, pas estimé : une première version de ce commentaire
       annonçait 7,4:1 sans l'avoir vérifié. */
    --rouge-erreur: #8c2f23;
    --blanc: #FFFFFF;

    --leaf-0: #DADDE6;
    --leaf-1: #E9F0E6;
    --leaf-2: #F5EFE1;
    --leaf-3: #E4DEE6;

    /* Alias conservés pour les classes de la phase 1 */
    --color-primary: var(--nuit);
    --color-primary-dark: #1F2749;
    --color-accent: var(--sauge);
    --color-accent-dark: #8AA982;
    --color-bg: var(--blanc);
    --color-bg-soft: var(--sauge-21);
    --color-text: var(--charbon);
    --color-text-soft: var(--gris);

    --font-heading: 'Nunito', system-ui, sans-serif;
    --font-body: 'Lato', system-ui, sans-serif;
    --font-num: ui-monospace, 'SF Mono', Menlo, monospace;
    --radius: 8px;
    --shadow: 0 2px 12px rgba(44, 56, 105, 0.12);
    --container: 1080px;
    --measure: 62ch;
    --band-pad: clamp(2.75rem, 6vw, 5.5rem);
    --slide-dur: 1600ms;
}

/* === Base === */
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-family: var(--font-body); color: var(--color-text); background: var(--color-bg); line-height: 1.6; }
/* h4 est proposé par la barre de l'éditeur riche (« Titre 4 », rich_text_controller.js) :
   sans cette ligne, il tombait au style par défaut du navigateur (gras, police de corps,
   1em) — plus petit que le paragraphe qui le suit. Choix retenu : le styliser en cohérence
   avec h2/h3 plutôt que le retirer de la barre, pour qu'Aurore garde un niveau de titre
   intermédiaire dans les contenus longs (articles, études de cas) sans avoir à réécrire un
   h3 à chaque fois qu'un sous-sous-titre suffirait. */
h1, h2, h3, h4 {
    font-family: var(--font-heading);
    color: var(--charbon);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 700;
    line-height: 1.2;
    text-wrap: balance;
}
h1 { font-size: clamp(1.8rem, 4vw, 2.6rem); }
h2 { font-size: clamp(1.4rem, 3vw, 1.9rem); }
h4 { font-size: 1.05rem; }
#about h1 { font-size: clamp(1.4rem, 3vw, 1.9rem); }
a { color: var(--color-primary); }
img { max-width: 100%; height: auto; }
main { max-width: var(--container); margin: 0 auto; padding: 2rem 1rem 4rem; }

/* === Boutons === */
.btn { display: inline-block; padding: 0.6rem 1.4rem; border-radius: var(--radius); font-family: var(--font-heading); font-weight: 600; text-decoration: none; border: 2px solid transparent; cursor: pointer; }
.btn-primary { background: var(--color-primary); color: #fff; }
.btn-primary:hover { background: var(--color-primary-dark); }
.btn-accent { background: var(--color-accent); color: var(--color-primary-dark); }
.btn-accent:hover { background: var(--color-accent-dark); }
.btn-outline { border-color: var(--color-primary); color: var(--color-primary); background: transparent; }
.btn-outline:hover { background: var(--color-primary); color: #fff; }

/* === Header / Footer === */
.site-header {
    display: flex; align-items: center; justify-content: space-between;
    gap: 1rem; flex-wrap: wrap;
    padding: 0.9rem 1.25rem;
    position: sticky; top: 0; z-index: 100;
    background: var(--color-bg);
    border-bottom: 1px solid var(--filet);
}
.site-header .site-logo { width: min(13rem, 52vw); flex-shrink: 0; }
.site-nav ul { display: flex; gap: 1.25rem; list-style: none; margin: 0; padding: 0; align-items: center; }
.site-nav a {
    text-decoration: none;
    font-family: var(--font-heading);
    font-weight: 600;
    padding-bottom: 0.5em;
    border-bottom: 3px solid transparent;
    border-bottom-left-radius: 5%;
    border-bottom-right-radius: 5%;
    transition: border-bottom-color 180ms ease-out;
}
.site-nav a:hover, .site-nav a:focus-visible { border-bottom-color: var(--sauge); }
.site-nav a.btn:hover, .site-nav a.btn:focus-visible { border-bottom-color: transparent; }
.nav-toggle { display: none; background: none; border: none; font-size: 1.6rem; color: var(--color-primary); }
.site-footer { background: var(--color-primary); color: #fff; padding: 2rem 1rem; text-align: center; }
.site-footer a { color: var(--color-accent); margin: 0 0.5rem; }

/* === Cartes projet === */
/* Une grille CSS (même en auto-fit) ne centre que les pistes totalement vides sur
   TOUTE la grille : quand une dernière rangée est incomplète mais que les pistes
   qu'elle occupe sont remplies par une rangée précédente, aucune piste n'est vide et
   justify-content n'a rien à répartir. Seule une disposition en ligne (flex-wrap) centre
   chaque rangée individuellement, dernière incluse. La base et le plafond de largeur sur
   la carte évitent qu'elle s'étire démesurément quand les cartes sont peu nombreuses. */
.projects-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 1.5rem; }
.project-card { flex: 1 1 17rem; max-width: 21rem; border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; background: var(--color-bg); display: flex; flex-direction: column; }
.project-card img { width: 100%; object-fit: cover; }
.project-card-body { padding: 1.25rem; display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.project-card-body h3 { margin: 0; }
.project-year { color: var(--color-text-soft); font-size: 0.9rem; margin: 0; }
.tech-badges { display: flex; flex-wrap: wrap; gap: 0.4rem; list-style: none; padding: 0; margin: 0; }
.tech-badges li { background: var(--color-bg-soft); color: var(--color-primary); padding: 0.15rem 0.6rem; border-radius: 999px; font-size: 0.8rem; }
.project-card-actions { display: flex; gap: 0.6rem; margin-top: auto; padding-top: 0.75rem; flex-wrap: wrap; }

/* === Galerie « En images » d'une étude de cas === */
/* Cette galerie dispose des <img> nues, sans le conteneur .project-card qui porte
   la largeur de base et le plafond des cartes projet : elle a besoin de ses propres
   règles de dimensionnement plutôt que de partager .projects-grid. */
.case-study-gallery { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 1.5rem; }
.case-study-gallery img { width: 100%; height: 100%; object-fit: cover; border-radius: var(--radius); box-shadow: var(--shadow); }
.tech-badges-label { font-family: var(--font-heading); font-weight: 700; font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-text-soft); text-align: center; margin: 1.5rem 0 0.5rem; }

/* === Filtres par nature === */
.filtres { display: flex; flex-wrap: wrap; gap: 0.5rem; justify-content: center; margin: 1.5rem 0; }
.filtre {
    font-family: var(--font-heading); font-weight: 700;
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.08em;
    text-decoration: none; color: var(--color-primary);
    padding: 0.45rem 0.9rem; border: 1px solid var(--gris); border-radius: 999px;
}
.filtre:hover, .filtre:focus-visible { border-color: var(--sauge); }
.filtre.is-active { background: var(--color-primary); color: var(--rose); border-color: var(--color-primary); }

.project-card__meta { font-family: var(--font-num); font-size: 0.72rem; letter-spacing: 0.02em; margin: 0; color: var(--color-text-soft); }
.project-card__nature { font-family: var(--font-heading); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-primary); }

/* === Entrées du flux des publications === */
.feed-item h2 { margin-bottom: 0.25rem; font-size: 1.15rem; }
.feed-item h2 a { color: var(--color-primary); text-decoration: underline; text-decoration-color: var(--sauge); text-decoration-thickness: 2px; text-underline-offset: 4px; }
.feed-item h2 a:hover, .feed-item h2 a:focus-visible { text-decoration-color: var(--sable); }
.feed-item p { color: var(--color-text-soft); }
.feed-item__meta { font-family: var(--font-num); font-size: 0.72rem; letter-spacing: 0.02em; margin: 0; }
.feed-item__kind { font-family: var(--font-heading); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-primary); }

/* === Sections accueil === */
.hero { text-align: center; padding: 3rem 1rem; }
.hero p.lead { font-size: 1.2rem; color: var(--color-text-soft); max-width: 640px; margin: 1rem auto; }
.dual-paths { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.5rem; margin: 2rem 0; }
.section { margin: 3rem 0; }

/* === Formulaires === */
form label { display: block; font-family: var(--font-heading); font-weight: 600; margin-top: 1rem; }
form input, form textarea, form select { width: 100%; padding: 0.6rem; border: 1px solid var(--filet); border-radius: var(--radius); font-family: var(--font-body); font-size: 1rem; }
form button[type="submit"] { margin-top: 1.5rem; }
/* Les erreurs de formulaire n'avaient AUCUN style : elles sortaient en liste à puces
   noire, indiscernables du texte courant, sur la page dont le métier est de convertir.
   Constaté par Aurore sur la production.
   Le champ fautif porte déjà « aria-invalid » — posé par Symfony, pas par nous : c'est
   donc lui qu'on cible, plutôt que d'ajouter une classe qui dirait la même chose. */
.form-errors { list-style: none; margin: 0.4rem 0 0; padding: 0; color: var(--rouge-erreur); font-size: 0.9rem; }
.form-errors li + li { margin-top: 0.15rem; }
form :is(input, textarea, select)[aria-invalid="true"] { border-color: var(--rouge-erreur); }
form :is(input, textarea, select)[aria-invalid="true"]:focus-visible { outline: 2px solid var(--rouge-erreur); outline-offset: 1px; }
.alert { padding: 1rem; border-radius: var(--radius); margin: 1rem 0; }
.alert-success { background: #e6f2e2; color: #2c5626; }
.alert-error { background: #fbe9e7; color: var(--rouge-erreur); }
.hp-field { position: absolute; left: -9999px; }

/* === Bandes de l'accueil === */
.band { padding: var(--band-pad) 1.25rem; }
/* L'en-tête est collant (~5.67rem de haut : logo 3.81rem + paddings 1.8rem + filet 1px) :
   sans cette marge, l'arrivée sur une ancre place le haut de la section sous l'en-tête,
   notamment au retour d'envoi du formulaire (/#contact). */
section[id] { scroll-margin-top: 6.5rem; }
.band--dark {
    background: linear-gradient(45deg, var(--nuit) 1%, var(--nuit-10) 22%, var(--nuit-20) 51%, var(--nuit-10) 83%, var(--nuit) 100%);
    color: var(--rose);
}
.band--dark h1, .band--dark h2, .band--dark h3, .band--dark h4 { color: var(--rose); }
.band--flat { background: var(--nuit); color: var(--rose); }
.band--flat h2 { color: var(--rose); }
.wrap { max-width: 68rem; margin: 0 auto; }
.stack { display: flex; flex-direction: column; gap: 1.5rem; }
.centered { text-align: center; align-items: center; }
/* Geste signature repris du CV : un court tiret sauge sous le titre de section */
.rule-under {
    display: block; width: 2.6rem; height: 3px;
    background: var(--sauge); border-radius: 1px;
    margin: 0.75rem 0 0.25rem;
}
.centered .rule-under { margin-left: auto; margin-right: auto; }
.lede { max-width: var(--measure); color: var(--color-text-soft); margin: 0; }
.band--dark .lede, .band--flat .lede { color: #DDE0EC; }

/* === Ouverture en deux volets === */
.opening { position: fixed; inset: 0; z-index: 900; display: flex; overflow: hidden; }
.opening[hidden] { display: none; }
.opening__half { position: absolute; top: 0; height: 100vh; width: 50%; transition: transform var(--slide-dur) linear; }
.opening__half--l { left: 0; background: var(--nuit-10); }
.opening__half--r { left: 50%; background: var(--sauge-10); }
.opening.is-open .opening__half--l { transform: translateX(-150vw); }
.opening.is-open .opening__half--r { transform: translateX(150vw); }
/* La marque est centrée pile sur la frontière des deux moitiés : son nuit tombe à
   1,42 pour 1 sur le bleu de gauche. Aucune couleur de marque ne satisfait les deux
   fonds à la fois — il faut donc un fond franc sous elle. Ce fond est porté par la
   marque elle-même (background + padding + border-radius sur .opening__mark), pas
   par un élément séparé posé derrière : ainsi la carte entoure exactement le logo et
   la signature à toute largeur, sans qu'un dimensionnement indépendant puisse dériver
   et laisser une partie du nom retomber hors du blanc. Elle disparaît donc avec la
   marque, dans le même fondu — un objet, un mouvement.
   Le rembourrage horizontal (1.5rem de chaque côté, soit 3rem) se prend dans cette
   largeur puisque le projet est en border-box : sans compensation, le logo — à 100 %
   du contenu de la carte — rétrécirait de 3rem par rapport à sa taille d'avant la
   carte. On ajoute donc 3rem aux deux termes du min() d'origine (22rem et 62vw), pour
   que la largeur du *contenu* retrouve exactement min(22rem, 62vw), à toute largeur
   d'écran. */
.opening__mark {
    position: absolute; inset: 0; margin: auto; width: min(25rem, calc(62vw + 3rem)); height: fit-content;
    z-index: 2; transition: opacity 700ms ease-out;
    background: var(--blanc); border-radius: var(--radius); padding: 1.25rem 1.5rem;
}
.opening.is-open .opening__mark { opacity: 0; }

/* === Logo animé === */
.brand svg { display: block; width: 100%; height: auto; }
.brand .orbit { animation: orbite 7s linear infinite; transform-origin: 69.12px 72.32px; }
@keyframes orbite { to { transform: rotate(360deg); } }

/* === À propos === */
.about { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr)); gap: 2rem; align-items: center; width: 100%; text-align: left; }
.tilt-card {
    border-radius: 4px; overflow: hidden; will-change: transform;
    box-shadow: rgba(20, 26, 40, 0.32) 0 7px 42px;
    transform: perspective(1000px) rotateX(0deg) rotateY(0deg);
    transition: transform 220ms ease-out; margin: 0 auto; max-width: 20rem; width: 100%;
}
/* Le gabarit d'attente de cet emplacement est un SVG de 320 × 380. Sans « aspect-ratio »,
   la photo impose sa propre forme et le bloc change de hauteur au chargement : c'était la
   seule instabilité de mise en page mesurée sur l'accueil (le paragraphe de positionnement
   se décalait). « cover » recadre au centre plutôt que de déformer. */
.tilt-card img, .tilt-card svg { display: block; width: 100%; height: auto; aspect-ratio: 320 / 380; }
.tilt-card img { object-fit: cover; }
.about__text > :first-child { margin-top: 0; }
.about__text > :last-child { margin-bottom: 0; }

/* === Arbre des compétences === */
.tree-frame { width: 100%; overflow-x: auto; }
.tree-frame svg { display: block; min-width: 34rem; width: 100%; height: auto; }
.tree__link { fill: none; stroke: rgba(44, 56, 105, 0.22); stroke-width: 1.5; }
.tree-frame text { font-family: var(--font-body); font-size: 15px; }
.tree__branch text { font-family: var(--font-heading); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.tree__branch-0 rect { fill: var(--nuit); } .tree__branch-0 text { fill: var(--rose); }
.tree__branch-1 rect { fill: var(--sauge); } .tree__branch-1 text { fill: #24381F; }
.tree__branch-2 rect { fill: var(--sable); } .tree__branch-2 text { fill: #453A22; }
.tree__branch-3 rect { fill: var(--prune); } .tree__branch-3 text { fill: var(--rose); }
.tree__leaf-0 rect { fill: var(--leaf-0); } .tree__leaf-1 rect { fill: var(--leaf-1); }
.tree__leaf-2 rect { fill: var(--leaf-2); } .tree__leaf-3 rect { fill: var(--leaf-3); }
.tree__leaf text { fill: var(--nuit); }
.tree__node { transition: transform 320ms cubic-bezier(.2,.7,.3,1), opacity 220ms ease-out, visibility 0s; }
.tree__node.is-hidden { opacity: 0; visibility: hidden; pointer-events: none; transition: transform 320ms cubic-bezier(.2,.7,.3,1), opacity 220ms ease-out, visibility 0s linear 220ms; }
.tree__branch { cursor: pointer; }
.tree__branch:focus { outline: none; }
.tree__branch:focus-visible rect { stroke-width: 2; vector-effect: non-scaling-stroke; }
/* Le liseré doit contraster avec le fond du nœud : clair sur les teintes sombres,
   sombre sur les teintes claires. */
.tree__branch-0:focus-visible rect, .tree__branch-3:focus-visible rect { stroke: var(--rose); }
.tree__branch-1:focus-visible rect, .tree__branch-2:focus-visible rect { stroke: var(--charbon); }
.tree__link path { transition: opacity 220ms ease-out; }
.tree__link path.is-hidden { opacity: 0; }
/* Le premier rendu du contrôleur peut déplacer tous les nœuds d'un coup (le serveur
   pose les coordonnées horizontales, l'écran étroit demande les verticales). Cette
   classe, retirée à l'image suivante, empêche ce recalage de s'animer au chargement.
   Elle ne fait que *supprimer* du mouvement : elle ne peut donc pas contredire
   « prefers-reduced-motion », contrairement à une règle qui en réintroduirait. */
.tree-frame.is-instant .tree__node,
.tree-frame.is-instant .tree__node.is-hidden,
.tree-frame.is-instant .tree__link path { transition: none; }
@media (prefers-reduced-motion: reduce) {
    .tree__node, .tree__node.is-hidden, .tree__link path { transition-duration: 1ms; }
}

/* Le « + » dit que la case se déplie, et bascule en croix une fois dépliée. L'état est
   déjà porté par aria-expanded : la marque est purement décorative et ne doit pas être
   annoncée une seconde fois. */
/* Un « + » plutôt qu'un chevron parce que l'arbre se déplie vers la droite sur grand
   écran et vers le bas sur mobile : un chevron devrait changer de direction avec la mise
   en page, alors qu'une croix dit « fermer » quelle que soit l'orientation. */
/* « transform-box: fill-box » n'est pas cosmétique : dans un SVG, transform-box vaut
   « view-box » par défaut, et « transform-origin: center » désigne alors le centre du
   *dessin entier*, pas celui de la marque. Sans cette ligne, la rotation projette la
   marque à l'autre bout du dessin (mesuré : +276px à droite, +128px en bas, hors du
   cadre) au lieu de la faire pivoter sur elle-même. Voir « .brand .orbit », qui contourne
   le même piège avec une origine en pixels explicite. */
.tree__plus { transform-box: fill-box; transform-origin: center; transition: transform 220ms ease-out; }
.tree__branch-0 .tree__plus, .tree__branch-3 .tree__plus { color: var(--rose); }
.tree__branch-1 .tree__plus, .tree__branch-2 .tree__plus { color: var(--charbon); }
.tree__branch[aria-expanded="true"] .tree__plus { transform: rotate(45deg); }

.tree-hint { margin: 0; font-size: 0.85rem; color: var(--color-text-soft); }

/* === Carrousel 3D === */
/* La scène rogne ce qui dépasse : sans cela, une carte décalée hors de l'écran
   élargit la page et déclenche un défilement horizontal de tout l'accueil. */
.cover-stage { position: relative; height: 22rem; width: 100%; perspective: 1200px; overflow: hidden; }
.cover {
    position: absolute; top: 0; left: 50%; width: min(18rem, 74vw); height: 20rem;
    border-radius: 4px; overflow: hidden; cursor: pointer; text-decoration: none;
    background: var(--blanc); border: 1px solid rgba(247, 240, 245, 0.28);
    transition: transform 520ms cubic-bezier(.2,.7,.3,1), opacity 520ms ease-out;
    display: flex; flex-direction: column; text-align: left;
    transform: translateX(-50%);
}
.cover__visual { height: 60%; background: var(--nuit-10); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.cover__visual img { width: 100%; height: 100%; object-fit: cover; }
.cover__visual span { font-family: var(--font-heading); font-weight: 700; font-size: 2rem; letter-spacing: 0.12em; color: var(--rose); }
.cover__body { padding: 0.85rem 1rem; display: flex; flex-direction: column; gap: 0.3rem; }
.cover__body h3 { margin: 0; font-size: 0.95rem; color: var(--charbon); }
.cover__body p { margin: 0; font-size: 0.85rem; color: var(--color-text-soft); }
.cover__year { font-family: var(--font-num); font-size: 0.75rem; color: var(--gris); font-variant-numeric: tabular-nums; }
.cover-nav { display: flex; gap: 0.75rem; justify-content: center; }
.cover-nav button {
    font-family: var(--font-heading); font-weight: 700; font-size: 0.95rem;
    width: 2.6rem; height: 2.6rem; border-radius: var(--radius); cursor: pointer;
    background: var(--sauge); color: #24381F; border: none; box-shadow: 1px 1px 0 #8AA982;
}
.cover-nav button:hover { background: var(--sable); color: #453A22; box-shadow: 1px 1px 0 #C4AF79; }

/* === Accordéon CV ===
   Un <button> ne peut contenir que du contenu de phrasé : le déclencheur ne porte donc
   que l'icône et le libellé, la liste vit à côté de lui, et l'état d'ouverture se lit
   sur le bouton (aria-expanded) que le volet cible via :has(). */
.accordion { display: flex; width: 100%; min-height: 26rem; gap: 2px; border-radius: 12px; overflow: hidden; }
.accordion__part {
    flex: 1; position: relative; overflow: hidden;
    display: flex; flex-direction: column; justify-content: flex-start;
    transition: flex-grow 320ms ease-out;
}
.accordion__part:nth-child(1) { background: var(--sauge-10); }
.accordion__part:nth-child(2) { background: var(--sauge-14); }
.accordion__part:nth-child(3) { background: var(--sauge-21); }
.accordion__trigger {
    appearance: none; background: none; border: none; font: inherit; color: var(--nuit);
    cursor: pointer; width: 100%; padding: 1.5rem 1.1rem;
    display: flex; flex-direction: column; align-items: center; gap: 0.6rem;
}
.accordion__trigger:focus-visible { outline: 3px solid var(--nuit); outline-offset: -3px; }
.accordion__trigger svg { width: 2.75rem; height: 2.75rem; }
.accordion__label {
    font-family: var(--font-heading); font-weight: 700; font-size: 0.95rem;
    text-transform: uppercase; letter-spacing: 0.05em;
}
.accordion__body { padding: 0 1.1rem 1.5rem; max-width: 26rem; max-height: 18rem; overflow-y: auto; text-align: left; opacity: 0; visibility: hidden; transition: opacity 260ms ease-out 120ms, visibility 0s linear 380ms; }
.accordion__part:has(.accordion__trigger[aria-expanded="true"]) { flex-grow: 3; }
.accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__trigger {
    flex-direction: row; align-items: baseline; gap: 0.7rem; padding-bottom: 0.8rem; width: auto;
}
.accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__trigger svg { width: 1.5rem; height: 1.5rem; }
.accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__body { opacity: 1; visibility: visible; transition: opacity 260ms ease-out 120ms, visibility 0s; }
/* « > » et non un sélecteur de descendance : une liste à puces saisie dans l'éditeur
   riche d'une description (Experience, Training, OtherCvInfo) produit elle aussi un
   <ul>, imbriqué dans .cv-item__description. Un sélecteur de descendance l'aurait
   dépouillée de ses puces — constaté au navigateur, où la liste apparaissait sans
   aucune puce ni retrait. */
.accordion__body > ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.85rem; }
/* « li > » et non un sélecteur de descendance, pour la même raison que .accordion__body > ul
   ci-dessus : le <strong> du titre de poste et l'<em> de secours (« À compléter. ») sont des
   enfants directs du <li>, mais un <strong>/<em> tapé dans l'éditeur riche d'une description
   (Experience, Training, OtherCvInfo) est imbriqué dans .cv-item__description, lui-même enfant
   du <li> — un sélecteur de descendance l'attraperait aussi. Constaté au navigateur avant ce
   correctif : un <strong> saisi dans une description passait en display: block, seul sur sa
   ligne, en police de titre ; un <em> s'affichait SANS italique (font-style: normal ci-dessous
   visait à neutraliser l'ancien habillage de toute la description, pas un <em> volontaire). */
.accordion__body li > strong { font-family: var(--font-heading); font-weight: 700; display: block; font-size: 0.95rem; }
.accordion__body li > span { font-family: var(--font-num); font-size: 0.72rem; color: #4E5570; display: block; font-variant-numeric: tabular-nums; }
.accordion__body li > em { font-style: normal; font-size: 0.9rem; color: #3E4560; }

/* Ce bloc affiche shortDescription, rendu tel quel par le gabarit (aucun filtre |rich,
   texte échappé) : aucune balise n'y est jamais interprétée. Seules la taille et la couleur
   sont donc utiles ici — sorties du sélecteur .accordion__body em ci-dessus, qui portait
   leur typographie avant l'introduction de ce bloc, et rien ne l'avait remplacée. */
.cv-item__description { font-size: 0.9rem; color: #3E4560; }

/* === Frise du parcours (/profil) ===
   Un filet porté par la bordure gauche de .timeline, un point par .timeline__item,
   positionné en absolu sur ce filet. Le point est décalé d'un peu plus que le
   padding-left de .timeline pour se centrer sur la bordure, jamais au-delà : le
   débordement horizontal a été vérifié au navigateur (mobile 375px), aucune
   barre de défilement latérale n'apparaît. */
.timeline {
    list-style: none;
    margin: 1rem 0 2rem;
    padding: 0 0 0 1.5rem;
    border-left: 2px solid var(--filet);
}
.timeline__item { position: relative; margin: 0 0 1.75rem; }
.timeline__item:last-child { margin-bottom: 0; }
.timeline__item::before {
    content: '';
    position: absolute;
    /* Le filet est la bordure gauche de .timeline : son centre tombe à
       -(padding-left) - 1px du bord de l'élément, soit -1,5rem - 1px. Le point doit y
       centrer le sien, donc reculer en plus de sa demi-largeur (0,3rem). L'ancienne
       valeur le laissait 5 px à droite du filet — constaté par Aurore. */
    left: calc(-1.8rem - 1px);
    top: 0.3rem;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
    background: var(--charbon);
}
.timeline__dates { font-family: var(--font-num); font-size: 0.78rem; color: var(--color-text-soft); margin: 0 0 0.15rem; font-variant-numeric: tabular-nums; }
.timeline__title { font-size: 1rem; margin: 0 0 0.15rem; }
.timeline__org { margin: 0 0 0.4rem; color: var(--color-text-soft); }
/* .timeline__body contient du HTML produit par l'éditeur riche (Experience::description,
   Training::description, et OtherCvInfo::description — la liste « Divers » de /profil
   réutilise cette classe pour bénéficier des mêmes règles) : toute règle visant p, ul,
   ol, strong ou em ici doit rester ancrée sur un enfant direct (« > »), jamais un
   sélecteur de descendance — voir le correctif documenté plus haut sur .accordion__body,
   qui a déjà cassé le gras et l'italique du CV en visant trop large. */
.timeline__body { font-size: 0.95rem; }
.timeline__body > p { margin: 0.4rem 0; }
.timeline__body > p:first-child { margin-top: 0; }
.timeline__body > p:last-child { margin-bottom: 0; }
.timeline__body > ul { list-style: disc; padding-left: 1.2rem; }
.timeline__body > ol { list-style: decimal; padding-left: 1.2rem; }
/* La barre de l'éditeur propose Titre 2 à 4, et la liste blanche les laisse passer. Un
   h2 saisi dans une description sortait donc à 30 px À L'INTÉRIEUR d'un élément de frise,
   soit la taille du titre de la section qui le contient, et juste après le h4 du poste.
   Mesuré au navigateur. On les ramène au corps du texte : la mise en forme reste
   utilisable, elle cesse d'écraser la page.
   Décision d'Aurore, avec sa limite dite : le PLAN du document reste faux pour un moteur,
   puisqu'un h2 de contenu ferme toujours la section « Expériences » dans son analyse.
   Seul un usage — ne pas mettre de titre dans une description de CV — le corrigerait. */
.timeline__body > h2,
.timeline__body > h3,
.timeline__body > h4 {
    font-size: 1em;
    text-transform: none;
    letter-spacing: normal;
    margin: 0.6rem 0 0.2rem;
}

.career-other { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-direction: column; gap: 1rem; }
.career-other > li > h4 { font-size: 1rem; margin: 0 0 0.15rem; }

/* Le lien vit dans #cv, une bande band--flat (fond --nuit) : sans cette redéfinition,
   il hérite du bleu marine par défaut de « a » (var(--color-primary), lui-même --nuit) et
   devient illisible, texte marine sur fond marine — constaté au navigateur. Même correctif
   que .section-link et .privacy-notice ci-dessus pour les bandes sombres. */
.cv-link { margin: 1.5rem 0 0; text-align: center; }
.cv-link a {
    color: var(--color-primary);
    text-decoration: underline;
    text-decoration-color: var(--sauge);
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
}
.cv-link a:hover, .cv-link a:focus-visible { text-decoration-color: var(--sable); }
.band--dark .cv-link a, .band--flat .cv-link a { color: var(--rose); }

@media (max-width: 720px) {
    /* Le point déborde très légèrement à gauche du filet (-1.55rem contre un
       padding-left de 1.5rem) : sur un écran étroit, .timeline garde donc sa marge
       gauche pour que ce point reste dans la zone de contenu plutôt que de pousser
       le viewport en défilement horizontal. */
    .timeline { margin-left: 0.1rem; }
}

/* L'accordéon vit désormais sur la bande nuit, mais ses volets restent des panneaux
   clairs — comme les cartes du carrousel sur la bande Portfolio. Sans ce correctif,
   tout texte du panneau qui n'a pas de couleur codée en dur (le <strong> du corps)
   hérite du rose de .band--flat au lieu du sombre attendu sur fond clair. */
.accordion { color: var(--charbon); }

@media (max-width: 720px) {
    .accordion { flex-direction: column; min-height: 0; }
    .accordion__part, .accordion__part:has(.accordion__trigger[aria-expanded="true"]) { flex: 0 0 auto; }
    .accordion__trigger, .accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__trigger {
        margin: 0; flex-direction: row; justify-content: center; gap: 0.6rem; padding: 1rem;
    }
    .accordion__trigger svg, .accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__trigger svg {
        width: 1.5rem; height: 1.5rem;
    }
    .accordion__body { max-width: none; max-height: none; overflow: visible; }
    .accordion__part:not(:has(.accordion__trigger[aria-expanded="true"])) .accordion__body { display: none; }
}

@media (prefers-reduced-motion: reduce) {
    .brand .orbit { animation: none; }
    .opening__half, .opening__mark, .cover, .accordion__part, .accordion__body, .tilt-card, .tree__plus { transition-duration: 1ms; }
    /* La règle d'ouverture du volet redéfinit « transition » avec une spécificité de quatre
       sélecteurs : il faut la reprendre telle quelle ici pour que le sens « apparition »
       respecte lui aussi la préférence de mouvement réduit. */
    .accordion__part:has(.accordion__trigger[aria-expanded="true"]) .accordion__body { transition-duration: 1ms; }
}

/* === Contenu rédactionnel des pages légales === */
.prose { max-width: var(--measure); }
.prose h2 { font-size: 1.15rem; margin: 2rem 0 0.6rem; }
.prose p, .prose li { color: var(--color-text-soft); }
.prose ul { padding-left: 1.2rem; }
.prose a { color: var(--color-primary); }

/* === Petit balisage manuscrit de l'éditeur riche ===
   blockquote, pre, code et hr sont dans la liste blanche de l'assainisseur
   (config/packages/html_sanitizer.yaml) mais inatteignables depuis la barre d'outils de
   TinyMCE aujourd'hui (rich_text_controller.js) : ils ne servent qu'au contenu déjà écrit
   à la main par Aurore, ou à un futur collage. Sans règle, le premier arrivé tombe en
   habillage brut du navigateur au milieu d'une page soignée. Global et non scopé à .prose :
   ces balises peuvent apparaître dans tout contenu rendu par |rich (about, profil, pages
   statiques, articles, études de cas, prestations, descriptions du CV). */
blockquote {
    margin: 1rem 0;
    padding: 0.6rem 1rem;
    border-left: 3px solid var(--sauge);
    color: var(--color-text-soft);
    font-style: italic;
}
pre {
    margin: 1rem 0;
    padding: 0.85rem 1rem;
    background: var(--sauge-21);
    border-radius: var(--radius);
    overflow-x: auto;
    font-family: var(--font-num);
    font-size: 0.85rem;
    line-height: 1.5;
}
code {
    font-family: var(--font-num);
    font-size: 0.9em;
    background: var(--sauge-21);
    padding: 0.1em 0.35em;
    border-radius: 4px;
}
/* Un <code> imbriqué dans un <pre> ne doit pas recevoir un second fond par-dessus le
   premier — sinon la balise se voit, bordée d'un carré plus sombre au milieu du bloc. */
pre code { background: none; padding: 0; border-radius: 0; }
hr {
    margin: 1.5rem 0;
    border: none;
    border-top: 1px solid var(--filet);
}

/* === Notice RGPD sous les formulaires === */
.privacy-notice {
    max-width: var(--measure); margin: 1.25rem 0 0;
    font-size: 0.82rem; line-height: 1.6; color: var(--color-text-soft);
}
.band--flat .privacy-notice, .band--dark .privacy-notice { color: #C9CEE0; }
.band--flat .privacy-notice a, .band--dark .privacy-notice a { color: var(--sauge); }

/* === Profils sociaux du pied de page === */
.social-links { display: flex; flex-wrap: wrap; gap: 1rem; justify-content: center; margin: 0.75rem 0; }

/* === Publications === */
.publications { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; justify-content: center; gap: 1.25rem; width: 100%; }
.publication { flex: 1 1 14rem; max-width: 19rem; background: var(--panel, #fff); border: 1px solid var(--filet); border-radius: 12px; padding: 1.1rem 1.25rem; text-align: left; display: flex; flex-direction: column; gap: 0.5rem; }
.publication h3 { margin: 0; font-size: 1rem; }
.publication h3 a {
    color: var(--color-primary);
    text-decoration: underline;
    text-decoration-color: var(--sauge);
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
}
.publication h3 a:hover, .publication h3 a:focus-visible { text-decoration-color: var(--sable); }
.publication p { margin: 0; font-size: 0.9rem; color: var(--color-text-soft); }
.publication__meta { font-family: var(--font-num); font-size: 0.72rem; letter-spacing: 0.02em; }
.publication__kind { font-family: var(--font-heading); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; color: var(--color-primary); }

/* Frontière discrète entre deux sections claires consécutives, à la manière des filets du CV */
.band:not(.band--dark):not(.band--flat) + .band:not(.band--dark):not(.band--flat) { border-top: 1px solid var(--filet); }

/* === Lien de prolongement en fin de section === */
.section-link { margin: 0.5rem 0 0; }
.section-link a {
    font-family: var(--font-heading); font-weight: 700;
    font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.09em;
    text-decoration: none; color: var(--color-primary);
    border-bottom: 2px solid var(--sauge); padding-bottom: 3px;
}
.section-link a:hover, .section-link a:focus-visible { border-bottom-color: var(--sable); }
.band--dark .section-link a, .band--flat .section-link a { color: var(--rose); }

/* === Formulaire de contact ===
   La section centre ses enfants : sans largeur explicite, le formulaire se réduit
   à la largeur de son contenu et les champs à 100 % n'occupent presque rien. */
#contact form { width: min(38rem, 100%); text-align: left; }
/* L'en-tête est collant (91 px mesurés) : sans cette marge de défilement, la cible de
   l'ancre atterrit DESSOUS et l'on croit que la page a sauté en haut. */
#formulaire { scroll-margin-top: 7rem; }

/* === Signature, positionnement et domaines === */
.signature {
    font-family: var(--font-heading); font-weight: 700;
    letter-spacing: 0.02em; color: var(--color-primary);
    margin: 0;
    /* La signature est une phrase personnelle, pas un titre de section : la règle générale
       « h1, h2, h3 { text-transform: uppercase } » (spécificité de type, 0-0-1) ne doit pas
       s'y appliquer. Une classe (0-1-0) suffit à l'emporter sur un sélecteur de type, mais
       seulement si elle redéfinit la propriété — d'où ce texte-transform explicite. */
    text-transform: none;
}
.signature--titre { font-size: clamp(1.35rem, 4vw, 2rem); line-height: 1.25; }
/* « #about h1 » (spécificité 1-0-1, avec l'ID) l'emporterait sur « .signature--titre » seule
   (0-1-0) et imposerait sa propre taille : il faut au moins autant de poids pour rétablir celle
   voulue ici. « #about h1.signature--titre » (1-1-1) bat 1-0-1 sans dépendre de l'ordre des
   règles dans la feuille. */
#about h1.signature--titre { font-size: clamp(1.35rem, 4vw, 2rem); }
/* La signature repose désormais sur la carte blanche portée par .opening__mark, plus
   sur les deux fonds nuit-10 / sauge-10 qu'elle chevauchait : elle n'a donc plus besoin
   d'un fond opaque à elle pour garantir son contraste, la carte y pourvoit déjà. */
.opening .signature {
    font-size: 0.95rem; text-align: center; margin-top: 0.75rem;
    color: var(--nuit);
}
.positionnement {
    max-width: var(--measure); margin: 0;
    font-size: clamp(1rem, 2.4vw, 1.2rem); line-height: 1.55; color: var(--color-text);
}
.domaines {
    max-width: var(--measure); margin: 0;
    font-family: var(--font-num); font-size: 0.78rem; letter-spacing: 0.06em;
    text-transform: uppercase; color: var(--color-text-soft);
}

/* === Responsive === */
@media (max-width: 720px) {
    .nav-toggle { display: block; }
    /* En vertical, le dessin tient dans la largeur : plus de largeur plancher.
       Tant que le contrôleur n'a pas basculé les coordonnées, le dessin est encore
       à l'horizontale et garde son plancher : sans JavaScript, l'arbre reste lisible
       en défilant plutôt qu'écrasé. Le plafond, lui, évite l'excès inverse : sans
       lui, le viewBox de 288 unités s'étirerait sur toute la largeur disponible et
       l'arbre deviendrait géant entre ~500 et 720 px (texte à 36 px à 720 px). */
    .tree-frame.is-vertical svg { min-width: 0; max-width: 26rem; margin-inline: auto; }
    .site-nav ul { display: none; position: absolute; right: 1rem; top: 4rem; background: var(--color-bg); box-shadow: var(--shadow); flex-direction: column; padding: 1rem; border-radius: var(--radius); z-index: 10; }
    .site-nav ul.is-open { display: flex; }
}
