/* =============================================================================
   Polices auto-hebergees (assets/fonts/)
   -----------------------------------------------------------------------------
   Newsreader, Inter et IBM Plex Mono sont sous SIL Open Font License 1.1, qui
   autorise l'hebergement sur son propre domaine. Les fichiers woff2 sont ceux
   servis par Google Fonts, recopies tels quels.

   Pourquoi les auto-heberger : la feuille de style distante de Google bloquait
   le premier rendu et imposait une connexion supplementaire (DNS, TCP puis TLS
   vers fonts.googleapis.com, avant fonts.gstatic.com) sans meme savoir encore
   quels fichiers demander. Declarees ici, les polices partent sur la connexion
   deja ouverte, la CSP n'autorise plus aucun domaine tiers, et le site ne fait
   plus une seule requete exterieure.

   Seules les graisses REELLEMENT utilisees sont demandees :
     Inter        400 et 500  (texte courant, demi-gras)
     Newsreader   400         (nom et intitules de section)
     IBM Plex Mono 400        (classe .mono)
   Ce detail pese lourd : Newsreader en 400+500 fait 132 ko, en 400 seul 57 ko.
   Inter et Newsreader sont des fontes variables, un meme fichier sert donc
   plusieurs graisses. Ajouter une graisse ici sans retelecharger le fichier
   correspondant ne ferait que de la fausse graisse synthetisee.

   Les `unicode-range` de Google sont conserves : le sous-ensemble latin-ext
   n'est telecharge que si un caractere de cette plage apparait dans la page.
   `font-display: swap` : le texte s'affiche immediatement dans la pile de repli.

   Pour mettre a jour ou ajouter une graisse, voir assets/README.md.
   ========================================================================== */

/* latin-ext */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/ibm-plex-mono-latin-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;
}
/* latin */
@font-face {
  font-family: 'IBM Plex Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/ibm-plex-mono-latin.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;
}
/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/inter-latin-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;
}
/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/inter-latin.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;
}
/* latin-ext */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(assets/fonts/inter-latin-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;
}
/* latin */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(assets/fonts/inter-latin.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;
}
/* latin-ext */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/newsreader-latin-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;
}
/* latin */
@font-face {
  font-family: 'Newsreader';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(assets/fonts/newsreader-latin.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;
}

/* =============================================================================
   Portfolio — design system
   Page personnelle en deux colonnes : identite a gauche, contenu a droite.
   Palette plate, hairlines, typographie editoriale. Le socle reste sans degrade
   ni ombre : la matiere de verre est posee a part, en fin de fichier (section
   "Couche verre"), et se retire en un bloc.
   Tous les tokens sont centralises dans :root.
   ========================================================================== */

:root {
  /* --- couleurs ---------------------------------------------------------- */
  --bg:          #FAFAF8;
  --bg-sunken:   #F2F2EE;
  --ink:         #16160F;
  --muted:       #6B6B62;   /* 5,00:1 sur --bg, 4,66:1 sur --bg-sunken (WCAG AA) */
  --line:        #E4E4DE;
  --accent:      #185FA5;
  --accent-ink:  #12477B;

  /* --- typographie ------------------------------------------------------- */
  --font-serif: "Newsreader", Georgia, "Times New Roman", Times, serif;
  --font-sans:  "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
                Helvetica, Arial, sans-serif;
  --font-mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas,
                "Liberation Mono", monospace;

  --fs-name:    clamp(1.55rem, 1.3rem + 1vw, 1.95rem);
  --fs-h2:      clamp(1.4rem, 1.15rem + 1vw, 1.75rem);
  --fs-h3:      clamp(1.08rem, 1rem + 0.35vw, 1.22rem);
  --fs-body:    1.0313rem;
  --fs-small:   0.9375rem;
  --fs-mono:    0.75rem;

  --lh-body:    1.72;

  /* --- espacements / grille ---------------------------------------------- */
  --maxw:       1080px;
  --gutter:     clamp(20px, 4.5vw, 56px);
  --rail-w:     264px;
  --col-gap:    clamp(40px, 6vw, 84px);
  --block-gap:  clamp(48px, 6vw, 76px);

  --radius:     12px;
  --radius-s:   8px;
  --hair:       1px solid var(--line);

  /* --- mouvement --------------------------------------------------------- */
  --ease:       cubic-bezier(.22, .61, .36, 1);
  --dur:        .6s;
}

/* =============================================================================
   Reset minimal
   ========================================================================== */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: 32px;
}

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

h1, h2, h3, h4, p, figure, ul, ol, dl, dd, blockquote { margin: 0; }
ul, ol { padding: 0; list-style: none; }
img, svg, video { max-width: 100%; display: block; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

a {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  text-decoration-color: color-mix(in srgb, var(--accent) 32%, transparent);
  transition: color .2s var(--ease), text-decoration-color .2s var(--ease);
}
a:hover { color: var(--accent-ink); text-decoration-color: currentColor; }

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

/* Cible du lien d'evitement. `tabindex="-1"` rend <main> focalisable par script
   sans l'ajouter a l'ordre de tabulation ; main.js y place explicitement le
   focus a l'activation du lien, sans quoi le navigateur se contente de faire
   defiler la page et le focus reste sur <body>.

   L'anneau est trace vers l'INTERIEUR : pose a l'exterieur d'un element pleine
   largeur, il deborderait horizontalement sur petit ecran. Et il n'apparait
   qu'en :focus-visible, sinon un simple clic dans la page l'allumerait, le
   focus remontant alors jusqu'a l'ancetre focalisable le plus proche. */
#main:focus { outline: none; }
#main:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

::selection { background: color-mix(in srgb, var(--accent) 18%, transparent); }

/* =============================================================================
   Utilitaires
   ========================================================================== */

.wrap {
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

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

.skip-link {
  position: absolute;
  top: 8px; left: 8px;
  z-index: 200;
  transform: translateY(-160%);
  background: var(--ink);
  color: var(--bg);
  padding: 10px 16px;
  border-radius: var(--radius-s);
  text-decoration: none;
  font-size: var(--fs-small);
}
.skip-link:focus-visible { transform: none; color: var(--bg); }

/* Label monospace : intitules de blocs, metadonnees. Sentence case, jamais de
   capitales generalisees. */
.mono {
  font-family: var(--font-mono);
  font-size: var(--fs-mono);
  font-weight: 400;
  letter-spacing: .085em;
  line-height: 1.6;
  color: var(--muted);
}

.prose { max-width: 64ch; }
.prose p + p { margin-top: 1.1em; }
.prose--muted { color: var(--muted); }

/* =============================================================================
   Boutons (page etude de cas)
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .55em;
  padding: .72em 1.3em;
  min-height: 44px;             /* cible tactile confortable */
  border-radius: var(--radius-s);
  border: 1px solid transparent;
  font-size: var(--fs-small);
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color .2s var(--ease), border-color .2s var(--ease),
              color .2s var(--ease);
}
.btn--primary { background: var(--ink); color: var(--bg); }
.btn--primary:hover { background: var(--accent); color: #fff; }
.btn--ghost { border-color: var(--line); color: var(--ink); }
.btn--ghost:hover { border-color: var(--ink); }

.btn .arrow { transition: transform .25s var(--ease); }
.btn:hover .arrow { transform: translateX(3px); }

.btn-row { display: flex; flex-wrap: wrap; align-items: center; gap: 12px 18px; }

.language-switcher {
  display: flex;
  align-items: center;
  gap: 8px;
}
.language-switcher a {
  color: var(--muted);
  text-decoration: none;
  min-width: 28px;
  min-height: 28px;
  display: inline-grid;
  place-items: center;
}
.language-switcher a:hover,
.language-switcher a[aria-current="page"] {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: .28em;
}

/* Confirmation de copie de l'adresse e-mail, inseree par main.js a cote du lien
   clique. Elle se replie pour ne jamais creer de debordement. Les contextes qui
   ne doivent pas etre remis en page (rail et en-tete) la positionnent ensuite
   en superposition. */
.copied {
  color: var(--accent);
  overflow-wrap: anywhere;
}

/* =============================================================================
   Page personnelle — grille a deux colonnes
   ========================================================================== */

.profile {
  display: grid;
  gap: var(--col-gap);
  padding-block: clamp(36px, 5vw, 64px) clamp(48px, 6vw, 88px);
}

/* --- colonne d'identite --------------------------------------------------- */

.rail { align-self: start; }
.rail .language-switcher { margin-top: 18px; }

.rail__photo {
  width: 100%;
  max-width: 200px;
  /* height:auto est indispensable : l'attribut height du HTML l'emporterait
     sinon sur aspect-ratio. */
  height: auto;
  /* La photo fournie est carree : un cadre 1/1 l'affiche entiere, sans rogner
     les cotes. Passer a 4/5 si elle est remplacee par un portrait vertical. */
  aspect-ratio: 1 / 1;
  object-fit: cover;
  border: var(--hair);
  border-radius: var(--radius);
  background: var(--bg-sunken);
}

.rail__name {
  margin-top: 22px;
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-name);
  line-height: 1.14;
  letter-spacing: -.02em;
}

/* L'intitule de poste redescend en gris : c'est l'affiliation juste en dessous
   qui porte l'emphase, les deux noms d'institution devant se voir au premier
   coup d'oeil. */
.rail__title {
  margin-top: 10px;
  font-size: var(--fs-small);
  line-height: 1.5;
  color: var(--muted);
  max-width: 30ch;
}

/* Sans la classe .mono : a 12 px l'affiliation passait pour une note de bas de
   page alors qu'elle porte une information de premier plan. Meme taille que
   l'intitule au-dessus, mais en encre plutot qu'en gris pour la distinguer. */
/* Encre + demi-gras : a taille egale, c'est ce qui distingue le plus nettement
   ces deux lignes de tout ce qui les entoure, sans les grossir au point de
   concurrencer le nom. */
.rail__affil {
  margin-top: 14px;
  font-size: var(--fs-small);
  font-weight: 500;
  line-height: 1.75;
  color: var(--ink);
}

/* Liste de liens en colonne a toutes les tailles : avec une icone devant chaque
   libelle, un retour a la ligne en mode "row" devient illisible sur mobile. */
.rail__links {
  display: grid;
  gap: 2px;
  margin-top: 20px;
  padding-top: 18px;
  border-top: var(--hair);
  font-size: var(--fs-small);
}
/* La confirmation remplace visuellement la ligne e-mail pendant six secondes,
   sans ajouter une nouvelle ligne a la grille. Le rail et tous les liens qui
   suivent gardent donc exactement leur position apres le clic. */
.rail__links > li { position: relative; }
.rail__links > li > .copied {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  min-width: 0;
  padding: 4px 0;
  background: var(--bg);
  pointer-events: none;
}
/* Meme traitement que les liens du reste du site : bleu accent, souligne discret
   qui s'affirme au survol. L'icone suit la couleur du lien (fill="currentColor")
   pour que la ligne entiere se lise comme un seul lien. */
.rail__links a {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Zone interactive d'au moins 44 px sans changer l'aspect : le padding
     vertical remplace l'ancien `gap` de la liste. */
  min-height: 44px;
  padding-block: 4px;
  color: var(--accent);
  text-decoration: none;
  min-width: 0;
  transition: color .2s var(--ease);
}
.rail__links a span {
  overflow-wrap: anywhere;
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: .22em;
  text-decoration-color: color-mix(in srgb, var(--accent) 32%, transparent);
  transition: text-decoration-color .2s var(--ease);
}
.rail__links a:hover { color: var(--accent-ink); }
.rail__links a:hover span { text-decoration-color: currentColor; }

.ico {
  width: 16px;
  height: 16px;
  flex: none;
  color: inherit;
}

.rail__meta {
  margin-top: 16px;
  padding-top: 16px;
  border-top: var(--hair);
  line-height: 1.9;
  max-width: 30ch;
}
.rail__meta + .rail__meta { margin-top: 10px; padding-top: 10px; border-top: 0; }

/* --- colonne principale --------------------------------------------------- */

.main-col { min-width: 0; }

.bio {
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  max-width: 64ch;
}
.bio p + p { margin-top: 1.05em; }
.bio strong { font-weight: 500; color: var(--ink); }

.block { margin-top: var(--block-gap); }

/* En label monospace 12 px, ces intitules etaient plus PETITS que les titres
   d'articles qu'ils coiffent (serif ~19 px) : la hierarchie etait inversee.
   Passes en serif a l'echelle h2, ils dominent clairement leur section et
   s'alignent sur les titres de la page etude de cas. */
.block__head {
  padding-bottom: 12px;
  margin-bottom: clamp(24px, 2.6vw, 32px);
  border-bottom: var(--hair);
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h2);
  line-height: 1.2;
  letter-spacing: -.016em;
  color: var(--ink);
}

/* =============================================================================
   Projets
   ========================================================================== */

.project {
  display: grid;
  gap: 20px;
  padding-bottom: clamp(26px, 3vw, 34px);
  margin-bottom: clamp(26px, 3vw, 34px);
  border-bottom: var(--hair);
}
.project:last-child { padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }

.project__thumb {
  display: block;
  border: var(--hair);
  border-radius: var(--radius-s);
  background: var(--bg-sunken);
  overflow: hidden;
  text-decoration: none;
  transition: border-color .2s var(--ease);
}
.project__thumb:hover { border-color: var(--muted); }
.project__thumb img {
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.project__body > * + * { margin-top: 8px; }

.project__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.project__title a {
  color: var(--ink);
  text-decoration-color: color-mix(in srgb, var(--ink) 22%, transparent);
}
.project__title a:hover { color: var(--accent); }

.project__body p:not(.project__meta):not(.project__links) {
  font-size: var(--fs-small);
  color: var(--muted);
  max-width: 62ch;
}

.project__links { font-size: var(--fs-small); }
/* Cible uniquement le separateur : un `.project__links span` generique
   attrapait aussi le span qui enveloppe le libelle du lien, et le repeignait
   en couleur de filet. */
.project__links .sep { color: var(--muted); opacity: .55; margin-inline: 6px; }

/* =============================================================================
   Publications
   ========================================================================== */

.pub {
  padding-bottom: clamp(20px, 2.4vw, 26px);
  margin-bottom: clamp(20px, 2.4vw, 26px);
  border-bottom: var(--hair);
}
.pub:last-child { padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }
.pub > * + * { margin-top: 5px; }

.pub__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.32;
  max-width: 52ch;
}
.pub__authors { font-size: var(--fs-small); color: var(--muted); }
/* Un DOI est une seule « suite de caracteres » sans espace : sur un ecran tres
   etroit il ne peut se replier nulle part et pousse la page. `anywhere` autorise
   la coupure a n'importe quel caractere, et — contrairement a `break-word` —
   compte aussi dans le calcul de la largeur minimale de contenu, ce qui evite le
   debordement au lieu de simplement le rendre moins visible. */
.pub__links { font-size: var(--fs-small); overflow-wrap: anywhere; }
.pub__links a { overflow-wrap: anywhere; }

/* =============================================================================
   Parcours
   ========================================================================== */

.entry {
  display: grid;
  gap: 6px;
  padding-bottom: clamp(20px, 2.4vw, 26px);
  margin-bottom: clamp(20px, 2.4vw, 26px);
  border-bottom: var(--hair);
}
.entry:last-child { padding-bottom: 0; margin-bottom: 0; border-bottom: 0; }

.entry__org {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.3;
}
.entry__body > * + * { margin-top: 5px; }
.entry__body p { font-size: var(--fs-small); color: var(--muted); max-width: 62ch; }

/* Ligne d'etablissement : en encre et en demi-gras, pour que le nom de
   l'institution se detache du detail qui le suit. Specificite portee a deux
   classes, sinon `.entry__body p` l'emporterait sur la couleur. */
.entry__body .entry__where {
  color: var(--ink);
  font-weight: 500;
}

.page-foot {
  margin-top: var(--block-gap);
  padding-top: 24px;
  border-top: var(--hair);
}

/* =============================================================================
   Lecteur video
   ========================================================================== */

.player {
  position: relative;
  border: var(--hair);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg-sunken);
  aspect-ratio: 16 / 9;
}
.player img,
.player video,
.player iframe {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border: 0;
  display: block;
}

.player__play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  background: color-mix(in srgb, var(--bg) 18%, transparent);
  transition: background-color .25s var(--ease);
}
.player__play:hover { background: color-mix(in srgb, var(--bg) 32%, transparent); }

.player__disc {
  width: 64px; height: 64px;
  border-radius: 50%;
  background: var(--bg);
  border: 1px solid var(--line);
  display: grid;
  place-items: center;
  padding-left: 4px;
  transition: border-color .25s var(--ease);
}
.player__play:hover .player__disc { border-color: var(--ink); }

/* Repli du lecteur : message et lien direct vers le fichier, sous l'affiche.
   Deux cas l'affichent — l'absence de JavaScript (bloc <noscript> dans la page)
   et l'echec de chargement ou de decodage du MP4 (insere par main.js).

   Volontairement SOUS le lecteur et non dedans : .player a un ratio 16/9 fixe et
   `overflow: hidden`, un message a l'interieur serait tronque sur ecran etroit
   et masquerait l'affiche, que l'on veut justement conserver. */
.player__fallback {
  margin: 10px 0 0;
  color: var(--muted);
  overflow-wrap: anywhere;
}

/* =============================================================================
   Figures scientifiques
   ========================================================================== */

.fig {
  border: var(--hair);
  border-radius: var(--radius);
  background: var(--bg);
  overflow: hidden;
}
.fig img { width: 100%; height: auto; padding: 14px 10px 4px; }

/* Cadre de defilement, pose UNIQUEMENT sur les figures a panneaux multiples.
   -----------------------------------------------------------------------------
   `coarse_to_fine.png` fait 2988 px pour quatre panneaux, `method1_vs_method2`
   1890 px pour deux. Reduites a la largeur d'un telephone, elles tombent a
   350 px : les titres de panneaux et les graduations d'axes passent sous le
   pixel et la figure ne demontre plus rien.

   Sous 700 px, elles sont donc posees dans un cadre qui defile horizontalement,
   a une largeur ou les annotations redeviennent dechiffrables. Le defilement
   reste INTERIEUR au cadre : la page, elle, ne defile jamais horizontalement —
   c'est la regle que `npm run e2e` verifie de 256 a 1440 px.

   Les deux autres figures n'ont pas ce cadre, et c'est delibere :
   `discrete_region` est presque carree, donc la plus lisible des quatre, et
   `continuous_neural_surfaces` est servie par un `srcset` dont le `sizes`
   devrait alors annoncer 700 px — le telephone telechargerait la variante
   1524 px (78 ko) au lieu de la 756 px (26 ko), pour deux surfaces 3D dont la
   forme se lit deja. Le lien « Open the full-size image » de la legende reste
   le recours pour la resolution native, sur les quatre.

   `overscroll-behavior-x: contain` empeche le geste de deborder sur
   l'historique du navigateur une fois le bord du cadre atteint. */
.fig__frame { overflow-x: auto; overscroll-behavior-x: contain; }

/* L'anneau de focus est trace vers l'INTERIEUR : .fig a `overflow: hidden`, un
   anneau pose a l'exterieur du cadre serait coupe par le bord arrondi de la
   figure. Meme raison que pour #main. */
.fig__frame:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

@media (max-width: 699px) {
  .fig__frame img { min-width: 700px; }
}

.fig figcaption {
  border-top: var(--hair);
  padding: 12px 16px 14px;
  color: var(--muted);
}
.fig figcaption strong {
  display: block;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: 0;
  margin-bottom: 3px;
  text-align: center;
}
.fig--wide img { padding: 18px 16px 6px; }

/* Lien vers l'image d'origine, sous la legende.
   Les annotations internes des figures (etiquettes d'axes, valeurs, mentions
   portees sur les panneaux) tombent sous le seuil de lisibilite des qu'une
   figure de 2000 px est reduite a la largeur d'un telephone. Le lien ouvre le
   PNG tel qu'il sort du depot scientifique, a sa resolution native, dans un
   nouvel onglet.

   L'IMAGE, elle, reste hors de tout <a> : son texte alternatif est une
   description, pas un libelle de lien, et la figure entiere ne devient pas une
   cible de clic. Le lien est explicite, nomme, et atteignable au clavier comme
   n'importe quel autre lien de la page.

   `display: flex` avec `width: fit-content` : le lien passe a la ligne sous le
   texte de legende sans s'etirer sur toute la largeur, ce qui limite la cible
   de pointage a son libelle. La legende est en .mono 12 px ; le lien remonte a
   la taille du texte courant et perd l'interlettrage, pour rester lisible et
   depasser les 24 px de hauteur demandes par le critere de cible (WCAG 2.2,
   2.5.8). */
.fig__full {
  display: flex;
  align-items: center;
  gap: .45em;
  width: fit-content;
  margin-top: 12px;
  min-height: 24px;
  font-family: var(--font-sans);
  font-size: var(--fs-small);
  letter-spacing: 0;
}
.fig__full .arrow { transition: transform .25s var(--ease); }
.fig__full:hover .arrow { transform: translate(2px, -2px); }

/* Encart sobre : portee, licence, avertissement de placeholder. */
.note {
  border-left: 2px solid var(--line);
  padding: 4px 0 4px 18px;
  color: var(--muted);
  font-size: var(--fs-small);
  max-width: 62ch;
}
.note--accent { border-left-color: var(--accent); }

/* =============================================================================
   En-tete (page etude de cas uniquement)
   ========================================================================== */

.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  background: var(--bg);
  border-bottom: 1px solid transparent;
  transition: border-color .25s var(--ease);
}
.site-header.is-stuck { border-bottom-color: var(--line); }

/* Telephone tenu a l'horizontale : en 844x390, l'en-tete mesure 103 px, soit
   26 % de la hauteur de la fenetre, et il reste colle pendant toute la lecture.
   Sur une fenetre aussi basse il rend son espace au texte. La marque et les
   deux actions restent atteignables en haut de page, et le pied de page porte
   deja un retour vers l'accueil. */
@media (max-height: 480px) {
  .site-header { position: static; }
}

.site-header__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 20px;
  min-height: 64px;
  padding-block: 10px;
}

.brand {
  display: inline-flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: .2em .6em;
  min-width: 0;                 /* sinon un flex item refuse de retrecir */
  text-decoration: none;
  color: var(--ink);
  font-weight: 500;
  font-size: var(--fs-small);
}
.brand .mono { color: var(--muted); }

.header-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  min-width: 0;
}

/* Sur l'étude de cas FR, la confirmation de copie reste sous le bouton de
   contact sans devenir un nouvel élément de la rangée de boutons. */
.header-actions--copy-below { position: relative; }
.header-actions--copy-below > .copied {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  z-index: 1;
  max-width: calc(100vw - 24px);
  padding: .25em .45em;
  border: var(--hair);
  border-radius: var(--radius-s);
  background: var(--bg);
  font-size: .75rem;
  line-height: 1.3;
  text-align: right;
  white-space: normal;
  overflow-wrap: anywhere;
}

.site-footer { border-top: var(--hair); padding-block: 32px 48px; }
.site-footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px 28px;
}

/* =============================================================================
   Page etude de cas
   ========================================================================== */

.cs-header { padding-top: clamp(36px, 4.5vw, 64px); padding-bottom: clamp(30px, 3.5vw, 48px); }
.cs-title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: clamp(2rem, 1.3rem + 2.8vw, 3.2rem);
  line-height: 1.08;
  letter-spacing: -.024em;
  max-width: 18ch;
  margin-top: clamp(16px, 2vw, 24px);
}
.cs-standfirst {
  margin-top: clamp(18px, 2.2vw, 26px);
  font-size: clamp(1.05rem, .98rem + .4vw, 1.22rem);
  line-height: 1.55;
  color: var(--muted);
  max-width: 54ch;
}

.meta-bar {
  display: grid;
  gap: 18px 36px;
  padding-block: 20px;
  border-top: var(--hair);
  border-bottom: var(--hair);
  margin-top: clamp(28px, 3.5vw, 44px);
}
.meta-bar div { display: grid; gap: 4px; }
.meta-bar dd { font-size: var(--fs-small); }

.cs-body { display: grid; gap: 0; }

.cs-section {
  display: grid;
  gap: 18px;
  padding-block: clamp(36px, 4.5vw, 68px);
  border-top: var(--hair);
}
/* Un enfant de grille a `min-width: auto` par defaut : sa largeur minimale de
   contenu s'impose alors a la piste, qui deborde. Le bloc de formules, en
   monospace et sans espace secable sur certaines lignes, elargissait ainsi
   toute la page sous 280 px, malgre son propre `overflow-x: auto` — celui-ci
   ne peut rien tant que l'element a le droit de grandir. */
.cs-section > * { min-width: 0; }
.cs-section h2 {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h2);
  line-height: 1.24;
  letter-spacing: -.016em;
  max-width: 22ch;
}

.card {
  border: var(--hair);
  border-radius: var(--radius);
  padding: clamp(20px, 2.2vw, 28px);
  background: var(--bg);
}
.card > * + * { margin-top: 12px; }
.card__title {
  font-family: var(--font-serif);
  font-weight: 400;
  font-size: var(--fs-h3);
  line-height: 1.3;
}

.tick-list { display: grid; gap: 12px; color: var(--muted); font-size: var(--fs-small); }
/* Puce en position absolue plutot qu'en colonne de grille : un <li> contenant du
   balisage inline (<strong>, <em>) verrait sinon chaque element devenir un item
   de grille distinct et partir a la ligne. */
.tick-list li { position: relative; padding-left: 18px; }
.tick-list li::before {
  content: "";
  position: absolute;
  left: 0; top: .62em;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
}
.tick-list strong { font-weight: 500; color: var(--ink); }

.math {
  font-family: var(--font-mono);
  font-size: var(--fs-small);
  line-height: 1.9;
  color: var(--ink);
  background: var(--bg-sunken);
  border-radius: var(--radius);
  padding: 18px 22px;
  overflow-x: auto;
  max-width: 62ch;
}
.math .c { color: var(--muted); }

/* Le bloc de formules est une figure : c'est le <figcaption> qui le nomme pour
   les technologies d'assistance. Un aria-label pose sur le <div> generique
   n'etait pas expose de facon fiable, le div n'ayant aucun role implicite. */
.math-fig { margin: 0; max-width: 62ch; }
.math-fig figcaption { margin-top: 10px; color: var(--muted); }

.split { display: grid; gap: clamp(20px, 2.5vw, 32px); }

.def-list { display: grid; gap: 18px; max-width: 62ch; }
.def-list div { display: grid; gap: 4px; }
.def-list dt { font-size: var(--fs-small); font-weight: 500; color: var(--ink); line-height: 1.5; }
.def-list dd { color: var(--muted); font-size: var(--fs-small); }

/* =============================================================================
   Reveal au scroll (tres retenu)
   ========================================================================== */

.reveal {
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: var(--reveal-delay, 0ms);
}
/* Le contenu est VISIBLE par defaut. main.js n'arme `.is-armed` que sur les
   elements situes sous la ligne de flottaison, donc invisibles a l'ecran au
   moment ou ils sont masques : aucun clignotement, et si le script ne demarre
   pas, la page reste simplement lisible. */
.reveal.is-armed { opacity: 0; transform: translateY(12px); }

/* =============================================================================
   Points de rupture (mobile first)
   ========================================================================== */

/* --- en-tete sur petit ecran ------------------------------------------------
   A 320 px, le nom suivi de deux boutons ne tient pas sur une ligne. Le
   sous-titre de marque disparait, les actions passent sous le nom et se
   partagent la largeur. Les deux actions restent presentes : aucune n'est
   masquee, contrairement a la solution precedente. */
@media (max-width: 639px) {
  .brand .mono { display: none; }

  /* Un libelle long se replie au lieu de deborder. Vaut pour TOUS les boutons,
     pas seulement ceux de l'en-tete : « Explore the code on GitHub », dans le
     corps de l'etude de cas, ne tient pas sur une ligne sous 280 px, et
     `white-space: nowrap` en faisait alors la cause du debordement. Lever la
     contrainte n'impose aucun retour a la ligne la ou le libelle tient. */
  .btn { white-space: normal; min-width: 0; }

  .site-header__inner { gap: 8px 12px; padding-block: 8px; }
  .site-header__inner > .language-switcher { margin-left: auto; }
  .header-actions { width: 100%; gap: 8px; }
  .header-actions .btn {
    flex: 1 1 0;
    justify-content: center;
    padding-inline: .9em;
    text-align: center;
  }
}

@media (max-width: 380px) {
  .header-actions .btn { font-size: .875rem; padding-inline: .6em; }
}

/* --- espacements ponctuels, en remplacement des attributs style="" ---------- */
.mt-8 { margin-top: 8px; }
.mt-24 { margin-top: 24px; }
.measure { max-width: 62ch; }

@media (min-width: 560px) {
  .project { grid-template-columns: 160px minmax(0, 1fr); gap: 24px; align-items: start; }
  .entry { grid-template-columns: 104px minmax(0, 1fr); gap: 8px 24px; align-items: baseline; }
  .meta-bar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
  .profile { grid-template-columns: var(--rail-w) minmax(0, 1fr); }
  .rail__photo { max-width: 100%; }

  .meta-bar { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .cs-section { grid-template-columns: minmax(0, 3fr) minmax(0, 9fr); gap: clamp(24px, 3vw, 44px); align-items: start; }
  .cs-section > h2 { grid-column: 1; }
  .cs-section > :not(h2) { grid-column: 2; }
  .split { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* La colonne d'identite ne devient collante que si la fenetre est assez haute
   pour l'afficher en entier : sinon son bas resterait inatteignable. */
@media (min-width: 900px) and (min-height: 900px) {
  .rail { position: sticky; top: 48px; }
}

/* =============================================================================
   Mouvement reduit
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .reveal { opacity: 1; transform: none; }
}

/* =============================================================================
   Impression
   ========================================================================== */

@media print {
  .site-header, .site-footer, .player__play, .skip-link { display: none; }
  .reveal { opacity: 1; transform: none; }
  body { background: #fff; }
  .profile { display: block; }
  .rail { position: static; margin-bottom: 24px; }
}

/* =============================================================================
   Couche verre ("liquid glass") - additive et reversible
   -----------------------------------------------------------------------------
   Le systeme de base ne bouge pas : memes tokens de couleur, memes hairlines,
   meme typographie. Cette section est posee APRES tout le reste et se contente
   de redonner une matiere a des surfaces qui existent deja (cartes, figures,
   bloc de formules, lecteur, vignettes, en-tete collant, boutons). La supprimer
   en bloc redonne exactement le site d'origine, sans toucher au HTML.

   Trois regles ont regle le dosage :

   1. Le verre ne se voit que s'il a quelque chose a filtrer. La page etant
      quasi unie, une lueur d'ambiance tres faible est posee DERRIERE le
      contenu (body::before) : c'est elle que les surfaces translucides
      laissent transparaitre, et elle seule qui donne au blanc du verre sa
      variation d'un bout a l'autre de la page. Le flou reel (`backdrop-filter`)
      est reserve aux deux endroits ou du contenu passe dessous, l'en-tete
      collant et la pastille de lecture ; ailleurs il couterait un plan de
      composition pour un resultat invisible (voir la note plus bas).

   2. Le contraste reste le juge. La lueur est plafonnee (6 % d'accent, 22 % de
      sable) pour que --muted pose directement sur la page ne descende pas sous
      4,5:1 : environ 4,7:1 au coeur des taches, contre 5,00:1 sur le fond nu.
      Les surfaces de verre, elles, sont teintees de BLANC : elles font donc
      remonter le contraste du texte qu'elles portent. Les tokens ne changent
      pas, le controle de contraste de `npm run e2e` mesure les memes valeurs.

   3. Aucune dependance ajoutee : pas de JavaScript, pas d'image, et aucun
      pseudo-element pose par-dessus le contenu. Les reflets sont des couches
      de `background-image` sous le texte, jamais un calque au-dessus.

   Replis prevus : sans `backdrop-filter`, en `prefers-reduced-transparency`,
   en `prefers-reduced-motion` et a l'impression, les surfaces redeviennent
   opaques et immobiles.
   ========================================================================== */

/* Les tokens du verre vivent avec la couche qu'ils servent, et non dans le
   :root du haut, pour que la section entiere parte d'un seul bloc. */
:root {
  --glass-tint:    color-mix(in srgb, #fff 58%, transparent);
  --glass-tint-hi: color-mix(in srgb, #fff 76%, transparent);
  --glass-line:    color-mix(in srgb, #fff 55%, var(--line));

  /* Reflet oblique : large en haut a gauche, eteint avant le milieu. Un bord de
     verre n'est jamais eclaire uniformement. */
  --glass-sheen: linear-gradient(158deg,
                   rgba(255, 255, 255, .60) 0%,
                   rgba(255, 255, 255, .16) 34%,
                   rgba(255, 255, 255, 0)   58%);

  /* Arete superieure eclairee et ombre portee tres etalee, tres pale : c'est
     elle qui detache la surface du fond, pas le trait de bordure. */
  --glass-inset:   inset 0 1px 0 rgba(255, 255, 255, .85);
  --glass-drop:    0 1px 1px color-mix(in srgb, var(--ink) 4%, transparent),
                   0 16px 32px -22px color-mix(in srgb, var(--ink) 32%, transparent);
  --glass-drop-hi: 0 1px 1px color-mix(in srgb, var(--ink) 5%, transparent),
                   0 22px 40px -22px color-mix(in srgb, var(--ink) 40%, transparent);
}

/* --- lueur d'ambiance -------------------------------------------------------
   `fixed` : la lueur ne defile pas, le verre garde la meme matiere du haut au
   bas de la page. `inset` negatif pour qu'aucun bord de degrade n'entre dans le
   cadre. `z-index: -1` la place derriere tout le contenu mais devant le fond de
   page, et `pointer-events: none` la rend inerte au clic. */
body::before {
  content: "";
  position: fixed;
  inset: -12vmax;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(46vmax 34vmax at 86% -8%,
      color-mix(in srgb, var(--accent) 6%, transparent), transparent 70%),
    radial-gradient(44vmax 36vmax at 2% 6%,
      color-mix(in srgb, #E9DFCB 22%, transparent), transparent 72%),
    radial-gradient(50vmax 40vmax at 68% 104%,
      color-mix(in srgb, var(--accent) 5%, transparent), transparent 70%);
}

/* --- surfaces pleines de verre ---------------------------------------------
   Celles dont le fond est reellement visible : la teinte blanche et le reflet
   y jouent. Pas de `backdrop-filter` ici, et c'est delibere : ce qui passe
   dessous est la lueur d'ambiance, un degrade deja lisse, que le flou laisse
   visuellement identique. On paierait un plan de composition par surface pour
   un resultat que personne ne verrait. Firefox, ou ce cout est le plus lourd,
   perdait assez de temps de rendu pour que `npm run e2e` prenne un bloc en
   retard de revelation. Le flou est donc reserve aux deux endroits ou du
   contenu passe VRAIMENT dessous : l'en-tete collant et la pastille de
   lecture. */
.card,
.fig,
.math,
.btn--ghost,
.header-actions--copy-below > .copied {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border-color: var(--glass-line);
  box-shadow: var(--glass-inset), var(--glass-drop);
}

/* Le bloc de formules n'avait pas de bordure : sans elle, l'arete eclairee
   flotte dans le vide. Un filet de verre la referme. */
.math { border: 1px solid var(--glass-line); }

/* --- surfaces occupees par une image ----------------------------------------
   Le contenu couvre tout le cadre : le flou n'aurait rien a filtrer. Seuls
   l'arete eclairee, le filet et l'ombre portee restent utiles. */
.player,
.project__thumb,
.rail__photo {
  border-color: var(--glass-line);
  box-shadow: var(--glass-inset), var(--glass-drop);
}
.project__thumb {
  transition: border-color .2s var(--ease), box-shadow .25s var(--ease),
              transform .25s var(--ease);
}
.project__thumb:hover {
  transform: translateY(-2px);
  box-shadow: var(--glass-inset), var(--glass-drop-hi);
}

/* --- en-tete collant --------------------------------------------------------
   Le seul endroit du site ou du contenu passe reellement SOUS une surface :
   c'est la que le verre a le plus de sens et le moins besoin d'etre appuye. */
.site-header {
  background-color: color-mix(in srgb, var(--bg) 72%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.7);
  backdrop-filter: blur(18px) saturate(1.7);
}
.site-header.is-stuck {
  border-bottom-color: var(--glass-line);
  box-shadow: 0 1px 0 rgba(255, 255, 255, .55),
              0 18px 30px -30px color-mix(in srgb, var(--ink) 55%, transparent);
}

/* --- pastille de lecture ----------------------------------------------------
   Vraie pastille de verre : elle flotte au-dessus de l'affiche, qu'elle laisse
   deviner. Le blanc reste majoritaire pour que le triangle en encre garde son
   contraste quelle que soit l'image dessous. */
.player__disc {
  background-color: color-mix(in srgb, #fff 64%, transparent);
  background-image: var(--glass-sheen);
  border-color: rgba(255, 255, 255, .72);
  box-shadow: var(--glass-inset),
              0 10px 24px -12px color-mix(in srgb, var(--ink) 55%, transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.5);
  backdrop-filter: blur(10px) saturate(1.5);
  transition: background-color .25s var(--ease), border-color .25s var(--ease),
              transform .25s var(--ease);
}
.player__play:hover .player__disc {
  background-color: color-mix(in srgb, #fff 80%, transparent);
  border-color: rgba(255, 255, 255, .92);
  transform: scale(1.04);
}

/* --- boutons ---------------------------------------------------------------- */
.btn {
  transition: background-color .2s var(--ease), border-color .2s var(--ease),
              color .2s var(--ease), box-shadow .25s var(--ease),
              transform .25s var(--ease);
}
.btn:hover { transform: translateY(-1px); }
.btn:active { transform: translateY(0); }

.btn--ghost:hover {
  background-color: var(--glass-tint-hi);
  border-color: color-mix(in srgb, var(--ink) 45%, var(--glass-line));
  box-shadow: var(--glass-inset), var(--glass-drop-hi);
}

/* Le bouton plein n'est pas translucide : il porte du texte clair sur encre et
   doit le rester. Il recoit seulement le meme eclairage d'arete, pour ne pas
   paraitre decoupe dans un autre materiau que le reste.
   `background-image` est repete sur :hover parce que la regle d'origine utilise
   la propriete raccourcie `background`, qui remettrait le reflet a `none`. */
.btn--primary {
  background-image: linear-gradient(158deg,
                      rgba(255, 255, 255, .20) 0%,
                      rgba(255, 255, 255, .05) 40%,
                      rgba(255, 255, 255, 0)   62%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .22),
              0 10px 22px -16px color-mix(in srgb, var(--ink) 70%, transparent);
}
.btn--primary:hover {
  background-image: linear-gradient(158deg,
                      rgba(255, 255, 255, .26) 0%,
                      rgba(255, 255, 255, .07) 40%,
                      rgba(255, 255, 255, 0)   62%);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28),
              0 14px 26px -16px color-mix(in srgb, var(--accent) 70%, transparent);
}

/* --- repli : moteur sans backdrop-filter -------------------------------------
   Les deux seules surfaces a en dependre sont aussi les deux qui laissent
   passer du contenu : sans flou, l'en-tete rendrait le texte illisible pendant
   le defilement et la pastille se confondrait avec l'affiche. On les
   reopacifie. Les autres surfaces ne demandent rien : leur teinte blanche ne
   couvre que la lueur d'ambiance. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .site-header { background-color: var(--bg); }
  .player__disc { background-color: var(--bg); }
}

/* --- repli : transparence reduite --------------------------------------------
   Reglage systeme explicite du visiteur : tout redevient opaque et la lueur
   disparait. Le site revient a sa version plate. */
@media (prefers-reduced-transparency: reduce) {
  body::before { display: none; }

  .card,
  .fig,
  .btn--ghost,
  .header-actions--copy-below > .copied,
  .site-header,
  .player__disc {
    background-color: var(--bg);
    background-image: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .math {
    background-color: var(--bg-sunken);
    background-image: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }

  .card, .fig, .math, .btn--ghost, .player, .project__thumb, .rail__photo,
  .header-actions--copy-below > .copied {
    border-color: var(--line);
    box-shadow: none;
  }
  .btn--primary { background-image: none; box-shadow: none; }
}

/* --- repli : mouvement reduit ------------------------------------------------
   La regle globale plus haut ramene deja les durees a zero. Ici on supprime le
   deplacement lui-meme, qui sinon sauterait d'un coup. */
@media (prefers-reduced-motion: reduce) {
  .btn:hover,
  .project__thumb:hover { transform: none; }
  .player__play:hover .player__disc { transform: none; }
}

/* --- repli : impression ------------------------------------------------------ */
@media print {
  body::before { display: none; }

  .card, .fig, .math, .player, .project__thumb, .rail__photo, .btn--ghost,
  .site-header, .header-actions--copy-below > .copied {
    background-image: none;
    box-shadow: none;
    border-color: var(--line);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .card, .fig, .btn--ghost { background-color: #fff; }
  .math { background-color: var(--bg-sunken); }
  .btn--primary { background-image: none; box-shadow: none; }
}
