/*
 * Rôle : styles communs aux 7 pages de laugoarms.fr (accueil, Conception,
 * Compétition, Standard, Creator, Remus, mentions légales). Composition et mouvements inspirés de
 * porsche.com/france (vidéo plein écran, cartes arrondies, menu en panneau),
 * en noir avec mots en dégradé blanc / gris, sans rien en reprendre : ni
 * code, ni police, ni image.
 * Couches, de la plus faible à la plus forte priorité :
 *   base (éléments HTML), layout (sections), components.
 * Animations regroupées en fin de fichier, uniquement si le visiteur n'a pas
 * demandé moins de mouvement.
 */

@layer base, layout, components;

/* Police Titillium Web (licence libre SIL OFL, fichier fonts/OFL.txt),
   hébergée ici. « swap » : le texte s'affiche tout de suite avec la police du
   système. Deux fichiers par graisse : l'alphabet latin courant (français
   compris : é, œ, €, « ») et un complément (Š, ě de « Štěpán »…) que le
   navigateur ne télécharge que si la page contient l'un de ces caractères. */
@font-face {
  font-family: "Titillium Web";
  font-weight: 300;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-300.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Titillium Web";
  font-weight: 300;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-300-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Titillium Web";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Titillium Web";
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-400-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Titillium Web";
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-600.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Titillium Web";
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/titillium-web-600-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Police des titres Funnel Display (licence libre SIL OFL, fichier
   fonts/OFL-funnel-display.txt), choisie par Christophe : Light (300) pour
   les titres, Bold (700) pour leurs mots accentués, les noms géants, les
   chiffres clés, les prix et les noms des modèles. Mêmes principes que
   Titillium Web ci-dessus (« swap », latin courant + complément). */
@font-face {
  font-family: "Funnel Display";
  font-weight: 300;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/funnel-display-300.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Funnel Display";
  font-weight: 300;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/funnel-display-300-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: "Funnel Display";
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/funnel-display-700.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212,
    U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: "Funnel Display";
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  src: url("/fonts/funnel-display-700-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  color-scheme: dark;

  /* Couleurs */
  --color-ink: #07070a;
  --color-ink-soft: #15161b;
  --color-paper: #ffffff;
  --color-surface: #101116;
  --color-line: rgb(255 255 255 / 0.13);
  --color-muted: #a8aab0;
  /* Titres en gris léger ; leurs mots accentués (.textAccent) en dégradé. */
  --color-title: #8e9097;
  --card-fill: linear-gradient(180deg, #26272e 0%, #131419 62%);

  /* Dégradé des mots accentués et des boutons */
  --gradient-metal: linear-gradient(180deg, #ffffff 0%, #ecedef 45%, #a4a6ad 100%);

  /* Pistolets qui flottent : halo pour les détacher du noir, puis ombre
     portée qui suit leur silhouette. */
  --gun-shadow: drop-shadow(0 0 16px rgb(255 255 255 / 0.13)) drop-shadow(0 26px 20px rgb(0 0 0 / 0.8));
  --color-muted-on-dark: #a8aab0;
  --color-accent: #db0009;
  --color-accent-hover: #b80008;

  /* Typographie */
  --font-sans: "Titillium Web", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-display: "Funnel Display", var(--font-sans);
  --fs-xs: 0.8125rem;
  --fs-sm: 0.9375rem;
  --fs-md: 1.125rem;
  --fs-lg: clamp(1.3rem, 1.1rem + 0.9vw, 1.75rem);
  --fs-xl: clamp(2rem, 1.3rem + 2.8vw, 3.75rem);
  --fs-xxl: clamp(3.2rem, 1.6rem + 6.5vw, 7.5rem);

  /* Espaces et formes */
  --gutter: clamp(1rem, 4vw, 3.5rem);
  --section: clamp(4rem, 3rem + 5vw, 8rem);
  /* Deux largeurs seulement sur tout le site (Christophe, 2026-11-14) :
     la pleine page, ou le conteneur commun, centré, de --content-max au
     plus et jamais à moins de --gutter du bord. --page-inset est le
     retrait de ce conteneur : posé en marge intérieure (ou en position)
     d'un bloc pleine page, il aligne son contenu sur le conteneur. Le
     « 100 % » se lit sur le bloc qui l'utilise. */
  --content-max: 96rem;
  --page-inset: max(var(--gutter), calc((100% - var(--content-max)) / 2));
  /* Écart entre les cartes de toutes les grilles du site : 5rem sur grand
     écran (Christophe, 2026-11-15), 1,25rem sur téléphone. */
  --card-gap: clamp(1.25rem, 8vw - 2.5rem, 5rem);
  --header-height: 4rem;
  --radius-button: 4px;
  --radius-card: 16px;

  --ease-out: cubic-bezier(0.2, 0.7, 0.2, 1);

  /* Apparitions : un bloc part quand son haut est monté de 25 % de
     l'écran (seuil dans js/main.js) et joue son animation en entier
     (Christophe, 2026-11-14). --reveal-to : mi-écran, où commencent les
     fonds tenus. --reveal-stagger : retard de chaque bloc sur le précédent
     quand plusieurs partent ensemble (0,2 s était trop faible, Christophe,
     2026-11-15). */
  --reveal-to: 50svh;
  --reveal-stagger: 0.35s;
  /* Amplitude : montée, taille de départ, arrivée de côté. */
  --reveal-rise: 8rem;
  --reveal-scale: 0.92;
  --reveal-side: 12rem;

  /* Icônes (flèche, croix, pause, lecture) : dessinées en SVG et colorées
     par la couleur du texte (masque CSS). */
  --icon-arrow: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M3 11h14.2l-5.6-5.6L13 4l8 8-8 8-1.4-1.4 5.6-5.6H3z'/%3E%3C/svg%3E");
  --icon-pause: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M6 4h4v16H6zM14 4h4v16h-4z'/%3E%3C/svg%3E");
  --icon-play: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7 4l13 8-13 8z'/%3E%3C/svg%3E");
}

/* ---------------------------------------------------------------- base */
@layer base {

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

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

  body {
    margin: 0;
    background: var(--color-ink);
    color: var(--color-paper);
    font-family: var(--font-sans);
    font-size: var(--fs-md);
    font-weight: 400;
    line-height: 1.55;
  }

  h1,
  h2,
  h3,
  h4 {
    margin: 0;
    color: var(--color-title);
    font-family: var(--font-display);
    font-weight: 300;
    line-height: 1.05;
    text-wrap: balance;
  }

  h2 {
    margin-block-end: 1.25rem;
    font-size: var(--fs-xl);
    letter-spacing: -0.01em;
  }

  h3 {
    margin-block-end: 0.5rem;
    font-size: var(--fs-lg);
  }

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

  ul {
    margin: 0;
    padding: 0;
  }

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

  a {
    color: inherit;
    text-decoration: none;
  }

  button {
    font: inherit;
    color: inherit;
    cursor: pointer;
  }

  dl,
  dd {
    margin: 0;
  }

  [id] {
    scroll-margin-top: var(--header-height);
  }

  :focus-visible {
    outline: 2px solid var(--color-accent);
    outline-offset: 3px;
  }
}

/* -------------------------------------------------------------- layout */
@layer layout {

  /* Sections : fond sur toute la largeur, contenu dans le conteneur
     commun. La grande photo garde ce retrait sur les côtés : son texte
     s'aligne sur le conteneur. */
  main>section {
    padding-block: var(--section);
    padding-inline: var(--page-inset);
  }

  main>.hero {
    padding: 0;
  }

  main>.fullBleed {
    padding-block: 0;
  }
}

/* ---------------------------------------------------------- components */
@layer components {
  .skipLink {
    position: absolute;
    top: 0.5rem;
    left: 0.5rem;
    z-index: 30;
    padding: 0.6rem 1rem;
    background: var(--color-paper);
    color: var(--color-ink);
    translate: 0 -200%;

    &:focus-visible {
      translate: 0 0;
    }
  }

  /* Textes récurrents */
  .label {
    margin-block-end: 0.75rem;
    color: var(--color-muted);
    font-size: var(--fs-sm);
  }

  .lead {
    max-width: 40rem;
    color: var(--color-muted);
    font-size: var(--fs-lg);
    font-weight: 300;
    line-height: 1.4;
  }

  /* Titres centrés : titres choisis de l'accueil, et tous les titres de
     section des pages modèles (celles qui s'ouvrent sur .modelHero). */
  .sectionTitle,
  main:has(> .modelHero)>section>h2 {
    max-width: 52rem;
    margin-inline: auto;
    text-align: center;
  }

  /* Pages modèles : titres de section 20 % plus petits que ceux de
     l'accueil (Christophe, 2026-11-12). */
  main:has(> .modelHero)>section>h2 {
    font-size: calc(var(--fs-xl) * 0.8);
  }

  .price {
    font-size: var(--fs-md);
  }

  /* Étiquettes en pilule : teinte tirée de la couleur du texte, donc
     lisibles sur fond clair comme sur fond sombre. */
  .pills {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
  }

  .pill {
    padding: 0.2rem 0.65rem;
    border-radius: 999px;
    background: color-mix(in srgb, currentColor 14%, transparent);
    font-size: var(--fs-xs);
    line-height: 1.5;
    white-space: nowrap;
  }

  /* Boutons */
  .button {
    display: inline-flex;
    align-items: center;
    min-height: 3rem;
    padding: 0.75rem 1.5rem;
    border-radius: var(--radius-button);
    background: var(--gradient-metal);
    color: var(--color-ink);
    font-size: var(--fs-sm);

    &:hover {
      background: var(--color-paper);
    }
  }

  /* Boutons d'action et boutons ronds : rouges, arrondis (Christophe,
     2026-11-14 : plus de boutons translucides). Survol : rouge éclairci. */
  .ctaButton {
    display: inline-flex;
    align-items: center;
    min-height: 3rem;
    padding: 0.75rem 1.75rem;
    border: 0;
    border-radius: 999px;
    background: var(--color-accent);
    color: var(--color-paper);
    font-size: var(--fs-sm);
    font-weight: 600;
    transition: background-color 0.2s;

    &:hover {
      background: var(--color-accent-hover);
    }
  }

  /* Bouton rond à flèche (cartes) */
  .roundArrow {
    display: grid;
    place-items: center;
    width: 2.5rem;
    height: 2.5rem;
    border-radius: 50%;
    background: var(--color-accent);
    color: var(--color-paper);
    transition: translate 0.3s var(--ease-out);

    &::before {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
    }
  }

  /* ------------------------------------------------------------ en-tête */
  /* « Menu » à gauche, nom centré. Sur l'accueil : transparent sur la
     vidéo puis noir en descendant. Il se cache quand on descend et revient
     quand on remonte (attribut posé par js/main.js) ; le bouton « Menu »,
     lui, reste toujours visible (Christophe, 2026-11-12). */
  .siteHeader {
    position: fixed;
    inset: 0 0 auto;
    z-index: 10;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    column-gap: 0.75rem;
    align-items: center;
    height: var(--header-height);
    padding-inline: var(--page-inset);
    background-color: rgb(14 14 18 / 0.55);
    color: var(--color-paper);
    backdrop-filter: blur(12px);
    transition: translate 0.4s var(--ease-out);

    &[data-hidden="true"] {
      translate: 0 -100%;

      /* Le bouton redescend d'autant que l'en-tête monte, dans le même
         temps : il ne bouge pas à l'écran. Sans l'en-tête derrière lui,
         il prend son propre fond pour rester lisible sur la page. */
      &>.menuButton {
        translate: 0 var(--header-height);
        background-color: rgb(7 7 10 / 0.86);
        box-shadow: 0 0 0 1px var(--color-line);
      }
    }
  }

  .siteHeader--solid {
    background-color: rgb(7 7 10 / 0.86);
    box-shadow: 0 1px 0 var(--color-line);
  }

  @supports (animation-timeline: scroll()) {
    .siteHeader:not(.siteHeader--solid) {
      animation: headerTint linear both;
      animation-timeline: scroll(root);
      animation-range: 40svh 90svh;
    }

    @keyframes headerTint {
      from {
        background-color: rgb(255 255 255 / 0);
        color: var(--color-paper);
        backdrop-filter: blur(0);
      }

      to {
        background-color: rgb(7 7 10 / 0.50);
        backdrop-filter: blur(50px);
      }
    }
  }

  .menuButton {
    display: inline-flex;
    align-items: center;
    gap: 0.6rem;
    justify-self: start;
    min-height: 2.75rem;
    /* Marge négative = rembourrage ajouté pour le fond du bouton quand
       l'en-tête est caché : le texte ne se décale pas. */
    margin-inline-start: -0.6rem;
    padding: 0.4rem 0.8rem;
    border: 0;
    border-radius: 999px;
    background-color: transparent;
    font-size: var(--fs-sm);
    transition: translate 0.4s var(--ease-out), background-color 0.4s var(--ease-out), box-shadow 0.4s var(--ease-out);

    /* Trois traits du « hamburger » */
    &::before {
      content: "";
      width: 1.1rem;
      height: 0.75rem;
      background:
        linear-gradient(currentColor 0 0) top / 100% 1.5px no-repeat,
        linear-gradient(currentColor 0 0) center / 100% 1.5px no-repeat,
        linear-gradient(currentColor 0 0) bottom / 100% 1.5px no-repeat;
    }
  }

  /* Logo Laugo Arms (img/logo-laugo-arms.svg) : à droite de l'en-tête sur
     téléphone, au centre à partir de 48em. */
  .wordmark {
    grid-column: 3;
    justify-self: end;

    @media (min-width: 48em) {
      grid-column: 2;
      justify-self: center;
    }

    &>img {
      width: auto;
      height: clamp(0.875rem, 0.65rem + 0.7vw, 1.35rem);
    }
  }

  /* --------------------------------------------------------------- menu */
  /* Panneau natif « popover » : glisse depuis la gauche, le reste de la
     page s'assombrit. Une colonne de rubriques ; « Collection » ouvre le
     volet des modèles (.menuModels), qui se cale sur --menu-width. */
  .menuPanel {
    --menu-width: 100vw;
    position: fixed;
    inset: 0 auto 0 0;
    width: var(--menu-width);
    max-width: 100vw;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    padding: 0;
    overflow: auto;
    border: 0;
    background: var(--color-ink);
    color: var(--color-paper);
    translate: -100% 0;
    transition:
      translate 0.55s var(--ease-out),
      display 0.55s allow-discrete,
      overlay 0.55s allow-discrete;

    @media (min-width: 48em) {
      --menu-width: 23rem;
    }

    &:popover-open {
      translate: 0 0;
    }

    /* Volet ouvert : une seule croix, celle du volet, tout à droite. */
    &:has(> .menuModels:popover-open)>.closeButton {
      visibility: hidden;
    }

    &::backdrop {
      background-color: rgb(0 0 0 / 0);
      transition:
        background-color 0.55s,
        display 0.55s allow-discrete,
        overlay 0.55s allow-discrete;
    }

    &:popover-open::backdrop {
      background-color: rgb(0 0 0 / 0.55);
    }
  }

  @starting-style {
    .menuPanel:popover-open {
      translate: -100% 0;
    }

    .menuPanel:popover-open::backdrop {
      background-color: rgb(0 0 0 / 0);
    }
  }

  /* Logo dans le menu : même taille que dans l'en-tête (.wordmark), en
     haut à gauche, à la hauteur de la croix et aligné sur les rubriques.
     Les placements de l'en-tête (colonnes, centrage) sont annulés : ils
     viseraient les colonnes du menu. */
  .menuPanel>.wordmark {
    display: flex;
    position: absolute;
    top: 1rem;
    left: 2.25rem;
    align-items: center;
    grid-column: auto;
    justify-self: start;
    height: 2.75rem;
  }

  /* Croix de fermeture des panneaux (menu, fiche des chargeurs). */
  .closeButton {
    position: absolute;
    top: 1rem;
    right: 1rem;
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 50%;
    background:
      linear-gradient(45deg, transparent calc(50% - 0.75px), currentColor 0 calc(50% + 0.75px), transparent 0) center / 1.1rem 1.1rem no-repeat,
      linear-gradient(-45deg, transparent calc(50% - 0.75px), currentColor 0 calc(50% + 0.75px), transparent 0) center / 1.1rem 1.1rem no-repeat,
      var(--color-surface);
  }

  .menuMain {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    padding: 5rem 1.25rem 2rem;

    &>:is(a, button) {
      display: flex;
      justify-content: space-between;
      align-items: center;
      width: 100%;
      padding: 0.8rem 1rem;
      border: 0;
      border-radius: 8px;
      background: none;
      font-size: var(--fs-lg);
      text-align: start;
      transition: background-color 0.2s;

      &:hover {
        background: var(--color-surface);
      }
    }

    /* « Collection » : flèche vers le volet ; volet ouvert, le bouton prend
       la couleur du volet, comme un onglet qui s'y prolonge. */
    &>button::after {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
      opacity: 0.5;
      transition: translate 0.3s var(--ease-out), opacity 0.2s;
    }

    &>button:hover::after {
      translate: 0.25rem 0;
      opacity: 1;
    }
  }

  .menuPanel:has(> .menuModels:popover-open) .menuMain>button {
    /* Jusqu'au bord du menu, coins droits : il touche le volet. La flèche
       ne bouge pas (marge droite agrandie d'autant). */
    width: calc(100% + 1.25rem);
    padding-inline-end: 2.25rem;
    border-radius: 8px 0 0 8px;
    background: var(--color-surface);

    &::after {
      opacity: 1;
    }
  }

  /* Volet des modèles : second panneau natif, dans le menu. Téléphone : il
     glisse depuis la droite et couvre les rubriques (« Retour » y ramène).
     Ordinateur : il se déplie à droite des rubriques, qui restent visibles.
     Les valeurs par défaut des panneaux natifs (cadre, marges, taille au
     contenu) sont annulées ; display seulement à l'ouverture, sinon il
     écraserait le « caché » du navigateur. */
  .menuModels {
    position: fixed;
    inset: 0;
    width: auto;
    max-width: none;
    height: 100dvh;
    max-height: 100dvh;
    margin: 0;
    padding: 5rem 1.25rem 3rem;
    overflow: auto;
    border: 0;
    background: var(--color-surface);
    color: var(--color-paper);
    translate: 100% 0;
    transition:
      translate 0.45s var(--ease-out),
      clip-path 0.45s var(--ease-out),
      display 0.45s allow-discrete,
      overlay 0.45s allow-discrete;

    &:popover-open {
      display: grid;
      align-content: start;
      gap: 1rem;
      translate: 0 0;
    }

    /* Ordinateur : un peu moins large que l'ancienne colonne (Christophe,
       2026-11-13), et jamais au-delà du bord de l'écran. Il se découvre de
       gauche à droite, comme s'il sortait de derrière les rubriques. */
    @media (min-width: 48em) {
      inset: 0 auto 0 var(--menu-width);
      width: min(29rem, 100% - var(--menu-width));
      translate: 0 0;
      clip-path: inset(0 100% 0 0);

      &:popover-open {
        clip-path: inset(0);
      }
    }
  }

  @starting-style {
    .menuModels:popover-open {
      translate: 100% 0;
    }
  }

  @media (min-width: 48em) {
    @starting-style {
      .menuModels:popover-open {
        translate: 0 0;
        clip-path: inset(0 100% 0 0);
      }
    }
  }

  /* Retour aux rubriques, en haut à gauche comme le logo du menu.
     Téléphone seulement : sur ordinateur, les rubriques restent à côté. */
  .backButton {
    position: absolute;
    top: 1rem;
    left: 2.25rem;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    background: none;
    font-size: var(--fs-md);

    &::before {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
      rotate: 180deg;
    }

    @media (min-width: 48em) {
      display: none;
    }
  }

  .menuModel {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: center;
    gap: 0.25rem 1rem;
    padding: 1rem 1.25rem;
    border-radius: 12px;
    transition: background-color 0.2s;

    /* Nom en deux lignes, comme les titres (maquette de Christophe,
       2026-11-12) : « Alien » en Light gris, puis le modèle en Bold
       (.textAccent). */
    &>.name {
      color: var(--color-title);
      font-family: var(--font-display);
      font-size: var(--fs-md);
      font-weight: 300;
      line-height: 1.05;

      &>.textAccent {
        display: block;
        font-size: var(--fs-lg);
      }
    }

    &>.visual {
      grid-row: span 2;
      width: clamp(8rem, 18vw, 13rem);
      transition: translate 0.4s var(--ease-out);
    }

    &>.pills {
      align-self: start;
    }

    &:hover {
      background: rgb(255 255 255 / 0.06);
    }

    &:hover>.visual {
      translate: 0.4rem 0;
    }
  }

  /* --------------------------------------------------- ouverture (accueil) */
  /* .hero : conteneur ; .heroStage : la scène plein écran. Le conteneur
     s'allonge pour l'effet « carte » (voir animations en fin de fichier). */
  .hero {
    background: var(--color-ink);
  }

  .heroStage {
    position: relative;
    isolation: isolate;
    display: grid;
    align-items: end;
    height: 100svh;
    min-height: 34rem;
    padding: 0 var(--page-inset) clamp(6rem, 15vh, 10rem);
    overflow: clip;
    background: var(--color-ink);
    color: var(--color-paper);

    &::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background:
        linear-gradient(to top, rgb(0 0 0 / 0.8), rgb(0 0 0 / 0) 65%),
        linear-gradient(to right, rgb(0 0 0 / 0.55), rgb(0 0 0 / 0) 55%),
        linear-gradient(to bottom, rgb(0 0 0 / 0.5), rgb(0 0 0 / 0) 22%);
    }

    &>picture,
    &>.heroVideo {
      position: absolute;
      inset: 0;
      z-index: -2;
    }

    &>picture>img,
    &>.heroVideo {
      width: 100vw;
      height: 100vh;
      object-fit: cover;
      position: fixed;
    }
  }

  .heroContent {
    max-width: 50rem;

    /* Le titre est le logo Laugo Arms (image), à la largeur qu'avait le
       nom écrit en très grand. */
    &>h1 {
      margin-block-end: 1.5rem;
      width: 50%;
    }

    &>h1>img {
      width: min(100%, 40rem);
    }

    &>p {
      max-width: 34rem;
      margin-block-end: 2rem;
      color: rgb(255 255 255 / 0.75);
      font-size: var(--fs-lg);
      font-weight: 300;
      line-height: 1.35;
    }
  }

  /* Flèche ↓ au centre, en bas de l'ouverture */
  .scrollCue {
    position: absolute;
    bottom: 1.75rem;
    left: 50%;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    margin-left: -1.375rem;

    &::before {
      content: "";
      width: 1.3rem;
      height: 1.3rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
      rotate: 90deg;
    }
  }

  .pauseButton {
    position: absolute;
    right: var(--page-inset);
    bottom: 1.75rem;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 50%;
    background: var(--color-accent);
    color: var(--color-paper);
    transition: background-color 0.2s;

    &:hover {
      background: var(--color-accent-hover);
    }

    &::before {
      content: "";
      width: 0.95rem;
      height: 0.95rem;
      background: currentColor;
      mask: var(--icon-pause) center / contain no-repeat;
    }

    &[aria-pressed="true"]::before {
      mask-image: var(--icon-play);
    }

    &[hidden] {
      display: none;
    }
  }

  /* -------------------------------------------------- grilles de cartes */
  /* Toutes les rangées de cartes du site (Christophe, 2026-11-15 : « trop
     de choses identiques qui réagissent différemment ») : même écart, même
     distance sous le titre ou le chapeau, mêmes colonnes. Une colonne sur
     téléphone ; à partir de 48em, une colonne par carte, sans jamais
     laisser une carte seule sur sa rangée : 2 cartes côte à côte, 3 en
     rangée, 4 en 2 × 2 puis en rangée à partir de 80em (sauf les vidéos,
     qui restent en 2 × 2 pour rester regardables). */
  :is(.teaserList, .modelTiles, .featureList, .photoCards, .videoGrid) {
    display: grid;
    grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
    gap: var(--card-gap);
    margin: 2.5rem 0 0;
    padding: 0;
    list-style: none;

    @media (min-width: 48em) {
      --cols: 3;

      &:has(> :nth-child(2):last-child),
      &:has(> :nth-child(4):last-child) {
        --cols: 2;
      }

      &:has(> :only-child) {
        --cols: 1;
      }
    }

    @media (min-width: 80em) {
      &:not(.videoGrid):has(> :nth-child(4):last-child) {
        --cols: 4;
      }
    }
  }

  /* Cartes photo (accroches, modèles, photos des matchs) : même cadre, même
     voile en bas, même réaction au survol (la photo grossit, la flèche
     avance). Chaque carte n'ajoute que sa mise en page. */
  :is(.teaserCard, .modelTile, .photoCard) {
    position: relative;
    isolation: isolate;
    overflow: clip;
    border-radius: var(--radius-card);
    color: var(--color-paper);

    &>.visual {
      position: absolute;
      inset: 0;
      z-index: -2;
      width: 100%;
      height: 100%;
      object-fit: cover;
      transition: scale 0.9s var(--ease-out);
    }

    &::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background: linear-gradient(to top, rgb(0 0 0 / 0.65), rgb(0 0 0 / 0) 55%);
    }

    &>.roundArrow {
      position: absolute;
      right: 1.25rem;
      bottom: 1.25rem;
    }

    &:hover>.visual {
      scale: 1.05;
    }

    &:hover>.roundArrow {
      translate: 0.25rem 0;
    }
  }

  /* --------------------------------------------- accroches sous l'ouverture */
  /* « Pourquoi l'Alien ? » : titre et phrase centrés, puis les trois
     raisons. */
  .teasers {
    padding-block-end: 0;

    &>.lead {
      margin-inline: auto;
      text-align: center;
    }
  }

  .teaserCard {
    display: block;
    aspect-ratio: 4 / 5;

    &>.caption {
      position: absolute;
      left: 1.25rem;
      right: 4.5rem;
      bottom: 1.25rem;
      font-size: var(--fs-md);
      line-height: 1.25;
    }

    /* Intitulé de la raison, au-dessus de la légende. */
    & .kicker {
      display: block;
      margin-block-end: 0.35rem;
      color: rgb(255 255 255 / 0.7);
      font-size: var(--fs-xs);
      letter-spacing: 0.14em;
      text-transform: uppercase;
    }
  }

  /* ------------------------------------------------------ cartes modèles */
  .tilesBand {
    background: var(--color-ink);
    color: var(--color-paper);
  }

  /* Cartes alignées entre elles (Christophe, 2026-11-14) : chaque carte
     occupe six lignes de la grille commune (sous-grille) : logo Alien,
     nom, place de la photo, pastilles, texte, prix. Pastilles, textes et
     prix des cartes d'une même rangée tombent donc à la même hauteur, quelle
     que soit la longueur du texte. Pas d'écart entre les lignes de la
     grille (la sous-grille en hériterait à l'intérieur des cartes) : les
     cartes empilées (téléphone) sont séparées par la marge du bas. */
  .modelTiles {
    row-gap: 0;

    &>li {
      display: grid;
      grid-row: span 6;
      grid-template-rows: subgrid;
    }

    &>li:not(:last-child) {
      @media (max-width: 47.99em) {
        margin-block-end: var(--card-gap);
      }
    }
  }

  .modelTile {
    container-type: inline-size;
    display: grid;
    grid-row: 1 / -1;
    grid-template-rows: subgrid;
    background: var(--color-ink-soft);

    &::after {
      background:
        linear-gradient(to bottom, rgb(0 0 0 / 0.55), rgb(0 0 0 / 0) 30%),
        linear-gradient(to top, rgb(0 0 0 / 0.92), rgb(0 0 0 / 0.6) 25%, rgb(0 0 0 / 0) 58%);
    }

    /* Place de la photo (3e ligne) : sa hauteur suit la largeur de la
       carte, pour garder à peu près les proportions 4:5. */
    &::before {
      content: "";
      grid-row: 3;
      aspect-ratio: 16 / 10;
    }

    /* Logo de la gamme Alien en petit au-dessus du nom du modèle, comme
       dans l'ouverture des pages modèles. */
    &>.family {
      grid-row: 1;
      width: clamp(6.5rem, 5.5rem + 2vw, 8.5rem);
      margin: 1.75rem auto 0;
      opacity: 0.85;
    }

    /* Nom : jamais plus large que la carte (cartes étroites des
       tablettes, trois par rangée). */
    &>.name {
      grid-row: 2;
      padding-block-start: 0.5rem;
      font-size: min(clamp(2rem, 1.4rem + 2.2vw, 3.25rem), 13cqi);
      letter-spacing: 0.04em;
      line-height: 1;
      text-align: center;
      text-transform: uppercase;
    }

    /* Pastilles noires, en bas de leur ligne : elles restent collées au
       texte quand une carte voisine a plus de pastilles. */
    &>.pills {
      grid-row: 4;
      align-self: end;
      margin: 0 1.5rem 0.75rem;
    }

    & .pill {
      background: rgb(7 7 10 / 0.82);
      color: var(--color-paper);
    }

    &>.tagline {
      grid-row: 5;
      margin-inline: 1.5rem;
      color: rgb(255 255 255 / 0.85);
      font-size: var(--fs-sm);
    }

    &>.price {
      grid-row: 6;
      align-self: end;
      margin: 0.75rem 5rem 1.5rem 1.5rem;
    }

  }

  /* Deux cartes côte à côte (bas des pages modèles) : moins hautes, la
     place de la photo est plus basse. */
  .modelTiles--pair>li>.modelTile::before {
    @media (min-width: 48em) {
      aspect-ratio: 3 / 1;
    }
  }

  /* Cartes de points techniques (grille : voir « grilles de cartes ») */
  .featureCard {
    height: 100%;
    padding: 1.25rem 1.25rem 1.75rem;
    border: 1px solid rgb(255 255 255 / 0.07);
    border-radius: var(--radius-card);
    background: var(--card-fill);

    &>.visual {
      width: 100%;
      aspect-ratio: 4 / 3;
      margin-block-end: 1.25rem;
      object-fit: contain;
    }

    &>.visual--cover {
      border-radius: 10px;
      object-fit: cover;
    }

    &>p {
      color: var(--color-muted);
      font-size: var(--fs-sm);
    }
  }

  /* ------------------------------------------ grande image plein écran */
  .fullBleed {
    position: relative;
    isolation: isolate;
    display: grid;
    align-items: end;
    min-height: 92svh;
    overflow: clip;
    color: var(--color-paper);

    &>.visual {
      position: absolute;
      inset: -10% 0;
      z-index: -2;
      width: 100%;
      height: 120%;
      max-width: none;
      object-fit: cover;
    }

    &::after {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background:
        linear-gradient(to top, var(--color-ink), rgb(0 0 0 / 0.25) 70%),
        linear-gradient(to right, rgb(0 0 0 / 0.6), rgb(0 0 0 / 0) 60%);
    }
  }

  /* Texte aligné sur le conteneur (retrait posé sur .fullBleed) ; sa
     largeur limitée est celle d'une ligne lisible. */
  .fullBleedContent {
    max-width: 48rem;
    padding-block-end: clamp(3rem, 8vw, 6rem);

    &>p {
      margin-block-end: 2rem;
      color: rgb(255 255 255 / 0.86);
      font-size: var(--fs-lg);
      font-weight: 300;
      line-height: 1.4;
    }
  }

  /* -------------------------------------------------------------- vidéos */
  .videoBand {
    background: var(--color-ink);
    color: var(--color-paper);

    & .lead {
      color: var(--color-muted-on-dark);
    }
  }

  .videoCard {
    margin: 0;

    &>video {
      width: 100%;
      aspect-ratio: 16 / 9;
      border-radius: var(--radius-card);
      background: var(--color-ink-soft);
      object-fit: cover;
    }

    &>figcaption {
      margin-block-start: 0.75rem;
      color: var(--color-muted-on-dark);
      font-size: var(--fs-sm);
    }
  }

  /* ------------------------------------------------- photos compétition */
  .photoCard {
    margin: 0;
    aspect-ratio: 4 / 5;

    &>figcaption {
      position: absolute;
      left: 1.25rem;
      right: 1.25rem;
      bottom: 1.25rem;
    }
  }

  /* ------------------------------------------------------- page modèle */
  /* Ouverture en couches, centrée (maquette de Christophe, 2026-11-12) :
     logo de la gamme Alien, nom géant (le titre de la page), pistolet
     par-dessus le bas du nom, puis le texte : sous le pistolet sur
     téléphone, à gauche sous le canon sur ordinateur.
     --name-font : taille du nom (largeur réglée par modèle, --name-size,
     bornée par la hauteur de l'écran) ; --gun-width : largeur du pistolet. */
  .modelHero {
    --name-font: min(var(--name-size, 22vw), 36svh);
    --gun-width: 94vw;
    display: grid;
    /* Colonne à la largeur de l'ouverture : le bloc qui réserve la place
       de la poignée (.modelIntro::before) ne doit pas l'élargir. */
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    padding-block-start: calc(var(--header-height) + 1.5rem);
    overflow: clip;
    background: radial-gradient(ellipse 85% 45% at 50% 40%, #2a2b32 0%, transparent 72%), var(--color-ink);

    @media (min-width: 60em) {
      --gun-width: min(54vw, 80svh);
      background: radial-gradient(ellipse 60% 60% at 50% 50%, #2c2d34 0%, transparent 72%), var(--color-ink);
    }

    /* Fondu du bas, par-dessus le pistolet et le texte (ordinateur) : ils
       montent moins vite que la page et la section suivante passe sur eux
       sans bord net. */
    @media (min-width: 60em) {
      &::after {
        content: "";
        position: absolute;
        inset: auto 0 0;
        z-index: 3;
        height: clamp(5rem, 14svh, 9rem);
        background: linear-gradient(transparent, var(--color-ink));
        pointer-events: none;
      }
    }

    &>.pauseButton {
      z-index: 4;
    }

    & .lead {
      margin-block-end: 1.5rem;
    }

    & .pills {
      margin-block: 1.25rem 2rem;
    }
  }

  /* Par modèle. --name-size : largeur du nom géant, à peu près la même pour
     les trois. Mesures de la photo du pistolet, pour le texte sur
     téléphone (voir .modelIntro) : --gun-ratio, hauteur / largeur ;
     --grip-top, dessous du pontet, en part de la hauteur ; --grip-left,
     bord gauche de la poignée, en part de la largeur. */
  .modelHero--standard {
    --name-size: 13.5vw;
    --gun-ratio: 0.728;
    --grip-top: 0.45;
    --grip-left: 0.63;
  }

  .modelHero--creator {
    --name-size: 15.8vw;
    --gun-ratio: 0.788;
    --grip-top: 0.425;
    --grip-left: 0.63;
  }

  .modelHero--remus {
    --name-size: 20.6vw;
    --gun-ratio: 0.786;
    --grip-top: 0.5;
    --grip-left: 0.6;
  }

  /* Page Conception : même ouverture, avec une vue éclatée de l'Alien
     (presque carrée : 985 / 1 073) à la place du profil. Sur téléphone,
     pas de place libre sous le canon : le texte passe sous l'image
     (--grip-top: 1, aucune place réservée). Sur ordinateur, l'image est
     décalée vers la droite (12vw) : le bas gauche, où le texte remonte,
     porte la carcasse et le canon ; le texte s'arrête 2rem avant le bord
     gauche de l'image (il part de 4vw). */
  .modelHero--conception {
    --name-size: 11vw;
    --gun-ratio: 0.918;
    --grip-top: 1;
    --grip-left: 1;

    @media (min-width: 60em) {
      --gun-width: min(46vw, 70svh);

      &>.modelHeroVisual {
        margin-inline: calc((100% - var(--gun-width)) / 2 + 12vw) calc((100% - var(--gun-width)) / 2 - 12vw);
      }

      &>.modelIntro {
        max-width: min(30rem, calc((100% - var(--gun-width)) / 2 + 8vw - 2rem));
      }
    }
  }

  .modelFamily {
    width: clamp(8rem, 21vw, 19rem);
    margin-block: 1.5rem clamp(0.75rem, 2svh, 1.5rem);
    opacity: 0.55;
  }

  /* Intitulé écrit à la place du logo de la gamme (page Conception) : un
     nom de rubrique, pour ne pas lire « Alien Conception » comme un
     modèle. */
  .modelFamily--text {
    width: auto;
    color: var(--color-paper);
    font-size: var(--fs-sm);
    letter-spacing: 0.24em;
    text-align: center;
    text-transform: uppercase;
  }

  /* Texte de l'ouverture : au-dessus du pistolet.
     Sur téléphone (demande de Christophe, 2026-11-12), il remonte sous le
     pontet et commence à gauche de la poignée, puis reprend toute la
     largeur sous le pistolet : un bloc vide posé à droite (::before)
     réserve la place de la poignée, de --grip-left jusqu'au bord droit du
     pistolet (qui dépasse un peu le texte : marge négative), sur la
     hauteur restante du pistolet (--grip-height).
     Sur ordinateur, remonté sous le canon (40 % de la hauteur du pistolet,
     qui fait environ 0,77 fois sa largeur), à gauche de la poignée. */
  .modelIntro {
    --grip-height: calc(var(--gun-width) * var(--gun-ratio) * (1 - var(--grip-top)));
    position: relative;
    z-index: 2;
    justify-self: stretch;
    margin-block-start: calc(0.5rem - var(--grip-height));

    &::before {
      content: "";
      float: right;
      width: calc(var(--gun-width) * (1 - var(--grip-left)) + 1rem);
      height: calc(var(--grip-height) - 0.5rem);
      margin-inline-end: calc(50% - var(--gun-width) / 2);
    }

    @media (min-width: 60em) {
      justify-self: start;
      max-width: min(30rem, 42vw);
      margin-block-start: calc(var(--gun-width) * -0.31);
      margin-inline-start: 4vw;

      &::before {
        content: none;
      }

      &>.lead {
        font-size: clamp(1.15rem, 0.9rem + 0.6vw, 1.4rem);
      }
    }
  }

  /* Lien « Toutes les versions », en haut à droite de l'ouverture, flèche
     vers le bas (demande de Christophe, 2026-11-12 ; remplace « Tous les
     modèles », toujours dans le menu). */
  .versionsLink {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    grid-column: 1 / -1;
    justify-self: end;
    font-size: var(--fs-sm);

    &::after {
      content: "";
      width: 1rem;
      height: 1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
      rotate: 90deg;
    }

    &:hover {
      text-decoration: underline;
    }
  }

  /* Nom du modèle en géant, gris bleuté plus clair à droite (dégradé :
     voir « background-clip »), derrière le pistolet. C'est le titre (h1) :
     lisible par les moteurs de recherche, éclairci par rapport à la
     maquette pour rester lisible. */
  .modelName {
    position: relative;
    z-index: -1;
    margin: 0;
    color: #4a4f5a;
    font-size: var(--name-font);
    letter-spacing: 0.1em;
    line-height: 0.8;
    text-align: center;
    text-indent: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
  }

  /* Pistolet : par-dessus le bas du nom ; largeur imposée même au-delà
     des marges de la page (marges négatives qui le gardent centré). */
  .modelHeroVisual {
    width: var(--gun-width);
    max-width: none;
    margin-block-start: calc(var(--name-font) * -0.42);
    margin-inline: calc((100% - var(--gun-width)) / 2);
  }

  /* Chiffres clés : la valeur en grand au-dessus de son libellé
     (dans le HTML : libellé puis valeur). */
  .keyFigures {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 2.5rem 1.5rem;

    @media (min-width: 48em) {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }

    &>div {
      display: flex;
      flex-direction: column-reverse;
      justify-content: flex-end;
      gap: 0.5rem;
      padding-inline-start: 1.25rem;
      border-inline-start: 1px solid var(--color-line);
    }

    /* Police : voir « Police des titres en Bold ». Sur quatre colonnes,
       taille bornée par la largeur de l'écran : « 102 mm » en Bold tient
       dans sa colonne dès 768 px. */
    & dd {
      font-size: var(--fs-xl);
      font-variant-numeric: tabular-nums;
      line-height: 1;

      @media (min-width: 48em) {
        font-size: min(var(--fs-xl), 4.4vw);
      }
    }

    & dt {
      color: var(--color-muted);
      font-size: var(--fs-sm);
    }
  }

  /* Grands chiffres en rouleaux, le temps de leur animation (posés par
     js/main.js) : chaque chiffre est une colonne 0…9 qui glisse dans une
     fenêtre d'une ligne de haut. La fenêtre contient le vrai chiffre,
     invisible, qui lui donne sa place exacte dans le texte. Le pas du
     rouleau est mesuré sur le rouleau lui-même (11 lignes : 100 % / 11),
     et non sur la hauteur de ligne théorique, qui ne tombe pas toujours
     juste dans un texte en ligne (prix). */
  .counterDigit {
    display: inline-block;
    position: relative;
    clip-path: inset(0);
  }

  .counterGhost {
    visibility: hidden;
  }

  .counterReel {
    position: absolute;
    inset: 0 0 auto;
    white-space: pre;
    translate: 0 calc(var(--roll, 0) * -100% / 11);
    transition: opacity 0.2s;
  }

  [data-count="ready"] .counterReel {
    opacity: 0;
  }

  /* Versions : grand carrousel horizontal, une version par écran, sur
     toute la largeur : la suivante reste cachée, sinon son prix
     commencerait à tourner à peine visible (Christophe, 2026-11-12).
     Au-dessus : onglets (liens #version) et
     flèches (affichées par js/main.js). Défilement natif, calé version par
     version (« scroll-snap »). Dans chaque version, une grande photo
     choisie par des vignettes qui sont des boutons radio : sans script. */
  .versions {
    background: radial-gradient(ellipse 80% 30% at 50% 0%, #1d1e24 0%, transparent 75%), var(--color-ink);
  }

  /* Au-dessus de la piste, qui remonte un peu sous elle (voir
     .versionTrack) : onglets et flèches restent cliquables. */
  .carouselBar {
    display: flex;
    position: relative;
    z-index: 1;
    align-items: center;
    gap: 1rem;
    margin-block: 2.5rem 1.25rem;
  }

  /* Onglets des versions. Le fond blanc de l'onglet allumé est une pastille
     unique (::before) qui glisse d'un onglet à l'autre : js/main.js lui
     donne la position (--pill-x) et la largeur (--pill-w) de l'onglet de la
     version visible. Sans script, pas de pastille. */
  .versionTabs {
    display: flex;
    position: relative;
    gap: 0.25rem;
    min-width: 0;
    padding: 0.3rem;
    overflow-x: auto;
    border-radius: 999px;
    background: var(--color-surface);
    scrollbar-width: none;

    &::before {
      content: "";
      position: absolute;
      inset-block: 0.3rem;
      left: 0;
      width: var(--pill-w, 0);
      border-radius: 999px;
      background: var(--gradient-metal);
      box-shadow: 0 4px 14px rgb(0 0 0 / 0.45);
      translate: var(--pill-x, 0) 0;
      transition:
        translate 0.5s var(--ease-out),
        width 0.5s var(--ease-out);
    }

    &>a {
      flex: none;
      position: relative;
      padding: 0.5rem 1.15rem;
      border-radius: 999px;
      color: var(--color-muted);
      font-size: var(--fs-sm);
      white-space: nowrap;
      transition: color 0.3s;

      &:hover {
        color: var(--color-paper);
      }

      /* Le texte fonce quand la pastille arrive sous lui, pas avant. */
      &[aria-current="true"] {
        color: var(--color-ink);
        transition-delay: 0.2s;
      }
    }
  }

  /* Flèches : sur grand écran seulement ; sur téléphone on glisse. */
  .carouselArrows {
    display: none;
    gap: 0.5rem;
    margin-inline-start: auto;

    @media (min-width: 48em) {
      &:not([hidden]) {
        display: flex;
      }
    }
  }

  .arrowButton {
    display: grid;
    place-items: center;
    width: 3rem;
    height: 3rem;
    padding: 0;
    border: 1px solid var(--color-line);
    border-radius: 50%;
    background: none;
    transition: background-color 0.2s, opacity 0.2s;

    &::before {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
    }

    &:hover:not(:disabled) {
      background: var(--color-surface);
    }

    &:disabled {
      opacity: 0.3;
      cursor: default;
    }
  }

  .arrowButton--prev::before {
    rotate: 180deg;
  }

  /* La piste : une colonne par version. position: relative sert au script
     (positions des versions mesurées depuis la piste). */
  .versionTrack {
    display: grid;
    position: relative;
    grid-auto-flow: column;
    grid-auto-columns: 100%;
    gap: 1rem;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;

    /* --photo-rise : hauteur dont la photo dépasse en haut de la carte
       (grand écran) ; la marge haute de la piste lui laisse la place, sans
       quoi le défilement horizontal la couperait. 2rem de plus pour le
       flottement du pistolet (0,75rem) et son halo, repris par une marge
       négative : la carte ne bouge pas. */
    --photo-rise: 3rem;

    @media (min-width: 60em) {
      gap: 1.5rem;
      margin-block-start: -2rem;
      padding-block-start: calc(var(--photo-rise) + 2rem);
    }

    /* Glisser à la souris (js/main.js) : main qui attrape ; pendant le
       geste, ni aimant ni sélection de texte. */
    @media (pointer: fine) {
      cursor: grab;
    }

    &[data-dragging] {
      cursor: grabbing;
      scroll-snap-type: none;
      user-select: none;
    }
  }

  /* Une version : une seule carte (maquettes de Christophe, 2026-11-12).
     Sur grand écran, le texte à gauche (un tiers), la photo à droite (deux
     tiers), qui dépasse en haut de la carte, ses vignettes en ligne
     dessous, à droite ; sur téléphone, photo, vignettes, puis le texte.
     --slide-pad : marge intérieure de la carte. La marge haute garde les
     onglets visibles quand on arrive par une adresse /modèle/#version. */
  .versionSlide {
    --slide-pad: clamp(1rem, 3vw, 2.5rem);
    display: grid;
    align-content: start;
    gap: 1.25rem;
    min-width: 0;
    padding: var(--slide-pad);
    border: 1px solid rgb(255 255 255 / 0.07);
    border-radius: clamp(var(--radius-card), 2.5vw, 2.5rem);
    background: var(--color-surface);
    scroll-snap-align: start;
    scroll-snap-stop: always;
    scroll-margin-top: calc(var(--header-height) + 6rem);

    @media (min-width: 60em) {
      grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
      grid-template-areas: "info gallery";
      /* Toutes les cartes ont la hauteur de la plus haute (même rangée
         de la piste) : la rangée s'étire jusqu'en bas, pour que les
         vignettes restent dans le coin. */
      align-content: stretch;
      align-items: start;
      gap: clamp(1.5rem, 3vw, 3rem);
    }
  }

  .versionGallery {
    display: grid;
    align-content: start;
    gap: 0.75rem;
    min-width: 0;

    /* Grand écran : toute la hauteur de la carte, vignettes en bas. */
    @media (min-width: 60em) {
      grid-area: gallery;
      align-self: stretch;
      align-content: space-between;
      gap: 1rem;
    }
  }

  /* Scène de la grande photo, avec l'ombre au sol sous le pistolet. */
  .versionStage {
    display: grid;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
    position: relative;
    isolation: isolate;
    aspect-ratio: 4 / 3;
    /*padding: clamp(1.25rem, 5%, 3rem);*/

    /* Grand écran : photo plus haute, remontée jusqu'à dépasser le haut de
       la carte (--photo-rise) ; bouton d'agrandissement ramené en haut à
       droite de la carte. */
    @media (min-width: 60em) {
      aspect-ratio: auto;
      height: clamp(24rem, 64vh, 40rem);
      margin-block-start: calc(-1 * (var(--slide-pad) + var(--photo-rise)));
      padding: 0 0 1.5rem;

      &>.zoomButton {
        top: calc(var(--photo-rise) + 1.25rem);
        right: calc(1.25rem - var(--slide-pad));
        bottom: auto;
      }
    }

    /* Ombre au sol, sous le bas de la photo et juste au-dessus des
       vignettes : plus haut, elle mordait sur les photos qui descendent
       jusqu'en bas de la scène (holster, puits de chargeur). */
    &::before {
      content: "";
      position: absolute;
      z-index: -1;
      right: 24%;
      bottom: 0;
      left: 24%;
      height: 1.4rem;
      border-radius: 50%;
      background: rgb(0 0 0 / 0.85);
      filter: blur(12px);

      @media (min-width: 60em) {
        bottom: -0.5rem;
      }
    }
  }

  /* Un jeu de photos par coloris, superposés ; une seule photo affichée.
     Les photos cachées (display: none) ne sont téléchargées qu'au choix. */
  .versionPhotos {
    display: grid;
    grid-area: 1 / 1;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);

    &>img {
      display: none;
      grid-area: 1 / 1;
      width: 100%;
      height: 100%;
      object-fit: contain;
    }
  }

  .versionSlide:has(.versionThumb:nth-of-type(1) > input:checked) .versionPhotos> :nth-child(1),
  .versionSlide:has(.versionThumb:nth-of-type(2) > input:checked) .versionPhotos> :nth-child(2),
  .versionSlide:has(.versionThumb:nth-of-type(3) > input:checked) .versionPhotos> :nth-child(3),
  .versionSlide:has(.versionThumb:nth-of-type(4) > input:checked) .versionPhotos> :nth-child(4),
  .versionSlide:has(.versionThumb:nth-of-type(5) > input:checked) .versionPhotos> :nth-child(5),
  .versionSlide:has(.versionThumb:nth-of-type(6) > input:checked) .versionPhotos> :nth-child(6) {
    display: block;
  }

  /* Coloris : la 2e pastille cochée remplace photos, vignettes et prix
     marqués .colorMain par leurs équivalents .colorAlt. */
  .colorAlt {
    display: none;
  }

  .versionSlide:has(.swatch:nth-of-type(2) > input:checked) {
    & .colorMain {
      display: none;
    }

    & .colorAlt {
      display: revert-layer;
    }

    & .versionPhotos.colorAlt {
      display: grid;
    }
  }

  /* Vignettes : boutons radio invisibles posés sur chaque vignette. En
     ligne sous la photo ; sur grand écran, carrées, de taille fixe, dans
     le coin bas droit de la carte (à 1,25rem des bords, comme le bouton
     d'agrandissement en haut : marges négatives sur --slide-pad). */
  .versionThumbs {
    display: grid;
    grid-template-columns: repeat(6, minmax(0, 1fr));
    gap: 0.5rem;
    min-width: 0;
    margin: 0;
    padding: 0;
    border: 0;

    @media (min-width: 60em) {
      grid-template-columns: none;
      grid-auto-flow: column;
      grid-auto-columns: 4.5rem;
      justify-content: end;
      margin: 0 calc(1.25rem - var(--slide-pad)) calc(1.25rem - var(--slide-pad)) 0;
    }
  }

  .versionThumb {
    display: grid;
    grid-template: minmax(0, 1fr) / minmax(0, 1fr);
    position: relative;
    aspect-ratio: 3 / 2;
    padding: 0.4rem;
    border-radius: 10px;
    background: var(--color-ink-soft);
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.07);

    @media (min-width: 60em) {
      aspect-ratio: 1;
    }

    cursor: pointer;
    transition: background-color 0.2s,
    box-shadow 0.2s;

    &>input {
      position: absolute;
      inset: 0;
      margin: 0;
      opacity: 0;
      cursor: pointer;
    }

    &>img {
      grid-area: 1 / 1;
      width: 100%;
      height: 100%;
      object-fit: contain;
      pointer-events: none;
    }

    &:hover {
      background: #1d1e24;
    }

    &:has(> input:checked) {
      background: #1d1e24;
      box-shadow: inset 0 0 0 2px var(--color-paper);
    }

    &:has(> input:focus-visible) {
      outline: 2px solid var(--color-accent);
      outline-offset: 3px;
    }
  }

  /* Texte de la version, à gauche de la photo sur grand écran. Titre sur
     deux lignes, comme le menu : le modèle en Light, puis la version en
     Bold, plus grande. */
  .versionInfo {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.85rem;

    @media (min-width: 60em) {
      grid-area: info;
    }

    &>h3 {
      margin: 0;
      font-size: clamp(1.5rem, 1.1rem + 1vw, 2.1rem);
      line-height: 1;

      &>.textAccent {
        display: block;
        font-size: 1.55em;
      }
    }

    &>p:not([class]) {
      color: var(--color-muted);
      font-size: var(--fs-sm);
    }

    &>h4 {
      margin-block-start: 0.4rem;
      color: var(--color-muted);
      font-size: var(--fs-xs);
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }
  }

  .versionPrice {
    font-size: var(--fs-sm);

    /* Interligne serré : les rouleaux du prix (js/main.js) tournent chiffre
       contre chiffre, comme ceux des chiffres clés. */
    &>strong {
      margin-inline-end: 0.35rem;
      font-size: var(--fs-lg);
      line-height: 1;
    }
  }

  .versionOption {
    margin-block-start: -0.5rem;
    color: var(--color-muted);
    font-size: var(--fs-xs);
  }

  .kitList {
    display: grid;
    gap: 0.4rem;
    font-size: var(--fs-sm);
    list-style: none;

    &>li {
      display: flex;
      gap: 0.65rem;
    }

    &>li::before {
      content: "";
      flex: none;
      width: 0.35rem;
      height: 0.35rem;
      margin-block-start: 0.6em;
      border-radius: 50%;
      background: var(--gradient-metal);
    }
  }

  .versionsNote {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1.5rem;
    margin-block-start: 1.5rem;
    color: var(--color-muted);
    font-size: var(--fs-sm);
  }

  /* Configurations côte à côte, sous le carrousel : un tableau, puis le
     bouton « Où l'acheter ? ». Sur téléphone, le tableau défile de côté
     dans son cadre (la page, elle, ne déborde pas) ; la colonne du nom
     reste collée à gauche. */
  .compare {
    margin-block-start: clamp(3rem, 6vw, 5rem);

    &>h3 {
      margin-block-end: 1.25rem;
    }

    &>.note {
      margin-block: 1rem 2rem;
      color: var(--color-muted);
      font-size: var(--fs-xs);
    }
  }

  .compareTable {
    overflow-x: auto;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-card);

    &>table {
      width: 100%;
      min-width: 46rem;
      border-collapse: collapse;
      font-size: var(--fs-sm);
    }

    & :is(th, td) {
      padding: 0.9rem 1rem;
      text-align: start;
      vertical-align: top;
    }

    & tbody :is(th, td) {
      border-block-start: 1px solid var(--color-line);
    }

    & thead th {
      color: var(--color-muted);
      white-space: nowrap;
      font-size: var(--fs-xs);
      font-weight: 400;
      letter-spacing: 0.08em;
      text-transform: uppercase;
    }

    /* Nom de la configuration : lien vers sa carte, colonne collée. */
    & :is(thead, tbody) th:first-child {
      position: sticky;
      inset-inline-start: 0;
      background: var(--color-ink);
    }

    & tbody th {
      font-family: var(--font-display);
      font-weight: 700;
      white-space: nowrap;

      &>a {
        color: var(--color-paper);
        text-decoration: underline;
        text-decoration-color: rgb(255 255 255 / 0.35);
        text-underline-offset: 0.3em;
      }
    }

    & td {
      color: var(--color-muted);
    }

    /* Prix : première colonne après le nom, visible sans défiler. */
    & td:first-of-type {
      color: var(--color-paper);
      font-weight: 600;
      white-space: nowrap;
    }
  }

  /* Bouton d'allure « lien », suivi d'une flèche. */
  .textButton {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.25rem 0;
    border: 0;
    background: none;
    color: var(--color-paper);
    font-size: var(--fs-sm);
    text-decoration: underline;
    text-decoration-color: rgb(255 255 255 / 0.35);
    text-underline-offset: 0.3em;
    transition: text-decoration-color 0.2s;

    &::after {
      content: "";
      width: 0.95rem;
      height: 0.95rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
    }

    &:hover {
      text-decoration-color: currentColor;
    }
  }

  /* Fiche des chargeurs : panneau natif « popover », centré, fond assombri. */
  .magazinePanel {
    width: min(100% - 2rem, 32rem);
    padding: 2.5rem 2rem 2rem;
    border: 1px solid var(--color-line);
    border-radius: var(--radius-card);
    background: var(--color-surface);
    color: var(--color-paper);
    opacity: 0;
    scale: 0.96;
    transition:
      opacity 0.3s var(--ease-out),
      scale 0.3s var(--ease-out),
      display 0.3s allow-discrete,
      overlay 0.3s allow-discrete;

    &:popover-open {
      opacity: 1;
      scale: 1;
    }

    &::backdrop {
      background-color: rgb(0 0 0 / 0.6);
    }

    &>h3 {
      margin-block-end: 1.5rem;
      padding-inline-end: 3rem;
    }

    &>.closeButton {
      background-color: #26272e;
    }

    &>.note {
      margin-block-start: 1.25rem;
      color: var(--color-muted);
      font-size: var(--fs-xs);
    }
  }

  @starting-style {
    .magazinePanel:popover-open {
      opacity: 0;
      scale: 0.96;
    }
  }

  .magazineList {
    list-style: none;

    &>li {
      display: flex;
      justify-content: space-between;
      gap: 1rem;
      padding-block: 0.85rem;
      border-block-start: 1px solid var(--color-line);
    }

    & strong {
      font-weight: 600;
      white-space: nowrap;
    }
  }

  /* Photos des versions en plein écran : bouton « + » sur la grande photo
     et fenêtre native <dialog> (ouverte par js/main.js ; sans script, le
     bouton reste caché). */
  .versionStage:has(> .zoomButton:not([hidden])) {
    cursor: zoom-in;
  }

  .zoomButton {
    display: grid;
    place-items: center;
    position: absolute;
    right: 1rem;
    bottom: 1rem;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--color-accent);
    color: var(--color-paper);
    transition: background-color 0.2s, scale 0.3s var(--ease-out);

    /* Le « + » */
    &::before {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background:
        linear-gradient(currentColor 0 0) center / 100% 1.5px no-repeat,
        linear-gradient(currentColor 0 0) center / 1.5px 100% no-repeat;
    }
  }

  .versionStage:hover>.zoomButton {
    background: var(--color-accent-hover);
    scale: 1.08;
  }

  /* La page ne défile pas derrière la photo agrandie. */
  html:has(.zoomPanel[open]) {
    overflow: hidden;
  }

  .zoomPanel {
    width: 100%;
    max-width: none;
    height: 100%;
    max-height: none;
    margin: 0;
    padding: 4.5rem clamp(1rem, 7vw, 7rem) 1.5rem;
    border: 0;
    background: radial-gradient(ellipse 70% 60% at 50% 45%, #1d1e24 0%, var(--color-ink) 75%);
    color: var(--color-paper);
    overflow: hidden;
    /* Le glisser du doigt est lu par le script ; le zoom à deux doigts
       reste au navigateur. */
    touch-action: pinch-zoom;

    &[open] {
      display: grid;
      grid-template: minmax(0, 1fr) auto / minmax(0, 1fr);
      gap: 1rem;
    }

    &::backdrop {
      background: var(--color-ink);
    }

    /* À l'ouverture, c'est la fenêtre qui reçoit le focus (attribut
       autofocus) : pas d'anneau sur la croix après un clic de souris ; au
       clavier, Tab mène à la croix et aux flèches. */
    &:focus-visible {
      outline: none;
    }

    &>.closeButton {
      z-index: 1;
      background-color: #26272e;
    }

    &>.arrowButton {
      position: absolute;
      top: 50%;
      right: clamp(0.5rem, 2vw, 2rem);
      background: rgb(255 255 255 / 0.06);
      backdrop-filter: blur(8px);
      translate: 0 -50%;
    }

    &>.arrowButton--prev {
      right: auto;
      left: clamp(0.5rem, 2vw, 2rem);
    }

    /* Loupe à côté de la croix ; « − » quand la photo est agrandie. */
    &>.zoomButton {
      z-index: 1;
      top: 1rem;
      right: 4.25rem;
      bottom: auto;
    }

    &[data-zoomed]>.zoomButton::before {
      background: linear-gradient(currentColor 0 0) center / 100% 1.5px no-repeat;
    }

    /* Photo agrandie : le doigt la déplace au lieu de faire défiler. */
    &[data-zoomed] {
      touch-action: none;
    }
  }

  /* Compteur et légende restent lisibles par-dessus la photo agrandie. */
  .zoomCounter {
    position: absolute;
    z-index: 1;
    top: 1.6rem;
    left: clamp(1rem, 2vw, 2rem);
    color: var(--color-muted);
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
  }

  /* Taille et position fixées par js/main.js (--zoom-k, --zoom-x, --zoom-y). */
  .zoomPhoto {
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: var(--gun-shadow);
    cursor: zoom-in;
    user-select: none;

    .zoomPanel[data-zoomed]>& {
      position: relative;
      z-index: 0;
      cursor: grab;
      translate: var(--zoom-x, 0) var(--zoom-y, 0);
      scale: var(--zoom-k, 1);
    }

    .zoomPanel[data-gesture]>& {
      cursor: grabbing;
    }
  }

  .zoomCaption {
    position: relative;
    z-index: 1;
    color: var(--color-muted);
    font-size: var(--fs-sm);
    text-align: center;
  }

  .swatches {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.5rem 1rem;
    margin: 0;
    padding: 0;
    border: 0;

    &>legend {
      float: left;
      margin-inline-end: 0.25rem;
      padding: 0;
      color: var(--color-muted);
      font-size: var(--fs-xs);
    }
  }

  /* Pastille : bouton radio redessiné en rond bicolore. */
  .swatch {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    font-size: var(--fs-xs);
    cursor: pointer;

    &>input {
      appearance: none;
      width: 1.6rem;
      height: 1.6rem;
      margin: 0;
      border-radius: 50%;
      background: var(--swatch);
      box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.25);
      cursor: pointer;
      transition: box-shadow 0.2s;
    }

    &>input:checked {
      box-shadow: inset 0 0 0 1px rgb(255 255 255 / 0.25), 0 0 0 2px var(--color-ink), 0 0 0 3.5px var(--color-paper);
    }
  }

  .swatch--blackGreen {
    --swatch: linear-gradient(135deg, #17171b 50%, #8b7f5f 50%);
  }

  .swatch--blackBlack {
    --swatch: linear-gradient(135deg, #17171b 50%, #3a3b40 50%);
  }

  .swatch--greyBlack {
    --swatch: linear-gradient(135deg, #8e9196 50%, #17171b 50%);
  }

  /* Caractéristiques techniques : deux colonnes sur toute la largeur du
     contenu dès 768 px. */
  .specList {
    display: grid;

    @media (min-width: 48em) {
      grid-template-columns: 1fr 1fr;
      column-gap: 3rem;
    }

    &>div {
      display: grid;
      grid-template-columns: minmax(9rem, 2fr) 3fr;
      gap: 1rem;
      padding-block: 1rem;
      border-block-start: 1px solid var(--color-line);
    }

    & dt {
      color: var(--color-muted);
      font-size: var(--fs-sm);
    }
  }

  .specs .note {
    margin-block-start: 1.5rem;
    color: var(--color-muted);
    font-size: var(--fs-xs);
  }

  /* --------------------------------------------- page des mentions légales */
  /* Page de lecture : une colonne centrée, titres plus petits que sur les
     pages de présentation, liens soulignés. Aucune animation. */
  .legalPage {
    padding-block-start: calc(var(--header-height) + 3rem);

    &>* {
      max-width: 46rem;
      margin-inline: auto;
    }

    &>h2 {
      margin-block: 3.5rem 1rem;
      font-size: var(--fs-lg);
    }

    &>h3 {
      margin-block: 1.75rem 0.5rem;
      font-size: var(--fs-md);
      font-weight: 600;
    }

    &>.lead {
      margin-block: 1.5rem;
    }

    &> :is(p, ul)+ :is(p, ul, .factList) {
      margin-block-start: 1rem;
    }

    &>ul {
      padding-inline-start: 1.25em;
    }

    & li+li {
      margin-block-start: 0.4rem;
    }

    & :is(p, li, dd)>a:not(.button) {
      text-decoration: underline;
      text-underline-offset: 0.2em;
    }
  }

  /* Fiche d'identité (éditeur, hébergeur) : libellé puis valeur, l'un sous
     l'autre sur téléphone, côte à côte au-delà. */
  .factList {
    display: grid;

    &>div {
      display: grid;
      gap: 0.25rem 1.5rem;
      padding-block: 0.75rem;
      border-block-start: 1px solid var(--color-line);

      @media (min-width: 40em) {
        grid-template-columns: 15rem 1fr;
      }
    }

    & dt {
      color: var(--color-muted);
      font-size: var(--fs-sm);
    }
  }

  /* ---------------------------------------------------- page Conception */
  .architecture {
    padding-block-end: 0;

    &>.lead {
      margin-inline: auto;
      text-align: center;
    }
  }

  /* Un principe : texte puis schéma (téléphone), côte à côte au-delà. */
  .principle {
    padding-block-start: var(--section);
  }

  .principleStage {
    display: grid;
    gap: 2rem;
    align-items: center;

    @media (min-width: 60em) {
      grid-template-columns: 5fr 7fr;
      gap: clamp(2rem, 4vw, 5rem);
    }
  }

  .principleText {
    &>h3 {
      margin-block-end: 1.25rem;
      font-size: calc(var(--fs-xl) * 0.8);
    }

    &>p:not(.label) {
      max-width: 34rem;
      color: var(--color-muted);
    }
  }

  /* Points clés : pastille rouge devant chaque ligne. */
  .principleFacts {
    display: grid;
    gap: 0.6rem;
    margin-block-start: 1.5rem;
    list-style: none;
    font-size: var(--fs-sm);

    &>li {
      display: flex;
      gap: 0.75rem;

      &::before {
        content: "";
        flex: none;
        width: 0.45rem;
        height: 0.45rem;
        margin-block-start: 0.6em;
        border-radius: 50%;
        background: var(--color-accent);
      }
    }
  }

  /* Sécurités : étiquettes qui s'allument l'une après l'autre (voir
     animations). */
  .safetyList {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-block-start: 1.5rem;
    list-style: none;

    &>li {
      padding: 0.35rem 0.8rem;
      border: 1px solid rgb(227 38 46 / 0.7);
      border-radius: 999px;
      background: rgb(227 38 46 / 0.14);
      font-size: var(--fs-xs);
    }
  }

  /* Cadre du schéma : quadrillage de dessin technique très discret. */
  .principleFigure {
    margin: 0;
    padding: clamp(1rem, 3vw, 2.25rem);
    border: 1px solid rgb(255 255 255 / 0.07);
    border-radius: var(--radius-card);
    background:
      linear-gradient(rgb(255 255 255 / 0.035) 1px, transparent 1px) 0 0 / 2rem 2rem,
      linear-gradient(90deg, rgb(255 255 255 / 0.035) 1px, transparent 1px) 0 0 / 2rem 2rem,
      var(--card-fill);

    &>figcaption {
      margin-block-start: 1rem;
      color: var(--color-muted);
      font-size: var(--fs-xs);
    }
  }

  /* Schémas SVG. Pièces reprises par <use> : leur couleur passe par
     héritage (fill, stroke). Traits d'épaisseur constante à l'écran,
     quelle que soit la taille du schéma (non-scaling-stroke). */
  .scheme {
    /* Taille des étiquettes, en unités du schéma : environ 19 px sur
       ordinateur pour un schéma de 1 580 unités de large ; réglée par
       schéma selon sa largeur (viewBox). Agrandie d'un quart sur
       téléphone (--tag-scale), où le schéma est plus petit. */
    --tag-size: 40px;
    --tag-scale: 1.25;

    @media (min-width: 60em) {
      --tag-scale: 1;
    }

    display: block;
    width: 100%;
    height: auto;
    overflow: hidden;
    stroke-linejoin: round;
    stroke-linecap: round;

    & :is(.part, .focus, .guide, .leader, .dimensionMark, .spring, .accent--outline, .hand, .cgMark, .click, .zoomSpot, .zoomLens, .screwSlot) {
      vector-effect: non-scaling-stroke;
    }

    & .part {
      fill: rgb(255 255 255 / 0.035);
      stroke: rgb(255 255 255 / 0.5);
      stroke-width: 1.5px;
    }

    & .part--ghost {
      fill: none;
      stroke: rgb(255 255 255 / 0.3);
      stroke-dasharray: 6 6;
    }

    & .part--glass {
      fill: rgb(255 255 255 / 0.05);
      stroke: var(--color-paper);
    }

    & .detail {
      fill: rgb(255 255 255 / 0.1);
    }

    & .detail--bright {
      fill: rgb(255 255 255 / 0.3);
    }

    & .focus {
      fill: rgb(255 255 255 / 0.14);
      stroke: var(--color-paper);
      stroke-width: 2px;
    }

    & .accent,
    & .force {
      fill: var(--color-accent);
    }

    & .accent--outline {
      fill: rgb(227 38 46 / 0.16);
      stroke: var(--color-accent);
      stroke-width: 2px;
    }

    & .move {
      fill: var(--color-paper);
    }

    & .guide {
      fill: none;
      stroke: rgb(255 255 255 / 0.6);
      stroke-width: 1.5px;
      stroke-dasharray: 10 8;
    }

    & .guide--accent {
      stroke: var(--color-accent);
      stroke-width: 2px;
    }

    & .guide--bore {
      stroke: rgb(227 38 46 / 0.55);
    }

    & .guide--sight {
      stroke: var(--color-paper);
      stroke-dasharray: none;
    }

    & .dimensionMark {
      fill: none;
      stroke: var(--color-paper);
      stroke-width: 1.5px;
    }

    /* Détail agrandi : repère sur le schéma, loupe à côté. */
    & .zoomSpot {
      fill: none;
      stroke: rgb(255 255 255 / 0.6);
      stroke-width: 1px;
    }

    & .zoomLens {
      fill: var(--color-ink-soft);
      stroke: rgb(255 255 255 / 0.45);
      stroke-width: 1px;
    }

    & .leader {
      fill: none;
      stroke: rgb(255 255 255 / 0.4);
      stroke-width: 1px;
    }

    & .leader--accent {
      stroke: var(--color-accent);
    }

    & .tag {
      fill: var(--color-muted);
      font-family: var(--font-sans);
      font-size: calc(var(--tag-size) * var(--tag-scale));
      paint-order: stroke;
      stroke: var(--color-ink-soft);
      stroke-width: 12px;
    }

    & .tag--accent {
      fill: #ff7a7f;
    }

    /* Main et avant-bras du tireur : silhouette discrète. */
    & .hand {
      fill: rgb(255 255 255 / 0.06);
      stroke: rgb(255 255 255 / 0.28);
      stroke-width: 1px;
    }

    & .screwSlot {
      fill: none;
      stroke: var(--color-accent);
      stroke-width: 2px;
    }

    & .spring {
      fill: none;
      stroke: rgb(255 255 255 / 0.6);
      stroke-width: 1.5px;
    }

    & .gas {
      fill: rgb(227 38 46 / 0.75);
    }

    & .bullet {
      fill: #d8b46a;
    }

    & .steel {
      fill: url(#hatchSteel);
      stroke: var(--color-paper);
      stroke-width: 2px;
      vector-effect: non-scaling-stroke;
    }

    & .alloy {
      fill: url(#dotsAlloy);
      stroke: rgb(196 210 232 / 0.9);
      stroke-width: 2px;
      vector-effect: non-scaling-stroke;
    }

    & .cgMark {
      fill: var(--color-ink);
      stroke: var(--color-accent);
      stroke-width: 2px;
    }

    & .cgQuarter {
      fill: var(--color-accent);
    }

    & .click {
      fill: none;
      stroke: var(--color-accent);
      stroke-width: 2px;
    }
  }

  .scheme--barrel {
    --tag-size: 30px;
  }

  .scheme--trigger {
    --tag-size: 24px;
  }

  .scheme--materials {
    --tag-size: 31px;
  }

  .scheme--modular {
    --tag-size: 32px;
  }

  /* ------------------------------------------- étapes de la fabrication */
  .processSteps {
    display: grid;
    gap: clamp(3rem, 8vw, 7rem);
    margin-block-start: 2.5rem;
    list-style: none;
  }

  /* Image et texte, alternés de part et d'autre sur grand écran. */
  .processStep {
    display: grid;
    gap: 1.5rem;
    align-items: center;

    @media (min-width: 60em) {
      grid-template-columns: 7fr 5fr;
      gap: clamp(2rem, 4vw, 5rem);

      &:nth-child(even) {
        grid-template-columns: 5fr 7fr;

        &>.visual {
          order: 2;
        }
      }
    }

    &>.visual {
      width: 100%;
      aspect-ratio: 3 / 2;
      border-radius: var(--radius-card);
      object-fit: cover;
    }
  }

  .stepText {
    &>h3 {
      margin-block-end: 1rem;
      font-size: calc(var(--fs-xl) * 0.8);
    }

    &>p:not(.label) {
      max-width: 30rem;
      color: var(--color-muted);
    }
  }

  /* Endurance : deux chiffres seulement, en deux colonnes sur la largeur
     du conteneur. */
  .keyFigures--pair {
    margin-block-start: 2.5rem;

    @media (min-width: 48em) {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  /* -------------------------------------------------------- pied de page */
  .siteFooter {
    display: grid;
    gap: 2.5rem;
    padding: 0 max(var(--gutter), calc((100% - var(--content-max)) / 2)) 3rem;
    background: var(--color-ink);
    color: var(--color-paper);
  }

  .backToTop {
    display: grid;
    justify-items: center;
    gap: 0.5rem;
    padding-block: 3rem;
    border-block-end: 1px solid rgb(255 255 255 / 0.12);
    font-size: var(--fs-sm);

    &::before {
      content: "";
      width: 1.1rem;
      height: 1.1rem;
      background: currentColor;
      mask: var(--icon-arrow) center / contain no-repeat;
      rotate: -90deg;
    }
  }

  .footerColumns {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
    gap: 2rem;

    &>nav {
      display: grid;
      align-content: start;
      gap: 0.6rem;
      font-size: var(--fs-sm);
    }

    & a {
      color: var(--color-muted-on-dark);
      transition: color 0.2s;

      &:hover {
        color: var(--color-paper);
      }
    }
  }

  .footerLabel {
    margin-block-end: 0.4rem;
    color: var(--color-paper);
  }

  .legal {
    max-width: 52rem;
    color: var(--color-muted-on-dark);
    font-size: var(--fs-xs);

    & a {
      text-decoration: underline;
      text-underline-offset: 0.2em;

      &:hover {
        color: var(--color-paper);
      }
    }
  }

  .footerWordmark {
    padding-block-start: 1.5rem;

    &>img {
      width: auto;
      height: 1.25rem;
      margin-inline: auto;
    }
  }
}

/* ------------------------------------------------- composants v3 (effets) */
@layer components {

  /* Texte réservé aux lecteurs d'écran et à Google. */
  .visuallyHidden {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Ligne de titre dévoilée par un masque (voir animations). */
  .line {
    display: block;
    overflow: clip;
    padding-block-end: 0.08em;

    &>span {
      display: block;
    }
  }

  .modelHero {
    position: relative;
    isolation: isolate;
  }

  /* Vidéo d'ambiance de l'ouverture des pages modèles : discrète et
     assombrie (faible opacité, puis voile noir par-dessus), sous le nom
     géant et le pistolet. Image fixe si « moins de mouvement ». */
  .modelHero>.heroBackdrop {
    position: absolute;
    inset: 0;
    z-index: -3;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.6;
    pointer-events: none;
    filter: saturate(2);
  }

  /* Voile : plus sombre en haut (sous l'en-tête), sur les bords et en bas,
     où l'ouverture se fond dans la section suivante. */
  .modelHero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: -2;
    background:
      linear-gradient(to bottom, rgb(7 7 10 / 0.7), rgb(7 7 10 / 0) 30%, rgb(7 7 10 / 0) 65%, var(--color-ink)),
      radial-gradient(ellipse 65% 70% at 50% 40%, rgb(7 7 10 / 0) 0%, rgb(7 7 10 / 0.65) 100%);

    @media (min-width: 60em) {
      background:
        linear-gradient(to bottom, rgb(7 7 10 / 0.7), rgb(7 7 10 / 0) 30%, rgb(7 7 10 / 0) 65%, var(--color-ink)),
        linear-gradient(to right, rgb(7 7 10 / 0.7), rgb(7 7 10 / 0) 50%),
        radial-gradient(ellipse 65% 70% at 50% 40%, rgb(7 7 10 / 0) 0%, rgb(7 7 10 / 0.65) 100%);
    }
  }

  /* Séquence studio : scène noire, pistolet en vidéo (ou en photos pour
     le Standard), trois phrases. Sans prise en charge du défilement lié :
     simple bandeau avec l'image fixe et les trois phrases. */
  .studio {
    padding: 0;
    background: var(--color-ink);
    color: var(--color-paper);
  }

  .studioStage {
    position: relative;
    display: grid;
    height: 80svh;
    min-height: 28rem;
    overflow: clip;
    background: radial-gradient(ellipse at 50% 55%, #2a2b31 0%, var(--color-ink) 65%);
  }

  .studioMedia,
  .studioFrames {
    grid-area: 1 / 1;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .studioFrames {
    position: relative;

    &>img {
      position: absolute;
      inset: 12% 10%;
      width: 80%;
      height: 76%;
      object-fit: contain;
      opacity: 0;
    }

    &>img:first-child {
      opacity: 1;
    }
  }

  .studioLines {
    grid-area: 1 / 1;
    align-self: end;
    display: grid;
    gap: 0.25rem;
    padding: 0 var(--gutter) clamp(2rem, 6vh, 4rem);
    list-style: none;
    font-size: var(--fs-xl);
    line-height: 1.1;
    filter: drop-shadow(0 2px 26px rgb(0 0 0 / 0.75));
  }
}

/* ------------------------------------------ mots en dégradé, pistolets */
@layer components {

  /* Mots accentués des titres : blancs si le navigateur ne sait pas
     découper un dégradé dans le texte. */
  .textAccent {
    color: var(--color-paper);
  }

  /* Police des titres en Bold (Funnel Display 700) : mots accentués des
     titres et des noms du menu, noms géants, chiffres clés, prix, noms des
     modèles. Placé après leurs règles pour l'emporter à précision égale. */
  :is(h1, h2, h3, h4, .menuModel > .name) .textAccent,
  .modelName,
  .keyFigures dd,
  .price>strong,
  .versionPrice>strong,
  .modelTile>.name {
    font-family: var(--font-display);
    font-weight: 700;
  }

  /* Marge sous les titres : les jambages (g, p, q) ne sont pas coupés par
     le masque des lignes animées (.line). */
  :is(h1, h2:not(:has(.line)), h3, .line > span) {
    padding-block-end: 0.14em;
  }

  /* Dégradé blanc → gris découpé dans le texte. Dans les titres, il n'est
     posé que sur les mots accentués (.textAccent), jamais sur une ligne
     animée (.line > span) : un enfant animé sort de la découpe de son
     parent et deviendrait invisible. La marge du bas évite de couper les
     jambages (g, p). */
  @supports (background-clip: text) or (-webkit-background-clip: text) {
    :is(.textAccent, .modelTile > .name, .keyFigures dd, .price > strong, .versionPrice > strong, .studioLines > li) {
      padding-block-end: 0.14em;
      background: var(--gradient-metal);
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
    }

    /* Rouleaux des chiffres clés : le dégradé est posé sur chaque rouleau
       (et répété à chaque chiffre), pas sur le chiffre clé qui le contient. */
    .counterReel {
      background: var(--gradient-metal) 0 0 / 100% calc(100% / 11) repeat-y;
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
    }

    .modelName {
      background: linear-gradient(90deg, rgb(255 255 255 / 0.1), rgb(196 210 232 / 0.3));
      -webkit-background-clip: text;
      background-clip: text;
      color: transparent;
    }
  }

  :is(.versionPhotos > img, .featureCard > .visual:not(.visual--cover), .menuModel > .visual, .modelHeroVisual) {
    filter: var(--gun-shadow);
  }
}

/* --------------------------------------------------- page Compétition */
/* Placé après les « composants v3 » : la vidéo d'ambiance de l'ouverture
   y est réglée, et ce bloc la règle autrement pour cette page. */
@layer components {

  /* Ouverture : même construction que les pages modèles, sans pistolet
     par-dessus le nom (le tireur de la vidéo est le sujet). Pas de place à
     réserver pour un pistolet (--gun-width: 0) ; intitulé, nom et texte
     groupés au milieu de l'écran (lignes 1fr au-dessus et au-dessous) ;
     vidéo moins voilée et moins saturée que celle de l'espace. */
  .modelHero--competition {
    --name-size: 10.2vw;
    --gun-width: 0px;
    --gun-ratio: 1;
    --grip-top: 1;
    --grip-left: 1;
    grid-template-rows: auto 1fr auto auto 1fr;
    min-height: 100svh;
    padding-block-end: clamp(3rem, 8svh, 6rem);

    &>.modelFamily {
      align-self: end;
    }

    &>.heroBackdrop {
      opacity: 0.8;
      filter: saturate(1.15);
    }

    &>.modelIntro {
      margin-block-start: clamp(1.5rem, 5svh, 3rem);

      &::before {
        content: none;
      }
    }
  }

  /* Rang (« 2e », « 1er ») : lettres en exposant, sans écarter les lignes. */
  :is(.keyFigures, .matchResult) sup {
    font-size: 0.42em;
    line-height: 0;
    vertical-align: 1.25em;
  }

  /* Lien sous le texte d'une carte. */
  .featureCard>.textButton {
    margin-block-start: 1rem;
  }

  /* Palmarès. overflow-x: clip : l'année géante qui glisse et la bande de
     photos du téléphone ne débordent pas de la page. */
  .palmares {
    padding-block-end: 0;
    overflow-x: clip;
  }

  /* Le tireur : portrait, puis texte et vidéo. */
  .athlete {
    display: grid;
    gap: 2rem;
    align-items: center;
    margin-block-start: 2.5rem;

    @media (min-width: 48em) {
      grid-template-columns: minmax(0, 4fr) minmax(0, 7fr);
      gap: clamp(2rem, 4vw, 5rem);
    }

    &>.visual {
      width: 100%;
      max-width: 26rem;
      aspect-ratio: 4 / 5;
      border-radius: var(--radius-card);
      object-fit: cover;
    }
  }

  .athleteText {
    &>h3 {
      margin-block-end: 1rem;
      font-size: calc(var(--fs-xl) * 0.8);
    }

    &>p:not(.label) {
      max-width: 36rem;
      color: var(--color-muted);
    }

    &>.videoCard {
      max-width: 36rem;
      margin-block-start: 2rem;
    }
  }

  /* Un match : année géante en filigrane (contour seul), puis le texte et
     les photos, côte à côte sur grand écran. */
  .matchList {
    margin-block-start: var(--section);
    padding: 0;
    list-style: none;
  }

  .match {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 1.5rem;
    padding-block: clamp(2.5rem, 6vw, 5rem);
    border-block-start: 1px solid var(--color-line);

    @media (min-width: 60em) {
      grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
      gap: 1.5rem clamp(2rem, 4vw, 5rem);
      align-items: start;
    }
  }

  .matchYear {
    grid-column: 1 / -1;
    margin-block-end: -0.42em;
    color: transparent;
    font-family: var(--font-display);
    font-size: clamp(5.5rem, 21vw, 15rem);
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 0.8;
    white-space: nowrap;
    -webkit-text-stroke: 1px rgb(255 255 255 / 0.22);
    pointer-events: none;
    user-select: none;
  }

  .matchText {
    position: relative;

    &>h3 {
      margin-block-end: 1rem;
      font-size: calc(var(--fs-xl) * 0.8);
    }

    &>p:not(.label, .matchResult, .credit) {
      max-width: 30rem;
      color: var(--color-muted);
    }

    &>.credit {
      margin-block-start: 1rem;
      color: var(--color-muted);
      font-size: var(--fs-xs);
    }
  }

  /* Résultat : le rang en grand, la division à côté. */
  .matchResult {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.75rem;
    margin-block-end: 1rem;
    color: var(--color-paper);

    &>strong {
      font-family: var(--font-display);
      font-size: var(--fs-xxl);
      font-weight: 700;
      line-height: 0.9;
    }
  }

  /* Photos d'un match. Téléphone : bande qui défile au doigt, calée photo
     par photo, jusqu'aux bords de l'écran. À partir de 768 px : mosaïque
     sur quatre colonnes, la première photo en grand (deux sur deux). */
  .matchPhotos {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 82%;
    gap: 0.75rem;
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-padding-inline: var(--gutter);
    scrollbar-width: none;

    &>.visual {
      width: 100%;
      aspect-ratio: 3 / 2;
      border-radius: var(--radius-card);
      object-fit: cover;
      scroll-snap-align: start;
    }

    @media (min-width: 48em) {
      grid-auto-flow: row;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      margin-inline: 0;
      padding-inline: 0;
      overflow: visible;

      &>.visual:first-child {
        grid-column: span 2;
        grid-row: span 2;
        height: 100%;
        aspect-ratio: auto;
      }
    }
  }

  /* Le maillot : photos et texte côte à côte sur grand écran.
     overflow-x: clip : la petite photo arrive de la droite. */
  .jersey {
    display: grid;
    gap: 2.5rem;
    align-items: center;
    overflow-x: clip;

    @media (min-width: 60em) {
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: clamp(2rem, 5vw, 6rem);
    }
  }

  /* Dos du maillot en grand, haut du maillot en petit à côté, en bas. */
  .jerseyPhotos {
    display: grid;
    grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
    gap: 1rem;
    align-items: end;
    margin: 0;

    &>.visual {
      width: 100%;
      border-radius: var(--radius-card);
      object-fit: cover;
    }
  }

  .jerseyText {
    &>h2 {
      font-size: calc(var(--fs-xl) * 0.8);
    }

    &>p:not(.label) {
      max-width: 34rem;
      color: var(--color-muted);
    }

    &>.textButton {
      margin-block-start: 0.5rem;
    }
  }

  /* Les huit lignes du maillot : chevron métal, ligne d'origine en petites
     capitales, traduction dessous. */
  .jerseyList {
    display: grid;
    margin-block: 1.5rem;
    padding: 0;
    list-style: none;

    &>li {
      display: grid;
      grid-template-columns: 2rem minmax(0, 1fr);
      column-gap: 1rem;
      align-items: center;
      padding-block: 0.6rem;
      border-block-end: 1px solid var(--color-line);

      &::before {
        content: "";
        grid-row: span 2;
        width: 2rem;
        height: 0.8rem;
        background: var(--gradient-metal);
        clip-path: polygon(0 0, 45% 0, 100% 50%, 45% 100%, 0 100%, 55% 50%);
      }

      &>[lang="en"] {
        color: var(--color-muted);
        font-size: var(--fs-xs);
        letter-spacing: 0.12em;
        text-transform: uppercase;
      }
    }
  }

  /* Au service des tireurs : chapeau centré, vidéo sous les photos. */
  .service {
    &>.lead {
      margin-inline: auto;
      text-align: center;
    }

    &>.videoGrid {
      margin-block-start: clamp(3rem, 6vw, 5rem);
    }
  }

  /* Accueil, compétition et vidéos : bouton vers la page Compétition,
     sous le chapeau. */
  .videoBand>.ctaButton {
    margin-block-start: 2rem;
  }

  /* Fond vidéo fixe (accueil, « Éprouvé sur les pas de tir » et « Acheter
     un Alien » ; Christophe, 2026-11-15) : la vidéo reste immobile à
     l'écran pendant que le texte de la section défile dessus, et l'on ne
     la voit qu'à travers la section : clip-path découpe aussi ce qui est
     fixe (overflow, lui, ne le ferait pas). D'une section à la suivante,
     un fond remplace l'autre comme un volet. Voile : vidéo assombrie, et
     fondue dans le noir en haut et en bas de la section. */
  section:has(> .fixedBackdrop) {
    position: relative;
    isolation: isolate;
    clip-path: inset(0);

    &::before {
      content: "";
      position: absolute;
      inset: 0;
      z-index: -1;
      background: linear-gradient(var(--color-ink), rgb(7 7 10 / 0) 20svh, rgb(7 7 10 / 0) calc(100% - 20svh), var(--color-ink));
      pointer-events: none;
    }
  }

  .fixedBackdrop {
    position: fixed;
    inset: 0;
    z-index: -2;
    width: 100%;
    height: 100%;
    object-fit: cover;
    opacity: 0.45;
    pointer-events: none;
  }

  .photoCard>figcaption>strong {
    display: block;
    margin-block-end: 0.25rem;
    font-family: var(--font-display);
    font-size: var(--fs-lg);
    font-weight: 400;
  }
}

/* ----------------------------------------------------------- animations */
/* Tout ce qui bouge est ici. Rien ne part masqué si le navigateur ne sait
   pas animer ; tout s'arrête si le visiteur a demandé moins de mouvement. */
@layer components {
  @media (prefers-reduced-motion: no-preference) {
    html {
      scroll-behavior: smooth;
    }

    /* Ouverture : l'image s'approche lentement, le texte monte en fondu. */
    .heroStage>picture>img {
      animation: settle 6s var(--ease-out) both;
    }

    .heroContent>* {
      animation: rise 1s var(--ease-out) both;
    }

    .heroContent> :nth-child(2) {
      animation-delay: 0.15s;
    }

    .heroContent> :nth-child(3) {
      animation-delay: 0.3s;
    }

    .scrollCue::before {
      animation: nudge 2s ease-in-out infinite;
    }

    /* Page modèle : le logo descend en fondu, le nom géant apparaît, le
       pistolet arrive par la droite, le texte monte ligne à ligne. */
    .modelFamily {
      animation: settleDown 1s var(--ease-out) both;
    }

    .modelName {
      animation: giantFade 1.4s var(--ease-out) both;
    }

    .modelIntro>* {
      animation: rise 0.9s 0.3s var(--ease-out) both;
    }

    .modelIntro> :nth-child(2) {
      animation-delay: 0.38s;
    }

    .modelIntro> :nth-child(3) {
      animation-delay: 0.46s;
    }

    .modelHeroVisual {
      animation: slideIn 1.2s 0.2s var(--ease-out) both;
    }

    /* Menu : les rubriques arrivent l'une après l'autre ; les modèles de
       même, à chaque ouverture du volet, un peu après lui. */
    .menuPanel:popover-open .menuMain>*,
    .menuModels:popover-open>.menuModel {
      animation: rise 0.6s var(--ease-out) both;
    }

    .menuPanel:popover-open .menuMain> :nth-child(2) {
      animation-delay: 0.05s;
    }

    .menuPanel:popover-open .menuMain> :nth-child(3) {
      animation-delay: 0.1s;
    }

    .menuModels:popover-open>.menuModel:nth-child(1) {
      animation-delay: 0.1s;
    }

    .menuModels:popover-open>.menuModel:nth-child(2) {
      animation-delay: 0.17s;
    }

    .menuModels:popover-open>.menuModel:nth-child(3) {
      animation-delay: 0.24s;
    }

    /* Pistolets en apesanteur : ils montent et descendent doucement, leur
       ombre au sol se resserre ; décalés d'une carte à l'autre. */
    :is(.versionPhotos, .featureCard > .visual:not(.visual--cover)) {
      animation: levitate 5s ease-in-out infinite;
      animation-delay: var(--float-delay, 0s);
    }

    .versionStage::before {
      animation: levitateShadow 5s ease-in-out infinite;
    }

    .featureList> :nth-child(3n + 2) {
      --float-delay: -1.7s;
    }

    .featureList> :nth-child(3n + 3) {
      --float-delay: -3.4s;
    }

    /* Plein écran : la fenêtre arrive en fondu (la photo, elle, s'agrandit
       depuis sa place : voir « transitions » en fin de fichier). */
    .zoomPanel {
      transition:
        opacity 0.35s var(--ease-out),
        display 0.35s allow-discrete,
        overlay 0.35s allow-discrete;

      &:not([open]) {
        opacity: 0;
      }
    }

    @starting-style {
      .zoomPanel[open] {
        opacity: 0;
      }
    }

    /* Zoom au détail : la photo grandit ou revient en douceur, mais suit
       le doigt sans délai quand on la déplace. */
    .zoomPhoto {
      transition: translate 0.45s var(--ease-out), scale 0.45s var(--ease-out);
    }

    .zoomPanel[data-gesture]>.zoomPhoto {
      transition: none;
    }

    /* Versions : la photo choisie arrive en fondu, avec un léger zoom. */
    .versionPhotos>img {
      transition: opacity 0.5s var(--ease-out), scale 0.6s var(--ease-out);

      @starting-style {
        opacity: 0;
        scale: 0.94;
      }
    }

    /* Apparitions, en tout ou rien (Christophe, 2026-11-14 : une
       apparition arrêtée à moitié avec le défilement est laide). Les blocs
       [data-reveal] encore sous l'écran au premier défilement attendent
       (data-reveal-state="waiting" : animation arrêtée sur sa première
       image) ; quand leur haut est monté de 25 % de l'écran depuis le bas,
       js/main.js les passe à "shown" et l'animation joue en entier, à son
       rythme. Blocs arrivés ensemble décalés de --reveal-stagger l'un
       après l'autre (data-reveal-order, posé par le script). Sans script, ou sans
       défilement (robots), rien n'est caché. --reveal-name : effet du bloc
       (montée et grossissement par défaut). */
    [data-reveal-state] {
      overflow-anchor: none;
      animation: var(--reveal-name, reveal) var(--reveal-duration, 0.9s) var(--ease-out) var(--reveal-delay, 0s) both;
    }

    [data-reveal-state="waiting"],
    [data-reveal-state="waiting"] .line>span {
      animation-play-state: paused;
    }

    [data-reveal-order="1"] {
      --reveal-delay: calc(1 * var(--reveal-stagger));
    }

    [data-reveal-order="2"] {
      --reveal-delay: calc(2 * var(--reveal-stagger));
    }

    [data-reveal-order="3"] {
      --reveal-delay: calc(3 * var(--reveal-stagger));
    }

    [data-reveal-order="4"] {
      --reveal-delay: calc(4 * var(--reveal-stagger));
    }

    [data-reveal-order="5"] {
      --reveal-delay: calc(5 * var(--reveal-stagger));
    }

    /* Titres : le titre ne bouge pas, chaque ligne sort de son masque,
       la seconde un peu après la première. */
    [data-reveal="lines"][data-reveal-state] {
      animation: none;
    }

    [data-reveal="lines"][data-reveal-state] .line>span {
      animation: lineUp 0.9s var(--ease-out) var(--reveal-delay, 0s) both;
    }

    [data-reveal="lines"][data-reveal-state] .line+.line>span {
      animation-delay: calc(var(--reveal-delay, 0s) + 0.15s);
    }

    /* Effets propres : images qui s'ouvrent en volet, photos qui arrivent
       de côté, lignes du maillot qui glissent. */
    :is(.athlete, .matchPhotos, .jerseyPhotos)>.visual:first-child,
    .processStep>.visual {
      --reveal-name: stepWipe;
    }

    .processStep:nth-child(even)>.visual {
      --reveal-name: stepWipeLeft;
    }

    .matchPhotos>.visual:not(:first-child),
    .jerseyPhotos>.visual:last-child {
      --reveal-name: photoIn;
    }

    .jerseyList>li {
      --reveal-name: jerseyLine;
      --reveal-duration: 0.7s;
    }

    /* Au défilement (navigateurs qui savent lier une animation à la
       position) : fonds tenus derrière certains textes, ouvertures. Tout ce qui bouge ici est exclu des repères
       que le navigateur prend pour garder la page en place
       (overflow-anchor) : Firefox compte leur mouvement et faisait arriver
       un saut direct (rechargement, retour) jusqu'à 150 px à côté. */
    @supports (animation-timeline: view()) {

      .fullBleed>.visual,
      .modelHero>.heroBackdrop,
      .modelFamily,
      .modelName,
      .modelHeroVisual,
      .modelIntro {
        overflow-anchor: none;
      }

      /* Versions : celle qui arrive au centre s'éclaire, les voisines
         restent en retrait (position dans la piste horizontale). Fondu
         seulement, sans changement de taille : la carte est l'élément sur
         lequel la piste se cale, et certains navigateurs (Chrome) calent
         sur sa taille transformée, ce qui la décalait et coupait son coin. */
      .versionSlide {
        view-timeline: --slide inline;
        animation: slideFocus linear both;
        animation-timeline: --slide;
        animation-range: cover 0% cover 100%;
      }

      /* Fonds tenus : quand le texte a fini d'apparaître (haut à
         mi-écran), le fond de sa section reste immobile à l'écran jusqu'à
         ce que le texte atteigne le haut de l'écran, puis repart avec la
         page (Christophe, 2026-11-12). Le texte prête sa position au fond
         (timeline-scope) ; le fond descend d'autant que la page monte. */
      .fullBleed {
        timeline-scope: --heldText;
      }

      .fullBleedContent {
        view-timeline: --heldText block;
      }

      /* Grande photo : 40svh de réserve au-dessus de son cadre, car elle y
         descend pendant l'arrêt (sans elle, bande sombre en haut sur les
         écrans bas) ; bord haut fondu sur 8svh, au cas où un écran très bas
         le laisserait voir. Hauteur donnée : une image ne s'étire pas
         entre top et bottom. */
      .fullBleed>.visual {
        inset-block-start: -40svh;
        height: calc(100% + 40svh);
        mask-image: linear-gradient(transparent, #000 8svh);
        animation: holdBackground linear both;
        animation-timeline: --heldText;
        animation-range: cover var(--reveal-to) cover 100svh;
      }

      /* Ouverture (accueil) : la scène reste en place pendant que la vidéo
         se resserre en carte arrondie et que le texte s'efface. */
      .hero {
        height: 170svh;
        view-timeline: --hero block;
      }

      .heroStage {
        position: sticky;
        top: 0;
        animation: heroCard linear both;
        animation-timeline: --hero;
        animation-range: contain 0% contain 100%;
      }

      .heroStage> :is(picture, .heroVideo) {
        animation: heroZoom linear both;
        animation-timeline: --hero;
        animation-range: contain 0% contain 100%;
      }

      :is(.heroContent, .scrollCue, .pauseButton) {
        animation: heroTextOut linear both;
        animation-timeline: --hero;
        animation-range: contain 0% contain 45%;
      }

      /* Ouverture des pages modèles (maquette de Christophe, 2026-11-12),
         sur la première hauteur d'écran de défilement : chaque couche
         monte moins vite que la page, d'autant moins qu'elle est au fond
         (--depth : retard pris sur la page ; logo et nom ensemble, pour
         que le logo reste au-dessus du nom) ; le pistolet grossit
         (--gun-grow) vers le bas. Sur ordinateur, le texte part ensuite
         vers la gauche. Sur téléphone, le pistolet ne prend pas de retard
         (il passerait sur le texte, placé dessous) et grossit vers le
         haut. */
      .modelHero {
        --gun-depth: 0svh;
        --gun-grow: 1.3;

        @media (min-width: 60em) {
          --gun-depth: 35svh;
          --gun-grow: 1.25;
        }
      }

      .modelHero>.heroBackdrop {
        --depth: 85svh;
        animation: heroDepth linear both;
        animation-timeline: scroll(root);
        animation-range: 0px 100svh;
      }

      .modelFamily {
        --depth: 60svh;
        animation: settleDown 1s var(--ease-out) both, heroDepth linear both;
        animation-timeline: auto, scroll(root);
        animation-range: normal, 0px 100svh;
      }

      .modelName {
        --depth: 60svh;
        animation: giantFade 1.4s var(--ease-out) both, heroDepth linear both;
        animation-timeline: auto, scroll(root);
        animation-range: normal, 0px 100svh;
      }

      .modelHeroVisual {
        --depth: var(--gun-depth);
        transform-origin: 50% 100%;
        animation: slideIn 1.2s 0.2s var(--ease-out) both, gunGrow linear both;
        animation-timeline: auto, scroll(root);
        animation-range: normal, 0px 100svh;

        @media (min-width: 60em) {
          transform-origin: 50% 0%;
        }
      }

      @media (min-width: 60em) {
        .modelIntro {
          animation: introAway linear both;
          animation-timeline: scroll(root);
          animation-range: 0px 100svh;
        }
      }

      /* Séquence studio : scène collée à l'écran sur 3 hauteurs d'écran ;
         la vidéo est avancée par js/main.js, les photos et les phrases par
         le défilement. */
      .studio {
        height: 320svh;
        view-timeline: --studio block;
      }

      .studioStage {
        position: sticky;
        top: 0;
        height: 100svh;
      }

      .studioMedia {
        animation: studioZoom linear both;
        animation-timeline: --studio;
        animation-range: contain 0% contain 100%;
      }

      .studioLines {
        align-self: center;
        justify-items: center;
        padding-block-end: 0;
        font-size: var(--fs-xxl);
        text-align: center;
      }

      .studioLines>li {
        grid-area: 1 / 1;
        animation: studioLine linear both;
        animation-timeline: --studio;
      }

      .studioLines>li:nth-child(1) {
        animation-range: contain 3% contain 34%;
      }

      .studioLines>li:nth-child(2) {
        animation-range: contain 35% contain 66%;
      }

      .studioLines>li:nth-child(3) {
        animation-range: contain 67% contain 98%;
      }

      .studioFrames>img {
        animation: studioFrame linear both;
        animation-timeline: --studio;
      }

      .studioFrames>img:nth-child(1) {
        animation-name: studioFrameFirst;
        animation-range: contain 0% contain 24%;
      }

      .studioFrames>img:nth-child(2) {
        animation-range: contain 18% contain 44%;
      }

      .studioFrames>img:nth-child(3) {
        animation-range: contain 38% contain 64%;
      }

      .studioFrames>img:nth-child(4) {
        animation-range: contain 58% contain 84%;
      }

      .studioFrames>img:nth-child(5) {
        animation-name: studioFrameLast;
        animation-range: contain 78% contain 100%;
      }
    }
  }

  @media (prefers-reduced-motion: reduce) {

    .siteHeader,
    .menuButton,
    .menuPanel,
    .menuPanel::backdrop,
    .menuModels,
    .menuMain>button::after,
    .roundArrow,
    :is(.teaserCard, .modelTile, .photoCard)> :is(.visual, img),
    .zoomButton,
    .versionTabs::before,
    .versionTabs>a,
    .arrowButton,
    .versionThumb,
    .textButton,
    .magazinePanel,
    .magazinePanel::backdrop,
    .swatch>input,
    .menuModel>.visual {
      transition: none;
    }
  }

  @keyframes levitate {
    50% {
      translate: 0 -0.75rem;
    }
  }

  @keyframes levitateShadow {
    50% {
      scale: 0.8;
      opacity: 0.55;
    }
  }

  @keyframes rise {
    from {
      opacity: 0;
      translate: 0 2.5rem;
    }
  }

  @keyframes slideFocus {

    0%,
    100% {
      opacity: 0.35;
    }

    38%,
    64% {
      opacity: 1;
    }
  }

  @keyframes reveal {
    from {
      opacity: 0;
      translate: 0 var(--reveal-rise);
      scale: var(--reveal-scale);
    }
  }

  @keyframes slideIn {
    from {
      opacity: 0;
      translate: var(--reveal-side) 0;
    }
  }

  @keyframes settle {
    from {
      scale: 1.08;
    }
  }

  @keyframes nudge {
    50% {
      translate: 0 0.4rem;
    }
  }

  @keyframes heroCard {
    from {
      clip-path: inset(0% 0% 0% 0% round 0px);
    }

    /* Resserrement franc (Christophe, 2026-11-14 : 7 % / 4 % passait
       inaperçu). */
    to {
      clip-path: inset(14% 11% 14% 11% round 36px);
    }
  }

  @keyframes heroZoom {
    to {
      scale: 1.15;
    }
  }

  @keyframes heroTextOut {
    to {
      opacity: 0;
      translate: 0 -5rem;
    }
  }

  @keyframes lineUp {
    from {
      translate: 0 110%;
    }
  }

  @keyframes giantFade {
    from {
      opacity: 0;
    }
  }

  @keyframes studioZoom {
    from {
      scale: 1.08;
    }

    to {
      scale: 1;
    }
  }

  @keyframes studioLine {
    0% {
      opacity: 0;
      translate: 0 3rem;
      filter: blur(8px);
    }

    25%,
    75% {
      opacity: 1;
      translate: 0 0;
      filter: blur(0);
    }

    100% {
      opacity: 0;
      translate: 0 -3rem;
      filter: blur(8px);
    }
  }

  @keyframes studioFrame {
    0% {
      opacity: 0;
      scale: 1.06;
    }

    25%,
    75% {
      opacity: 1;
    }

    100% {
      opacity: 0;
      scale: 0.97;
    }
  }

  @keyframes studioFrameFirst {

    0%,
    75% {
      opacity: 1;
    }

    100% {
      opacity: 0;
      scale: 0.97;
    }
  }

  @keyframes studioFrameLast {
    0% {
      opacity: 0;
      scale: 1.06;
    }

    30%,
    100% {
      opacity: 1;
      scale: 1;
    }
  }

  /* Couches de l'ouverture des pages modèles : descendent dans leur
     section de --depth pendant que la page monte d'une hauteur d'écran,
     donc montent moins vite qu'elle. « transform » : s'ajoute aux autres
     mouvements (translate, scale) sans les remplacer. */
  @keyframes heroDepth {
    to {
      transform: translateY(var(--depth));
    }
  }

  @keyframes gunGrow {
    to {
      transform: translateY(var(--depth)) scale(var(--gun-grow));
    }
  }

  /* Texte de l'ouverture (ordinateur) : monte avec le pistolet le temps
     d'être lu (même retard : 35 % de la page), puis part vers la gauche
     en s'effaçant. */
  @keyframes introAway {
    40% {
      transform: translate(0, 14svh);
      opacity: 1;
    }

    75% {
      opacity: 1;
    }

    to {
      transform: translate(-50vw, 35svh);
      opacity: 0;
    }
  }

  @keyframes settleDown {
    from {
      opacity: 0;
      translate: 0 -1.5rem;
    }
  }

  /* Fond tenu : descend dans sa section d'autant que la page monte pendant
     l'arrêt, donc reste immobile à l'écran. --hold : durée de l'arrêt,
     en défilement ; par défaut, de la mi-écran au haut de l'écran. */
  @keyframes holdBackground {
    to {
      transform: translateY(var(--hold, calc(100svh - var(--reveal-to))));
    }
  }
}

/* ------------------------------------------- animations (page Conception) */
/* Schémas : chaque pièce suit la position du principe dans l'écran
   (--scene). Sur téléphone, le principe se lit d'un bloc et le schéma
   s'anime pendant qu'il traverse l'écran ; sur grand écran, texte et schéma
   restent à l'écran (« collés ») le temps de l'animation. Sans défilement
   lié ou en « moins de mouvement » : schéma fixe, déjà complet. */
@layer components {
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {

      .principle,
      .principleFigure,
      .scheme * {
        overflow-anchor: none;
      }

      .principle {
        --scene-range: cover 30% cover 75%;
      }

      .principleFigure {
        view-timeline: --scene block;
      }

      @media (min-width: 60em) {
        .principle {
          --scene-range: contain 0% contain 100%;
          height: 190svh;
          padding-block-start: 0;
          view-timeline: --scene block;
        }

        .principleFigure {
          view-timeline: none;
        }

        .principleStage {
          position: sticky;
          top: 0;
          height: 100svh;
          padding-block: calc(var(--header-height) + 1rem) 1.5rem;
        }

        /* Sécurités : allumées l'une après l'autre pendant l'arrêt. */
        .safetyList>li {
          animation: safetyOn linear both;
          animation-timeline: --scene;
        }

        .safetyList>li:nth-child(1) { animation-range: contain 50% contain 58%; }
        .safetyList>li:nth-child(2) { animation-range: contain 57% contain 65%; }
        .safetyList>li:nth-child(3) { animation-range: contain 64% contain 72%; }
        .safetyList>li:nth-child(4) { animation-range: contain 71% contain 79%; }
        .safetyList>li:nth-child(5) { animation-range: contain 78% contain 86%; }
        .safetyList>li:nth-child(6) { animation-range: contain 85% contain 93%; }
      }

      /* Pièces animées : même chronologie, même plage. */
      .scheme--axis :is(.boreLine, .gripLine, .dimension, .recoil, .gun, .rise),
      .scheme--barrel :is(.bullet, .gas, .slideGroup, .brake, .travel),
      .scheme--rail :is(.guide--sight, .slideGroup),
      .scheme--trigger :is(.trigger, .push, .travelArc, .travelTag),
      .scheme--materials :is(.upper, .lower, .upper > .tag, .lower > .tag, .gravity),
      .scheme--modular :is(.click, .topRail, .panel, .backstrap, .magwell, .screw) {
        animation-duration: auto;
        animation-timing-function: linear;
        animation-fill-mode: both;
        animation-timeline: --scene;
        animation-range: var(--scene-range);
      }

      /* 1. Axe du canon : les axes se tracent, le recul file dans
         l'avant-bras, l'arme se relève à peine et revient. */
      .scheme--axis {
        & :is(.boreLine, .gripLine) {
          transform-box: fill-box;
          transform-origin: 0 50%;
          animation-name: axisDraw;
        }

        & .dimension {
          animation-name: axisDimension;
        }

        & .recoil {
          animation-name: axisRecoil;
        }

        & .gun {
          transform-box: view-box;
          transform-origin: 1000px 250px;
          animation-name: axisRise;
        }

        & .rise {
          animation-name: axisRiseTag;
        }
      }

      /* 2. Canon fixe : la balle file, le gaz remplit la chambre du piston,
         la culasse recule freinée puis revient ; le canon ne bouge pas. */
      .scheme--barrel {
        & .bullet {
          animation-name: barrelBullet;
        }

        & .gas {
          animation-name: barrelGas;
        }

        & .slideGroup {
          animation-name: barrelSlide;
        }

        & .brake {
          animation-name: barrelBrake;
        }

        & .travel {
          animation-name: barrelTravel;
        }
      }

      /* 3. Rail immobile : la ligne de visée se trace, puis la culasse
         recule et revient deux fois sous le rail qui ne bouge pas. */
      .scheme--rail {
        & .guide--sight {
          transform-box: fill-box;
          transform-origin: 100% 50%;
          animation-name: railSight;
        }

        & .slideGroup {
          animation-name: railSlide;
        }
      }

      /* 4. Détente : appui de 15 N, course courte, retour. */
      .scheme--trigger {
        & .trigger {
          transform-box: view-box;
          transform-origin: 560px 222px;
          animation-name: triggerPull;
        }

        & .push {
          animation-name: triggerPush;
        }

        & :is(.travelArc, .travelTag) {
          animation-name: triggerTravel;
        }
      }

      /* 5. Matériaux : les deux parties de la carcasse s'écartent, chacune
         avec sa matière, se rejoignent, puis le centre de gravité se pose. */
      .scheme--materials {
        & .upper {
          animation-name: materialsUpper;
        }

        & .lower {
          animation-name: materialsLower;
        }

        & :is(.upper, .lower)>.tag {
          animation-name: materialsTag;
        }

        & .gravity {
          animation-name: materialsGravity;
        }
      }

      /* 6. Modularité : clic, le rail se lève ; la vis tourne, plaquette,
         dos et puits de chargeur s'écartent ; tout se remonte. */
      .scheme--modular {
        & .click {
          transform-box: fill-box;
          transform-origin: 50% 50%;
          animation-name: modularClick;
        }

        & .topRail {
          animation-name: modularRail;
        }

        & .screw {
          transform-box: fill-box;
          transform-origin: 50% 50%;
          animation-name: modularScrew;
        }

        & .panel {
          animation-name: modularPanel;
        }

        & .backstrap {
          animation-name: modularBackstrap;
        }

        & .magwell {
          animation-name: modularMagwell;
        }
      }
    }
  }

  @keyframes axisDraw {
    0% {
      scale: 0 1;
    }

    18%,
    100% {
      scale: 1 1;
    }
  }

  @keyframes axisDimension {

    0%,
    16% {
      opacity: 0;
    }

    24%,
    100% {
      opacity: 1;
    }
  }

  @keyframes axisRecoil {

    0%,
    24% {
      opacity: 0;
      translate: 0 0;
    }

    30% {
      opacity: 1;
      translate: 40px 0;
    }

    50% {
      opacity: 1;
      translate: 420px 0;
    }

    56% {
      opacity: 0;
      translate: 470px 0;
    }

    57% {
      opacity: 0;
      translate: 0 0;
    }

    84%,
    100% {
      opacity: 1;
      translate: 0 0;
    }
  }

  @keyframes axisRise {

    0%,
    50% {
      rotate: 0deg;
    }

    60%,
    70% {
      rotate: 5deg;
    }

    82%,
    100% {
      rotate: 0deg;
    }
  }

  @keyframes axisRiseTag {

    0%,
    54% {
      opacity: 0;
    }

    62%,
    100% {
      opacity: 1;
    }
  }

  @keyframes barrelBullet {

    0%,
    6% {
      opacity: 1;
      translate: 0 0;
    }

    30% {
      opacity: 1;
      translate: -540px 0;
    }

    34% {
      opacity: 0;
      translate: -620px 0;
    }

    35%,
    100% {
      opacity: 0;
      translate: 0 0;
    }
  }

  @keyframes barrelGas {

    0%,
    16% {
      opacity: 0;
    }

    24%,
    60% {
      opacity: 1;
    }

    78% {
      opacity: 0;
    }

    92%,
    100% {
      opacity: 1;
    }
  }

  @keyframes barrelSlide {

    0%,
    30% {
      translate: 0 0;
    }

    58%,
    64% {
      translate: 80px 0;
    }

    84%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes barrelBrake {

    0%,
    22% {
      opacity: 0;
    }

    30%,
    58% {
      opacity: 1;
    }

    66% {
      opacity: 0;
    }

    90%,
    100% {
      opacity: 1;
    }
  }

  @keyframes barrelTravel {

    0%,
    30% {
      opacity: 0;
    }

    38%,
    62% {
      opacity: 1;
    }

    70% {
      opacity: 0;
    }

    90%,
    100% {
      opacity: 1;
    }
  }

  @keyframes railSight {
    0% {
      scale: 0 1;
    }

    16%,
    100% {
      scale: 1 1;
    }
  }

  @keyframes railSlide {

    0%,
    20% {
      translate: 0 0;
    }

    32%,
    36% {
      translate: 120px 0;
    }

    48%,
    56% {
      translate: 0 0;
    }

    68%,
    72% {
      translate: 120px 0;
    }

    84%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes triggerPull {

    0%,
    24% {
      rotate: 0deg;
    }

    40%,
    54% {
      rotate: -10deg;
    }

    64%,
    100% {
      rotate: 0deg;
    }
  }

  @keyframes triggerPush {

    0%,
    6% {
      opacity: 0;
      translate: -60px 0;
    }

    20% {
      opacity: 1;
      translate: 0 0;
    }

    40%,
    54% {
      opacity: 1;
      translate: 18px 0;
    }

    62% {
      opacity: 0;
      translate: 18px 0;
    }

    63% {
      opacity: 0;
      translate: 0 0;
    }

    88%,
    100% {
      opacity: 1;
      translate: 0 0;
    }
  }

  @keyframes triggerTravel {

    0%,
    32% {
      opacity: 0;
    }

    42%,
    100% {
      opacity: 1;
    }
  }

  @keyframes materialsUpper {

    0%,
    14% {
      translate: 0 0;
    }

    32%,
    56% {
      translate: 0 -70px;
    }

    72%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes materialsLower {

    0%,
    14% {
      translate: 0 0;
    }

    32%,
    56% {
      translate: 0 70px;
    }

    72%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes materialsTag {

    0%,
    20% {
      opacity: 0;
    }

    32%,
    100% {
      opacity: 1;
    }
  }

  @keyframes materialsGravity {

    0%,
    70% {
      opacity: 0;
      translate: 0 -160px;
    }

    76% {
      opacity: 1;
    }

    86%,
    100% {
      opacity: 1;
      translate: 0 0;
    }
  }

  @keyframes modularClick {

    0%,
    8% {
      opacity: 0;
      scale: 0.4;
    }

    14% {
      opacity: 1;
      scale: 1;
    }

    24%,
    100% {
      opacity: 0;
      scale: 1.8;
    }
  }

  @keyframes modularRail {

    0%,
    14% {
      translate: 0 0;
    }

    30%,
    64% {
      translate: 0 -120px;
    }

    80%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes modularScrew {

    0%,
    20% {
      rotate: 0deg;
    }

    32%,
    66% {
      rotate: -540deg;
    }

    78%,
    100% {
      rotate: 0deg;
    }
  }

  @keyframes modularPanel {

    0%,
    28% {
      translate: 0 0;
    }

    42%,
    66% {
      translate: -120px 90px;
    }

    80%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes modularBackstrap {

    0%,
    30% {
      translate: 0 0;
    }

    44%,
    66% {
      translate: 130px 0;
    }

    80%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes modularMagwell {

    0%,
    32% {
      translate: 0 0;
    }

    46%,
    66% {
      translate: 0 100px;
    }

    80%,
    100% {
      translate: 0 0;
    }
  }

  @keyframes safetyOn {
    from {
      border-color: rgb(255 255 255 / 0.15);
      background-color: transparent;
      color: var(--color-muted);
    }
  }

  @keyframes stepWipe {
    from {
      clip-path: inset(0 100% 0 0 round var(--radius-card));
      scale: 1.06;
    }

    to {
      clip-path: inset(0 0 0 0 round var(--radius-card));
    }
  }

  @keyframes stepWipeLeft {
    from {
      clip-path: inset(0 0 0 100% round var(--radius-card));
      scale: 1.06;
    }

    to {
      clip-path: inset(0 0 0 0 round var(--radius-card));
    }
  }
}

/* ------------------------------------------ animations (page Compétition) */
/* Au défilement, sur la position de chaque match dans l'écran : l'année
   géante traverse l'écran de droite à gauche. Les apparitions (portrait,
   textes, photos en volet ou de côté, lignes du maillot) sont déclenchées,
   en tout ou rien : voir « Apparitions ». Sans défilement lié ou en
   « moins de mouvement » : tout est en place d'emblée. */
@layer components {
  @media (prefers-reduced-motion: no-preference) {
    @supports (animation-timeline: view()) {

      .matchYear {
        overflow-anchor: none;
      }

      /* Un match prête sa position (--match) à son année. */
      .match {
        view-timeline: --match block;
      }

      .matchYear {
        animation: yearSlide linear both;
        animation-timeline: --match;
        animation-range: cover 0% cover 100%;
      }
    }
  }

  @keyframes yearSlide {
    from {
      translate: 30vw 0;
    }

    to {
      translate: -12vw 0;
    }
  }

  @keyframes photoIn {
    from {
      opacity: 0;
      translate: var(--reveal-side) 0;
      scale: 0.9;
    }
  }

  @keyframes jerseyLine {
    from {
      opacity: 0;
      translate: -4rem 0;
      clip-path: inset(0 100% 0 0);
    }

    to {
      clip-path: inset(0 0 0 0);
    }
  }
}

/* --------------------------------------------- transitions entre pages */
/* Hors couches : règles propres au navigateur. Chrome, Edge et Safari font
   un fondu glissé entre les pages ; le nom du modèle et sa photo passent de
   la carte à l'ouverture de sa page. Firefox change de page normalement. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition {
    navigation: auto;
  }
}

::view-transition-old(root) {
  animation: vtOut 0.4s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

::view-transition-new(root) {
  animation: vtIn 0.5s cubic-bezier(0.2, 0.7, 0.2, 1) both;
}

::view-transition-group(*) {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.2, 0.7, 0.2, 1);
}

.siteHeader {
  view-transition-name: site-header;
}

.modelTile[href="/standard/"]>.name,
.modelHero--standard .modelName {
  view-transition-name: standard-name;
}

.modelTile[href="/creator/"]>.name,
.modelHero--creator .modelName {
  view-transition-name: creator-name;
}

.modelTile[href="/remus/"]>.name,
.modelHero--remus .modelName {
  view-transition-name: remus-name;
}

.modelTile[href="/standard/"]>.visual,
.modelHero--standard .modelHeroVisual {
  view-transition-name: standard-visual;
}

.modelTile[href="/creator/"]>.visual,
.modelHero--creator .modelHeroVisual {
  view-transition-name: creator-visual;
}

.modelTile[href="/remus/"]>.visual,
.modelHero--remus .modelHeroVisual {
  view-transition-name: remus-visual;
}

/* Photo des versions agrandie (js/main.js) : la photo passe de sa place au
   plein écran et inversement ; le reste de la page ne fait qu'un fondu
   (data-zoom-morph posé sur <html> le temps de la transition). Les deux
   images gardent leurs proportions pendant le trajet. */
::view-transition-group(zoom-photo) {
  animation-duration: 0.55s;
}

/* Pendant l'agrandissement, seule la photo voyage : l'en-tête et les noms
   des modèles (transitions entre pages) restent dans le fondu de la page.
   « !important » l'emporte sur leurs règles plus précises ; la photo garde
   son nom, posé par le script, lui aussi en « important », sur l'élément. */
html[data-zoom-morph] * {
  view-transition-name: none !important;
}

::view-transition-old(zoom-photo),
::view-transition-new(zoom-photo) {
  height: 100%;
  object-fit: contain;
}

html[data-zoom-morph]::view-transition-old(root) {
  animation: zoomFadeOut 0.4s ease both;
}

html[data-zoom-morph]::view-transition-new(root) {
  animation: zoomFadeIn 0.4s ease both;
}

@keyframes zoomFadeOut {
  to {
    opacity: 0;
  }
}

@keyframes zoomFadeIn {
  from {
    opacity: 0;
  }
}

@keyframes vtOut {
  to {
    opacity: 0;
    translate: 0 -2rem;
  }
}

@keyframes vtIn {
  from {
    opacity: 0;
    translate: 0 2rem;
  }
}

/* ------------------------------------------------- essai : fond fixe */
/* Essai demandé par Christophe (2026-11-12) : une image de fond reste
   immobile derrière tout le site, les pages défilent dessus. Image choisie
   (--backdrop-image) : aucune, fond noir (Christophe, 2026-11-12) ;
   essayées : le dessin technique url("/img/bg-Sketch.jpg") et la texture
   carbone url("/img/bg-carbon.webp").
   Les sections à fond noir uni deviennent transparentes (leurs lueurs
   restent) ; gardent leur fond : ouverture vidéo, séquence studio, cartes,
   menu, visionneuse, pied de page. --backdrop-veil : voile noir posé sur
   l'image (0 = image telle quelle). */
@layer components {
  :root {
    --backdrop-image: none;
    --backdrop-veil: 0;
  }

  body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    background:
      linear-gradient(rgb(7 7 10 / var(--backdrop-veil)), rgb(7 7 10 / var(--backdrop-veil))),
      var(--backdrop-image) center / cover no-repeat;
    pointer-events: none;
  }

  :is(.hero, .tilesBand, .videoBand, .modelHero, .versions) {
    background-color: transparent;
  }

  /* Bords doux : la grande photo et la séquence studio se fondent dans
     l'image de fond au lieu de s'arrêter net sur elle (en bas de la photo, le fondu
     reste dans la marge sous le texte). */
  .fullBleed {
    mask-image: linear-gradient(transparent, #000 12svh, #000 calc(100% - clamp(3rem, 8vw, 6rem)), transparent);
  }

  .studio {
    mask-image: linear-gradient(transparent, #000 12svh, #000 calc(100% - 12svh), transparent);
  }
}