/* =========================================================================
   Vintage Optical, feuille de style du site (direction « La vitrine »).

   Trois parties, dans cet ordre :
     1. le socle : couleurs de la charte, échelle typographique, rythme, et les
        composants communs (cadre 2:3, loupe, galerie, pied de page) ;
     2. la vitrine : réaffectation des rôles de couleur et mise en page, sous
        [data-theme='vitrine'], posé sur <html> par base.html.twig ;
     3. les zones image du module d'administration (.image-zone-*).

   L'ordre des couches est délibéré : un composant définit son apparence, une
   mise en page peut le repositionner, et les utilitaires tranchent en dernier.
   Aucune de ces règles ne dépend de la spécificité des sélecteurs, donc rien
   ne se casse en ajoutant une classe.
   ========================================================================= */


@layer tokens, base, composants, layout, utilitaires;

/* ------------------------------------------------------------------ polices */

@font-face {
    font-family: 'Fraunces';
    src: url('../fonts/fraunces.woff2') format('woff2-variations');
    font-weight: 300 900;
    font-style: normal;
    font-display: swap;
}

/* Archivo ne sert qu'aux pages à l'habillage neutre (contact, mentions légales, confidentialité) :
   le navigateur ne la télécharge que sur une page qui l'appelle. */
@font-face {
    font-family: 'Archivo';
    src: url('../fonts/archivo.woff2') format('woff2-variations');
    font-weight: 100 900;
    font-stretch: 62% 125%;
    font-style: normal;
    font-display: swap;
}


@layer tokens {
    :root {
        /* La charte, telle quelle. Ces six valeurs ne bougent jamais. */
        --brique: #7b311b;
        --sable: #f2d7b0;
        --flash: #ef4d8d;
        --soleil: #ea8f2f;
        --soleil-clair: #f7a546;
        --terre: #cd7043;

        /* Dérivées. Le brun-nuit est le 7b311b poussé au noir : il sert
           d'encre et de fond sombre sans jamais virer au gris. */
        --nuit: #1e0b06;
        --nuit-douce: #2c150d;

        /* Hors charte, tirée des ciels du shooting. Sans ce neutre froid la
           page est un aplat orange monochrome. Usage strictement mineur :
           filets, légendes, fonds de champs. */
        --ciel: #bfd3d6;

        /* Rôles. C'est tout ce que les gabarits emploient, jamais une
           couleur brute. Chaque direction les réaffecte. */
        --fond: var(--sable);
        --fond-doux: color-mix(in srgb, var(--sable) 70%, white);
        --encre: var(--nuit);
        --encre-douce: color-mix(in srgb, var(--nuit) 62%, var(--sable));
        --accent: var(--brique);
        --trait: color-mix(in srgb, var(--nuit) 18%, transparent);
        --focus: var(--brique);

        /* Familles. Substituts libres, montés pour être remplacés :
           Spirits Soft Medium → Fraunces, Agrandir Medium → Archivo,
           Brittany → Sacramento. Le jour où les vraies polices sont
           achetées, ce sont ces trois lignes qui changent. */
        --f-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
        --f-ui: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
        --f-script: 'Sacramento', 'Brush Script MT', cursive;

        /* Fraunces a un axe SOFT et un axe WONK : c'est ce qui en fait le
           substitut honnête de Spirits Soft, et ce qui porte le « chill »
           dans une serif de luxe. */
        --display-axes: 'opsz' 144, 'SOFT' 100, 'WONK' 1;

        /* Échelle fluide. Les bornes sont choisies pour que rien ne déborde
           à 360 px et que les titres restent tenus à 1600 px. */
        --t-xs: clamp(0.72rem, 0.70rem + 0.10vw, 0.78rem);
        --t-sm: clamp(0.80rem, 0.77rem + 0.15vw, 0.90rem);
        --t-base: clamp(1rem, 0.96rem + 0.20vw, 1.12rem);
        --t-md: clamp(1.12rem, 1.02rem + 0.50vw, 1.45rem);
        --t-lg: clamp(1.45rem, 1.15rem + 1.40vw, 2.35rem);
        --t-xl: clamp(1.95rem, 1.35rem + 2.90vw, 3.60rem);
        --t-2xl: clamp(2.50rem, 1.30rem + 5.80vw, 5.80rem);
        --t-3xl: clamp(3.20rem, 0.90rem + 10.50vw, 10rem);

        /* Rythme */
        --marge: clamp(1.25rem, 5vw, 4.5rem);
        --gouttiere: clamp(1rem, 2.5vw, 2rem);
        --section: clamp(4rem, 10vw, 9rem);
        --max: 78rem;
        --max-texte: 34rem;

        --rayon: 0px;
        --transition: 280ms cubic-bezier(0.2, 0.7, 0.3, 1);
    }
}

@layer base {
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

    html {
        -webkit-text-size-adjust: 100%;
        scroll-behavior: smooth;
    }

    body {
        margin: 0;
        background: var(--fond);
        color: var(--encre);
        font-family: var(--f-ui);
        font-size: var(--t-base);
        line-height: 1.6;
        font-synthesis-weight: none;
        -webkit-font-smoothing: antialiased;
        text-rendering: optimizeLegibility;
    }

    h1, h2, h3, h4 {
        margin: 0;
        font-family: var(--f-display);
        font-variation-settings: var(--display-axes);
        font-weight: 500;
        line-height: 1.02;
        letter-spacing: -0.025em;
        text-wrap: balance;
    }

    p {
        margin: 0 0 1em;
        text-wrap: pretty;
    }

    p:last-child {
        margin-bottom: 0;
    }

    ul, ol {
        margin: 0;
        padding: 0;
        list-style: none;
    }

    a {
        color: inherit;
        text-decoration-thickness: 1px;
        text-underline-offset: 0.22em;
    }

    img, picture, svg {
        display: block;
        max-width: 100%;
    }

    /* Les attributs width et height portent les dimensions réelles du fichier,
       ce qui réserve la place avant l'arrivée de l'image. Sans height: auto,
       la hauteur reste figée sur l'attribut pendant que la largeur se réduit,
       et l'image se déforme. Les cadres qui imposent leur propre hauteur la
       redéfinissent ensuite. */
    img {
        height: auto;
    }

    button {
        font: inherit;
        color: inherit;
        border: 0;
        background: none;
        cursor: pointer;
    }

    /* Un anneau de focus visible partout, jamais supprimé. Le décalage évite
       qu'il se confonde avec la bordure d'un champ. */
    :focus-visible {
        outline: 2px solid var(--focus);
        outline-offset: 3px;
        border-radius: 2px;
    }

    ::selection {
        background: var(--flash);
        color: #fff;
    }
}

@layer composants {

    /* --------------------------------------------------------- accès clavier */

    .skip-link {
        position: absolute;
        top: 0;
        left: 0;
        z-index: 100;
        padding: 0.75rem 1.25rem;
        background: var(--encre);
        color: var(--fond);
        font-size: var(--t-sm);
        transform: translateY(-120%);
        transition: transform var(--transition);
    }

    .skip-link:focus-visible {
        transform: translateY(0);
    }

    .sr-only {
        position: absolute;
        width: 1px;
        height: 1px;
        margin: -1px;
        padding: 0;
        overflow: hidden;
        clip-path: inset(50%);
        white-space: nowrap;
    }

    /* --------------------------------------------------------- le cadre 2:3

       Tout le shooting est en portrait 2:3, le format 35 mm. Le cadre
       impose ce ratio et ne recadre jamais : il n'existe aucune photo paysage
       dans le lot, et un recadrage en 16:9 détruirait chaque composition. */

    /* Un figure porte une marge horizontale de 40 px par défaut dans tous les
       navigateurs. Elle passe inaperçue en flux, mais fausse tout calcul de
       débord en positionnement absolu. */
    .frame-bloc {
        margin: 0;
        /* Une cellule de grille refuse par défaut de descendre sous la largeur
           de son contenu le plus large. Les légendes en capitales espacées
           dépassent vite cette limite et élargissent la piste, ce qui fait
           déborder toute la grille sur petit écran. */
        min-width: 0;
    }

    .frame {
        position: relative;
        margin: 0;
        aspect-ratio: 2 / 3;
        overflow: hidden;
        background: var(--nuit-douce);
    }

    /* Ces règles ne valent qu'à l'intérieur d'un cadre.
       Photos::picture() pose la classe sur toutes les images, y compris hors
       cadre : appliqué partout, height: 100% faisait revendiquer à la boîte
       <picture> toute la hauteur de sa cellule de grille, et la légende
       passait sous la rangée suivante. */
    .frame .frame__picture,
    .frame img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    /* Hors cadre, la boîte suit simplement son image. */
    .frame__picture {
        display: block;
    }

    /* La légende porte le numéro de frame réel du rouleau. Ce n'est pas une
       numérotation décorative : le lot est une séquence, et l'ordre dit
       quelque chose de vrai sur la journée photographiée. */
    .frame__legende {
        display: flex;
        flex-wrap: wrap;
        gap: 0.2em 0.6em;
        align-items: baseline;
        margin-top: 0.7rem;
        font-family: var(--f-ui);
        font-size: var(--t-xs);
        font-stretch: 108%;
        font-weight: 500;
        letter-spacing: 0.14em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    .frame__numero {
        font-variant-numeric: tabular-nums;
        color: var(--accent);
    }

    /* ------------------------------------------------------------ typographie */

    .eyebrow {
        margin: 0 0 1.2rem;
        font-family: var(--f-ui);
        font-size: var(--t-sm);
        font-stretch: 110%;
        font-weight: 600;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--accent);
    }

    .lead {
        max-width: var(--max-texte);
        font-size: var(--t-md);
        line-height: 1.45;
        color: var(--encre-douce);
    }


    /* ------------------------------------------------------------------ liens */

    .cta {
        display: inline-flex;
        align-items: center;
        gap: 0.7em;
        padding: 0.95em 1.7em;
        background: var(--encre);
        color: var(--fond);
        font-size: var(--t-sm);
        font-stretch: 106%;
        font-weight: 600;
        letter-spacing: 0.11em;
        text-transform: uppercase;
        text-decoration: none;
        border-radius: var(--rayon);
        transition: background var(--transition), color var(--transition), transform var(--transition);
    }

    .cta:hover {
        background: var(--accent);
        color: var(--sable);
    }

    .cta--discret {
        padding-inline: 0;
        background: none;
        color: var(--encre);
        text-decoration: underline;
        text-decoration-color: var(--accent);
    }

    .cta--discret:hover {
        background: none;
        color: var(--accent);
    }

    /* ------------------------------------------------------------ le formulaire

       Le balisage existait, l'habillage non : les champs sortaient en blanc
       sur une page sombre, avec la police par défaut du navigateur. Une page
       qu'on donne à lire ne peut pas être finie à moitié.

       Tout passe par les jetons du thème, donc le formulaire suit la direction
       qui sera retenue sans une ligne de plus. */

    .formulaire {
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 1.2rem var(--gouttiere);
        margin-top: 1.6rem;
        max-width: 38rem;
    }

    .champ {
        display: grid;
        gap: 0.45rem;
        margin: 0;
    }

    /* Le message prend la largeur : un champ de six lignes dans une demi-
       colonne se lit comme une case à cocher. */
    .champ--long,
    .formulaire .cta {
        grid-column: 1 / -1;
    }

    .formulaire .cta {
        justify-self: start;
        margin-top: 0.4rem;
    }

    /* Les intitulés reprennent exactement le traitement des termes de la liste
       d'adresse au-dessus : c'est la même page, ce sont les mêmes étiquettes. */
    .champ label {
        font-size: var(--t-sm);
        font-weight: 600;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    .champ input,
    .champ textarea {
        /* font: inherit, sinon le navigateur impose sa police de formulaire et
           le champ n'appartient plus à la page. */
        font: inherit;
        width: 100%;
        padding: 0.8em 0.9em;
        color: var(--encre);
        background: color-mix(in srgb, var(--encre) 5%, transparent);
        border: 1px solid var(--trait);
        border-radius: var(--rayon);
        transition: border-color var(--transition), background var(--transition);
    }

    .champ textarea {
        /* Vertical seulement : un redimensionnement latéral ferait déborder la
           colonne de grille. */
        resize: vertical;
        min-height: 9rem;
        line-height: 1.5;
    }

    .champ input:hover,
    .champ textarea:hover {
        border-color: color-mix(in srgb, var(--encre) 28%, transparent);
    }

    .champ input:focus-visible,
    .champ textarea:focus-visible {
        border-color: var(--focus);
        background: color-mix(in srgb, var(--encre) 8%, transparent);
    }

    /* Le bouton est désactivé tant que l'envoi n'est pas branché. Il reste
       lisible : grisé au point de disparaître, il donnerait l'impression d'un
       formulaire cassé plutôt que d'une maquette assumée. */
    .cta:disabled {
        opacity: 0.55;
        cursor: not-allowed;
    }

    .cta:disabled:hover {
        background: var(--encre);
        color: var(--fond);
    }

    @media (max-width: 34rem) {
        .formulaire { grid-template-columns: minmax(0, 1fr); }
    }

    /* Ajout du site, absent de la maquette : un message d'erreur sous un champ
       allonge sa ligne de grille, et le champ voisin s'étirait pour la remplir. */
    .champ {
        align-content: start;
    }

    /* --------------------------------------------------- pages de texte simple */

    .page-etroite {
        max-width: 44rem;
        margin-inline: auto;
        padding: clamp(3rem, 9vw, 7rem) var(--marge);
    }

    .page-etroite h1 {
        font-size: var(--t-xl);
        margin-bottom: 1.5rem;
    }

    .page-etroite h2 {
        margin: 2.5rem 0 0.8rem;
        font-size: var(--t-lg);
    }

    .page-etroite dl {
        margin: 0;
        display: grid;
        grid-template-columns: 1fr;
        gap: 0.15rem 1.5rem;
    }

    /* Sur écran étroit, les paires terme/définition s'empilent : à deux
       colonnes, une adresse e-mail ou un SIRET dépasse et fait défiler la
       page horizontalement. */
    @media (min-width: 34rem) {
        .page-etroite dl {
            grid-template-columns: minmax(9rem, auto) 1fr;
            gap: 0.5rem 1.5rem;
        }
    }

    .page-etroite dd {
        overflow-wrap: anywhere;
    }

    .page-etroite dt {
        font-size: var(--t-sm);
        font-weight: 600;
        letter-spacing: 0.06em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    .page-etroite dd {
        margin: 0;
    }

    .a-confirmer {
        font-style: italic;
        color: var(--accent);
    }



    /* ------------------------------------------------------------- la loupe

       Habillage de base de la fenêtre d'agrandissement : sombre, centrée,
       neutre. Chaque direction peut la reprendre à son compte ; la
       planche-contact en fait une table lumineuse.

       Le <dialog> natif porte le piège au clavier, Échap et le retour du
       focus. Ces règles ne font que le placer et l'habiller. */

    .loupe {
        width: min(96vw, 78rem);
        max-width: none;
        max-height: 94vh;
        padding: 0;
        border: 0;
        color: var(--sable);
        background: #0d0805;
        overflow: hidden;
    }

    .loupe::backdrop {
        background: rgba(8, 5, 3, 0.86);
        backdrop-filter: blur(3px);
    }

    .loupe__barre {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        padding: 0.7rem 0.9rem 0.7rem 1.2rem;
    }

    .loupe__compteur {
        margin: 0;
        font-size: var(--t-xs);
        font-variant-numeric: tabular-nums;
        letter-spacing: 0.14em;
    }

    .loupe__fermer {
        display: grid;
        place-items: center;
        width: 2.4rem;
        height: 2.4rem;
        border-radius: 50%;
        font-size: 1rem;
        line-height: 1;
        transition: background var(--transition);
    }

    .loupe__fermer:hover {
        background: rgba(255, 255, 255, 0.12);
    }

    .loupe__corps {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr) auto;
        align-items: center;
        gap: clamp(0.3rem, 1.5vw, 1.2rem);
        padding-inline: clamp(0.3rem, 1.5vw, 1rem);
    }

    .loupe__pas {
        display: grid;
        place-items: center;
        width: clamp(2.4rem, 5vw, 3.2rem);
        height: clamp(2.4rem, 5vw, 3.2rem);
        border-radius: 50%;
        font-size: 1.1rem;
        transition: background var(--transition);
    }

    .loupe__pas:hover {
        background: rgba(255, 255, 255, 0.12);
    }

    .loupe__vue {
        margin: 0;
        display: grid;
        justify-items: center;
    }

    /* Le cadre garde le 2:3 du rouleau et se limite à la hauteur de la
       fenêtre : la vue est agrandie, jamais recadrée. */
    .loupe__cadre {
        aspect-ratio: 2 / 3;
        height: min(58vh, 28rem);
        max-width: 100%;
        overflow: hidden;
        background: #000;
    }

    /* Au-delà de 60rem, la fiche technique passe à droite de la vue. C'est la
       disposition d'une table lumineuse, et surtout la vue récupère toute la
       hauteur de la fenêtre au lieu de la partager avec le texte. */
    @media (min-width: 60rem) {
        /* Le sélecteur porte [open] : sans lui, ce display écraserait le
           display: none que le navigateur applique à un <dialog> fermé, et la
           loupe resterait affichée en bas de page. */
        .loupe[open] {
            display: grid;
            grid-template-columns: minmax(0, 1fr) minmax(16rem, 22rem);
            grid-template-areas:
                'corps barre'
                'corps fiche'
                'corps aide';
            grid-template-rows: auto 1fr auto;
            align-items: start;
            column-gap: clamp(1rem, 2vw, 2rem);
            padding-bottom: 1rem;
        }

        .loupe__barre  { grid-area: barre; }
        .loupe__corps  { grid-area: corps; padding: 1rem 0 1rem 1rem; }
        .loupe__fiche  { grid-area: fiche; padding-inline: 0 1.6rem; align-self: center; }
        .loupe__aide   { grid-area: aide; padding-inline: 0 1.6rem; }

        .loupe__cadre { height: min(80vh, 52rem); }
    }

    .loupe__cadre picture,
    .loupe__cadre img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: contain;
    }

    .loupe__fiche {
        padding: 1rem clamp(1rem, 3vw, 2rem) 0;
    }

    .loupe__titre {
        display: flex;
        flex-wrap: wrap;
        align-items: baseline;
        gap: 0.5em 0.8em;
        margin: 0 0 1rem;
        font-size: var(--t-sm);
        letter-spacing: 0.1em;
        text-transform: uppercase;
    }

    .loupe__donnees {
        display: flex;
        flex-wrap: wrap;
        gap: 0.4rem 1.6rem;
        margin: 0;
        font-size: var(--t-xs);
        letter-spacing: 0.08em;
        text-transform: uppercase;
    }

    /* Chaque paire est un seul élément flexible : sans ce groupe, le terme et
       sa valeur se retrouvent sur deux lignes différentes dès que la place
       manque. */
    .loupe__donnee {
        display: flex;
        gap: 0.5em;
        white-space: nowrap;
    }

    .loupe__donnees dt {
        opacity: 0.6;
    }

    .loupe__donnees dd {
        margin: 0;
    }

    .loupe__aide {
        margin: 0;
        padding: 0.9rem clamp(1rem, 3vw, 2rem) 1.1rem;
        font-size: var(--t-xs);
        letter-spacing: 0.08em;
        opacity: 0.5;
    }

    /* Le pas à pas au clavier n'existe pas sur un écran tactile, et les
       flèches y prennent la place de l'image. */
    @media (max-width: 46rem) {
        .loupe__cadre { height: min(56vh, 26rem); }
        .loupe__aide { display: none; }
    }

    @media (hover: none) {
        .loupe__pas { width: 2.6rem; height: 2.6rem; }
    }

    /* ------------------------------------------------------------ la galerie

       Structure commune : une grille de vues au format 2:3, chacune cliquable
       et légendée de son numéro de rouleau. Chaque direction fixe le nombre de
       colonnes et l'habillage ; ici on ne pose que ce qui ne change pas. */

    .galerie {
        display: grid;
        gap: var(--gouttiere);
    }

    .galerie .vue {
        position: relative;
        min-width: 0;
    }

    .galerie .vue__cadre {
        display: block;
        position: relative;
        aspect-ratio: 2 / 3;
        overflow: hidden;
        background: var(--nuit-douce);
    }

    .galerie .vue__cadre picture,
    .galerie .vue__cadre img {
        width: 100%;
        height: 100%;
        object-fit: cover;
    }

    .galerie .vue__pied {
        display: flex;
        flex-wrap: wrap;
        gap: 0.2em 0.6em;
        align-items: baseline;
        margin-top: 0.55rem;
        font-family: var(--f-ui);
        font-size: var(--t-xs);
        font-stretch: 106%;
        letter-spacing: 0.12em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    .galerie .vue__numero {
        color: var(--accent);
        font-variant-numeric: tabular-nums;
    }

    .galerie-bloc {
        position: relative;
    }


    /* ------------------------------------------------ la loupe sur la vignette */

    .vue__ouvrir {
        display: block;
        color: inherit;
        text-decoration: none;
    }

    .vue__loupe {
        position: absolute;
        right: 0.4rem;
        bottom: 0.4rem;
        display: grid;
        place-items: center;
        width: 1.9rem;
        height: 1.9rem;
        border-radius: 50%;
        background: rgba(13, 8, 5, 0.72);
        color: var(--sable);
        opacity: 0;
        translate: 0 0.3rem;
        transition: opacity var(--transition), translate var(--transition);
        pointer-events: none;
    }

    .vue__loupe svg {
        width: 1rem;
        height: 1rem;
    }

    .vue__ouvrir:hover .vue__loupe,
    .vue__ouvrir:focus-visible .vue__loupe {
        opacity: 1;
        translate: 0 0;
    }

}

@layer utilitaires {
    .contenu {
        max-width: var(--max);
        margin-inline: auto;
        padding-inline: var(--marge);
    }

    .section {
        padding-block: var(--section);
    }

    /* Révélation au défilement.
       Le masquage est conditionné à la classe « js », posée en tête de page
       par un script d'une ligne. Sans JavaScript la règle ne s'applique
       jamais, et la page reste entièrement visible : une erreur de script ne
       peut pas vider le site. */
    .js .reveal {
        opacity: 0;
        transform: translateY(1.4rem);
        transition: opacity 700ms ease, transform 700ms cubic-bezier(0.2, 0.7, 0.3, 1);
    }

    .js .reveal.est-visible {
        opacity: 1;
        transform: none;
    }
}

/* Le mouvement est un agrément, jamais un prérequis : rien ici ne porte
   d'information qui disparaîtrait sans animation. */
@media (prefers-reduced-motion: reduce) {
    html {
        scroll-behavior: auto;
    }

    *,
    *::before,
    *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }

    .js .reveal {
        opacity: 1;
        transform: none;
    }
}

/* =========================================================================
   Direction 5 : « La vitrine »

   Thèse : la page se tait, les photos parlent. Fond blanc tiède, une seule
   typographie, de grandes plages vides. Aucune couleur ne vient de la mise en
   page : tout ce qui est coloré sur cette page est dans une photo.

   C'est ce qui permet de tenir ensemble les deux moitiés de sa demande.
   « Élégant, luxueux, n'en fait pas trop » vit dans le contenant ; « rétro,
   décalé, rock'n'roll » vit dans le shooting. Si le décalage passait dans la
   mise en page, on retomberait sur les directions 2 et 4, dont elle n'a rien
   dit.

   Signature : la fiche. Les informations données en clair, en paires
   terme/valeur, comme une spécification.

   Les couleurs sont celles du PDF de la charte, relevées sur les aplats, et
   plus les approximations tirées du PNG. Deux règles de lecture qui en
   découlent et qui ne se négocient pas :

     - l'or 7549c est à 1,7 pour 1 sur ce fond. Il ne porte jamais de texte,
       il ne sert qu'aux filets et aux aplats ;
     - le brun 154c est à 5,16. C'est lui qui porte les accents écrits.
   ========================================================================= */

@layer tokens {
    [data-theme='vitrine'] {
        /* La charte, page 2 et 3 du PDF, en valeurs relevées. */
        --v-encre: #401f20;      /* Pantone 4975c  · 13,94 sur le fond */
        --v-brun: #9d5917;       /* Pantone 154c   ·  5,16 sur le fond */
        --v-ecaille: #633410;    /* Pantone 732c   ·  9,85 */
        --v-magenta: #c43883;    /* Pantone 7424c  ·  4,72 */
        --v-or: #fab625;         /* Pantone 7549c  ·  1,70, filets seulement */
        --v-sable: #efcc90;      /* Pantone 155c */

        /* Hors charte, et c'est assumé : les deux couleurs du drapeau. Elles
           ne servent qu'à la marque tricolore de l'atelier, jamais ailleurs.
           Valeurs officielles, pas des approximations chaudes : une marque
           tricolore réaccordée sur la palette n'est plus une marque
           tricolore. */
        --v-bleu: #000091;
        --v-rouge: #e1000f;

        --fond: #fbf9f7;
        --fond-doux: #f7f2ec;
        --encre: var(--v-encre);
        --encre-douce: #745c5c;  /* 5,83 : le gris tiède du 4975c délavé */
        --accent: var(--v-brun);
        --trait: rgba(64, 31, 32, 0.13);
        --focus: var(--v-brun);

        /* « On va trouver une typo sobre pour tout le site. » La charte ne
           demande qu'une police, Spirits Soft Regular ; Fraunces en est le
           substitut libre. Il n'y a donc plus de grotesque ici : les petites
           capitales, les légendes et les boutons sont composés dans la même
           famille que les titres, et le contraste vient des tailles. */
        --f-ui: var(--f-display);

        /* Moins de fantaisie que sur les autres directions : WONK à 0 calme
           les terminaisons, l'axe SOFT garde la douceur qui justifiait
           Fraunces comme substitut. */
        --display-axes: 'opsz' 144, 'SOFT' 60, 'WONK' 0;

        /* Le vide fait partie du dessin. Les sections respirent plus large
           que partout ailleurs, c'est la moitié de l'effet. */
        --section: clamp(5rem, 13vw, 12rem);
        --max: 78rem;
        --max-texte: 36rem;

        /* Le fond de la page, repris tel quel par l'en-tête collant : même
           dégradé, même ancrage à la fenêtre, donc mêmes pixels. */
        --v-degrade: linear-gradient(
            180deg,
            #fdfcfb 0%,
            var(--fond) 38%,
            color-mix(in srgb, var(--v-sable) 26%, var(--fond)) 100%
        );

        /* Hauteur de l'en-tête collant : rembourrage (2 × 1,4rem) plus le
           monogramme (2,1rem), le plus haut de ses enfants. Sert de réserve
           aux ancres, qui tomberaient sinon sous l'en-tête. */
        --entete-hauteur: 4.9rem;
    }
}

@layer base {

    /* Le dégradé qu'elle demande : « retour au fond blanc avec un dégradé en
       gardant mes couleurs pour rappeler le côté chaud et cocooning ».

       Il est délibérément à la limite du perceptible. Un dégradé qu'on
       remarque devient un parti pris de mise en page, et cette direction tient
       sur le fait que la mise en page ne prend aucun parti. Le sable n'arrive
       qu'à 45 % de dilution, en bas de la fenêtre, et reste fixe : la page ne
       change pas de couleur au défilement. */
    [data-theme='vitrine'] body {
        background-color: var(--fond);
        background-image: var(--v-degrade);
        background-attachment: fixed;
        letter-spacing: -0.004em;
    }

    /* Les ancres (#maison, #maisons, #atelier, #contenu du lien d'évitement)
       et le focus au clavier s'arrêtent sous l'en-tête collant, pas dessous. */
    [data-theme='vitrine'] {
        scroll-padding-top: var(--entete-hauteur);
    }

    [data-theme='vitrine'] ::selection {
        background: var(--v-magenta);
        color: #fff;
    }
}

@layer layout {

    /* ------------------------------------------------------------ le cadrage

       Rien n'est centré. C'est le réflexe de Meyrowitz, qu'elle range parmi
       les sites « d'un ennui MORTEL », et c'est aussi ce qui fait qu'une page
       de vente ressemble à toutes les autres. Le bloc est calé à gauche, avec
       une marge assez large pour qu'on voie que c'est un choix. */

    [data-theme='vitrine'] .v-contenu {
        max-width: var(--max);
        margin-inline: auto;
        padding-inline: var(--marge);
    }

    @media (min-width: 68rem) {
        [data-theme='vitrine'] .v-contenu {
            padding-left: calc(var(--marge) + 7rem);
        }
    }

    /* -------------------------------------------------------------- en-tête

       Réduite au strict minimum, posée sur le blanc, sans filet ni ombre.
       Elle ne doit rien peser à côté de la photo d'ouverture.

       Collante, elle reste en haut de l'écran pendant le défilement. Son fond
       est celui de body, même dégradé ancré à la fenêtre : tant que la page
       n'a pas défilé on ne la distingue pas du fond (rendu inchangé), ensuite
       elle masque ce qui passe dessous sans rien ajouter à la page.

       Elle prend toute la largeur pour que ce fond couvre aussi les côtés ; le
       rembourrage recentre le contenu exactement comme l'ancien
       « max-width: var(--max) » centré. z-index 10 : au-dessus des photos
       (transformées par la parallaxe et la révélation, donc empilées), sous le
       lien d'évitement (100) et les barres du module d'administration ; la
       loupe est un <dialog> modal, dans la couche supérieure, au-dessus de
       tout. */

    [data-theme='vitrine'] .entete {
        position: sticky;
        top: 0;
        z-index: 10;
        display: flex;
        align-items: center;
        gap: var(--gouttiere);
        padding: 1.4rem max(var(--marge), calc((100% - var(--max)) / 2 + var(--marge)));
        background-color: var(--fond);
        background-image: var(--v-degrade);
        background-attachment: fixed;
    }

    /* Safari sur iPhone et iPad ignore « background-attachment: fixed » : le
       dégradé serait étiré sur les 78 px de l'en-tête et le bas en virerait au
       sable. Le fond uni y est à deux niveaux près celui du haut de page. */
    @supports (-webkit-touch-callout: none) {
        [data-theme='vitrine'] .entete {
            background-image: none;
        }
    }

    /* Filet sous l'en-tête une fois la page défilée (classe posée par main.js).
       Une ombre et non une bordure : la hauteur de l'en-tête ne bouge pas, donc
       --entete-hauteur et scroll-padding-top restent justes. --trait suit
       l'habillage neutre, ses jetons étant posés sur .entete. */
    [data-theme='vitrine'] .entete {
        box-shadow: 0 1px 0 transparent;
        transition: box-shadow 0.2s ease;
    }

    [data-theme='vitrine'] .entete.est-decolle {
        box-shadow: 0 1px 0 var(--trait);
    }

    @media (prefers-reduced-motion: reduce) {
        [data-theme='vitrine'] .entete { transition: none; }
    }

    [data-theme='vitrine'] .entete__marque {
        display: flex;
        align-items: center;
        gap: 0.6rem;
        text-decoration: none;

        /* Le nom ne se coupe pas : comprimée, la marque ne rétrécissait donc
           pas vraiment, elle laissait le nom sortir de sa propre boîte. La
           gouttière était alors mesurée sur une boîte plus étroite que son
           contenu, et ce qui suivait venait s'y superposer. */
        flex-shrink: 0;
    }

    /* Le monogramme vient maintenant du PDF de la charte, en tracé plein, et
       plus de la séparation colorimétrique du PNG. Il tient donc au trait fin
       sans se remplir de gris. */
    [data-theme='vitrine'] .entete__monogramme {
        width: auto;
        height: 2.1rem;
    }

    [data-theme='vitrine'] .entete__nom {
        font-size: 0.82rem;
        font-weight: 500;
        letter-spacing: 0.26em;
        text-transform: uppercase;
        white-space: nowrap;
    }

    /* La taille est posée sur la navigation entière, et pas seulement sur les
       liens. Portée par les seuls <a>, les <li> gardaient la taille de
       l'en-tête : leur ligne faisait 28,7 px là où le bouton de rendez-vous,
       qui porte la taille directement, en faisait 18,9. Les deux boîtes
       centrées dans la même rangée, les lignes de base ne tombaient pas
       ensemble et le bouton flottait au-dessus du menu. */
    [data-theme='vitrine'] .entete__nav {
        margin-left: auto;
        font-size: 0.74rem;
        line-height: 1.6;
    }

    [data-theme='vitrine'] .entete__nav ul {
        display: flex;
        gap: clamp(1rem, 2.4vw, 2.2rem);
    }

    [data-theme='vitrine'] .entete__nav a,
    [data-theme='vitrine'] .entete__action {
        font-size: inherit;
        font-weight: 500;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        text-decoration: none;
        color: var(--encre-douce);
        transition: color var(--transition);
    }

    [data-theme='vitrine'] .entete__nav a:hover,
    [data-theme='vitrine'] .entete__action:hover {
        color: var(--accent);
    }

    /* Le rendez-vous garde un filet plutôt qu'un aplat : un bouton plein dans
       l'en-tête serait le seul élément appuyé de la page.

       Le filet est un pseudo-élément détaché, et pas une bordure. En bordure,
       il ajoutait un pixel et un quart de rem de fond de casse à la boîte du
       lien ; l'en-tête centrant ses enfants, le texte du bouton remontait de
       trois pixels au-dessus de celui du menu. Détaché, la boîte du bouton est
       exactement celle d'une entrée de menu et les quatre s'alignent. */
    [data-theme='vitrine'] .entete__action {
        position: relative;
        font-size: 0.74rem;
        line-height: 1.6;
        color: var(--encre);

        /* Le filet est un trait unique posé sur la boîte du lien. Si le
           libellé passe à la ligne, il ne souligne plus chaque ligne : il
           barre le bloc entier d'un seul trait, au ras du bas. Le bouton
           tient donc sur une ligne, en toutes largeurs. */
        white-space: nowrap;
    }

    [data-theme='vitrine'] .entete__action::after {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        bottom: -0.45em;
        height: 1px;
        background: var(--v-or);
        transition: background var(--transition);
    }

    [data-theme='vitrine'] .entete__action:hover::after {
        background: var(--accent);
    }

    /* Marque, menu et rendez-vous demandent 819 px à eux trois, marges non
       comprises : les quatre entrées ne tiennent au large qu'à partir de
       61rem. Le menu revenait à 52rem, cent quarante pixels trop tôt, et se
       posait alors à six pixels du nom. 62rem est le seuil que « plein
       soleil » retient déjà pour la même en-tête.

       Sous ce seuil la navigation disparaît, et le « margin-left: auto »
       qu'elle portait disparaît avec elle : le rendez-vous remplaçait le menu
       à la suite immédiate du nom, au milieu d'un en-tête vide sur sa droite.
       La marge automatique passe donc sur le bouton. */
    @media (max-width: 62rem) {
        [data-theme='vitrine'] .entete__nav { display: none; }
        [data-theme='vitrine'] .entete__action { margin-left: auto; }
    }

    /* Le nom composé ne se comprime pas : « nowrap » et 0,26em d'interlettrage
       lui font 176 px fixes. Avec le monogramme et le bouton, l'en-tête
       réclame 502 px de fenêtre pour tenir sur une ligne. En dessous, le nom
       débordait de sa propre boîte et passait sous le rendez-vous : les deux
       textes se superposaient.

       Le monogramme porte alors la marque à lui seul, comme sur les quatre
       autres directions. Le nom n'est pas perdu pour autant, il revient en
       toutes lettres dans le titre d'ouverture, juste dessous. */
    @media (max-width: 34rem) {
        [data-theme='vitrine'] .entete__nom { display: none; }
    }

    /* -------------------------------------------------------------- bandeau

       L'élément qu'elle a retenu de la direction 3. Une image seule, toute la
       largeur, une légende minuscule dans le coin. C'est lui qui donne son
       rythme à la page : quatre respirations, du plein midi à la fête foraine.

       Le débord passe par une marge négative calculée sur la largeur de la
       fenêtre plutôt que par 100vw : sous Windows, la barre de défilement
       occupe de la place et 100vw déclenche un défilement horizontal. */

    [data-theme='vitrine'] .bandeau {
        position: relative;
        margin: 0;
        width: 100%;
        height: var(--bandeau-hauteur, 70svh);
        overflow: hidden;
    }

    /* L'image déborde du cadre de l'amplitude de la parallaxe, en haut comme
       en bas. Le script la déplace ensuite dans cette réserve : sans elle, un
       liseré de fond apparaîtrait à chaque extrémité de la course. Sans
       script, l'image reste centrée et le débordement ne se voit pas. */
    [data-theme='vitrine'] .bandeau picture {
        position: absolute;
        left: 0;
        right: 0;
        top: calc(-1 * var(--bandeau-derive, 0px));
        bottom: calc(-1 * var(--bandeau-derive, 0px));
        will-change: transform;
    }

    [data-theme='vitrine'] .bandeau img {
        display: block;
        width: 100%;
        height: 100%;
        object-fit: cover;
        /* La hauteur du regard change d'une vue à l'autre : chaque bandeau
           donne la sienne, mesurée sur la photo. */
        object-position: 50% var(--bandeau-foyer, 40%);
    }

    /* L'ouverture. La photo se pose au chargement au lieu d'être là d'un coup :
       un peu plus de deux secondes, une échelle qui rend la main. C'est la
       seule animation d'entrée de la page, et elle ne joue qu'une fois. */
    [data-theme='vitrine'] .bandeau--ouverture img {
        animation: vitrine-pose 2200ms cubic-bezier(0.16, 0.8, 0.24, 1) both;
    }

    @keyframes vitrine-pose {
        from { transform: scale(1.06); }
        to   { transform: scale(1); }
    }

    @media (prefers-reduced-motion: reduce) {
        [data-theme='vitrine'] .bandeau--ouverture img { animation: none; }
    }

    /* La légende est posée sur l'image, donc elle a besoin de son propre
       contraste : un voile sombre très localisé sous le texte, plutôt qu'un
       assombrissement de toute la photo. La photo est le sujet, on ne la
       ternit pas pour poser six caractères dessus. */
    [data-theme='vitrine'] .bandeau__legende {
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        padding: 3.5rem var(--marge) 1.1rem;
        display: flex;
        flex-wrap: wrap;
        gap: 0.2em 0.7em;
        align-items: baseline;
        font-size: var(--t-xs);
        font-weight: 500;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.92);
        background: linear-gradient(to top, rgba(20, 8, 6, 0.5), transparent);
        pointer-events: none;
    }

    [data-theme='vitrine'] .bandeau__numero {
        font-variant-numeric: tabular-nums;
        color: var(--v-or);
    }

    /* ------------------------------------------------------------- l'accroche

       Le titre arrive après la photo, et il est petit. C'est le contraire du
       réflexe habituel, et c'est le sens de « un site qui n'en fait pas
       trop » : la page n'a pas besoin de crier, l'image vient de le faire. */

    [data-theme='vitrine'] .v-titre {
        max-width: 20ch;
        font-size: clamp(1.85rem, 1.15rem + 2.6vw, 3.15rem);
        font-weight: 400;
        line-height: 1.08;
        letter-spacing: -0.02em;
    }

    [data-theme='vitrine'] .v-titre--section {
        font-size: clamp(1.55rem, 1.05rem + 1.9vw, 2.5rem);
    }

    [data-theme='vitrine'] .eyebrow {
        margin-bottom: 1.4rem;
        font-size: 0.74rem;
        font-weight: 500;
        letter-spacing: 0.2em;
    }

    [data-theme='vitrine'] .v-lead {
        margin-top: 1.5rem;
        font-size: var(--t-md);
        line-height: 1.5;
    }

    [data-theme='vitrine'] .v-actions {
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 1rem 2rem;
        margin-top: 2.2rem;
    }

    /* Le bouton perd son aplat et ses capitales espacées : dans une page où
       tout est composé dans la même serif, un pavé en grotesque sonnerait
       comme une pièce rapportée. */
    [data-theme='vitrine'] .cta {
        padding: 0.85em 1.9em;
        font-size: 0.8rem;
        font-weight: 500;
        letter-spacing: 0.14em;
        background: var(--encre);
        color: var(--fond);
    }

    [data-theme='vitrine'] .cta:hover {
        background: var(--v-ecaille);
        color: var(--fond);
    }

    [data-theme='vitrine'] .cta--discret {
        background: none;
        color: var(--encre);
        text-decoration-color: var(--v-or);
        text-decoration-thickness: 2px;
    }

    [data-theme='vitrine'] .cta--discret:hover {
        background: none;
        color: var(--accent);
    }

    /* ------------------------------------------------------------ les repères

       C'était six lignes terme/valeur séparées de filets, soit le tableau
       impersonnel une troisième fois sur la même page. Le défaut de fond : six
       faits de natures différentes rendus dans six lignes identiques. Une
       adresse n'est pas une référence d'acétate.

       Deux traitements, donc. L'adresse est une destination et se compose
       comme un panneau. Les horaires deviennent une semaine où l'on voit aussi
       les jours fermés, ce qu'une phrase ne montre pas. Les trois autres faits
       forment une bande de trois, jamais une pile. */

    [data-theme='vitrine'] .v-repere {
        display: grid;
        gap: clamp(2rem, 5vw, 4rem);
        align-items: end;
    }

    @media (min-width: 56rem) {
        [data-theme='vitrine'] .v-repere {
            grid-template-columns: minmax(0, auto) minmax(0, auto);
            justify-content: start;
            column-gap: clamp(3rem, 8vw, 7rem);
        }
    }

    [data-theme='vitrine'] .v-repere__adresse {
        margin: 0;
        display: grid;
        gap: 0.15rem;
    }

    /* La rue est l'information la plus utile de la page : c'est là qu'on va.
       Elle se compose donc à la taille d'un titre de section, et pas dans une
       cellule de tableau. */
    [data-theme='vitrine'] .v-repere__rue {
        font-size: clamp(1.6rem, 1.1rem + 1.8vw, 2.5rem);
        line-height: 1.05;
        letter-spacing: -0.022em;
    }

    [data-theme='vitrine'] .v-repere__ville {
        font-size: var(--t-md);
        color: var(--encre-douce);
    }

    /* ------------------------------------------------------------ la semaine

       Sept initiales, cinq soulignées d'or. On y lit l'ouverture et la
       fermeture d'un coup d'œil, ce que « du mardi au samedi » oblige à
       reconstituer. Les traits des jours ouverts se touchent et forment une
       barre continue, donc l'amplitude d'ouverture se voit comme un segment.

       C'est le seul trait de la section, et il porte une information. */

    [data-theme='vitrine'] .v-semaine {
        display: grid;
        grid-template-columns: repeat(7, minmax(1.6rem, 2.2rem));
        margin-bottom: 1rem;
    }

    [data-theme='vitrine'] .v-semaine__jour {
        position: relative;
        padding-bottom: 0.7rem;
        text-align: center;
        font-size: var(--t-md);
        line-height: 1;
        color: color-mix(in srgb, var(--encre) 28%, var(--fond));
    }

    [data-theme='vitrine'] .v-semaine__jour.est-ouvert {
        color: var(--encre);
    }

    /* Le trait couvre toute la cellule, sans gouttière : deux jours ouverts
       voisins donnent un segment continu. */
    [data-theme='vitrine'] .v-semaine__jour.est-ouvert::after {
        content: '';
        position: absolute;
        left: 0;
        right: 0;
        bottom: 0;
        height: 2px;
        background: var(--v-or);
    }

    [data-theme='vitrine'] .v-repere__heures {
        margin: 0;
        font-size: var(--t-md);
        letter-spacing: -0.01em;
    }

    /* -------------------------------------------------------------- les notes

       Trois faits qui restent, en bande. Une bande de trois n'est pas une pile
       de six : c'est ce qui l'empêche de redevenir un tableau. */

    [data-theme='vitrine'] .v-notes {
        display: grid;
        gap: clamp(1.8rem, 4vw, 3.5rem);
        margin-top: clamp(3rem, 7vw, 5rem);
        max-width: 62rem;
    }

    @media (min-width: 52rem) {
        [data-theme='vitrine'] .v-notes {
            grid-template-columns: repeat(3, minmax(0, 1fr));
        }
    }

    [data-theme='vitrine'] .v-notes__terme {
        margin-bottom: 0.55rem;
        font-size: 0.72rem;
        font-weight: 500;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--accent);
    }

    [data-theme='vitrine'] .v-notes__valeur {
        margin: 0;
        font-size: var(--t-md);
        line-height: 1.35;
        letter-spacing: -0.012em;
    }

    /* ------------------------------------------------------------ La Maison

       Le seul endroit de la page où deux colonnes se répondent, et la seule
       photo qui n'est pas en pleine largeur. Un portrait garde son cadre. */

    [data-theme='vitrine'] .v-maison__grille {
        display: grid;
        gap: clamp(2.5rem, 6vw, 5rem);
        align-items: start;
    }

    @media (min-width: 60rem) {
        [data-theme='vitrine'] .v-maison__grille {
            grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr);
        }
    }

    [data-theme='vitrine'] .v-maison__texte {
        max-width: var(--max-texte);
    }

    [data-theme='vitrine'] .v-maison__texte p {
        margin-top: 1.2rem;
        color: var(--encre-douce);
    }

    [data-theme='vitrine'] .v-maison__texte .v-titre {
        margin-bottom: 0.4rem;
    }

    [data-theme='vitrine'] .v-citation {
        margin: 2.4rem 0 0;
        padding-left: 1.5rem;
        border-left: 2px solid var(--v-or);
    }

    [data-theme='vitrine'] .v-citation p {
        margin: 0 0 1rem;
        font-size: var(--t-md);
        line-height: 1.42;
        color: var(--encre);
    }

    [data-theme='vitrine'] .v-citation footer {
        display: grid;
        gap: 0.1rem;
    }

    [data-theme='vitrine'] .v-citation__nom {
        font-size: 0.78rem;
        font-weight: 600;
        letter-spacing: 0.18em;
        text-transform: uppercase;
        color: var(--accent);
    }

    /* La mention de l'école des Meilleurs Ouvriers de France est ici et dans
       la fiche. Le prestataire précédent l'avait retirée du site, c'est le
       premier reproche qu'elle lui fait. */
    [data-theme='vitrine'] .v-citation__role {
        font-size: var(--t-xs);
        letter-spacing: 0.06em;
        color: var(--encre-douce);
    }

    [data-theme='vitrine'] .v-maison__vue .frame {
        background: var(--fond-doux);
    }

    /* ----------------------------------------------------------- les maisons

       Cette section était cinq lignes filetées, intitulé à gauche et
       description à droite. C'est le motif retiré des quatre autres directions
       en août : « trop typé développement Claude », « un tableau listé, c'est
       impersonnel ». Il est revenu ici, et deux fois sur la même page.

       Le remplacement tient sur une contrainte assumée : il n'existe aucune
       découpe de monture sur fond neutre, donc rien à montrer à côté d'un nom
       de maison. Kaneko peut poser une image par marque, nous non. Ce sont donc
       les noms eux-mêmes qui font le graphisme, composés assez grand pour ça,
       et décalés en quinconce pour que la grille ne se lise pas comme un
       tableau.

       Zéro filet dans toute la section. Le seul de la page est celui de la
       fiche, où il sépare des lignes de tableau, ce qui est son métier. */

    /* L'alternance.

       La section n'était que du texte, quatre blocs décalés en quinconce, et
       c'était la plus vide de la page. Chaque maison a maintenant sa vue, et
       le côté change à chaque rang : une maison arrive à gauche avec son image
       à droite, la suivante fait l'inverse. Le va-et-vient tient lieu de
       rythme, ce qu'une grille régulière ne donne jamais.

       L'alternance est portée par `nth-child` et pas par une classe dans le
       gabarit : c'est une propriété du rang dans la liste, pas de la maison. */

    [data-theme='vitrine'] .v-phares {
        display: grid;
        gap: clamp(3.5rem, 9vw, 7rem);
        margin-top: clamp(3rem, 7vw, 5rem);
    }

    [data-theme='vitrine'] .v-phares__item {
        display: grid;
        gap: clamp(1.5rem, 4vw, 3rem);
        align-items: center;
    }

    @media (min-width: 56rem) {
        [data-theme='vitrine'] .v-phares__item {
            grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
        }

        /* Un rang sur deux échange les colonnes. Les deux enfants sont placés
           explicitement sur la même rangée : sans `grid-row`, l'inversion les
           empilerait sur deux rangées au lieu de les permuter.

           Le gabarit de colonnes est inversé en même temps. Sans ça, seuls les
           contenus permutaient : l'image passait de 420 à 524 px de large d'un
           rang à l'autre, et le texte de 524 à 420. Une justification qui
           change à chaque paragraphe se voit, et la page paraît bancale sans
           qu'on sache pourquoi. Les deux inversions ensemble donnent un miroir :
           l'image garde sa largeur, le texte la sienne, seul le côté change. */
        [data-theme='vitrine'] .v-phares__item:nth-child(even) {
            grid-template-columns: minmax(0, 1fr) minmax(0, 0.8fr);
        }

        [data-theme='vitrine'] .v-phares__item:nth-child(even) .v-phares__vue {
            grid-column: 2;
            grid-row: 1;
        }

        [data-theme='vitrine'] .v-phares__item:nth-child(even) .v-phares__texte {
            grid-column: 1;
            grid-row: 1;
        }
    }

    /* Chaque visuel garde son cadrage d'origine, du carré au 3:2. Ce sont des
       images de presse de natures différentes, et les forcer au 2:3 du rouleau
       les abîmerait sans rien apporter. Les rangs n'ont donc pas tous la même
       hauteur, ce qui vaut mieux qu'un recadrage. */
    [data-theme='vitrine'] .v-phares__vue {
        margin: 0;
    }

    [data-theme='vitrine'] .v-phares__vue img {
        width: 100%;
        height: auto;
        background: var(--fond-doux);
    }

    /* La provenance est écrite sous chaque visuel : ils appartiennent aux
       maisons et ils sont provisoires. Autant que la page le dise plutôt que
       de les faire passer pour des images de la boutique. */
    [data-theme='vitrine'] .v-phares__source {
        margin-top: 0.6rem;
        font-size: 0.68rem;
        font-weight: 500;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    [data-theme='vitrine'] .v-phares__nom {
        font-size: clamp(1.7rem, 1.1rem + 2.1vw, 2.75rem);
        font-weight: 400;
        line-height: 1.05;
        letter-spacing: -0.022em;
    }

    /* La provenance est l'argument de la section : elle se donne sous le nom,
       en accent, et pas noyée dans la phrase. Le brun 154c tient à 5,16 ; l'or
       de la charte serait à 1,70 et illisible. */
    [data-theme='vitrine'] .v-phares__pays {
        margin: 0.55rem 0 0.9rem;
        font-size: 0.72rem;
        font-weight: 500;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--accent);
    }

    [data-theme='vitrine'] .v-phares__note {
        margin: 0;
        max-width: 34ch;
        color: var(--encre-douce);
    }

    /* ------------------------------------------------------------- l'index

       Les sept autres maisons. En ligne unique séparée de points médians,
       elles ne disaient rien de plus qu'une énumération. Rangées par pays de
       fabrication, elles deviennent un relevé : c'est le même registre que la
       fiche, et ça soutient « aucune chaîne » au lieu de le répéter. */

    [data-theme='vitrine'] .v-index {
        margin-top: clamp(3.5rem, 8vw, 6rem);
    }

    [data-theme='vitrine'] .v-index__amorce {
        margin: 0 0 clamp(1.5rem, 3vw, 2.2rem);
        font-size: 0.72rem;
        font-weight: 500;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    [data-theme='vitrine'] .v-index__pays {
        display: grid;
        gap: clamp(1.8rem, 4vw, 3rem);
        grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
        max-width: 58rem;
    }

    /* Le pays est composé dans la même serif que les noms, à la même taille
       que le texte courant, mais en capitales espacées : c'est la hiérarchie
       de l'étiquette, obtenue sans changer de famille. */
    [data-theme='vitrine'] .v-index__lieu {
        margin-bottom: 0.7rem;
        font-size: 0.8rem;
        font-weight: 500;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        color: var(--accent);
    }

    [data-theme='vitrine'] .v-index__noms {
        display: grid;
        gap: 0.35rem;
        font-size: var(--t-base);
        line-height: 1.35;
    }

    /* --------------------------------------------------------- les montures

       Huit vues, et aucun bouton pour déplier le reste : une sélection qui
       propose d'en voir davantage n'est plus une sélection. Le repli de la
       galerie n'est donc pas demandé, et le voile qui l'accompagne non plus. */

    [data-theme='vitrine'] .galerie--vitrine {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: clamp(0.9rem, 2vw, 1.8rem);
        margin-top: clamp(2.5rem, 6vw, 4rem);
    }

    @media (max-width: 64rem) {
        [data-theme='vitrine'] .galerie--vitrine { grid-template-columns: repeat(3, minmax(0, 1fr)); }
    }

    @media (max-width: 40rem) {
        [data-theme='vitrine'] .galerie--vitrine { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    }

    [data-theme='vitrine'] .galerie--vitrine .vue__cadre {
        background: var(--fond-doux);
    }

    [data-theme='vitrine'] .galerie--vitrine .vue__pied {
        font-size: 0.68rem;
        letter-spacing: 0.14em;
    }

    /* La vignette se lève d'un cheveu au survol. C'est le seul mouvement de la
       page en dehors des révélations : sur une direction qui tient sur la
       retenue, un effet de plus se verrait. */
    [data-theme='vitrine'] .galerie--vitrine .vue__cadre picture {
        transition: transform 600ms cubic-bezier(0.2, 0.7, 0.3, 1);
    }

    [data-theme='vitrine'] .galerie--vitrine .vue__ouvrir:hover .vue__cadre picture,
    [data-theme='vitrine'] .galerie--vitrine .vue__ouvrir:focus-visible .vue__cadre picture {
        transform: scale(1.035);
    }

    [data-theme='vitrine'] .vue__loupe {
        background: rgba(64, 31, 32, 0.78);
        color: var(--fond);
    }

    /* ------------------------------------------------------------ l'atelier

       Même reprise que les maisons, et pour la même raison. Ce qui la
       construit ici, c'est une information vraie que le site actuel ne donne
       nulle part : il y a deux postes derrière la vitrine, l'établi et la
       cabine, et Charlotte tient les deux. Opticienne et artisan lunetier,
       c'est sa phrase, et c'est ce que la section montre au lieu de le dire.

       Les réparations sortent du lot, seules et en grand. */

    [data-theme='vitrine'] .v-phare {
        margin-top: clamp(3rem, 7vw, 5rem);
        max-width: 46rem;
    }

    /* Le trait qui désigne les réparations. Il ne sépare rien : il promeut.
       Trois rem d'or au-dessus du service le plus demandé. L'or de la charte
       ne peut pas porter de texte à 1,70 pour 1 ; en aplat de 3 px il est
       enfin à sa place.

       Le tricolore a été essayé ici avant d'aller au pied de page : sur un
       fond crème, sa bande blanche disparaît et il ne reste qu'un trait bleu
       et un trait rouge séparés d'un vide. Voir plus bas. */
    [data-theme='vitrine'] .v-phare::before {
        content: '';
        display: block;
        width: 3rem;
        height: 3px;
        margin-bottom: 1.4rem;
        background: var(--v-or);
    }

    [data-theme='vitrine'] .v-phare__nom {
        font-size: clamp(1.9rem, 1.2rem + 2.4vw, 3rem);
        font-weight: 400;
        line-height: 1.05;
        letter-spacing: -0.022em;
    }

    [data-theme='vitrine'] .v-phare__note {
        margin: 1rem 0 0;
        max-width: 42ch;
        font-size: var(--t-md);
        line-height: 1.5;
        color: var(--encre-douce);
    }

    /* Les deux postes. Deux colonnes qui se répondent, sans filet entre elles :
       l'écart suffit à les séparer, et il est plus large que celui qui sépare
       les services d'un même poste. */

    [data-theme='vitrine'] .v-postes {
        display: grid;
        gap: clamp(2.5rem, 6vw, 5rem);
        margin-top: clamp(3rem, 7vw, 5rem);
        max-width: 62rem;
    }

    @media (min-width: 52rem) {
        [data-theme='vitrine'] .v-postes {
            grid-template-columns: repeat(2, minmax(0, 1fr));
        }
    }

    [data-theme='vitrine'] .v-poste__titre {
        font-size: var(--t-md);
        font-weight: 400;
        letter-spacing: -0.01em;
    }

    [data-theme='vitrine'] .v-poste__note {
        margin: 0.3rem 0 0;
        font-size: 0.72rem;
        font-weight: 500;
        letter-spacing: 0.18em;
        text-transform: uppercase;
        color: var(--accent);
    }

    [data-theme='vitrine'] .v-poste__liste {
        display: grid;
        gap: clamp(1.4rem, 3vw, 2rem);
        margin-top: clamp(1.6rem, 3vw, 2.2rem);
    }

    [data-theme='vitrine'] .v-poste__nom {
        font-size: var(--t-base);
        font-weight: 600;
        letter-spacing: -0.005em;
    }

    [data-theme='vitrine'] .v-poste__detail {
        margin: 0.3rem 0 0;
        max-width: 40ch;
        font-size: var(--t-sm);
        line-height: 1.55;
        color: var(--encre-douce);
    }

    /* ------------------------------------------------------------ la boutique

       Les vues du lieu, entre les horaires et les notes. Elles étaient d'abord
       en bas de page, après l'atelier, où elles n'avaient plus de sens : on
       donne une adresse en tête de site, autant montrer tout de suite la porte
       qu'on va pousser.

       Les images viennent de la fiche Google et font 340 à 680 px de large.
       Trop petites pour un bandeau : servies en pleine largeur elles seraient
       molles. Elles sont donc montrées à leur taille native, dans une rangée
       justifiée où la largeur de chacune est proportionnelle à son rapport.
       Les trois finissent à la même hauteur sans qu'aucune soit recadrée ni
       agrandie, et les trois cadrages d'origine sont conservés.

       Le bloc est borné à 58 rem pour que les images ne dépassent jamais leur
       définition réelle sur un grand écran. */

    [data-theme='vitrine'] .v-vitres {
        display: flex;
        gap: clamp(0.8rem, 1.6vw, 1.4rem);
        margin-top: clamp(2.5rem, 6vw, 4rem);
        max-width: 58rem;
    }

    /* `flex-grow` vaut le rapport largeur/hauteur, base zéro : c'est ce qui
       donne à chaque image une largeur proportionnelle à son cadrage, donc
       une hauteur commune. `min-width: 0` empêche la légende d'élargir sa
       piste et de fausser le calcul. */
    [data-theme='vitrine'] .v-vitres__item {
        flex: var(--rapport, 1) 1 0;
        min-width: 0;
    }

    [data-theme='vitrine'] .v-vitres__figure {
        margin: 0;
    }

    [data-theme='vitrine'] .v-vitres__item img {
        width: 100%;
        height: auto;
        background: var(--fond-doux);
    }

    [data-theme='vitrine'] .v-vitres__legende {
        margin-top: 0.6rem;
        font-size: 0.68rem;
        font-weight: 500;
        letter-spacing: 0.16em;
        text-transform: uppercase;
        color: var(--encre-douce);
    }

    /* En dessous de 46 rem, la rangée justifiée donnerait trois timbres-poste.
       Les images s'empilent et reprennent toute la largeur, ce qui reste sous
       leur définition réelle. */
    @media (max-width: 46rem) {
        [data-theme='vitrine'] .v-vitres {
            flex-direction: column;
            gap: clamp(1.5rem, 4vw, 2.2rem);
            max-width: 26rem;
        }

        [data-theme='vitrine'] .v-vitres__item {
            flex: 0 0 auto;
        }
    }

    /* -------------------------------------------------------------- venir */

    [data-theme='vitrine'] .v-venir__adresse {
        margin: 2rem 0;
        font-size: var(--t-md);
        line-height: 1.5;
    }

    [data-theme='vitrine'] .v-venir__adresse a {
        color: var(--accent);
        text-decoration-color: var(--v-or);
    }

    /* --------------------------------------------------------- pied de page

       Le seul bloc sombre de la page, et il est là pour ça : après huit
       sections sur du blanc, la page a besoin d'un sol. C'est aussi ce que
       fait Kaneko, dont l'accueil finit sur du noir.

       Le brun 4975c porte le blanc cassé à 13,94 pour 1. */

    [data-theme='vitrine'] .pied {
        margin-top: clamp(3rem, 8vw, 7rem);
        padding-block: clamp(3.5rem, 8vw, 6rem) 2rem;
        background: var(--v-encre);
        color: var(--fond);
    }

    [data-theme='vitrine'] .pied__grille {
        display: grid;
        gap: clamp(2rem, 5vw, 3.5rem);
    }

    @media (min-width: 60rem) {
        [data-theme='vitrine'] .pied__grille {
            grid-template-columns: 1.3fr repeat(3, 1fr);
        }
    }

    [data-theme='vitrine'] .pied__nom {
        font-size: var(--t-md);
        letter-spacing: 0.02em;
    }

    [data-theme='vitrine'] .pied__note,
    [data-theme='vitrine'] .pied__adresse,
    [data-theme='vitrine'] .pied p {
        color: color-mix(in srgb, var(--fond) 74%, var(--v-encre));
    }

    [data-theme='vitrine'] .pied__titre {
        margin-bottom: 0.8rem;
        font-size: 0.72rem;
        font-weight: 500;
        letter-spacing: 0.2em;
        text-transform: uppercase;
        color: var(--v-sable);
    }

    [data-theme='vitrine'] .pied__adresse {
        font-style: normal;
        line-height: 1.9;
    }

    [data-theme='vitrine'] .pied a {
        color: inherit;
        text-decoration-color: color-mix(in srgb, var(--v-or) 70%, transparent);
    }

    [data-theme='vitrine'] .pied a:hover {
        color: var(--v-sable);
    }

    [data-theme='vitrine'] .pied__liens {
        display: grid;
        gap: 0.45rem;
    }

    [data-theme='vitrine'] .pied__bas {
        display: flex;
        flex-wrap: wrap;
        justify-content: space-between;
        gap: 1rem 2rem;
        margin-top: clamp(2.5rem, 6vw, 4rem);
        font-size: var(--t-xs);
        letter-spacing: 0.06em;
    }

    /* ---------------------------------------------------- la marque tricolore

       Un Meilleur Ouvrier de France porte un col bleu blanc rouge sur sa veste
       de travail. C'est un code d'atelier que tout le métier reconnaît, et il
       vaut mieux qu'un filet décoratif.

       Deux contraintes ont décidé de l'endroit.

       La première est optique. Sur le crème de la page, la bande blanche est à
       deux pour cent du fond : elle disparaît, et il ne reste qu'un trait bleu
       et un trait rouge séparés d'un vide. Un tricolore a besoin d'un fond
       sombre, et la page n'en a qu'un, le pied. Qui se trouve être exactement
       l'endroit où l'on attendait un séparateur.

       La seconde est de fond. Le col est l'insigne du **titre**, et nos textes
       disent « formée à l'école des Meilleurs Ouvriers de France », ce qui
       n'est pas la même chose. Posée sous cette mention, la marque se lirait
       comme un badge de titulaire et ferait revendiquer à Charlotte un titre
       d'État qu'elle n'a peut-être pas. Posée sous son nom de maison, elle dit
       l'artisanat français et ne revendique rien : elle est juste dans les
       deux cas.

       Le jour où Charlotte confirme qu'elle porte le titre, la marque peut
       remonter sous sa signature dans « La Maison » et devenir ce qu'elle est
       vraiment. C'est un sélecteur à changer.

       Elle remplace le filet translucide qui séparait le bas du pied : deux
       traits superposés auraient fait deux fois le même travail. La ruse du
       `flex: 0 0 100%` avec un `max-width` lui donne sa ligne à elle tout en
       la limitant à six rem, et elle démarre sur le bord du contenu sans avoir
       à recopier la marge du cadrage. */
    [data-theme='vitrine'] .pied__bas::before {
        content: '';
        flex: 0 0 100%;
        max-width: 6rem;
        height: 3px;
        margin-bottom: 1.7rem;
        background: linear-gradient(
            90deg,
            var(--v-bleu) 0 33.333%,
            #fff 33.333% 66.666%,
            var(--v-rouge) 66.666% 100%
        );
    }

    [data-theme='vitrine'] .pied__liens--enligne {
        display: flex;
        gap: 1.5rem;
    }

    [data-theme='vitrine'] .pied :focus-visible {
        outline-color: var(--v-or);
    }

    /* --------------------------------------------------------------- loupe

       La loupe de base est une table lumineuse sombre, montée pour la
       planche-contact. Sur une direction blanche elle ouvrirait un trou noir
       au milieu de la page. Reprise en clair, elle reste ce qu'elle est :
       le moyen de voir réellement la monture. */

    [data-theme='vitrine'] .loupe {
        background: var(--fond);
        color: var(--encre);
    }

    [data-theme='vitrine'] .loupe::backdrop {
        background: rgba(64, 31, 32, 0.42);
        backdrop-filter: blur(6px);
    }

    [data-theme='vitrine'] .loupe__cadre {
        background: var(--fond-doux);
    }

    [data-theme='vitrine'] .loupe__fermer:hover,
    [data-theme='vitrine'] .loupe__pas:hover {
        background: rgba(64, 31, 32, 0.09);
    }

    [data-theme='vitrine'] .loupe__numero {
        color: var(--accent);
    }

    [data-theme='vitrine'] .loupe__donnees dt {
        opacity: 1;
        color: var(--accent);
    }

    [data-theme='vitrine'] .loupe__aide {
        opacity: 0.62;
    }

    /* Les perforations de pellicule appartiennent à la planche-contact. Ici
       elles n'auraient aucun sens, et le fragment de la loupe les porte en
       dur. */
    [data-theme='vitrine'] .loupe .bande__perfos {
        display: none;
    }
}

/* -------------------------------------------------------- rythme vertical

   Ces trois règles vivent dans la couche des utilitaires et pas dans celle
   de la mise en page, parce que `.section` y est déclarée. Écrites plus haut,
   elles étaient bien appliquées et systématiquement écrasées : la couche
   gagne quelle que soit la spécificité du sélecteur, et une section portait
   donc 12 rem de marge en haut comme en bas quoi qu'on écrive.

   Trois endroits demandent autre chose que l'espacement par défaut :

     - l'accroche suit immédiatement la photo d'ouverture. Douze rem de vide
       entre les deux couperaient le lien qu'on vient d'établir ;
     - la fiche est la suite de l'accroche, pas une section nouvelle. Elles se
       lisent ensemble, elles ne se séparent pas ;
     - la dernière section précède le pied, qui pose déjà sa propre marge.
       Cumulées, les deux faisaient 300 px de blanc pour rien. */
@layer utilitaires {

    /* Le pied de page emploie `.contenu`, déclarée dans cette couche : écrit
       ailleurs, le décalage était perdu et la colonne de gauche du pied
       tombait 110 px avant celle de toute la page. Comme la vitrine n'emploie
       `.contenu` qu'au pied, la redéfinir ici revient à donner un seul
       cadrage à la page entière. */
    @media (min-width: 68rem) {
        [data-theme='vitrine'] .contenu {
            padding-left: calc(var(--marge) + 7rem);
        }
    }

    [data-theme='vitrine'] .v-accroche.section {
        padding-block: clamp(3rem, 7vw, 5.5rem) clamp(2rem, 4vw, 3rem);
    }

    [data-theme='vitrine'] .v-reperes.section {
        padding-block: 0 var(--section);
    }

    /* ---------------------------------------------- l'arrivée au défilement

       Trois traitements, un par nature de contenu. C'est ce qui les rend
       travaillés plutôt que décoratifs : un titre, une photo et un paragraphe
       n'arrivent pas de la même façon.

       Tous vont du bas vers le haut, sans exception. Une direction qui
       mélangerait les sens de lecture ferait exactement le « VRAI BORDEL »
       que Charlotte reproche à certains sites japonais.

       La courbe est ferme et pas moelleuse, sans rebond : le rock'n'roll
       qu'elle demande est une question d'aplomb, pas de ressort.

       `js` et `data-theme` sont portés par le même élément, la racine. Le
       sélecteur est donc `html.js[data-theme=…]` sans espace : avec un espace,
       il cherche un descendant qui porte le thème et ne trouve jamais rien. */

    /* 1. Par défaut, le texte courant monte et se révèle. */
    html.js[data-theme='vitrine'] .reveal {
        opacity: 0;
        transform: translateY(2.4rem);
        transition:
            opacity 900ms cubic-bezier(0.16, 0.84, 0.28, 1),
            transform 900ms cubic-bezier(0.16, 0.84, 0.28, 1);
    }

    html.js[data-theme='vitrine'] .reveal.est-visible {
        opacity: 1;
        transform: none;
    }

    /* 1 bis. Dans les maisons, le texte entre par le bord dont il vient.
             Le bloc est à droite au premier rang, à gauche au deuxième, et le
             mouvement suit : c'est ce qui donne à la section son va-et-vient
             plutôt qu'une suite de blocs qui montent tous pareil.

             Les deux règles doivent porter la même spécificité que l'état
             d'arrivée, sinon le `transform: none` du socle gagne au départ et
             rien ne bouge. D'où le `.est-visible` répété au même niveau.

             Sous 56 rem la mise en page s'empile : le décalage latéral n'a
             plus de sens et la montée par défaut reprend la main. */
    @media (min-width: 56rem) {
        html.js[data-theme='vitrine'] .v-phares__item:nth-child(odd) .v-phares__texte.reveal {
            transform: translate(2.6rem, 1.2rem);
        }

        html.js[data-theme='vitrine'] .v-phares__item:nth-child(even) .v-phares__texte.reveal {
            transform: translate(-2.6rem, 1.2rem);
        }

        html.js[data-theme='vitrine'] .v-phares__item .v-phares__texte.reveal.est-visible {
            transform: none;
        }
    }

    /* 2. Les titres se lèvent sous un masque, comme une ligne qu'on remonte
          dans un composteur. Le titre porte le masque, le span porte le
          mouvement, donc rien ne dépasse pendant la course.

          Le masque déborde de 0,18 em en bas : sans cette réserve, la boucle
          du g de « Passez » et la cédille se faisaient couper à l'arrivée. */
    html.js[data-theme='vitrine'] .v-titre.reveal {
        opacity: 1;
        transform: none;
        overflow: hidden;
        padding-bottom: 0.18em;
        margin-bottom: -0.18em;
    }

    html.js[data-theme='vitrine'] .v-titre.reveal > span {
        display: block;
        transform: translateY(105%);
        transition: transform 1000ms cubic-bezier(0.16, 0.84, 0.28, 1);
    }

    html.js[data-theme='vitrine'] .v-titre.reveal.est-visible > span {
        transform: none;
    }

    /* 3. Les photos se découvrent du bas vers le haut, et l'image se pose en
          même temps : le cadre s'ouvre pendant que le cliché revient à son
          échelle. C'est le geste d'un tirage qui monte dans le bain, et c'est
          ce que fait Kaneko sur ses blocs d'image.

          Le voile est un clip-path et pas une transformation : les bandeaux
          portent déjà une translation écrite par le script de parallaxe, et
          deux mains sur la même propriété se battraient.

          **Le clip porte sur les enfants, jamais sur l'élément observé.**
          Posé sur l'élément lui-même, il ramenait son rectangle d'intersection
          à zéro : l'observateur ne le voyait donc jamais entrer à l'écran, ne
          posait pas « est-visible », et l'image restait clippée pour toujours.
          Quatorze images sur quatorze s'étaient ainsi retirées du monde
          elles-mêmes. Les trente-neuf autres blocs, qui ne portent pas de
          clip, arrivaient normalement.

          `> *` couvre les trois cas d'un seul sélecteur : le <picture> et sa
          légende dans un bandeau, le lien dans une vignette de galerie, la
          <figure> dans une vue de boutique. */
    html.js[data-theme='vitrine'] .reveal--voile {
        opacity: 1;
        transform: none;
    }

    html.js[data-theme='vitrine'] .reveal--voile > * {
        clip-path: inset(100% 0 0 0);
        transition: clip-path 1100ms cubic-bezier(0.16, 0.84, 0.28, 1);
    }

    html.js[data-theme='vitrine'] .reveal--voile.est-visible > * {
        clip-path: inset(0 0 0 0);
    }

    /* L'échelle se pose sur l'image et pas sur le <picture>, qui porte la
       parallaxe sur les bandeaux et le survol sur les vignettes. */
    html.js[data-theme='vitrine'] .reveal--voile img {
        transform: scale(1.07);
        transition: transform 1400ms cubic-bezier(0.16, 0.84, 0.28, 1);
    }

    html.js[data-theme='vitrine'] .reveal--voile.est-visible img {
        transform: none;
    }

    @media (prefers-reduced-motion: reduce) {
        html.js[data-theme='vitrine'] .reveal,
        html.js[data-theme='vitrine'] .reveal--voile {
            opacity: 1;
            transform: none;
        }

        html.js[data-theme='vitrine'] .reveal--voile > * {
            clip-path: none;
        }

        html.js[data-theme='vitrine'] .v-titre.reveal > span,
        html.js[data-theme='vitrine'] .reveal--voile img {
            transform: none;
        }
    }

    [data-theme='vitrine'] .v-venir.section {
        padding-bottom: 0;
    }
}


/* ------------------------------------------------ habillage du thème neutre

   Contact, mentions légales et confidentialité étaient rendues dans la
   maquette avec le thème « neutre » (gris chaud sur fond sombre). Elles
   gardent data-theme="vitrine", dont dépend toute la mise en page, et
   reçoivent data-habillage="neutre" (page_habillage en tête du gabarit) : les
   couleurs de v-neutre.css et la typographie du socle de la maquette (le thème
   neutre n'en redéfinit aucune) sont reprises ici, pas sa mise en page.

   Les jetons sont posés sur l'en-tête et le contenu, pas sur body : le pied de
   page reste celui de la vitrine (fond brun, texte en --fond), et il hériterait
   sinon d'un --fond sombre sur son propre fond sombre. La typographie, elle,
   ne vise que le contenu (et le lien d'évitement) : l'en-tête et le pied de
   page gardent celle de la vitrine. */

@layer tokens {
    [data-theme='vitrine'][data-habillage='neutre'] {
        --n-fond: #14110f;
        --n-encre: #ece7e1;
        --n-encre-douce: #9a918a;
        --n-accent: #c9a37a;
        --n-trait: rgba(236, 231, 225, 0.14);
    }

    [data-theme='vitrine'][data-habillage='neutre'] :is(.skip-link, .entete, #contenu) {
        --fond: var(--n-fond);
        --encre: var(--n-encre);
        --encre-douce: var(--n-encre-douce);
        --accent: var(--n-accent);
        --trait: var(--n-trait);
        --focus: var(--n-accent);
    }

    /* La typographie du socle de la maquette : Archivo pour le texte courant,
       les intitulés et les petites capitales, Fraunces aux axes d'origine pour
       les titres. La vitrine les remplace sur <html>, on les rétablit ici. */
    [data-theme='vitrine'][data-habillage='neutre'] :is(.skip-link, #contenu) {
        --f-ui: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
        --display-axes: 'opsz' 144, 'SOFT' 100, 'WONK' 1;
    }
}

@layer layout {
    [data-theme='vitrine'][data-habillage='neutre'] body {
        background-color: var(--n-fond);
        background-image: none;
        color: var(--n-encre);
    }

    /* Fond uni, comme body sur ces pages : le dégradé de la vitrine n'y a pas
       cours. --fond vaut ici celui du thème neutre (jetons posés sur .entete). */
    [data-theme='vitrine'][data-habillage='neutre'] .entete {
        background-image: none;
    }

    /* La sélection du socle (rose flash), que la vitrine remplace par son magenta */
    [data-theme='vitrine'][data-habillage='neutre'] :is(.entete, #contenu) ::selection {
        background: var(--flash);
        color: #fff;
    }

    /* Le survol du bouton du socle : la vitrine le passe en écaille */
    [data-theme='vitrine'][data-habillage='neutre'] .cta:hover {
        background: var(--accent);
        color: var(--sable);
    }

    /* Le monogramme est un tracé noir sur fond transparent : sans ça, il
       disparaît sur le fond sombre. Écart avec la maquette, qui masquait
       l'en-tête de ces pages. */
    /* Le jeton ne suffit pas : font-family est hérité de body déjà calculé. La
       vitrine ajoute aussi un interlettrage à body, que le socle n'a pas. */
    [data-theme='vitrine'][data-habillage='neutre'] :is(.skip-link, #contenu) {
        font-family: var(--f-ui);
        letter-spacing: normal;
    }

    /* Les petites capitales et le bouton du socle, que la vitrine recompose
       plus petits et plus légers. Seule la typographie est rétablie : la marge
       de l'eyebrow et le rembourrage du bouton restent ceux de la vitrine. */
    [data-theme='vitrine'][data-habillage='neutre'] #contenu .eyebrow {
        font-size: var(--t-sm);
        font-weight: 600;
        letter-spacing: 0.2em;
    }

    [data-theme='vitrine'][data-habillage='neutre'] #contenu .cta {
        font-size: var(--t-sm);
        font-weight: 600;
        letter-spacing: 0.11em;
    }

    [data-theme='vitrine'][data-habillage='neutre'] .entete__monogramme {
        filter: invert(1);
    }
}

/* Zones image à format imposé (option « ratio » d'une zone image dans config/contenus.php). Sans ça,
   une photo prend la largeur de sa colonne et impose SA hauteur : dans le même emplacement, un
   fichier carré s'affiche en 534 x 534 et un portrait 1024 x 1536 en 526 x 789, soit 48 % de hauteur
   en plus et toute la page poussée vers le bas (mesuré). Le cadre commande, la photo le remplit et
   déborde en étant rognée.

   « width: 100% » et non « max-width » : une image plus petite que sa colonne resterait à sa taille
   et laisserait un blanc à côté, ce qui viderait la contrainte de son sens. Contrepartie assumée :
   une image nettement plus petite que son emplacement sera étirée, donc floue — l'aide de la zone
   dans le back-office doit donner la taille attendue. */
.image-zone {
    display: block;
    width: 100%;
    height: auto;
    object-fit: cover;
}

/* Liste fermée, tenue avec Contenus::RATIOS : une zone déclare son format dans config/contenus.php */
.image-zone-4-3 { aspect-ratio: 4 / 3; }
.image-zone-3-2 { aspect-ratio: 3 / 2; }
.image-zone-16-9 { aspect-ratio: 16 / 9; }
.image-zone-1-1 { aspect-ratio: 1 / 1; }
.image-zone-4-5 { aspect-ratio: 4 / 5; }
.image-zone-3-4 { aspect-ratio: 3 / 4; }
