/*
 * tb-blocs.css — habillage du skin "classic" des blocs de l'app pages.
 * / Styling of the pages app "classic" skin blocks.
 *
 * LOCALISATION : pages/static/pages/css/tb-blocs.css
 * PORTEE : chargé par pages/classic/page.html ET par pages/faire_festival/page.html.
 * Ce dernier n'habille que 11 des 19 types de blocs : les 8 autres retombent sur le
 * socle classic, dont le balisage est en .tb-bloc*. Il prend les JETONS via la classe
 * `tb-jetons`, mais PAS l'habillage `.tb-page` (qui imposerait son fond et sa couleur
 * de texte par-dessus l'identité du skin).
 * / SCOPE: loaded by the classic page template AND by the faire_festival one. The
 * latter only styles 11 of the 19 block types; the other 8 fall back to the classic
 * base. It takes the TOKENS via `tb-jetons`, but NOT the `.tb-page` styling.
 *
 * PARTI PRIS : thème ÉDITORIAL autonome (magazine). Titres en serif (caractère),
 * corps en sans hérité (lisibilité), UNE seule teinte d'accent — celle du tenant
 * (--bs-primary) — utilisée avec parcimonie (filets, intitulés, bouton principal).
 * Contraste fort, rythme de sections marqué, profondeur sobre (ombres douces).
 * / DESIGN INTENT: self-contained EDITORIAL theme (magazine). Serif titles
 * (character), inherited sans body (readability), a SINGLE accent — the tenant's
 * (--bs-primary) — used sparingly (rules, labels, primary button). Strong
 * contrast, clear section rhythm, restrained depth (soft shadows).
 *
 * Hallmark : accent unique (jamais de dégradé violet), asymétrie (contenu biaisé),
 * focus visible, reduced-motion respecté, minmax(0,1fr), largeur de lecture bornée.
 * Zéro CDN : polices = piles système (aucun @import), aucune ressource externe.
 */

/* Les JETONS des blocs, sans aucun habillage.
   .tb-page = le socle classic, qui prend les jetons ET son habillage (plus bas).
   .tb-jetons = pour un skin qui affiche des blocs du socle sans adopter son
   habillage : sans ces jetons, var(--tb-gouttiere) & co sont INDEFINIS, donc
   les paddings tombent a 0 et les blocs se collent aux bords.
   / The block TOKENS, with no styling. .tb-page = the classic base (tokens +
   styling below). .tb-jetons = for a skin rendering base blocks without adopting
   the base styling: without these tokens the gutters resolve to 0. */
.tb-page,
.tb-jetons {
    /* Jetons locaux / Local tokens */
    --tb-espace-section: clamp(2.5rem, 5.5vw, 4.5rem);
    --tb-largeur-max: 72rem;

    /* Hauteur reservee au-dessus des colonnes collantes et des ancres. Un skin
       dont l'entete reste fixe la surcharge, sinon le haut du menu et du
       sommaire disparait dessous au defilement.
       / Space reserved above sticky columns and anchors. A skin with a fixed
       header overrides it, otherwise the top of the menu and table of contents
       vanishes underneath when scrolling. */
    --tb-sticky-top: 1.5rem;
    --tb-largeur-lecture: 40rem;
    /* LA gouttière horizontale unique de tous les blocs (audit grille
       2026-07-05 : chaque bloc avait la sienne, rien ne s'alignait).
       / THE single horizontal gutter of every block. */
    --tb-gouttiere: clamp(1.25rem, 5vw, 4rem);
    /* Largeur d'une boîte AUTONOME (qui porte sa gouttière en padding).
       Géométriquement identique à une section pleine largeur + contenu
       centré : contenu à max((100vw - largeur-max)/2, gouttière) du bord.
       Sans ce calc, une boîte « max-width + padding gouttière » décale son
       contenu de +gouttière par rapport aux sections (les cartes semblaient
       « plus à l'intérieur » que les titres).
       / Width of a SELF-CONTAINED box (carrying its gutter as padding).
       Geometrically identical to a full-width section with centered content.
       Without this calc, such boxes shift their content by +gutter vs
       sections (cards looked more inset than titles). */
    --tb-largeur-boite: calc(var(--tb-largeur-max) + 2 * var(--tb-gouttiere));

    /* Accent unique : couleur du tenant, repli bleu éditorial sobre.
       / Single accent: tenant color, sober editorial blue fallback. */
    --tb-accent: var(--bs-primary, #1d4ed8);
    --tb-accent-contraste: #ffffff;

    /* Texte/surfaces : contraste fort, fond cassé (pas blanc pur) pour l'œil.
       / Text/surfaces: strong contrast, off-white background (easier on the eye). */
    --tb-texte: var(--bs-body-color, #18181b);
    --tb-texte-doux: color-mix(in srgb, var(--tb-texte) 66%, transparent);
    --tb-surface: var(--bs-body-bg, #ffffff);
    --tb-surface-alt: var(--bs-tertiary-bg, #f6f5f1);
    --tb-bordure: var(--bs-border-color, #e3e1da);

    /* Fond du HERO sans image : neutre legerement teinte accent, pour creer un
       « moment d'arrivee » (le surface-alt seul est trop proche du blanc).
       / Imageless HERO background: neutral slightly tinted with accent, to create
       an "arrival" moment (surface-alt alone is too close to white). */
    --tb-hero-fond: color-mix(in srgb, var(--tb-accent) 4%, var(--tb-surface-alt));

    /* Police de titre éditoriale (serif système, zéro téléchargement).
       / Editorial title font (system serif, zero download). */
    --tb-police-titre: ui-serif, Georgia, "Iowan Old Style", "Palatino Linotype",
                       Palatino, "Times New Roman", serif;

    /* Rayons & ombres : sobres, échelle cohérente. / Radii & shadows: sober, consistent. */
    --tb-rayon: 10px;
    --tb-rayon-sm: 6px;
    --tb-ombre: 0 1px 2px rgba(0, 0, 0, 0.04), 0 8px 24px rgba(0, 0, 0, 0.06);
    --tb-ombre-forte: 0 2px 6px rgba(0, 0, 0, 0.08), 0 18px 40px rgba(0, 0, 0, 0.12);
    --tb-transition: 180ms ease;
}

/* Habillage du socle classic UNIQUEMENT : un skin qui ne veut que les jetons
   (.tb-jetons) garde son propre fond et sa propre couleur de texte.
   / Classic base styling ONLY: a skin taking just the tokens (.tb-jetons) keeps
   its own background and text color. */
.tb-page {
    /* Pas de scroll horizontal sur mobile / No horizontal scroll on mobile */
    overflow-x: clip;
    color: var(--tb-texte);
    background-color: var(--tb-surface);
}

/* GRILLE DE PAGE — 12 colonnes.
   C'est ici que se joue la mise en page cote a cote. Chaque bloc occupe la
   largeur qu'il declare (voir --tb-colonnes plus bas) : par defaut les 12
   colonnes, donc pleine largeur ; une carte 4 ; une FAQ 6. Des blocs voisins
   qui n'occupent pas tout se rangent donc d'eux-memes sur la meme ligne.

   Classe SEPAREE de .tb-page : le skin faire_festival pose .tb-flux pour
   obtenir la grille sans .tb-page, dont le fond blanc et le texte noir
   ecraseraient son identite.
   / PAGE GRID — 12 columns. Each block spans what it declares (12 by default,
   4 for a card, 6 for a FAQ), so neighbours line up on their own. Kept apart
   from .tb-page: the faire_festival skin adds .tb-flux for the grid without
   .tb-page's white background, which would erase its identity. */
.tb-flux {
    /* Air entre deux blocs-colonnes voisins (cartes, FAQ).
       / Air between two neighbouring column blocks. */
    --tb-grille-gap: clamp(1.25rem, 3vw, 2rem);

    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--tb-grille-gap);

    /* CADRAGE : la zone de contenu vaut min(100% - 2 gouttieres, largeur-max)
       et reste centree. C'est la grille qui le porte, parce que les
       blocs-colonnes (cartes, FAQ) sont des enfants directs : eux seuls
       dependent du conteneur pour ne pas toucher le bord de l'ecran.
       / FRAMING: the content zone is min(100% - 2 gutters, max-width), centered.
       The grid carries it because column blocks are direct children and depend
       on the container to stay off the screen edge. */
    padding-inline: max(var(--tb-gouttiere), calc((100% - var(--tb-largeur-max)) / 2));

    /* La page ouvre et ferme sur la meme respiration que l'espace entre blocs.
       / The page opens and closes on the same breathing room. */
    padding-block: calc(var(--tb-espace-section) / 2);

    /* Les blocs pleine largeur debordent d'une largeur d'ascenseur : leurs
       marges negatives se mesurent en 100vw, qui compte l'ascenseur vertical,
       alors que la grille se mesure en 100%, qui ne le compte pas. Sans cette
       coupe, un ascenseur HORIZONTAL apparait sur toute la page.
       `clip` et non `hidden` : `hidden` creerait un conteneur de defilement et
       casserait tout `position: sticky` place dans un bloc.
       / Full-bleed blocks overflow by one scrollbar width: their negative
       margins use 100vw (which counts the vertical scrollbar) while the grid
       uses 100% (which does not). Without this, a HORIZONTAL scrollbar shows
       up. `clip`, not `hidden`: `hidden` would create a scroll container and
       break any `position: sticky` inside a block. */
    overflow-x: clip;

    /* Les voisins d'une meme rangee s'etirent a la hauteur du plus grand.
       C'est cet etirement qui permet a un pied de carte de se caler en bas
       (margin-top: auto) : sans lui, les boutons de trois cartes de textes
       inegaux forment un escalier.
       / Same-row neighbours stretch to the tallest. This is what lets a card
       footer pin to the bottom; without it, buttons form a staircase. */
    align-items: stretch;
}

/* La grille porte deja la gouttiere : un bloc du socle ne pose pas la sienne
   par-dessus, sinon le retrait est compte deux fois et les cartes paraissent
   plus larges que les textes.
   / The grid already carries the gutter: base blocks must not add theirs on
   top, or the inset counts twice. */
.tb-flux > .tb-bloc,
.tb-flux > .tb-grille,
.tb-flux > .tb-faq-colonnes {
    padding-inline: 0;
    max-width: none;
    /* `margin-inline: auto` est a retirer, pas seulement a neutraliser en
       largeur : sur un ITEM DE GRILLE, une marge automatique annule
       l'etirement (`justify-self: stretch`). L'element se dimensionne alors
       sur son contenu au lieu d'occuper sa zone — une grille de cartes se
       reduit a la largeur d'UNE carte et les cartes s'empilent.
       / `margin-inline: auto` must be removed, not just capped: on a GRID
       ITEM an auto margin cancels `justify-self: stretch`, so the element
       sizes to its content instead of filling its area — a card grid shrinks
       to one card wide and the cards stack. */
    margin-inline: 0;
}

/* PLEINE LARGEUR (banniere a image de fond, bandes teintees) : le bloc annule
   le cadrage par marges negatives, puis reprend le meme calcul en padding pour
   recentrer son contenu. On mesure en 100vw et non en 100% : le pourcentage
   d'un item de grille se resout contre sa zone de grille, deja retrecie.
   / FULL-BLEED: cancel the framing with negative margins, re-add the same calc
   as padding to re-center the content. Measured in 100vw, not 100%: a grid
   item's percentage resolves against its already-shrunk grid area. */
.tb-flux > .tb-pleine-largeur,
.tb-flux > .tb-bloc--hero,
.tb-flux > .tb-bloc--cta,
.tb-flux > .tb-bloc--temoignage {
    /* `width: auto` est indispensable, pas decoratif : un bloc qui fixe sa
       largeur (une `.container-fluid` de Bootstrap vaut `width: 100%`) n'est
       plus etire par la grille. Il garde alors la largeur de sa ZONE, les
       marges negatives ne font que le decaler, et son propre padding ampute
       le contenu d'autant — les colonnes se retrouvent ecrasees a gauche.
       / `width: auto` is required, not decorative: a block that fixes its own
       width (Bootstrap's `.container-fluid` is `width: 100%`) is no longer
       stretched by the grid. It keeps its GRID AREA width, the negative
       margins merely shift it, and its own padding eats the content. */
    width: auto;
    max-width: none;
    margin-inline: calc(-1 * max(var(--tb-gouttiere), (100vw - var(--tb-largeur-max)) / 2));
    padding-inline: max(var(--tb-gouttiere), calc((100vw - var(--tb-largeur-max)) / 2));
}

/* UNE PAGE QUI OUVRE SUR UN BLOC PLEINE LARGEUR COLLE A L'ENTETE.
   Le `padding-block` d'ouverture de .tb-flux laisse sinon une bande de fond
   entre le menu et l'image du hero : la bande et le menu se lisent comme un
   seul aplat, et le menu parait bien plus epais qu'il n'est.
   Ne s'applique qu'au PREMIER enfant : les pages qui ouvrent sur du texte, un
   fil d'Ariane ou un menu lateral gardent leur respiration, et le padding du
   bas n'est pas touche.
   / A PAGE OPENING ON A FULL-BLEED BLOCK SITS FLUSH AGAINST THE HEADER.
   Otherwise .tb-flux's opening padding leaves a band of background between the
   menu and the hero image: band and menu read as one solid area, making the
   menu look far thicker than it is. First child only, so pages opening on
   text, a breadcrumb or a side menu keep their breathing room. */
.tb-flux:has(> :first-child:is(.tb-pleine-largeur, .tb-bloc--hero)) {
    padding-block-start: 0;
}

/* Chaque enfant direct de la page occupe le nombre de colonnes qu'il declare.
   `minmax(0, ...)` et non `1fr` : sans lui, une image ou un <pre> trop large
   force la colonne a s'elargir et fait deborder la page horizontalement.
   / Every direct child spans the columns it declares. `minmax(0, ...)`, not
   `1fr`: without it a wide image or <pre> blows the page out horizontally. */
.tb-flux > * {
    grid-column: span var(--tb-colonnes, 12);
    min-width: 0;
}

/* Largeur d'un bloc dans la grille. Classes STRUCTURELLES, volontairement
   separees des classes d'habillage (.tb-bloc--carte & co) : un skin qui a son
   propre look pose la largeur sans heriter du style du socle classic.
   / A block's width in the grid. STRUCTURAL classes, deliberately kept apart
   from styling classes: a skin with its own look can set the width without
   inheriting the classic base styling. */
.tb-flux > .tb-colonnes-4 { --tb-colonnes: 4; }
.tb-flux > .tb-colonnes-6 { --tb-colonnes: 6; }

/* Sur petit ecran, tout repasse sur une seule colonne : un tiers de telephone
   est illisible. / On small screens everything stacks: a third of a phone is
   unreadable. */
@media (max-width: 62rem) {
    .tb-flux > .tb-colonnes-4,
    .tb-flux > .tb-colonnes-6 { --tb-colonnes: 12; }
}

/* Fil d'Ariane (sous-pages) : discret, accent sur les liens, aligné aux blocs.
   / Breadcrumb (sub-pages): discreet, accent links, aligned with the blocks. */
.tb-fil-ariane {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    max-width: var(--tb-largeur-boite);
    margin-inline: auto;
    padding-top: 1.5rem;
    padding-inline: var(--tb-gouttiere);
    font-size: 0.85rem;
    color: var(--tb-texte-doux);
}
.tb-fil-ariane a { color: var(--tb-accent); text-decoration: none; }
.tb-fil-ariane a:hover { text-decoration: underline; }
.tb-fil-ariane a:focus-visible { outline: 2px solid var(--tb-accent); outline-offset: 2px; }

/* Conteneur commun à chaque bloc : largeur max + respiration verticale.
   / Common container for each block: max width + vertical breathing room. */
.tb-bloc {
    box-sizing: border-box;
    /* RYTHME VERTICAL : chaque bloc porte une DEMI-section en haut et en bas,
       donc deux blocs qui se suivent totalisent exactement un espace-section.
       Les bandes a fond colore reprennent la section pleine plus bas : leur
       padding vit DANS leur fond et se lit comme la hauteur de la bande, pas
       comme un espacement.
       / VERTICAL RHYTHM: every block carries half a section top and bottom, so
       two neighbours add up to exactly one section. Tinted bands take the full
       section back below: their padding lives inside their background. */
    padding-block: calc(var(--tb-espace-section) / 2);
    /* Gouttière ALIGNANTE : au moins la gouttière, et au moins ce qu'il faut
       pour poser le bord du contenu sur le conteneur commun (largeur-max
       centrée). Sans ce max(), les blocs à contenu calé à gauche (CTA,
       témoignage, images, médias) démarraient à la gouttière brute (64px sur
       un écran de 1440) pendant que les blocs à contenu centré (titres,
       textes) démarraient au conteneur (144px) — « tout semble non aligné ».
       / ALIGNING gutter: at least the gutter, and at least enough to sit the
       content edge on the shared container (centered max-width). Without the
       max(), left-anchored blocks (CTA, testimonial, images) started at the
       raw gutter while centered ones started at the container. */
    padding-inline: max(var(--tb-gouttiere), calc((100% - var(--tb-largeur-max)) / 2));
    color: var(--tb-texte);
}

.tb-bloc__contenu {
    max-width: var(--tb-largeur-max);
    margin-inline: auto;
    min-width: 0; /* titres qui ne débordent jamais / titles never overflow */
}

/* Titres : serif éditoriale, contraste fort, interlignage serré.
   / Titles: editorial serif, strong contrast, tight leading. */
.tb-bloc__titre {
    margin: 0 0 0.5em;
    font-family: var(--tb-police-titre);
    font-weight: 600;
    line-height: 1.12;
    letter-spacing: -0.01em;
    overflow-wrap: anywhere;
}

/* Kicker éditorial : court filet d'accent au-dessus des titres de section (h2).
   Le HERO (h1) en est exempté (il a sa propre mise en page).
   / Editorial kicker: a short accent rule above section titles (h2). The HERO
   (h1) is exempt (it has its own layout). */
h2.tb-bloc__titre::before {
    content: "";
    display: block;
    width: 2.5rem;
    height: 3px;
    margin-bottom: 1rem;
    background-color: var(--tb-accent);
    border-radius: 2px;
}

.tb-bloc__sous-titre {
    margin: 0 0 1rem;
    font-size: clamp(1.1rem, 2.2vw, 1.4rem);
    line-height: 1.5;
    color: var(--tb-texte-doux);
    max-width: var(--tb-largeur-lecture);
}

.tb-bloc__texte {
    font-size: 1.075rem;
    line-height: 1.75;
    max-width: var(--tb-largeur-lecture);
}
.tb-bloc__texte > :first-child { margin-top: 0; }
.tb-bloc__texte > :last-child { margin-bottom: 0; }
.tb-bloc__texte a { color: var(--tb-accent); text-underline-offset: 0.15em; }
.tb-bloc__texte ul { padding-left: 1.2rem; }
.tb-bloc__texte li { margin-block: 0.25rem; }

/* Images : on neutralise toute décoration héritée du thème et on pose un style
   net (coins arrondis sobres, pas de cadre criard). / Images: neutralize any
   theme-inherited decoration, apply a clean style (sober rounded corners). */
.tb-bloc img { max-width: 100%; height: auto; }

/* ---------------------------------------------------------------------------
 * Boutons : plein (accent) ou contour, une seule teinte. Coin éditorial sobre.
 * / Buttons: filled (accent) or outline, single hue. Sober editorial corner.
 * ------------------------------------------------------------------------- */
.tb-bloc__boutons {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    margin-top: 1.75rem;
}

.tb-bloc__bouton {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    white-space: nowrap; /* jamais cliquable sur deux lignes (Hallmark) */
    min-height: 44px; /* cible tactile mobile (>= 44px, Apple HIG / WCAG) */
    padding: 0.6rem 1.5rem;
    border-radius: var(--tb-rayon-sm);
    border: 1.5px solid var(--tb-accent);
    font-weight: 600;
    letter-spacing: 0.01em;
    text-decoration: none;
    cursor: pointer;
    /* Supprime le delai de tap 300ms sur mobile. / Removes the 300ms tap delay. */
    touch-action: manipulation;
    transition: transform var(--tb-transition), box-shadow var(--tb-transition),
                background-color var(--tb-transition), color var(--tb-transition);
}

/* plein / filled */
.tb-bloc__bouton--plein {
    background-color: var(--tb-accent);
    color: var(--tb-accent-contraste);
}
.tb-bloc__bouton--plein:hover {
    transform: translateY(-2px);
    box-shadow: 0 6px 18px color-mix(in srgb, var(--tb-accent) 35%, transparent);
}
.tb-bloc__bouton--plein:active { transform: translateY(0); box-shadow: none; }

/* contour / outline */
.tb-bloc__bouton--contour {
    background-color: transparent;
    color: var(--tb-accent);
}
.tb-bloc__bouton--contour:hover { background-color: var(--tb-accent); color: var(--tb-accent-contraste); }
.tb-bloc__bouton--contour:active { transform: translateY(1px); }

/* focus visible : anneau net, jamais animé (Hallmark).
   / focus visible: crisp ring, never animated (Hallmark). */
.tb-bloc__bouton:focus-visible {
    outline: 3px solid var(--tb-accent);
    outline-offset: 3px;
}
.tb-bloc__bouton[aria-disabled="true"] { opacity: 0.5; pointer-events: none; }

/* ---------------------------------------------------------------------------
 * HERO — bannière d'ouverture, contenu biaisé à gauche (casse la symétrie).
 * / HERO — opening banner, left-biased content (breaks symmetry).
 * ------------------------------------------------------------------------- */
/* HERO sans image : banniere d'identite recentree (titre + sous-titre centres)
   sur un fond neutre teinte. Pas de biais gauche (il laissait un trou a droite
   sans image), pas de filet decoratif.
   / Imageless HERO: centered identity banner (title + subtitle centered) on a
   tinted neutral background. No left bias (it left a hole on the right without an
   image), no decorative rule. */
.tb-bloc--hero {
    background-color: var(--tb-hero-fond);
    padding-block: clamp(3.5rem, 10vw, 8rem);
    border-bottom: 1px solid var(--tb-bordure);
    text-align: center;
}
.tb-bloc--hero .tb-bloc__contenu {
    margin-inline: auto;
    max-width: 52rem;
}
.tb-bloc--hero .tb-bloc__titre {
    font-size: clamp(2.4rem, 6.5vw, 4.5rem);
    font-weight: 600;
    letter-spacing: -0.02em;
}
.tb-bloc--hero .tb-bloc__sous-titre {
    margin-inline: auto; /* centre le sous-titre / center the subtitle */
}

/* HERO avec image (config.img) : photo en fond + contenu dans une carte alignee
   a gauche (lisibilité assurée par la carte, pas de voile). / HERO with image
   (config.img): photo background + content in a left-aligned card (legibility
   from the card, no scrim). */
.tb-bloc--hero-image {
    background-image: var(--tb-hero-image);
    background-size: cover;
    /* Image centrée horizontalement ET verticalement (sinon, sur écran large et
       court, seul le haut de l'image reste visible).
       / Image centered horizontally AND vertically (otherwise, on wide short
       screens, only the top of the image stays visible). */
    background-position: center center;
    text-align: left;
    /* Hauteur minimale : sur écran large, l'image a de la place et la carte est
       centrée verticalement (pas de bande rognée en haut).
       / Minimum height: on wide screens the image has room and the card is
       vertically centered (no cropped top band). */
    min-height: clamp(360px, 44vh, 600px);
    display: flex;
    align-items: center;
}
.tb-bloc--hero-image .tb-bloc__contenu {
    background-color: var(--tb-surface);
    color: var(--tb-texte);
    border-radius: var(--tb-rayon);
    padding: clamp(1.75rem, 4vw, 3.25rem);
    box-shadow: var(--tb-ombre-forte);
    max-width: 44rem;
    margin-inline: 0 auto; /* carte alignée à gauche / left-aligned card */
}
.tb-bloc--hero-image .tb-bloc__sous-titre {
    margin-inline: 0; /* annule le centrage du cas sans image / cancel centering */
}

/* ---------------------------------------------------------------------------
 * PARAGRAPHE — colonne de lecture éditoriale.
 * / PARAGRAPH — editorial reading column.
 * ------------------------------------------------------------------------- */
.tb-bloc--paragraphe .tb-bloc__titre { font-size: clamp(1.7rem, 4vw, 2.4rem); }
/* Bloc « texte seul » (PARAGRAPHE) : bloc centré dans la page, titre centré,
   texte justifié (avec césure pour éviter les rivières de blanc). Demande
   mainteneur — scopé au seul type PARAGRAPHE. / "Text-only" block: centered
   block, centered title, justified text (with hyphenation). */
.tb-bloc--paragraphe .tb-bloc__titre { text-align: center; }
.tb-bloc--paragraphe .tb-bloc__titre::before { margin-inline: auto; }
.tb-bloc--paragraphe .tb-bloc__texte {
    max-width: var(--tb-largeur-lecture);
    margin-inline: auto;
    text-align: justify;
    text-wrap: pretty;
    hyphens: auto;
}

/* ---------------------------------------------------------------------------
 * IMAGE + TEXTE — grille deux colonnes, alternance gauche/droite.
 * / IMAGE + TEXT — two-column grid, left/right alternation.
 * ------------------------------------------------------------------------- */
.tb-bloc--image-texte {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(1.75rem, 5vw, 4rem);
    align-items: center;
    /* PAS de max-width/margin ici : le padding alignant de .tb-bloc pose déjà
       le contenu sur le conteneur commun. La double contrainte rétrécissait
       les colonnes (images timbre-poste) et les décalait d'une gouttière
       (audit 2026-07-05, « Une salle modulable »). L'image occupe ainsi toute
       sa moitié, bord posé sur le conteneur, quinconce via --image-droite.
       / NO max-width/margin here: .tb-bloc's aligning padding already sits
       the content on the shared container. The double constraint shrank the
       columns (stamp-sized images) and shifted them by one gutter. */
}
.tb-bloc--image-texte .tb-bloc__contenu { max-width: none; margin-inline: 0; }
.tb-bloc--image-texte .tb-bloc__titre { font-size: clamp(1.6rem, 3.5vw, 2.2rem); }
.tb-bloc--image-texte .tb-bloc__image {
    width: 100%;
    height: auto;
    border-radius: var(--tb-rayon);
    box-shadow: var(--tb-ombre);
    display: block;
}
/* Image à droite : on inverse l'ordre des colonnes. / Image right: reverse order. */
.tb-bloc--image-droite .tb-bloc__media { order: 2; }
/* Sans image : une seule colonne, largeur de lecture. / No image: single column. */
.tb-bloc--sans-image { grid-template-columns: 1fr; }
.tb-bloc--sans-image .tb-bloc__contenu { max-width: var(--tb-largeur-max); }

/* ---------------------------------------------------------------------------
 * CTA — bande teintée d'accent, filet à gauche (asymétrie), titre serif.
 * / CTA — accent-tinted band, left rule (asymmetry), serif title.
 * ------------------------------------------------------------------------- */
.tb-bloc--cta {
    background-color: color-mix(in srgb, var(--tb-accent) 7%, var(--tb-surface));
    border-block: 1px solid color-mix(in srgb, var(--tb-accent) 18%, var(--tb-bordure));
}
.tb-bloc--cta .tb-bloc__contenu {
    border-left: 4px solid var(--tb-accent);
    padding-left: clamp(1.25rem, 3vw, 2.25rem);
    max-width: 54rem;
    margin-inline: 0 auto;
}
.tb-bloc--cta .tb-bloc__titre { font-size: clamp(1.7rem, 4vw, 2.6rem); }
/* Le CTA n'a pas de kicker (le filet vertical fait l'accent). / CTA has no kicker. */
.tb-bloc--cta h2.tb-bloc__titre::before { content: none; }

/* ---------------------------------------------------------------------------
 * TEMOIGNAGE — citation éditoriale + grand guillemet typographique + auteur.
 * / TESTIMONIAL — editorial quote + large typographic quotation mark + author.
 * ------------------------------------------------------------------------- */
.tb-bloc--temoignage { margin: 0; background-color: var(--tb-surface-alt); }
.tb-bloc--temoignage .tb-bloc__citation {
    position: relative;
    max-width: 52rem;
    margin: 0 auto;
    padding-top: 2.5rem;
    font-family: var(--tb-police-titre);
    font-size: clamp(1.4rem, 3.2vw, 2rem);
    line-height: 1.4;
    font-style: italic;
    color: var(--tb-texte);
}
/* Grand guillemet ouvrant (décor typographique, pas un emoji).
   / Large opening quotation mark (typographic decor, not an emoji). */
.tb-bloc--temoignage .tb-bloc__citation::before {
    content: "\201C";
    position: absolute;
    top: -0.55em;
    left: -0.05em;
    font-size: 4.5rem;
    line-height: 1;
    color: var(--tb-accent);
    opacity: 0.85;
}
.tb-bloc__auteur {
    display: flex;
    align-items: center;
    gap: 0.85rem;
    max-width: 52rem;
    margin: 1.5rem auto 0;
}
.tb-bloc__auteur-photo {
    width: 52px; height: 52px;
    border-radius: 50%;
    object-fit: cover;
    flex: 0 0 auto;
    box-shadow: var(--tb-ombre);
}
.tb-bloc__auteur-infos { display: flex; flex-direction: column; }
.tb-bloc__auteur-nom { font-weight: 700; }
.tb-bloc__auteur-role { color: var(--tb-texte-doux); font-size: 0.95rem; }

/* ---------------------------------------------------------------------------
 * Grille de cartes (CARTE consécutives regroupées par la vue).
 * / Card grid (consecutive CARTE blocks grouped by the view).
 * ------------------------------------------------------------------------- */
.tb-grille {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
    gap: clamp(1.25rem, 3vw, 2rem);
    max-width: var(--tb-largeur-boite);
    margin-inline: auto;
    padding-block: var(--tb-espace-section);
    padding-inline: var(--tb-gouttiere);
}
/* Carte ISOLEE : ne pas s'etirer sur toute la largeur (auto-fit 1fr l'etirerait).
   On la cape et on la centre. 2-4 cartes remplissent normalement leurs colonnes.
   / LONE card: don't stretch full width (auto-fit 1fr would). Cap + center it.
   2-4 cards fill their columns normally. */
.tb-grille .tb-bloc--carte:only-child {
    max-width: 24rem;
    margin-inline: auto;
}
.tb-grille .tb-bloc { padding: 0; }

/* Carte : image en tete + sur-titre accent + titre serif + texte + bouton.
   Profondeur sobre + lift au survol. / Card: header image + accent overline +
   serif title + text + button. Sober depth + hover lift. */
.tb-bloc--carte {
    display: flex;
    flex-direction: column;
    overflow: hidden;
    border: 1px solid var(--tb-bordure);
    border-radius: var(--tb-rayon);
    background: var(--tb-surface);
    box-shadow: var(--tb-ombre);
    transition: transform var(--tb-transition), box-shadow var(--tb-transition);
}
.tb-bloc--carte:hover { transform: translateY(-4px); box-shadow: var(--tb-ombre-forte); }
/* Image de carte : ENTIERE, jamais recadree.
   `contain` montre toute l'image ; une affiche de concert ou un logo ne perd
   donc ni son titre ni ses bords. La boite garde un `aspect-ratio` FIXE : la
   hauteur d'image est la meme sur toutes les cartes d'une rangee, donc les
   titres, les textes et les boutons restent alignes entre eux. Avec une
   hauteur libre, chaque carte demarrerait son titre a un niveau different.
   Le fond neutre habille les bandes laissees par les images dont les
   proportions different de la boite.
   / Card image: WHOLE, never cropped. `contain` shows the entire image, so a
   gig poster or a logo keeps its edges. The box keeps a FIXED `aspect-ratio`:
   image height is identical across a row, so titles, texts and buttons stay
   aligned. With a free height, every card would start its title at a
   different level. The neutral background dresses the bands left by images
   whose proportions differ from the box. */
.tb-bloc__carte-image {
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: contain;
    background-color: var(--tb-surface-alt);
    display: block;
}
/* Corps de carte : padding interne (l'image est à fleur de bord). */
/* Card body: inner padding (image is flush to the edge). */
.tb-bloc--carte > :not(.tb-bloc__carte-image) { margin-inline: 1.25rem; }
.tb-bloc--carte > .tb-bloc__carte-surtitre { margin-top: 1.25rem; }
.tb-bloc--carte > :last-child { margin-bottom: 1.25rem; }
/* Aligne les boutons des cartes d'une meme grille : la grille etire deja chaque
   carte a la hauteur de la ligne (align-items: stretch), donc margin-top:auto
   pousse le bouton EN BAS de chaque carte -> tous les boutons a la meme hauteur,
   quelle que soit la longueur du texte. Le texte garde un ecart minimal via sa
   propre marge basse (les marges flex ne fusionnent pas).
   / Align buttons across cards in a grid: the grid already stretches each card to
   the row height, so margin-top:auto pushes the button to the BOTTOM of each card
   -> all buttons at the same height, whatever the text length. */
.tb-bloc--carte > .tb-bloc__bouton { margin-top: auto; }

/* Une carte n'est PAS une section : le padding de section gonflerait
   l'interieur de la bordure de ~70px verticaux. L'air interne vient des marges
   de ses enfants. / A card is NOT a section: section padding would inflate the
   inside of its border. Inner air comes from its children's margins. */
.tb-flux > .tb-bloc--carte { padding: 0; }

/* Le texte absorbe l'espace variable : le titre reste cale en haut et le
   bouton en bas, quelle que soit la longueur du texte.
   / The text soaks up the slack: title stays top, button stays bottom. */
.tb-bloc--carte > .tb-bloc__carte-texte { flex-grow: 1; }

/* Meme largeur de bouton sur toutes les cartes d'une rangee.
   / Same button width across a row of cards. */
.tb-bloc--carte .tb-bloc__bouton { align-self: stretch; }

/* De l'air entre l'image et le premier element qui la suit (une carte de
   sous-page commence par un titre, sans sur-titre).
   / Breathing room between the image and whatever follows it. */
.tb-bloc--carte > .tb-bloc__carte-image + * { margin-top: 1.25rem; }

/* Les blocs-colonnes ne sont pas des sections : leur air vertical est le gap
   de la grille, porte en marge. Deux rangees de cartes sont donc separees d'un
   gap complet. / Column blocks are not sections: their vertical air is the
   grid gap, carried as a margin, so two rows end up one full gap apart. */
.tb-flux > .tb-colonnes-4,
.tb-flux > .tb-colonnes-6 {
    padding-block: 0;
    margin-block: calc(var(--tb-grille-gap) / 2);
}
.tb-bloc--carte > .tb-bloc__carte-texte { margin-bottom: 1rem; }
.tb-bloc__carte-surtitre {
    margin-bottom: 0.35rem;
    font-weight: 700;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--tb-accent);
}
.tb-bloc__carte-badge {
    display: inline-block;
    margin-left: 0.4rem;
    padding: 0.05rem 0.55rem;
    border: 1px solid var(--tb-accent);
    border-radius: 999px;
    font-size: 0.72rem;
    letter-spacing: 0.04em;
    vertical-align: middle;
}
.tb-bloc__carte-titre {
    margin: 0 0 0.4rem;
    font-family: var(--tb-police-titre);
    font-weight: 600;
    font-size: 1.3rem;
    line-height: 1.2;
    /* Equilibre le retour a la ligne : evite un mot seul sur la derniere ligne
       (detail UI). / Balance wrapping to avoid an orphan word on the last line. */
    text-wrap: balance;
}
/* Meme equilibrage sur les titres/sous-titres de bloc (HERO, CTA, sections).
   / Same balancing on block titles/subtitles. */
.tb-bloc__titre,
.tb-bloc__sous-titre {
    text-wrap: balance;
}
.tb-bloc__carte-texte { font-size: 0.97rem; line-height: 1.55; color: var(--tb-texte-doux); }
/* Carte sans image : on remet un padding haut pour le surtitre/titre. */
/* Card without image: restore top padding for overline/title. */
.tb-bloc--carte:not(:has(.tb-bloc__carte-image)) > :first-child { margin-top: 1.25rem; }
.tb-bloc--carte .tb-bloc__bouton { align-self: start; }

/* ---------------------------------------------------------------------------
 * SECTION_CARTE : INFOS (gauche) + CARTE_LEAFLET (droite).
 * / INFOS (left) + CARTE_LEAFLET (right).
 * ------------------------------------------------------------------------- */
.tb-bloc--carte-leaflet {
    padding-block: var(--tb-espace-section);
    padding-inline: max(var(--tb-gouttiere), calc((100% - var(--tb-largeur-max)) / 2));
}
.tb-bloc--carte-leaflet .tb-bloc__leaflet-grille {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
    gap: clamp(1.75rem, 4vw, 3.5rem);
    align-items: start;
    max-width: var(--tb-largeur-max);
    margin-inline: auto;
}
.tb-bloc--carte-leaflet .tb-bloc__contenu { margin-inline: 0; }
.tb-bloc__leaflet-carte {
    width: 100%;
    height: 420px;
    border-radius: var(--tb-rayon);
    border: 1px solid var(--tb-bordure);
    box-shadow: var(--tb-ombre);
}
.tb-bloc__carte-legende {
    margin-top: 0.85rem;
    font-weight: 600;
    font-size: 0.9rem;
    letter-spacing: 0.02em;
    color: var(--tb-texte-doux);
}
@media (max-width: 768px) {
    .tb-bloc--carte-leaflet .tb-bloc__leaflet-grille { grid-template-columns: 1fr; }
}

/* Cas SOLO : la carte d'un bloc LIEU rendue SEULE (hors section
   côte-à-côte) n'a pas de wrapper porteur du gutter. On lui redonne le padding
   et le container d'un bloc normal — puis on l'annule dans la grille côte-à-côte
   (qui gère déjà l'espacement). `.tb-bloc__carte` = racine du partial Leaflet.
   / SOLO case: a standalone INFOS or Leaflet block has no gutter-bearing wrapper.
   We restore normal block padding + container, then cancel it inside the side-by
   -side grid (which already handles spacing). `.tb-bloc__carte` = Leaflet root. */
.tb-bloc__infos,
.tb-bloc__carte {
    box-sizing: border-box;
    padding-block: var(--tb-espace-section);
    padding-inline: var(--tb-gouttiere);
    max-width: var(--tb-largeur-boite);
    margin-inline: auto;
}
.tb-bloc__leaflet-grille .tb-bloc__infos,
.tb-bloc__leaflet-grille .tb-bloc__carte {
    padding: 0;
    max-width: none;
    margin-inline: 0;
}

/* ---------------------------------------------------------------------------
 * INFOS : intitulés (badges) soulignés d'accent + horaires + adresse + transports.
 * / INFOS: accent-underlined labels + schedule + address + transport.
 * ------------------------------------------------------------------------- */
.tb-bloc__infos-badge {
    display: inline-block;
    margin: 1.75rem 0 0.85rem;
    padding-bottom: 0.2rem;
    font-weight: 700;
    font-size: 0.85rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--tb-accent);
    border-bottom: 2px solid var(--tb-accent);
}
.tb-bloc__infos > .tb-bloc__infos-badge:first-child { margin-top: 0; }
.tb-bloc__infos-horaire { margin: 0.15rem 0; font-weight: 600; }
.tb-bloc__infos address { font-style: normal; font-weight: 600; line-height: 1.55; }
.tb-bloc__infos > p > em { color: var(--tb-texte-doux); }
.tb-bloc__infos-transport { margin-top: 1.5rem; }
.tb-bloc__infos-transport h3 {
    margin-bottom: 0.5rem;
    padding-bottom: 0.3rem;
    font-size: 0.95rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    border-bottom: 1px solid var(--tb-bordure);
}
.tb-bloc__infos-transport ul { margin: 0; padding-left: 1.1rem; line-height: 1.65; }

/* ---------------------------------------------------------------------------
 * FAQ : questions réparties en 2 colonnes, question en serif + marqueur d'accent.
 * / FAQ: questions in 2 columns, serif question + accent marker.
 * ------------------------------------------------------------------------- */
.tb-faq-colonnes {
    column-count: 2;
    column-gap: clamp(2rem, 5vw, 4rem);
    max-width: var(--tb-largeur-boite);
    margin-inline: auto;
    padding-block: var(--tb-espace-section);
    padding-inline: var(--tb-gouttiere);
}
/* Une entree de FAQ est posee directement dans le flux : c'est elle qui porte
   son filet et son air, sans conteneur intermediaire. Les memes declarations
   sont reprises ci-dessous pour le cas ou un conteneur en colonnes existe.
   / A FAQ entry sits directly in the flux and carries its own rule and air. */
.tb-flux > .tb-bloc--faq {
    padding-left: 1rem;
    border-left: 2px solid var(--tb-bordure);
}

.tb-faq-colonnes .tb-bloc--faq {
    break-inside: avoid;
    margin-bottom: 1.75rem;
    padding-left: 1rem;
    border-left: 2px solid var(--tb-bordure);
    /* Une ENTREE de FAQ n'est pas une SECTION. .tb-bloc pose padding-block:
       var(--tb-espace-section) sur tout bloc — 70px en haut et en bas — pour
       separer des sections les unes des autres. Ici le conteneur .tb-faq-colonnes
       porte deja ce padding : le laisser sur chaque entree le compte DEUX fois et
       matelasse chaque question de 140px pour ~44px de texte. Le filet de gauche
       rend la chose visible : il mesurait 185px de haut pour une ligne.
       L'air entre deux questions, c'est margin-bottom, pas le padding de section.
       / A FAQ ENTRY is not a SECTION. .tb-bloc gives every block the section
       padding meant to separate sections; the .tb-faq-colonnes wrapper already
       carries it, so keeping it on each entry counts it TWICE — 140px of padding
       for one line of text. The air between questions is margin-bottom. */
    padding-block: 0;
}
.tb-bloc__faq-question {
    margin: 0 0 0.5rem;
    font-family: var(--tb-police-titre);
    font-weight: 600;
    font-size: 1.18rem;
    line-height: 1.25;
    color: var(--tb-texte);
}
.tb-bloc__faq-reponse { font-size: 0.98rem; line-height: 1.65; color: var(--tb-texte-doux); }
.tb-bloc__faq-reponse > :first-child { margin-top: 0; }
.tb-bloc__faq-reponse > :last-child { margin-bottom: 0; }
@media (max-width: 768px) {
    .tb-faq-colonnes { column-count: 1; }
}

/* FAQ repliable (accordéon natif <details>) : marqueur +/- d'accent, zéro JS.
   / Collapsible FAQ (native <details> accordion): +/- accent marker, zero JS. */
.tb-bloc__faq-details summary.tb-bloc__faq-question {
    cursor: pointer;
    list-style: none; /* on remplace le triangle natif par notre marqueur */
    position: relative;
    /* Rangée cliquable confortable au doigt (cible tactile >= 44px). */
    /* Comfortable clickable row for touch (>= 44px target). */
    display: flex;
    align-items: center;
    min-height: 44px;
    margin-bottom: 0;
    touch-action: manipulation;
}
.tb-bloc__faq-details summary::-webkit-details-marker { display: none; }
.tb-bloc__faq-details summary::after {
    content: "+";
    /* Placé par le flux du flex (le summary est deja `display: flex`), et NON en
       position: absolute. Dans un conteneur `column-count`, Chrome peint un
       descendant absolu dans le mauvais fragment de colonne : le marqueur d'une
       question de la colonne gauche apparaissait en haut de la colonne droite,
       et sa question se retrouvait sans marqueur. `margin-left: auto` le pousse
       a droite sans dependre du fragment.
       / Placed by the flex flow, NOT position: absolute. Inside a `column-count`
       container Chrome paints an absolute descendant in the wrong column
       fragment: the marker of a left-column question showed at the top of the
       right column, leaving its own question unmarked. */
    margin-left: auto;
    padding-left: 0.75rem;
    font-weight: 400;
    color: var(--tb-accent);
}
.tb-bloc__faq-details[open] summary::after { content: "\2212"; } /* signe moins */
.tb-bloc__faq-details summary:focus-visible {
    outline: 3px solid var(--tb-accent);
    outline-offset: 3px;
    border-radius: 2px;
}
.tb-bloc__faq-details[open] .tb-bloc__faq-reponse { margin-top: 0.6rem; }

/* ---------------------------------------------------------------------------
 * GALERIE — grille responsive d'images avec légende.
 * / GALLERY — responsive image grid with caption.
 * ------------------------------------------------------------------------- */
.tb-galerie {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 14rem), 1fr));
    gap: clamp(0.75rem, 2vw, 1.25rem);
    margin-top: 1.75rem;
}
.tb-galerie__item { margin: 0; }
.tb-galerie__image {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: var(--tb-rayon-sm);
    box-shadow: var(--tb-ombre);
    display: block;
}
.tb-galerie__legende {
    margin-top: 0.4rem;
    font-size: 0.85rem;
    color: var(--tb-texte-doux);
}

/* ---------------------------------------------------------------------------
 * EMBED — contenu intégré (YouTube/Vimeo), wrapper 16:9 responsive.
 * / EMBED — embedded content (YouTube/Vimeo), responsive 16:9 wrapper.
 * ------------------------------------------------------------------------- */
.tb-embed {
    position: relative;
    aspect-ratio: 16 / 9;
    margin-top: 1.5rem;
    border-radius: var(--tb-rayon);
    overflow: hidden;
    box-shadow: var(--tb-ombre);
    background-color: #000;
}
.tb-embed iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

/* ---------------------------------------------------------------------------
 * IMAGE seule — centrée, profondeur sobre.
 * / Standalone IMAGE — centered, sober depth.
 * ------------------------------------------------------------------------- */
.tb-bloc--image {
    text-align: center;
    /* Une image pleine largeur EST le contenu, pas un texte pose dans une section :
       elle n'a pas besoin d'une section d'air autour d'elle pour se detacher, son
       cadre le fait deja. Avec le padding de section entier, une image de 640px
       occupait un bloc de 780px, et deux blocs consecutifs mettaient 140px entre
       leurs contenus. La moitie suffit a la separer de ses voisines.
       / A full-width image IS the content, not text sitting inside a section: its
       own frame already detaches it, so it does not need a section's worth of air.
       Half the section spacing is enough to separate it from its neighbours. */
    padding-block: calc(var(--tb-espace-section) / 2);
}
.tb-bloc__image-seule {
    max-width: 100%;
    height: auto;
    border-radius: var(--tb-rayon);
    box-shadow: var(--tb-ombre);
}

/* ---------------------------------------------------------------------------
 * VIDEO + TEXTE — grille 2 colonnes (vidéo plus large), empile en mobile.
 * / VIDEO + TEXT — 2-col grid (wider video), stacks on mobile.
 * ------------------------------------------------------------------------- */
.tb-bloc--video-texte .tb-bloc__video-grille {
    display: grid;
    grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
    gap: clamp(1.75rem, 4vw, 3.5rem);
    align-items: center;
    max-width: var(--tb-largeur-max);
    margin-inline: auto;
}
.tb-bloc--video-texte .tb-bloc__video-media {
    width: 100%;
    border-radius: var(--tb-rayon);
    box-shadow: var(--tb-ombre);
    display: block;
}
.tb-bloc--video-texte .tb-bloc__contenu { margin-inline: 0; }
.tb-bloc--video-texte .tb-bloc__titre { font-size: clamp(1.6rem, 3.5vw, 2.2rem); }
@media (max-width: 768px) {
    .tb-bloc--video-texte .tb-bloc__video-grille { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
 * Responsive : sous 768px on empile image+texte (image au-dessus du texte).
 * / Responsive: below 768px, stack image+text (image above text).
 * ------------------------------------------------------------------------- */
@media (max-width: 768px) {
    .tb-bloc--image-texte { grid-template-columns: 1fr; }
    .tb-bloc--image-droite .tb-bloc__media { order: 0; }
}

/* Respecte la préférence « moins d'animations ».
   / Respects the "reduced motion" preference. */
@media (prefers-reduced-motion: reduce) {
    .tb-bloc__bouton,
    .tb-bloc--carte { transition: none; }
    .tb-bloc__bouton--plein:hover,
    .tb-bloc--carte:hover { transform: none; box-shadow: var(--tb-ombre); }
}

/* ============================================================
   BLOC TEXTE — contenu long redige en Markdown.
   Habille le HTML produit par le filtre rendre_markdown (titres,
   listes, code, citations, tableaux). Le skin peut surcharger via
   son propre CSS. / MARKDOWN block — long-form text. Styles the
   HTML produced by the rendre_markdown filter.
   ============================================================ */
/* Interligne 1.8 : le confort de lecture d'un texte long tient d'abord a
   l'espace entre les lignes. Les sites de documentation qui se lisent bien
   sont tous autour de cette valeur.
   / 1.8 line height: reading comfort on long text comes first from the space
   between lines. Documentation sites that read well all sit around this. */
.tb-markdown { line-height: 1.8; max-width: 72ch; }
.tb-markdown h1, .tb-markdown h2, .tb-markdown h3,
.tb-markdown h4, .tb-markdown h5, .tb-markdown h6 {
    /* Beaucoup d'air AVANT, peu APRES : le titre appartient visuellement au
       paragraphe qu'il annonce, pas a celui qu'il termine.
       / Much air BEFORE, little AFTER: the heading visually belongs to the
       paragraph it announces, not the one it ends. */
    margin: 2.2em 0 .6em;
    line-height: 1.25;
}
.tb-markdown > :first-child { margin-top: 0; }
.tb-markdown p { margin: 0 0 1em; }
.tb-markdown ul, .tb-markdown ol { margin: 0 0 1em; padding-left: 1.5em; }
.tb-markdown li { margin-bottom: .35em; }
.tb-markdown blockquote {
    margin: 1em 0;
    padding: .5em 1em;
    border-left: 4px solid var(--tb-accent, currentColor);
    opacity: .9;
}
.tb-markdown code {
    font-family: ui-monospace, monospace;
    font-size: .9em;
    padding: .1em .35em;
    border-radius: .25em;
    background: var(--tb-surface-alt, rgba(127, 127, 127, .15));
}
.tb-markdown pre {
    overflow-x: auto;
    padding: 1em;
    border-radius: .5em;
    background: var(--tb-surface-alt, rgba(127, 127, 127, .12));
}
.tb-markdown pre code { padding: 0; background: none; }
.tb-markdown table { border-collapse: collapse; margin: 0 0 1em; width: 100%; }
.tb-markdown th, .tb-markdown td {
    border: 1px solid var(--tb-surface-alt, rgba(127, 127, 127, .35));
    padding: .4em .6em;
    text-align: left;
}
.tb-markdown img { max-width: 100%; height: auto; }
.tb-markdown hr { margin: 2em 0; border: 0; border-top: 1px solid var(--tb-surface-alt, rgba(127, 127, 127, .35)); }

/* Titre de secours d'une page sans banniere (h1 SEO/a11y).
   / Fallback title of a CMS page without a HERO block. */
.tb-page__titre {
    margin: 1.2em auto .4em;
    max-width: var(--tb-largeur-boite);
    padding-inline: var(--tb-gouttiere);
    line-height: 1.2;
    text-wrap: balance;
}

/* Signature date/auteur des articles (E-E-A-T).
   / Article date/author byline (E-E-A-T). */
.tb-page__signature {
    margin: 0 auto .5em;
    max-width: var(--tb-largeur-boite);
    padding-inline: var(--tb-gouttiere);
    font-size: .9em;
}


/* ============================================================
   RYTHME VERTICAL TITRE -> GRILLE (audit grille 2026-07-05).
   Une section-titre suivie d'une grille de cartes lui colle : sans ces
   règles, l'espacement bas de la section + l'espacement haut de la grille
   s'additionnaient (~2 x espace-section de vide entre « Nos prochains
   rendez-vous » et ses cartes).
   / TITLE -> GRID VERTICAL RHYTHM. A title section followed by a card grid
   sticks to it: without these rules, the section's bottom spacing + the
   grid's top spacing stacked up (~2 x section spacing of emptiness).
   ============================================================ */
/* Le titre d'une liste colle a la grille de cartes qui le suit.
   La classe rendue par bloc_liste.html est `tb-bloc--liste-titre`.
   / A list title hugs the card grid that follows it. */
.tb-bloc--liste-titre { padding-block-end: 0; }
.tb-bloc--liste-titre + .tb-grille { padding-block-start: 1.5rem; }

.tb-bloc--evenements-titre,
.tb-bloc--liste-sous-pages-titre {
    padding-block-end: 0;
}
.tb-bloc--evenements-titre + .tb-grille,
.tb-bloc--liste-sous-pages-titre + .tb-grille,
.tb-bloc--paragraphe + .tb-grille {
    padding-block-start: 1.5rem;
}

/* Titres équilibrés : pas de mot orphelin sur la dernière ligne.
   / Balanced titles: no orphan word on the last line. */
.tb-bloc__titre,
.tb-bloc__carte-titre {
    text-wrap: balance;
}


/* Bloc IMAGE en mode vignette-titre (champ explicite affichage_image) :
   taille naturelle, centrée — pour les petites images-titres dessinées.
   / IMAGE block in title-thumbnail mode (explicit affichage_image field):
   natural size, centered — for small drawn title-images. */
.tb-bloc__image-seule--vignette {
    width: auto;
    max-width: min(100%, 32rem);
    margin-inline: auto;
    display: block;
}

/* ---------------------------------------------------------------------------
 * IFRAME — contenu intégré libre (newsletter, formulaire). Hauteur fixée par le
 * bloc (attribut height sur l'iframe), largeur 100 %.
 * / IFRAME — free embedded content. Height set by the block, full width.
 * ------------------------------------------------------------------------- */
.tb-iframe { width: 100%; margin-top: 1.5rem; }
.tb-iframe iframe {
    width: 100%;
    border: 0;
    border-radius: var(--tb-rayon-sm);
    box-shadow: var(--tb-ombre);
    display: block;
    max-width: 100%;
}

/* ---------------------------------------------------------------------------
 * PARTENAIRES — bande de logos. Grille responsive ; logos en niveaux de gris au
 * repos, couleur au survol/focus (transition sur filter/opacity uniquement).
 * / PARTENAIRES — logo strip. Grayscale at rest, color on hover/focus.
 * ------------------------------------------------------------------------- */
.tb-partenaires {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 8rem), 1fr));
    gap: clamp(1rem, 3vw, 2rem);
    align-items: center;
    margin-top: 1.75rem;
}
.tb-partenaires__logo {
    width: 100%;
    max-height: 4.5rem;
    object-fit: contain;
    filter: grayscale(1);
    opacity: 0.75;
    transition: filter 0.2s ease, opacity 0.2s ease;
}
.tb-partenaires__lien:hover .tb-partenaires__logo,
.tb-partenaires__lien:focus-visible .tb-partenaires__logo,
.tb-partenaires__logo:hover {
    filter: grayscale(0);
    opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
    .tb-partenaires__logo { transition: none; }
}

/* ---------------------------------------------------------------------------
 * NEWSLETTER — formulaire d'inscription Ghost (script vendorisé injectant un
 * iframe). Le conteneur réserve une hauteur pour éviter le saut de mise en page
 * avant le rendu du formulaire.
 * / NEWSLETTER — Ghost signup form (vendored script injecting an iframe). The
 * container reserves a height to avoid layout shift before the form renders.
 * ------------------------------------------------------------------------- */
.tb-newsletter {
    min-height: 360px;
    margin-top: 1.5rem;
}
.tb-newsletter iframe {
    width: 100%;
    max-width: 100%;
    border: 0;
    display: block;
}

/* --------------------------------------------------------------------------
   BLOC SECTION / affichage MEDIA_ET_CARTES — le media, le texte, puis les
   sous-cartes portees par le champ `contenu` du bloc.
   / SECTION block / MEDIA_ET_CARTES display — media, text, then the sub-cards
   carried by the block's `contenu` field.
   -------------------------------------------------------------------------- */
.tb-bloc--media-cartes .tb-bloc__video,
.tb-bloc--media-cartes .tb-bloc__image {
    width: 100%;
    max-height: 26rem;
    object-fit: cover;
    border-radius: var(--tb-rayon);
    margin-bottom: 1.5rem;
}

/* Les sous-cartes se rangent d'elles-memes : autant de colonnes que la largeur
   en autorise, jamais moins de 14rem chacune.
   / Sub-cards flow on their own: as many columns as the width allows. */
.tb-sous-cartes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
    gap: 1.25rem;
    margin-block: 1.75rem;
}

.tb-sous-carte {
    padding-left: 1rem;
    border-left: 3px solid var(--tb-accent);
}

.tb-sous-carte__titre {
    margin: 0 0 0.35rem;
    font-family: var(--tb-police-titre);
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--tb-texte);
}

.tb-sous-carte__texte {
    font-size: 0.95rem;
    line-height: 1.6;
    color: var(--tb-texte-doux);
}

/* --------------------------------------------------------------------------
   SOMMAIRE DE PAGE — les titres des blocs de texte, replies dans un <details>.
   / PAGE TABLE OF CONTENTS — the text blocks' headings, in a <details>.
   -------------------------------------------------------------------------- */
/* Le sommaire vit dans la marge DROITE, en vis-a-vis du menu de rubrique a
   gauche : la page se lit alors entre deux reperes — ou je suis dans le site,
   ou j'en suis dans la page. C'est la disposition des sites de documentation.
   Ni fond ni cadre : du texte pose dans la marge.
   / The table of contents lives in the RIGHT margin, facing the section menu
   on the left: the page then reads between two markers — where I am in the
   site, where I am in the page. No background, no frame: text in the margin. */
.tb-sommaire {
    padding: 0;
    border: 0;
    background: none;
    margin-block: 0;
}

.tb-sommaire__titre {
    cursor: pointer;
    font-family: var(--tb-police-titre);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--tb-texte-doux);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.tb-sommaire__liste {
    margin: 0.9rem 0 0;
    padding-left: 0;
    list-style: none;
}

.tb-sommaire__entree { margin-block: 0; font-size: 0.9rem; line-height: 1.35; }
.tb-sommaire__entree a {
    display: block;
    padding: 0.3rem 0.6rem;
    border-radius: var(--tb-rayon-sm);
    color: var(--tb-texte-doux);
    text-decoration: none;
    transition: background-color var(--tb-transition), color var(--tb-transition);
}
.tb-sommaire__entree a:hover {
    color: var(--tb-texte);
    background: var(--tb-surface-alt);
}

/* Section en cours de lecture, posee par pages/js/sommaire_actif.js. Le trait
   vertical marque la position autant que la couleur : sur une liste de titres,
   un simple changement de teinte se repere mal du coin de l'oeil.
   Sans JavaScript, aucune entree ne porte cette classe et le sommaire reste
   une liste de liens ordinaire.
   / Section currently being read, set by pages/js/sommaire_actif.js. The
   vertical rule marks the position as much as the colour does. Without
   JavaScript no entry carries this class and the ToC stays a plain list. */
.tb-sommaire__entree--active > a {
    color: var(--tb-accent);
    font-weight: 600;
    box-shadow: inset 2px 0 0 var(--tb-accent);
}

/* Le deplacement du marqueur suit la lecture : anime, il attire l'oeil hors du
   texte. On ne l'anime donc pas, et on respecte de toute facon la preference
   systeme. / The marker follows the reading: animating it would pull the eye
   away from the text. */
@media (prefers-reduced-motion: reduce) {
    .tb-sommaire__entree a { transition: none; }
}

/* Le retrait dit la profondeur du titre. / Indentation shows heading depth. */
.tb-sommaire__entree--niveau-2 { padding-left: 0.6rem; }
.tb-sommaire__entree--niveau-3 { padding-left: 1.2rem; }
.tb-sommaire__entree--niveau-4 { padding-left: 1.8rem; }

/* Une ancre visee par le sommaire ne doit pas se retrouver sous une entete
   collante. / An anchor targeted by the summary must not land under a sticky
   header. */
.tb-markdown :is(h2, h3, h4, h5, h6) { scroll-margin-top: 5rem; }

/* --------------------------------------------------------------------------
   COLORATION SYNTAXIQUE (classes produites par codehilite/Pygments).
   Palette SOBRE et monochrome-plus-un-accent : un bloc de code doit se lire,
   pas clignoter. Seuls les elements qui portent du sens sont teintes —
   commentaires en retrait, chaines et nombres a l'accent, mots-cles en gras.
   / SYNTAX HIGHLIGHTING (codehilite/Pygments classes). SOBER palette: a code
   block should read, not blink. Only meaningful tokens are tinted.
   -------------------------------------------------------------------------- */
.tb-markdown .codehilite {
    overflow-x: auto;                 /* un code long defile DANS son cadre */
    padding: 1rem 1.25rem;
    border-radius: var(--tb-rayon-sm);
    background: var(--tb-surface-alt);
    border: 1px solid var(--tb-bordure);
}

.tb-markdown .codehilite pre { margin: 0; }

.tb-markdown .codehilite .c,                     /* commentaire */
.tb-markdown .codehilite .c1,
.tb-markdown .codehilite .cm { color: var(--tb-texte-doux); font-style: italic; }

.tb-markdown .codehilite .k,                     /* mot-cle */
.tb-markdown .codehilite .kd,
.tb-markdown .codehilite .kn { font-weight: 600; }

.tb-markdown .codehilite .s,                     /* chaine */
.tb-markdown .codehilite .s1,
.tb-markdown .codehilite .s2,
.tb-markdown .codehilite .mi,                    /* nombre */
.tb-markdown .codehilite .mf { color: var(--tb-accent); }

/* --------------------------------------------------------------------------
   MENU LATERAL — l'arbre de la rubrique, a gauche du contenu sur grand ecran.
   / SIDE MENU — the section's tree, left of the content on wide screens.
   -------------------------------------------------------------------------- */
.tb-menu-lateral {
    --tb-colonnes: 3;
    /* Padding horizontal minimal : la colonne fait ~264 px, et chaque rem
       rogne sur la largeur de texte. Avec 1.25rem de chaque cote il ne restait
       que 188 px, ou un titre d'article s'enroulait sur quatre lignes.
       / Minimal horizontal padding: the column is ~264px and every rem eats
       into the text width. With 1.25rem each side only 188px were left, where
       an article title wrapped onto four lines. */
    padding: 0.25rem 0.75rem 0.25rem 0;

    /* Le menu occupe sa colonne sur TOUTES les lignes de la grille.
       `1 / -1` ne conviendrait pas : `-1` designe la derniere ligne EXPLICITE,
       or les lignes sont ici implicites (creees par le flux des blocs) — le
       menu ne couvrirait que la premiere, les blocs suivants repasseraient
       dessous et le menu colle en `sticky` les recouvrirait au defilement.
       / The menu spans its column across EVERY row. `1 / -1` would not do:
       `-1` means the last EXPLICIT row, and rows here are implicit — the menu
       would cover only the first one and the sticky menu would then overlap
       the blocks flowing underneath. */
    grid-column: 1 / span 3;
    grid-row: 1 / span 999;
    align-self: start;
    /* Le menu suit la lecture sur les pages longues.
       PAS de `max-height` + `overflow-y` : une barre de defilement dans la
       marge attire l'oeil autant que le contenu qu'elle sert. Le menu coule
       librement ; sur une rubrique tres longue il defile avec la page, ce qui
       est le comportement attendu d'une table des matieres de site.
       / The menu follows the reading on long pages. NO `max-height` +
       `overflow-y`: a scrollbar in the margin draws as much attention as the
       content it serves. The menu flows freely. */
    position: sticky;
    top: var(--tb-sticky-top);
}

/* Le titre du menu est une etiquette, pas un titre de section : discret,
   il annonce la rubrique sans concurrencer le titre de la page.
   / The menu title is a label, not a section heading: discreet. */
.tb-menu-lateral__titre {
    cursor: pointer;
    font-family: var(--tb-police-titre);
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--tb-texte-doux);
    text-transform: uppercase;
    letter-spacing: 0.06em;
}

.tb-menu-lateral__liste { margin: 0.9rem 0 0; padding-left: 0; list-style: none; }
.tb-menu-lateral__entree { margin-block: 0; font-size: 0.9rem; line-height: 1.35; }

/* Chaque entree est une zone cliquable pleine largeur : la cible est confortable
   et le survol se lit comme un element d'interface, pas comme un lien de texte.
   / Each entry is a full-width click target: comfortable to hit, and the hover
   reads as a UI element rather than a text link. */
.tb-menu-lateral__entree a {
    display: block;
    padding: 0.3rem 0.6rem;
    border-radius: var(--tb-rayon-sm);
    color: var(--tb-texte-doux);
    text-decoration: none;
    transition: background-color var(--tb-transition), color var(--tb-transition);
}
.tb-menu-lateral__entree a:hover {
    color: var(--tb-texte);
    background: var(--tb-surface-alt);
}

/* La page ouverte se distingue sans dependre de la seule couleur : le fond
   plein la designe, le poids la confirme. Pas de filet lateral — il ajoutait
   un trait de plus dans une marge qu'on cherche a calmer.
   / The open page stands out without relying on colour alone: a solid
   background names it, the weight confirms it. No side rule — it added one
   more line to a margin we are trying to quieten. */
/* La page ouverte n'est PAS un lien (on y est deja) : le gabarit rend un
   <span>, pas un <a>. Le selecteur doit donc viser l'element lui-meme, et
   reprendre le calage du lien voisin pour que la colonne reste alignee.
   / The open page is NOT a link (we are already on it): the template renders a
   <span>. The selector targets the element itself and repeats the sibling
   link's padding so the column stays aligned. */
.tb-menu-lateral__courante {
    display: block;
    padding: 0.3rem 0.6rem;
    border-radius: var(--tb-rayon-sm);
    font-weight: 600;
    color: var(--tb-accent);
    background: var(--tb-surface-alt);
}

/* Le retrait dit la profondeur dans l'arbre. Il s'applique a l'entree, et le
   lien remplit ce qui reste : la zone cliquable suit le retrait.
   / Indentation shows tree depth, applied to the entry so the link fills what
   is left: the click target follows the indent. */
.tb-menu-lateral__entree--niveau-2 { padding-left: 0.6rem; }
.tb-menu-lateral__entree--niveau-3 { padding-left: 1.2rem; }
.tb-menu-lateral__entree--niveau-4 { padding-left: 1.8rem; }
.tb-menu-lateral__entree--niveau-5 { padding-left: 2.4rem; }
.tb-menu-lateral__entree--niveau-6 { padding-left: 3rem; }

/* Quand le menu est la, tout le reste du contenu lui laisse la place.
   On cible les ENFANTS DIRECTS plutot qu'une liste de classes : chaque skin
   habille ses blocs a sa facon (le skin festival enveloppe les siens dans des
   `container` Bootstrap), et une liste nommee en oublierait toujours un — il
   passerait alors en pleine largeur et casserait la colonne.
   Les blocs qui declarent deja leur largeur (cartes `.tb-colonnes-4`, FAQ
   `.tb-colonnes-6`) sont exclus nommement : sans cela ils recevaient toute la
   colonne de contenu et trois cartes s'empilaient au lieu de se ranger.
   / When the menu is there, everything else makes room for it. DIRECT CHILDREN
   are targeted rather than a list of classes: each skin dresses its blocks
   differently, and a named list would always miss one. Blocks that already
   declare their width are excluded. */
.tb-flux:has(> .tb-menu-lateral) > *:not(.tb-menu-lateral):not(.tb-sommaire):not(.tb-colonnes-4):not(.tb-colonnes-6) {
    /* Placement EXPLICITE dans la colonne de droite, et non un simple `span 9` :
       l'auto-placement remplirait les colonnes libres a partir de la premiere,
       donc sous le menu. / EXPLICIT placement in the right-hand column, not a
       plain `span 9`: auto-placement would fill free columns from the first
       one, i.e. underneath the menu. */
    grid-column: 4 / -1;
}

/* Un bloc pleine largeur (banniere, appel a l'action, citation) etale ses
   marges negatives sur 100vw pour toucher les bords de l'ecran. Des qu'une
   marge est occupee par le menu ou le sommaire, ces marges le recouvrent : sur
   un ecran large le bandeau mangeait la moitie du menu. Dans ces modes, le
   bloc reste donc dans sa zone et retrouve un padding normal.
   / A full-bleed block spreads negative margins over 100vw to touch the screen
   edges. As soon as a margin is taken by the menu or the table of contents,
   those margins cover it. In these modes the block stays inside its area. */
.tb-flux:has(> .tb-menu-lateral) > :is(.tb-pleine-largeur, .tb-bloc--hero, .tb-bloc--cta, .tb-bloc--temoignage),
.tb-flux:has(> .tb-sommaire) > :is(.tb-pleine-largeur, .tb-bloc--hero, .tb-bloc--cta, .tb-bloc--temoignage) {
    margin-inline: 0;
    padding-inline: clamp(1.25rem, 3vw, 2.25rem);
    border-radius: var(--tb-rayon);
}

/* Sur GRAND ecran, le menu et le sommaire sont deplies d'office : ce sont des
   colonnes de reperage, pas des tiroirs. L'ouverture se fait ICI et non par un
   attribut `open` dans le gabarit, parce qu'un `open` en dur vaut AUSSI pour le
   mobile — le menu deplie y occupait 845 px avant le titre de l'article, soit
   presque deux ecrans de defilement avant la premiere ligne de texte.
   `::details-content` permet de deplier depuis la feuille de style. Sur un
   navigateur qui ne le connait pas encore, les deux blocs restent replies et
   s'ouvrent au clic : une degradation lisible, pas une page cassee.
   / On LARGE screens the menu and table of contents are unfolded by default:
   they are locating columns, not drawers. The unfolding happens HERE and not
   through an `open` attribute in the template, because a hard-coded `open`
   also applies to mobile — the unfolded menu took 845px before the article
   title. Browsers without `::details-content` keep them folded, opening on
   click: a readable degradation, not a broken page. */
@media (min-width: 62.001rem) {
    .tb-menu-lateral::details-content,
    .tb-sommaire::details-content {
        content-visibility: visible;
        block-size: auto;
    }

    /* Deplies d'office, ces deux blocs ne sont plus des tiroirs : le triangle
       promettrait un pliage qui n'a plus lieu, et le curseur main suggererait
       une action. On retire les deux — l'intitule redevient une etiquette.
       En mobile le marqueur reste, puisque le pliage y sert vraiment.
       / Unfolded by default, these two are no longer drawers: the triangle
       would promise a fold that no longer happens. On mobile the marker stays,
       where folding genuinely serves. */
    .tb-menu-lateral > summary,
    .tb-sommaire > summary {
        list-style: none;
        cursor: default;
    }
    .tb-menu-lateral > summary::-webkit-details-marker,
    .tb-sommaire > summary::-webkit-details-marker {
        display: none;
    }
}

/* --------------------------------------------------------------------------
   TROIS COLONNES — menu de rubrique | contenu | sommaire de la page.
   / THREE COLUMNS — section menu | content | page table of contents.

   Le sommaire n'existe que si la page a des titres : une page-index qui ne
   liste que des cartes n'en a pas, et la grille retombe alors d'elle-meme sur
   deux colonnes. Aucune colonne vide a prevoir — c'est `:has()` qui decide,
   pas une classe posee dans le gabarit.
   / The table of contents only exists when the page has headings: an index
   page listing cards has none, and the grid falls back to two columns on its
   own. No empty column to plan for — `:has()` decides, not a class in the
   template.
   -------------------------------------------------------------------------- */
/* Avec deux marges occupees, le texte se retrouve a l'etroit : sur la largeur
   par defaut il ne restait que ~55 caracteres par ligne, en deca du confort de
   lecture (65 a 75). On elargit donc la grille dans ce mode — et seulement
   dans ce mode, pour ne pas toucher aux pages en pleine largeur.
   La grille garde ses 12 colonnes : les blocs-cartes s'y placent toujours de
   la meme facon. / With both margins occupied the text gets cramped: at the
   default width only ~55 characters per line remained, below the comfortable
   range (65-75). So we widen the grid in this mode only. The grid keeps its 12
   columns: card blocks still place themselves the same way. */
.tb-flux:has(> .tb-menu-lateral):has(> .tb-sommaire) {
    --tb-largeur-max: 84rem;
}

.tb-flux:has(> .tb-sommaire) > .tb-sommaire {
    --tb-colonnes: 3;
    grid-column: 10 / -1;
    grid-row: 1 / span 999;
    align-self: start;
    position: sticky;
    top: var(--tb-sticky-top);
}

/* Le contenu s'arrete avant la colonne du sommaire. Sans menu a gauche il
   part de la premiere colonne, avec menu il part de la quatrieme.
   / The content stops before the table-of-contents column. */
.tb-flux:has(> .tb-sommaire) > *:not(.tb-sommaire):not(.tb-menu-lateral):not(.tb-colonnes-4):not(.tb-colonnes-6) {
    grid-column: 1 / span 9;
}
.tb-flux:has(> .tb-menu-lateral):has(> .tb-sommaire) > *:not(.tb-sommaire):not(.tb-menu-lateral):not(.tb-colonnes-4):not(.tb-colonnes-6) {
    grid-column: 4 / span 6;
}

/* Le titre, le fil d'Ariane et la signature portent leur propre gouttiere,
   utile quand ils s'etendent sur toute la largeur de la page. En trois
   colonnes cette gouttiere fait double emploi avec celle de la grille : le
   titre se decalait alors de 12 px vers la droite par rapport au texte, ce qui
   se lit comme un defaut d'alignement.
   / Title, breadcrumb and byline carry their own gutter, useful when they span
   the full page width. In three columns that gutter duplicates the grid's: the
   title ended up 12px right of the body text, reading as a misalignment. */
.tb-flux:has(> .tb-sommaire) > .tb-page__titre,
.tb-flux:has(> .tb-sommaire) > .tb-fil-ariane,
.tb-flux:has(> .tb-sommaire) > .tb-page__signature,
.tb-flux:has(> .tb-menu-lateral) > .tb-page__titre,
.tb-flux:has(> .tb-menu-lateral) > .tb-fil-ariane,
.tb-flux:has(> .tb-menu-lateral) > .tb-page__signature {
    padding-inline: 0;
    margin-inline: 0;
    max-width: none;
}

/* Le fil d'Ariane precede le titre DANS LE GABARIT : plus besoin de le
   remonter en CSS, et l'ordre de lecture d'un lecteur d'ecran suit celui de
   l'oeil. Il ne reste que le calage vertical.
   / The breadcrumb precedes the title IN THE TEMPLATE: no CSS reordering
   needed, and a screen reader's reading order matches the eye's. */
.tb-flux:has(> .tb-menu-lateral) > .tb-fil-ariane {
    padding-top: 0;
}

/* Entre 62rem et 75rem (tablette en paysage), les trois colonnes TIENNENT mais
   le texte tombe a ~51 caracteres par ligne — sous le confort de lecture que
   cette disposition cherche justement a retrouver. On sacrifie donc le
   sommaire AVANT le menu : savoir ou l'on est dans le site importe plus que
   savoir ou l'on en est dans la page. Le sommaire repasse dans le flux, replie.
   / Between 62rem and 75rem the three columns FIT but the text drops to ~51
   characters per line. So we give up the table of contents BEFORE the menu:
   knowing where you are in the site matters more than where you are in the
   page. It returns to the flow, folded. */
@media (min-width: 62.001rem) and (max-width: 75rem) {
    .tb-flux:has(> .tb-menu-lateral):has(> .tb-sommaire) {
        --tb-largeur-max: 72rem;
    }
    .tb-flux:has(> .tb-menu-lateral) > .tb-sommaire {
        grid-column: 4 / -1;
        grid-row: auto;
        position: static;
    }
    .tb-flux:has(> .tb-menu-lateral):has(> .tb-sommaire) > *:not(.tb-sommaire):not(.tb-menu-lateral):not(.tb-colonnes-4):not(.tb-colonnes-6) {
        grid-column: 4 / -1;
    }
    /* Replie a nouveau : sur cette largeur le sommaire precede l'article.
       / Folded again: at this width the ToC precedes the article. */
    .tb-sommaire::details-content { content-visibility: hidden; }
}

/* Sur petit ecran, le menu passe au-dessus du contenu, replie en tiroir.
   / On small screens the menu moves above the content, folded as a drawer. */
@media (max-width: 62rem) {
    .tb-menu-lateral {
        --tb-colonnes: 12;
        position: static;
        max-height: none;
        padding: 0.5rem 0;
        border-bottom: 1px solid var(--tb-bordure);
    }
    /* Sur petit ecran, plus de colonne laterale : tout s'empile. Le sommaire
       repasse dans le flux, sous le titre, la ou il se lit avant l'article.
       / On small screens there is no side column: everything stacks. The table
       of contents returns to the flow, under the title. */
    /* Ces selecteurs REPRENNENT ceux du mode colonnes, exclusions comprises.
       Une media query ne l'emporte pas sur la specificite : la regle desktop
       compte six classes, une regle mobile qui n'en compterait que deux
       perdrait, et le contenu resterait sur six colonnes d'un ecran de
       telephone — soit une colonne de 200 px ou chaque mot du titre tombe sur
       sa propre ligne.
       / These selectors MIRROR the column-mode ones, exclusions included. A
       media query does not beat specificity: the desktop rule counts six
       classes, so a two-class mobile rule would lose and the content would
       stay on six columns of a phone screen. */
    .tb-flux:has(> .tb-menu-lateral) > *,
    .tb-flux:has(> .tb-menu-lateral) > .tb-menu-lateral,
    .tb-flux:has(> .tb-sommaire) > *,
    .tb-flux:has(> .tb-sommaire) > .tb-sommaire,
    .tb-flux:has(> .tb-menu-lateral) > *:not(.tb-menu-lateral):not(.tb-sommaire):not(.tb-colonnes-4):not(.tb-colonnes-6),
    .tb-flux:has(> .tb-sommaire) > *:not(.tb-sommaire):not(.tb-menu-lateral):not(.tb-colonnes-4):not(.tb-colonnes-6),
    .tb-flux:has(> .tb-menu-lateral):has(> .tb-sommaire) > *:not(.tb-sommaire):not(.tb-menu-lateral):not(.tb-colonnes-4):not(.tb-colonnes-6) {
        grid-column: 1 / -1;
        grid-row: auto;
    }
    .tb-flux:has(> .tb-sommaire) > .tb-sommaire {
        position: static;
        padding: 0.75rem 0;
        border-bottom: 1px solid var(--tb-bordure);
    }
}

/* --------------------------------------------------------------------------
   PRECEDENT / SUIVANT — les deux pages voisines, en pied de page.
   / PREVIOUS / NEXT — the two neighbouring pages, at the page's foot.
   -------------------------------------------------------------------------- */
.tb-voisines {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    justify-content: space-between;
    margin-block: var(--tb-espace-section);
    padding-top: 1.5rem;
    border-top: 1px solid var(--tb-bordure);
}

.tb-voisines__lien {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    max-width: 22rem;
    padding: 0.85rem 1.1rem;
    border: 1px solid var(--tb-bordure);
    border-radius: var(--tb-rayon-sm);
    text-decoration: none;
    transition: border-color var(--tb-transition);
}

.tb-voisines__lien:hover { border-color: var(--tb-accent); }

/* Le lien « suivant » se cale a droite, meme sans « precedent » en face.
   / The "next" link sits right, even with no "previous" facing it. */
.tb-voisines__lien--suivante { margin-left: auto; text-align: right; }

.tb-voisines__sens {
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--tb-texte-doux);
}

.tb-voisines__titre {
    font-family: var(--tb-police-titre);
    font-weight: 600;
    color: var(--tb-texte);
}
