/* ============ ALAÏS — styles globaux ============ */

/* Police des titres : Azonix, géométrique et propre. Elle ne porte aucun effet
   par elle-même : les titres d'affiche sont redessinés dans un canvas, contour
   fixe et remplissage en poussière (« TITRES EN POUSSIÈRE » plus bas, et
   static/js/particles-title.js). Le grésillement CSS qu'ils portaient avant —
   trois couches de masques animés au pied des lettres — a été retiré : deux
   traitements sur un même titre se seraient contrariés. */
@font-face {
  font-family: 'Azonix';
  src: url('/static/fonts/azonix.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Fond des pages catalogue / fiche produit / panier. Anciennement un crème
     verdâtre (#EFF1E3) ; désormais un brun très sombre, d'où l'inversion du
     texte via --on-ground. */
  --ground: #200c01;
  /* Les mêmes composantes, décomposées : une couleur CSS ne se rend pas
     translucide telle quelle, il faut ses trois nombres pour la passer à rgba().
     C'est ce qui permet au menu et à la modale d'être du marron des pages, et
     non d'un noir approchant qui virerait au gris sur les fonds clairs. */
  --ground-rvb: 32, 12, 1;
  --on-ground: #EFF1E3;
  --ink: #101010;
  --grey-dark: #6E6E6E;
  --grey-light: #A8A8A8;
  --panel: #161616;
  --red: #B00010;
  --lcd: #D9DDD6;
  --font-glitch: 'Azonix', 'Arial Narrow', sans-serif;
  --font-body: 'Jost', sans-serif;
  --font-lcd7: 'DSEG7-Classic', monospace;
  --font-lcd14: 'DSEG14-Classic', monospace;
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-body);
  font-weight: 300;
  color: #fff;
  background: var(--grey-dark);
  min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button { font-family: var(--font-body); }

body.theme-cream {
  background: var(--ground);
  color: var(--on-ground);
}

/* ============ HEADER ============ */
.site-header {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 60;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding: 22px 28px 0;
  pointer-events: none;
}
.site-header > * { pointer-events: auto; }

.burger {
  width: 46px; padding: 14px 0;
  cursor: pointer;
  background: none; border: none;
  /* les barres sont peintes en currentColor : sans color:inherit, un <button>
     reste au noir par défaut du navigateur et disparaît sur fond sombre */
  color: inherit;
}
.burger span {
  display: block; height: 3px; background: currentColor;
  margin-bottom: 7px; border-radius: 2px;
}
.burger span:last-child { margin-bottom: 0; }

.logo {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  line-height: 0;
  padding-top: 8px;
  cursor: pointer;
  user-select: none;
}
/* Le logo est appliqué en masque : il prend « currentColor », donc blanc sur
   les pages sombres et encre sur les pages crème, avec un seul fichier. */
.logo-mark {
  display: block;
  width: clamp(140px, 15vw, 200px);
  aspect-ratio: 493 / 95;
  background: currentColor;
  -webkit-mask: url("/static/assets/logo-alais.png") center / contain no-repeat;
  mask: url("/static/assets/logo-alais.png") center / contain no-repeat;
  transition: width 0.35s ease;
}
/* Dès qu'on descend, le logo se replie. Il n'est ni remplacé ni caché : c'est
   le même lien, simplement plus petit, et il reste cliquable. */
.site-header.reduit .logo-mark { width: clamp(88px, 9vw, 124px); }
/* …et le contenu remonte d'autant. C'est ce qui donne l'impression que le titre
   de la page monte vers l'en-tête à mesure qu'elle s'efface. L'accueil et la
   page du drop n'ont pas ce retrait : ils occupent la hauteur d'écran entière et
   n'ont rien à remonter. */
.page-cream { transition: padding-top 0.35s ease; }
body.defile .page-cream { padding-top: 74px; }
.product-strip { transition: margin-top 0.35s ease; }
body.defile .product-strip { margin-top: 52px; }

/* ============ LECTEUR CD ============ */
.cd-player {
  position: relative;
  width: 110px;
  display: block;
  cursor: pointer;
  transition: width 0.45s ease, transform 0.45s ease;
}
.cd-player img.cd-base { width: 100%; display: block; }
/* Calé sur cd-player.png (900 × 977) : le rond en relief du lecteur est centré
   en (593, 691) et mesure 240 × 198 de demi-axes — c'est une ellipse, la photo
   étant prise en léger plongé. Le scaleY(0.83) applique ce même écrasement au
   disque : il tourne donc en rond tout en épousant le relief. */
.cd-player .cd-disc {
  position: absolute;
  left: 68%; top: 67%;
  width: 44%; aspect-ratio: 1;
  transform: translate(-50%, -50%) scaleY(0.83);
  border-radius: 50%;
  background:
    radial-gradient(circle at 50% 50%, rgba(207,210,206,0.45) 0 5%, rgba(207,210,206,0) 11%),
    conic-gradient(from 0deg, rgba(255,255,255,0) 0deg, rgba(255,255,255,0.5) 40deg, rgba(255,255,255,0) 90deg, rgba(190,210,230,0.35) 160deg, rgba(255,255,255,0) 220deg, rgba(230,200,255,0.3) 300deg, rgba(255,255,255,0) 360deg);
  /* Fondu du pourtour : sans ce masque, le border-radius trancherait net dans le
     dégradé et on verrait le contour du disque se détacher sur l'argenté. */
  -webkit-mask: radial-gradient(closest-side, #000 0 38%, rgba(0,0,0,0.6) 68%, rgba(0,0,0,0) 99%);
  mask: radial-gradient(closest-side, #000 0 38%, rgba(0,0,0,0.6) 68%, rgba(0,0,0,0) 99%);
  opacity: 0;
  pointer-events: none;
}
.cd-player.spinning .cd-disc {
  opacity: 0.85;
  animation: cd-spin 1.6s linear infinite;
}
/* Diode témoin : pastille rouge posée sur le bandeau noir du bloc de commandes,
   juste à gauche de l'écran LCD. */
.cd-player .cd-led {
  position: absolute;
  left: 58.7%; top: 60.3%;
  width: 2.5%; aspect-ratio: 1;
  transform: translate(-50%, -50%);
  border-radius: 50%;
  background: #ff2f2f;
  box-shadow: 0 0 6px 2px rgba(255, 55, 55, 0.85);
  opacity: 0;
  pointer-events: none;
}
.cd-player.spinning .cd-led { animation: cd-led 0.9s steps(2, end) infinite; }
.cd-player.opening { animation: cd-open 0.9s ease forwards; }

@keyframes cd-spin {
  from { transform: translate(-50%, -50%) scaleY(0.83) rotate(0deg); }
  to   { transform: translate(-50%, -50%) scaleY(0.83) rotate(360deg); }
}
@keyframes cd-led { 50% { opacity: 1; } }
@keyframes cd-open {
  0%   { transform: none; }
  30%  { transform: scale(1.08) rotate(-3deg); }
  60%  { transform: scale(1.16) rotate(2deg); }
  100% { transform: scale(1.22) rotate(0deg); }
}

/* ============ MENU LATÉRAL ============ */
.menu-overlay {
  position: fixed; inset: 0;
  z-index: 90;
  background: rgba(0,0,0,0.2); /* allégé : le fond doit rester visible */
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s ease;
}
.menu-overlay.open { opacity: 1; pointer-events: auto; }
.menu-panel {
  position: absolute; top: 0; bottom: 0; left: 0;
  width: min(420px, 84vw);
  /* Translucide pour laisser voir les vidéos tourner derrière. Le flou garde le
     texte lisible quel que soit le plan qui défile ; sans lui, un passage clair
     de la vidéo effacerait les entrées du menu.
     Marron des pages plutôt que gris sombre : c'est la couleur du site, et elle
     tient la même place ici que sur les fonds de page. */
  background: rgba(var(--ground-rvb), 0.55);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
          backdrop-filter: blur(14px) saturate(140%);
  color: #fff;
  padding: 44px 48px;
  transform: translateX(-100%);
  transition: transform 0.3s ease;
}
.menu-overlay.open .menu-panel { transform: none; }
.menu-title {
  font-family: var(--font-glitch);
  font-size: 52px;
  margin: 0 0 48px;
}
.menu-panel nav a {
  display: block;
  font-size: 17px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 15px 0;
  opacity: 0.92;
}
.menu-panel nav a:hover { opacity: 1; letter-spacing: 0.12em; }

/* ============ MODALE INSCRIPTION ============ */
.signup-overlay {
  position: fixed; inset: 0;
  z-index: 100;
  display: none;
  align-items: center; justify-content: center;
  background: rgba(0,0,0,0.35);
}
.signup-overlay.open { display: flex; }
.signup-box {
  position: relative;
  width: min(760px, 92vw);
  /* Marron des pages, translucide — même parti que le panneau du menu. */
  background: rgba(var(--ground-rvb), 0.93);
  color: #fff;
  padding: 58px 64px 64px;
  text-align: center;
  animation: fade-up 0.3s ease;
}
.signup-close {
  position: absolute; top: 22px; right: 24px;
  width: 34px; height: 34px;
  background: none; border: none; cursor: pointer;
}
.signup-close::before, .signup-close::after {
  content: ""; position: absolute; inset: 50% auto auto 50%;
  width: 30px; height: 1.5px; background: #fff;
}
.signup-close::before { transform: translate(-50%, -50%) rotate(45deg); }
.signup-close::after  { transform: translate(-50%, -50%) rotate(-45deg); }
.signup-box h2 {
  font-family: var(--font-glitch);
  font-weight: 400;
  font-size: clamp(24px, 3.4vw, 34px);
  /* Azonix est déjà très ouverte ; l'interlettrage des titres courants la
     délierait trop sur une ligne aussi longue. */
  letter-spacing: 0.02em;
  margin: 0 0 18px;
}
.signup-box p { font-size: 16px; line-height: 1.55; margin: 0 auto 34px; max-width: 560px; }
.signup-form { display: flex; max-width: 620px; margin: 0 auto; }
.signup-form input {
  flex: 1;
  background: #9d9d9d;
  border: none;
  color: #1c1c1c;
  font-family: var(--font-body);
  font-size: 15px;
  letter-spacing: 0.06em;
  padding: 18px 20px;
  text-transform: uppercase;
}
.signup-form input::placeholder { color: #3a3a3a; }
.signup-form button {
  background: #e8e8e8;
  color: #111;
  border: none;
  font-family: var(--font-glitch);
  font-size: 15px;
  letter-spacing: 0.04em;
  padding: 18px 32px;
  cursor: pointer;
}
.signup-form button:hover { background: #fff; }
.signup-done { font-size: 16px; letter-spacing: 0.04em; padding: 12px 0; }

/* ============ ACCUEIL ============ */
/* Plus de hauteur fixe : l'accueil occupe ce que le corps en colonne lui laisse,
   c'est-à-dire la fenêtre moins le pied de page. Avec `height: 100vh` la page
   dépassait d'exactement la hauteur du pied, et l'accueil — qui ne doit pas
   défiler — gagnait une barre de défilement de quarante pixels. */
.home-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
}
.home-half {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  overflow: hidden;
}
.home-half.vestiaire { background: var(--grey-dark); }
.home-half.table { background: var(--grey-light); }

/* Fond animé. Il passe sous le contenu (z-index) et reçoit un voile sombre :
   sans lui, le titre blanc disparaîtrait sur les plans clairs de la vidéo. */
.home-bg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  pointer-events: none;
}
/* Le contenu passe au-dessus du voile ; encore faut-il qu'il soit positionné,
   sans quoi le rang ne s'applique pas. */
.home-half.has-video > :not(.home-bg) { position: relative; z-index: 2; }
/* …sauf l'invitation, déjà posée en absolu au bas de la moitié. La règle
   ci-dessus la repassait en relatif : elle retombait alors dans le flux, juste
   sous le compteur, et changeait donc de place à la seule apparition d'une
   vidéo. Elle garde ici sa position, et gagne le rang qui lui manquait. */
.home-half.has-video > .home-invite { position: absolute; }
.home-half.has-video::after {
  content: "";
  position: absolute; inset: 0; z-index: 1;
  background: rgba(0, 0, 0, 0.34);
  pointer-events: none;
}
.home-title {
  font-family: var(--font-glitch);
  /* Les deux titres tiennent sur une seule ligne. Le mot le plus long,
     « ART DE LA TABLE », mesure 10,9 fois la taille de police (interlettrage
     compris) ; sa moitié d'écran fait 50vw moins 40 px de marge, d'où la limite
     de 4vw. La taille étant en vw comme le conteneur, le rapport reste constant
     à toutes les largeurs : le titre ne repasse jamais à la ligne. Azonix étant
     deux fois plus étroite que la fonte précédente, les titres ont pu doubler. */
  font-size: min(4vw, 80px);
  white-space: nowrap;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
  filter: blur(0.3px);
  text-align: center;
  padding: 0 20px;
}
/* ============ TITRES EN POUSSIÈRE ============ */
/* Titre redessiné dans un canvas — contour fixe et remplissage de grains
   (static/js/particles-title.js). Le titre n'est pas retiré du document : il
   garde sa place dans le flux — c'est lui qui dit au canvas où et à quelle
   taille dessiner — et reste lisible par les lecteurs d'écran et les moteurs de
   recherche. Seule son encre disparaît, sinon les deux rendus se
   superposeraient à un demi-pixel près.
   La classe est posée par le script, et seulement une fois le canvas prêt : un
   titre qui n'aurait pas pu être rendu reste ainsi affiché normalement. */
.titre-poussiere { visibility: hidden; }

/* Un canvas par titre, accroché au <body> et fixé à la fenêtre : la poussière
   n'est bornée par rien et peut traverser la page de part en part. Elle ne prend
   aucun clic — tout ce qu'elle survole reste cliquable et sélectionnable.
   Ni taille ni rang ici : le script les pose lui-même. La taille, parce que les
   attributs width/height d'un canvas valent aussi taille CSS et rendraient toute
   règle écrite ici inopérante ; le rang, parce qu'il se calcule d'après le titre
   auquel le canvas se rapporte (cf. planDeTrace). */
.particle-canvas {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
}

.home-caption {
  font-size: 17px;
  margin-top: 46px;
}
/* L'invitation est la SEULE action possible tant qu'aucun drop n'est ouvert :
   c'est par elle qu'on reçoit le mot de passe, donc par elle que passe toute
   vente future. Elle était écrite plus petit que la légende au-dessus d'elle.
   Elle est désormais dimensionnée comme un bouton — et soulignée en permanence,
   pour qu'on voie qu'on peut cliquer sans avoir à la survoler. */
.home-invite {
  position: absolute;
  /* À mi-chemin entre le bas du compteur et le bas de l'écran, et non plus
     collée au bord. En pourcentage et non en pixels : la moitié fait toute la
     hauteur de la fenêtre, le rapport tient donc sur un portable comme sur un
     grand écran. Mesuré : le compteur finit vers 61 % de la hauteur, le milieu
     de ce qui reste tombe donc à 18 % du bas. */
  bottom: 18%;
  font-size: clamp(16px, 1.3vw, 21px);
  letter-spacing: 0.04em;
  text-decoration: underline;
  text-underline-offset: 7px;
  text-decoration-thickness: 1px;
  text-decoration-color: rgba(255, 255, 255, 0.45);
  transition: text-decoration-color 0.2s ease;
}
.home-invite:hover { text-decoration-color: #fff; }

/* ============ COMPTE À REBOURS LCD ============ */
.lcd {
  font-family: var(--font-lcd7);
  color: #fff;
  white-space: nowrap;
}
.lcd.small { font-size: clamp(17px, 1.6vw, 24px); margin-top: 16px; }
.lcd.big { font-size: clamp(34px, 4.4vw, 64px); margin-top: 22px; }
.theme-cream .lcd { color: var(--on-ground); }

/* ============ PAGE DROP (gate) ============ */
/* Fond animé de la page du drop : la MÊME vidéo que la moitié correspondante de
   l'accueil. Cliquer sur « Vestiaire » depuis l'accueil ne coupe donc plus le
   plan — on passe d'une demi-page à la page entière sans rupture d'image.

   Posé en `fixed` et non `absolute` : la page du drop fait la hauteur de
   l'écran, mais le clavier virtuel d'un téléphone la réduit au moment de saisir
   le mot de passe, et un fond absolu se décalerait alors. Rang négatif : la
   vidéo se glisse derrière le contenu tout en restant au-dessus du fond uni de
   la page, qui lui sert de repli quand aucune vidéo n'est déposée. */
.gate-bg,
.gate-voile {
  position: fixed;
  inset: 0;
  pointer-events: none;
}
.gate-bg {
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
}
/* Même voile que l'accueil, à la même densité : sans lui, le titre blanc et le
   compteur disparaîtraient sur les plans clairs de la vidéo. */
.gate-voile {
  z-index: -1;
  background: rgba(0, 0, 0, 0.34);
}

.gate {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 0 24px;
}
/* TITRE DE PAGE — réglage unique pour tout le site (drop, catalogue, panier,
   réseaux, contact, FAQ). Les trois blocs qui suivaient chacun leur propre
   clamp donnaient trois tailles différentes d'une page à l'autre ; ils partagent
   désormais la même déclaration, seules les marges varient. */
.gate-title,
.cat-title,
.cart-title,
.page-title {
  font-family: var(--font-glitch);
  font-size: clamp(38px, 5vw, 72px);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  text-align: center;
  margin: 0;
}
.gate-title { text-align: center; }
.gate-sub { font-size: 18px; margin: 26px 0 4px; }
.gate-invite { position: absolute; bottom: 44px; left: 0; right: 0; text-align: center; font-size: 17px; cursor: pointer; }
.gate-invite:hover { text-decoration: underline; }

.pw-display {
  font-family: var(--font-lcd14);
  font-size: clamp(30px, 4.6vw, 58px);
  color: #fff;
  margin-top: 26px;
  cursor: text;
  min-height: 1.3em;
  letter-spacing: 0.06em;
  user-select: none;
}
.pw-display.placeholder { opacity: 0.95; }
.pw-display .caret {
  display: inline-block;
  width: 0.08em; height: 1em;
  background: #fff;
  vertical-align: -0.12em;
  animation: caret-blink 1s steps(2, end) infinite;
}
@keyframes caret-blink { 50% { opacity: 0; } }
.pw-hidden-input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

/* ============ CATALOGUE ============ */
.page-cream {
  padding: 120px 60px 80px;
  max-width: 1720px;
  margin: 0 auto;
}
.cat-title { margin: 40px 0 70px; }
.grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 44px 34px;
}
@media (max-width: 1100px) { .grid { grid-template-columns: repeat(2, 1fr); } }
/* Une colonne seulement sur les écrans vraiment étroits. La règle valait
   auparavant sous 560 px, ce qui donnait sur un téléphone courant une vignette
   de 339 px pour 424 px de haut : un article par écran, et un drop de vingt
   pièces devenait vingt écrans à faire défiler. À deux colonnes, la vignette
   fait 163 px — assez pour lire la coupe et la couleur, et l'on parcourt. */
@media (max-width: 359px)  { .grid { grid-template-columns: 1fr; } }

.card { cursor: pointer; }
.card .card-visual {
  aspect-ratio: 4 / 5;
  background: #fff;
  position: relative;
  overflow: hidden;
}
.card .card-visual img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: opacity 0.18s ease;
}
.card .card-visual img.alt { opacity: 0; }
.card:hover .card-visual img.alt { opacity: 1; }
.card:hover .card-visual img.main { opacity: 0; }
.card-caption {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
  font-size: 16px;
  letter-spacing: 0.03em;
  text-transform: uppercase;
}
.card.sold .card-name { text-decoration: line-through; }
.card .sold-tag { font-size: 15px; text-transform: none; }
/* Prix en typo courante : l'afficheur à segments n'a ni « € » ni virgule, et
   un montant doit se lire d'un coup d'œil. L'afficheur reste au rebours et au
   mot de passe, où il fait sens. */
.card .card-price { font-family: var(--font-body); font-size: 18px; letter-spacing: 0.02em; }

.video-tile { position: relative; aspect-ratio: 4 / 5; overflow: hidden; }
.video-tile.dark { background: #4A4A4A; }
.video-tile.light { background: #B5B5B5; }
.video-tile video { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ============ FICHE PRODUIT ============ */
/* Bande de photos qui défile.

   La fiche n'était plus tenable à trois photos figées en grille : une pièce peut
   en demander cinq ou huit. La bande défile donc en boucle, lentement, et
   accepte n'importe quel nombre de vues.

   Le ruban contient la suite des photos DEUX FOIS, et se translate d'exactement
   la moitié de sa largeur : à l'instant où l'animation reboucle, le second
   exemplaire se trouve précisément là où le premier était. La jointure est donc
   invisible, sans qu'aucun calcul de position n'ait à tourner à chaque image. */
.product-strip {
  overflow: hidden;
  background: #fff;
  margin-top: 90px;
}
.product-ruban {
  display: flex;
  /* Gouttière en MARGE et non en `gap` : avec `gap`, une bande de 2n vignettes
     compte 2n−1 gouttières, et la translation de 50 % tombe une demi-gouttière
     trop court — la boucle sautait donc d'un pixel à chaque tour. En marge,
     chaque vignette occupe exactement la même largeur, et la moitié du ruban
     vaut très précisément un cycle. */
  width: max-content;
  animation: defile-photos linear infinite;
  /* La durée est posée par le script : elle suit le nombre de photos, pour que
     la vitesse reste la même qu'il y en ait trois ou dix. */
}
/* Cliquer suspend le ruban là où il est ; il repart du même point. */
.product-ruban.suspendu { animation-play-state: paused; }
@keyframes defile-photos {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}
.product-strip .shot {
  flex: 0 0 auto;
  width: min(30vw, 400px);
  aspect-ratio: 3 / 4;
  background: #fff url() center/cover no-repeat;
  border-left: 1px solid #e2e2da;
  margin-right: 2px;
  cursor: pointer;
}

/* Sans mouvement demandé, pas de défilement automatique : la bande redevient
   une simple rangée que l'on fait glisser soi-même. */
@media (prefers-reduced-motion: reduce) {
  .product-strip { overflow-x: auto; }
  .product-ruban { animation: none; }
}
.product-body { padding: 46px 24px 0; max-width: 1500px; margin: 0 auto; }
.product-name { font-size: 26px; letter-spacing: 0.04em; text-transform: uppercase; margin: 0 0 26px; }
.product-desc { font-size: 16.5px; line-height: 1.65; max-width: 1180px; white-space: pre-line; }
/* Fiche technique : taille / matières / état, saisis dans des champs dédiés. */
.spec { display: flex; flex-wrap: wrap; gap: 14px 46px; margin-top: 30px; font-size: 16px; }
.spec div { display: flex; flex-direction: column; gap: 5px; }
.spec span {
  font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.55;
}

.product-price { font-family: var(--font-body); font-size: 30px; margin: 44px 0 10px; }
.product-price s { opacity: 0.4; margin-right: 12px; }
.deal-tag {
  font-family: var(--font-body); font-size: 14px; letter-spacing: 0.1em;
  color: var(--red); vertical-align: middle; margin-left: 12px;
}
/* Pastille de remise sur la vignette du catalogue. */
.card-visual .deal {
  position: absolute; top: 10px; left: 10px; z-index: 2;
  background: var(--red); color: #fff;
  font-size: 12px; letter-spacing: 0.08em; padding: 4px 9px;
}
.card-price s { opacity: 0.45; margin-right: 6px; }
.sold-out {
  font-family: var(--font-glitch);
  color: var(--red);
  font-size: 44px;
  margin: 30px 0 10px;
}
.measure { margin-top: 40px; font-size: 17px; }
.measure h3 { font-weight: 400; font-size: 19px; letter-spacing: 0.05em; margin: 0 0 18px; }
.measure table { border-collapse: collapse; }
.measure td { padding: 6px 60px 6px 0; }
.btn-line {
  display: inline-block;
  margin: 40px 0 60px;
  border: 1px solid currentColor;
  /* un <button> n'hérite pas de la couleur du texte : sans ça il resterait au
     noir par défaut du navigateur, invisible sur le fond sombre */
  color: inherit;
  padding: 15px 44px;
  font-family: var(--font-glitch);
  font-size: 14px;
  /* Interlettrage resserré : Azonix porte déjà ses lettres larges, et l'écart
     d'avant, réglé pour une fonte de labeur, disloquait le mot. */
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  background: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.btn-line:hover { background: var(--on-ground); color: var(--ground); }

/* ============ PANIER ============ */
.cart-player {
  position: fixed;
  top: 8px; right: 10px;
  width: min(30vw, 430px);
  z-index: 55;
  transition: width 0.5s ease;
}
.cart-player.small { width: 110px; }
.cart-title { margin: 60px 0 70px; }
.cart-item .remove {
  background: none; border: none;
  font-size: 15px; cursor: pointer;
  padding: 0; margin-top: 6px;
  letter-spacing: 0.06em;
  color: var(--red);
  opacity: 0;
  transition: opacity 0.15s;
}
.cart-item:hover .remove { opacity: 1; }
/* Choix du pays et détail du port, entre la grille et le bouton Payer. Posé là
   parce que le montant du port doit se lire AVANT de cliquer : une commande dont
   le total change à l'écran suivant est une commande abandonnée. */
.cart-livraison {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 10px 14px;
  margin: 48px 0 0;
  font-size: 16px;
}
.cart-livraison label { opacity: 0.6; }
.cart-livraison select {
  font-family: var(--font-body);
  font-size: 16px;
  color: inherit;
  background: transparent;
  border: none;
  border-bottom: 1px solid currentColor;
  padding: 3px 22px 3px 2px;
  cursor: pointer;
  /* La flèche native est dessinée à la main : celle du système arrive dans sa
     propre couleur et jure avec le reste sur fond crème comme sur fond sombre. */
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 8px center, right 3px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
.cart-livraison select option { color: #1a1a1a; background: #f2ede9; }
.cart-port { flex-basis: 100%; text-align: center; opacity: 0.72; font-size: 15px; }
.port-poids { opacity: 0.6; font-size: 13.5px; margin-left: 8px; }
.port-indispo { color: var(--red); opacity: 1; }
.pay-btn:disabled { opacity: 0.35; cursor: not-allowed; }

.cart-footer {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 60px;
  margin: 90px 0 40px;
  flex-wrap: wrap;
}
.total-lcd { font-family: var(--font-body); font-weight: 300; font-size: clamp(26px, 3.2vw, 40px); letter-spacing: 0.04em; }
.pay-btn {
  position: relative;
  border: 1.5px solid var(--on-ground);
  background: none;
  color: var(--on-ground);
  font-family: var(--font-glitch);
  font-size: clamp(30px, 3.6vw, 50px);
  line-height: 1;
  padding: 18px 70px 22px;
  cursor: pointer;
}
/* Coche du bouton « Dans le panier ».

   Le caractère ✓ ne figure pas dans Azonix : le navigateur allait le chercher
   dans une police de secours, et l'on obtenait un signe étranger au reste —
   quelque part entre le radical et la coche manuscrite.

   Elle est donc DESSINÉE : deux segments droits, un quart de tour, aux mêmes
   proportions que les lettres qu'elle suit. Rien à charger, et elle suit le
   corps du texte comme sa couleur, puisqu'elle est faite de bordures en
   currentColor. */
.coche {
  display: inline-block;
  width: 0.42em;
  height: 0.72em;
  margin-left: 0.55em;
  /* Le trait reprend l'épaisseur du trait des lettres d'Azonix. */
  border-right: 0.11em solid currentColor;
  border-bottom: 0.11em solid currentColor;
  transform: rotate(42deg);
  /* Le quart de tour fait tomber la coche sous la ligne : on la relève. */
  transform-origin: 60% 60%;
  vertical-align: 0.12em;
}

/* Le mot était autrefois « écrit à la plume » au clic, de gauche à droite. Le
   bouton se défait maintenant en poussière — les deux se seraient contrariés, et
   la poussière est la matière du site. Le span demeure : il porte le libellé. */
.pay-btn .pay-ink { display: inline-block; }
.cart-empty { text-align: center; font-size: 18px; margin: 80px 0; }
.paid-box { text-align: center; margin: 60px 0; }
.paid-box .merci { font-family: var(--font-glitch); font-size: 62px; margin: 20px 0; }

/* ============ DIVERS ============ */
@keyframes fade-up {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}
.fade-up { animation: fade-up 0.35s ease; }
.hidden { display: none !important; }

/* ============ ADMIN ============ */
/* Même brun que le site public : l'admin n'est pas un outil à part, on doit y
   juger les visuels dans les conditions réelles d'affichage. */
body.theme-admin { background: var(--ground); color: #ece7e2; }
.admin-wrap { max-width: 1400px; margin: 0 auto; padding: 40px 30px 80px; }

.admin-head { display: flex; align-items: center; gap: 22px; margin: 0 0 34px; }
.admin-logo {
  display: block;
  width: 170px;
  aspect-ratio: 493 / 95;
  background: currentColor;
  -webkit-mask: url("/static/assets/logo-alais.png") center / contain no-repeat;
          mask: url("/static/assets/logo-alais.png") center / contain no-repeat;
}
/* L'admin reprend la typographie du site : Azonix sur ce qui structure — titre,
   onglets, intitulés de bloc, boutons —, Jost sur tout le reste. La séparation
   n'est pas décorative : Azonix ne possède pas de bas-de-casse (un « a » y est
   dessiné comme un « A »), si bien qu'elle change en capitales tout ce qu'elle
   touche. Parfaite pour des libellés courts, elle rendrait illisibles les noms
   d'articles, les adresses e-mail et les messages reçus. */
.admin-head span {
  font-family: var(--font-glitch);
  font-size: 13px; letter-spacing: 0.1em; text-transform: uppercase; opacity: 0.5;
}

.admin-tabs { display: flex; gap: 10px; margin-bottom: 30px; flex-wrap: wrap; }
.admin-tabs button {
  background: none; border: 1px solid #4a3227; color: #c4b8b0;
  padding: 9px 18px; cursor: pointer;
  font-family: var(--font-glitch); letter-spacing: 0.04em;
  text-transform: uppercase; font-size: 11.5px;
}
.admin-tabs button:hover { border-color: #7a5240; color: #fff; }
.admin-tabs button.active { border-color: #ece7e2; color: #fff; }

.admin-card {
  background: #2b1408; border: 1px solid #43210f; padding: 24px; margin-bottom: 22px;
}
.admin-card h3 {
  margin: 0 0 16px; font-weight: 400;
  font-family: var(--font-glitch); letter-spacing: 0.04em; font-size: 17px;
}
/* La glose qui suit l'intitulé revient à la fonte de labeur : c'est une phrase,
   pas un titre, et Azonix la passerait tout en capitales. */
.admin-card h3 .hint {
  font-family: var(--font-body);
  font-size: 12px; letter-spacing: 0; text-transform: none; opacity: 0.55; margin-left: 10px;
}

.admin-form { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }
.admin-form label { display: flex; flex-direction: column; gap: 6px; font-size: 11.5px; letter-spacing: 0.06em; text-transform: uppercase; color: #b09e93; }
.admin-form input, .admin-form select, .admin-form textarea {
  background: #1a0b03; border: 1px solid #4a3227; color: #f2ede9;
  padding: 10px 12px; font-family: var(--font-body); font-size: 14px;
}
.admin-form textarea { min-height: 74px; resize: vertical; }
.admin-form .full { grid-column: 1 / -1; }
.admin-form .row { grid-column: 1 / -1; display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

.admin-btn {
  background: #ece7e2; color: #200c01; border: none;
  padding: 11px 24px; cursor: pointer;
  font-family: var(--font-glitch); letter-spacing: 0.04em;
  text-transform: uppercase; font-size: 11.5px;
}
.admin-btn:hover { background: #fff; }
.admin-btn.ghost { background: none; color: #c4b8b0; border: 1px solid #4a3227; }
.admin-btn.ghost:hover { color: #fff; border-color: #7a5240; }
.admin-btn.danger { background: none; color: #ff8a7a; border: 1px solid #7a2323; }

.admin-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.admin-table th, .admin-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid #43210f; vertical-align: top; }
.admin-table th { color: #b09e93; font-weight: 400; text-transform: uppercase; font-size: 11px; letter-spacing: 0.08em; }
.admin-thumb { width: 52px; height: 64px; object-fit: cover; background: #fff; }

/* ---- Grille de rangement : même gabarit que le catalogue public ---- */
/* Reprend les proportions du site (4/5, 4 colonnes) pour que l'ordre choisi ici
   corresponde à ce que verra la cliente, sans avoir à se projeter depuis une
   liste. */
.admin-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 26px 18px;
}
@media (max-width: 1100px) { .admin-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 780px)  { .admin-grid { grid-template-columns: repeat(2, 1fr); } }

.gcase { position: relative; cursor: grab; }
.gcase.dragging { opacity: 0.35; }
.gcase.over { outline: 2px dashed #ff8a7a; outline-offset: 4px; }
.gcase-visual {
  aspect-ratio: 4 / 5; background: #fff; position: relative; overflow: hidden;
}
.gcase-visual img, .gcase-visual video { width: 100%; height: 100%; object-fit: cover; display: block; }
.gcase.video .gcase-visual { background: #4A4A4A; }
.gcase-caption {
  display: flex; justify-content: space-between; align-items: baseline; gap: 8px;
  margin-top: 10px; font-size: 12.5px; letter-spacing: 0.03em; text-transform: uppercase;
}
.gcase-rank {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  background: rgba(32,12,1,0.85); color: #fff;
  font-size: 11px; padding: 3px 8px; letter-spacing: 0.06em;
}
.gcase-tag {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  background: rgba(32,12,1,0.85); color: #ffb9ae;
  font-size: 10px; padding: 3px 8px; letter-spacing: 0.08em; text-transform: uppercase;
}
.gcase-actions { display: flex; gap: 6px; margin-top: 8px; }
.gcase-actions button { font-size: 10.5px; padding: 6px 10px; }
.gcase.sold .gcase-visual { opacity: 0.45; }

/* ---- Fréquentation ---- */
.stat-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px; }
.stat {
  background: #1a0b03; border: 1px solid #43210f; padding: 18px 20px;
}
.stat .v { font-family: var(--font-lcd7); font-size: 30px; color: #fff; }
/* DSEG7 est une fonte a sept segments : elle ne dessine que des chiffres. Les
   lettres de « min » et « s » y sortaient en segments incoherents — d'ou le
   « 20M in 145 » a la place de « 20 min 14 s ». Les unites reviennent donc a la
   fonte de labeur, les chiffres seuls restent en afficheur. */
.stat .v .unite {
  font-family: var(--font-body);
  font-size: 13px;
  letter-spacing: 0.06em;
  opacity: 0.6;
  margin: 0 8px 0 4px;
}
.stat .k { font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #b09e93; margin-top: 8px; }
.bars { display: flex; align-items: flex-end; gap: 3px; height: 130px; margin-top: 8px; }
/* max-width : sans elle, une seule journée de données occuperait toute la
   largeur et se lirait comme un aplat plutôt que comme une barre. */
.bars div { flex: 1 1 0; max-width: 46px; background: #a8543a; min-height: 2px; }
.bars div:hover { background: #ff8a7a; }


/* ============ PAGES DE CONTENU (réseaux, contact, FAQ) ============ */
/* Toutes reprennent le fond, la police et les tailles du reste du site : le
   corps de texte est calé sur .product-desc (16,5 px / 1,65) et les titres sur
   .page-title, pour qu'aucune page ne détonne. */
.page-title { margin: 40px 0 60px; }
.page-lead {
  font-size: 18px;
  line-height: 1.6;
  max-width: 720px;
  margin: 0 auto 64px;
  text-align: center;
  opacity: 0.85;
}
.page-section { max-width: 820px; margin: 0 auto 62px; }
.page-section h2 {
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.55;
  margin: 0 0 24px;
}
.page-section p { font-size: 16.5px; line-height: 1.65; margin: 0 0 16px; }

/* Grille tarifaire des pages légales : deux colonnes, poids à gauche et prix à
   droite, alignés sur leur point décimal par la même largeur fixe. Un tableau
   aurait mieux dit la chose au lecteur d'écran, mais il faut alors gérer son
   débordement sur téléphone ; ici deux colonnes qui se replient suffisent. */
.tarifs {
  font-size: 16.5px;
  line-height: 1.65;
  margin: 0 0 16px;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 24px;
  max-width: 460px;
}
.tarifs dt { opacity: 0.78; }
/* Pas de `nowrap` ici : une cellule qui refuse de se replier impose sa largeur à
   toute la colonne, et un texte un peu long — un repère « à compléter » — la
   pousse au-delà de la page en écrasant celle des poids. Les montants tiennent
   leur ligne par leur espace insécable, ce qui suffit. */
.tarifs dd { margin: 0; text-align: right; }
.tarifs .zone {
  grid-column: 1 / -1;
  margin-top: 14px;
  font-size: 13px;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  opacity: 0.5;
}
.tarifs .zone:first-child { margin-top: 0; }

/* Repère des informations qu'il reste à fournir sur les pages légales : raison
   sociale, SIRET, délais, frais de port. Volontairement voyant — ces pages ne
   doivent pas partir en ligne tant qu'il en subsiste un seul, et un gabarit
   discret finirait par être publié tel quel. Une recherche de « a-completer »
   dans le dossier les liste tous. */
/* Le rouge a disparu. Il avait sa raison d'être tant que le site n'était pas
   publié — un repère qu'on ne peut pas rater, pour qu'aucune valeur inventée ne
   parte en ligne. Mais le site EST en ligne : un surlignage rouge et un
   « à compléter » en toutes lettres s'y lisent comme un chantier, là où le reste
   des pages est soigné.
   Ce qui reste est neutre : l'italique et un pointillé pâle, qui se lisent comme
   une incise provisoire plutôt que comme une alarme. Le filet de sécurité n'est
   plus visuel mais textuel — `grep -rn "a-completer" frontend/` les liste tous,
   et il n'en reste qu'un. */
.a-completer {
  font-style: italic;
  opacity: 0.75;
  border-bottom: 1px dotted currentColor;
  padding-bottom: 1px;
}

/* Pied du menu : les liens obligatoires, en retrait des entrées principales. */
/* Le filet sépare désormais les deux univers du reste du menu, et non plus les
   pages légales — lesquelles ont quitté le menu pour le pied de page.
   C'est la bonne césure : au-dessus ce qu'on vient chercher, en dessous les
   chemins de service. */
.menu-suite {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}

.menu-legal {
  margin-top: 34px;
  padding-top: 18px;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
}
.menu-legal a {
  display: block;
  font-size: 12.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 8px 0;
  opacity: 0.6;
}
.menu-legal a:hover { opacity: 1; }

/* ============ VOILE DE TÊTE ============ */
/* L'en-tête est fixe : logo, barre de menu et lecteur CD restent en place
   pendant que la page défile dessous. Le texte venait donc buter dedans — un
   titre de page passait littéralement au travers du logo.

   Plutôt que de rogner le contenu, on le fait DISPARAÎTRE avant qu'il n'arrive :
   un dégradé du fond de page, fixé en haut, opaque derrière l'en-tête puis
   fondu à rien. Le texte s'efface donc à l'approche des trois éléments au lieu
   de les traverser.

   Rang 55 : au-dessus du contenu et des canvas de poussière (3), sous l'en-tête
   (60) — qui reste net et cliquable. Il ne prend aucun clic, ce qu'il recouvre
   reste donc accessible.

   Réservé aux pages qui défilent. L'accueil et la page du drop tiennent dans la
   hauteur d'écran et portent une vidéo : un voile marron y serait déplacé. */
body.theme-cream::before {
  content: "";
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  /* Assez haut pour couvrir le lecteur CD, le plus bas des trois. */
  height: 165px;
  z-index: 55;
  pointer-events: none;
  background: linear-gradient(
    to bottom,
    var(--ground) 0%,
    var(--ground) 34%,
    rgba(var(--ground-rvb), 0.86) 55%,
    rgba(var(--ground-rvb), 0) 100%
  );
}

/* ============ POP-UP (boutiques éphémères) ============ */
/* Une fiche par date. L'ordre de lecture suit celui de la décision : quand,
   quoi, où, puis les détails pratiques. */
.popup-carte {
  max-width: 820px;
  margin: 0 auto 34px;
  padding: 28px 30px 30px;
  border: 1px solid rgba(239, 241, 227, 0.22);
}
.popup-quand {
  font-size: 12.5px;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  opacity: 0.65;
  margin-bottom: 14px;
}
.popup-nom {
  font-family: var(--font-glitch);
  font-size: clamp(22px, 2.6vw, 34px);
  font-weight: 400;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: 0 0 12px;
}
.popup-lieu { font-size: 17px; line-height: 1.55; }
/* Les instructions sont saisies en texte libre : les retours à la ligne de
   l'admin doivent se retrouver tels quels à l'écran. */
.popup-notes {
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid rgba(239, 241, 227, 0.14);
  font-size: 16px;
  line-height: 1.65;
  white-space: pre-line;
  opacity: 0.85;
}
.popup-vide { text-align: center; font-size: 18px; line-height: 1.6; margin: 60px 0; }

/* Réseaux : une ligne par compte, le trait se remplit au survol. */
.social-list { max-width: 820px; margin: 0 auto; }
.social-list a {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 20px;
  padding: 26px 4px;
  border-top: 1px solid currentColor;
  font-size: clamp(22px, 3vw, 34px);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transition: padding-left 0.2s ease, opacity 0.2s ease;
}
.social-list a:last-child { border-bottom: 1px solid currentColor; }
.social-list a:hover { padding-left: 18px; }
.social-list .handle {
  font-size: 15px;
  letter-spacing: 0.04em;
  text-transform: none;
  opacity: 0.6;
  white-space: nowrap;
}

/* Contact : mêmes champs que la modale d'inscription, en pleine page. */
.contact-form { max-width: 820px; margin: 0 auto; display: grid; gap: 18px; }
.contact-form .pair { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 700px) { .contact-form .pair { grid-template-columns: 1fr; } }
.contact-form label { display: flex; flex-direction: column; gap: 8px; font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; opacity: 0.6; }
.contact-form input, .contact-form textarea {
  background: rgba(255, 255, 255, 0.06);
  border: 1px solid currentColor;
  color: inherit;
  font-family: var(--font-body);
  font-size: 16px;
  padding: 15px 16px;
}
.contact-form textarea { min-height: 190px; resize: vertical; }
.contact-form input:focus, .contact-form textarea:focus { outline: none; background: rgba(255,255,255,0.11); }
.contact-note { font-size: 15px; line-height: 1.6; opacity: 0.75; }

/* FAQ : dépliants natifs, sans script. */
.faq { max-width: 820px; margin: 0 auto; }
.faq details { border-top: 1px solid currentColor; }
.faq details:last-of-type { border-bottom: 1px solid currentColor; }
.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 24px 40px 24px 0;
  position: relative;
  font-size: 17px;
  letter-spacing: 0.04em;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  position: absolute; right: 6px; top: 50%;
  transform: translateY(-50%);
  font-size: 22px;
}
.faq details[open] summary::after { content: "–"; }
.faq details p { font-size: 16.5px; line-height: 1.65; margin: 0 0 24px; max-width: 680px; opacity: 0.88; }

/* ============ TÉLÉPHONE ============ */
/* Un seul point de rupture, à 700 px : en dessous, on tient un téléphone en
   main. Le site n'est pas rétréci, il est reposé — l'accueil passe de deux
   moitiés côte à côte à deux moitiés l'une sur l'autre, l'en-tête se resserre
   pour laisser la page respirer, et tout ce qui était rangé en ligne se remet
   en colonne.

   Les tailles de texte restent en `clamp()` : c'est la borne basse qui parle
   ici, celle qui était jusqu'à présent taillée pour un grand écran. */
@media (max-width: 700px) {

  /* -- En-tête -- */
  /* Le logo et le lecteur CD se chevauchaient : à eux deux ils occupaient plus
     que la largeur d'un téléphone. Les deux maigrissent. */
  .site-header { padding: 12px 14px 0; }
  .logo-mark { width: clamp(92px, 27vw, 128px); }
  .site-header.reduit .logo-mark { width: clamp(68px, 20vw, 92px); }
  .cd-player { width: 62px; }
  .burger { width: 38px; padding: 11px 0; }
  .burger span { height: 2.5px; margin-bottom: 6px; }
  /* Le voile de tête suit la hauteur réduite de l'en-tête. */
  body.theme-cream::before { height: 118px; }

  /* -- Accueil -- */
  /* Deux colonnes sur 375 px donnaient 187 px par moitié : les titres en
     sortaient et le compteur débordait. Elles passent l'une sur l'autre. */
  .home-split {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr 1fr;
  }
  /* Le titre se règle désormais sur la LARGEUR de l'écran et non sur la moitié
     qui n'existe plus : « ART DE LA TABLE » tient sur une ligne à 9 vw. */
  /* Mesuré : « ART DE LA TABLE » fait 10,9 fois la taille de police. À 9 vw il
     réclamait 366 px pour 347 disponibles et sortait des deux côtés. À 8 vw il
     en demande 325, et tient jusque sur un écran de 320 px. */
  .home-title { font-size: min(8vw, 40px); padding: 0 14px; }
  .home-caption { font-size: 14px; margin-top: 26px; }
  .lcd.small { font-size: clamp(15px, 4.6vw, 20px); margin-top: 12px; }
  /* Agrandie ici aussi, mais remontée : le pied de page occupe désormais le bas
     de l'écran, et sur un téléphone la moitié basse de l'accueil finit juste
     au-dessus de lui. Sans ce dégagement, l'invitation et les mentions légales
     se chevaucheraient. */
  /* Même règle qu'en grand écran — à mi-hauteur de l'espace libre —, mais les
     moitiés font ici la demi-fenêtre : 18 % y vaudraient 73 px, ce qui remonte
     trop l'invitation vers le compteur. */
  .home-invite { bottom: 13%; font-size: clamp(15px, 4vw, 18px); }

  /* Pied de page CENTRÉ sur téléphone : il n'y a pas de marge latérale à gagner
     sur un écran étroit, et trois liens alignés à gauche y pendaient de travers.
     Au besoin ils passent à la ligne plutôt que de déborder d'un 320 px. */
  .site-pied {
    padding: 26px 14px 14px;
    justify-content: center;
    flex-wrap: wrap;
    gap: 6px 14px;
    font-size: 10px;
  }

  /* -- Page du drop -- */
  .gate { padding: 0 18px; }
  .gate-sub { font-size: 15px; margin: 20px 0 4px; }
  .lcd.big { font-size: clamp(22px, 7.4vw, 34px); margin-top: 16px; }
  .pw-display { font-size: clamp(24px, 8vw, 38px); margin-top: 18px; }
  .gate-invite { bottom: 26px; font-size: 15px; }

  /* -- Menu latéral -- */
  .menu-panel { width: min(330px, 88vw); padding: 28px 24px; }
  .menu-title { font-size: 34px; margin: 0 0 26px; }
  .menu-panel nav a { font-size: 15px; padding: 12px 0; }
  .menu-legal { margin-top: 22px; padding-top: 14px; }

  /* -- Modale d'inscription -- */
  /* Le champ et le bouton étaient en ligne : le bouton sortait de l'écran de
     trente pixels. Ils se rangent l'un sous l'autre, et le remplissage tombe de
     soixante-quatre pixels à vingt. */
  /* Le remplissage du haut dégage la croix de fermeture : sur deux lignes, le
     titre venait sinon buter dedans. Descendre le contenu vaut mieux que rétrécir
     le titre, qui se serait alors coupé en trois. */
  .signup-box { padding: 54px 20px 36px; }
  .signup-box h2 { font-size: clamp(19px, 6.2vw, 26px); }
  .signup-box p { font-size: 14.5px; margin-bottom: 24px; }
  .signup-form { flex-direction: column; gap: 10px; }
  .signup-form input,
  .signup-form button { width: 100%; padding: 15px 18px; }
  .signup-close { top: 12px; right: 12px; width: 30px; height: 30px; }

  /* -- Pages de contenu -- */
  /* Soixante pixels de marge de chaque côté ne laissaient que 255 px de texte. */
  .page-cream { padding: 96px 18px 60px; }
  body.defile .page-cream { padding-top: 64px; }
  /* Le plancher de 38 px du réglage commun était taillé pour un grand écran :
     « ART DE LA TABLE » y réclamait 414 px pour 339 disponibles. Les titres
     descendent, et ceux qui restent trop longs se replient désormais sur deux
     lignes — le rendu en poussière sait les couper. */
  .gate-title, .cat-title, .cart-title, .page-title {
    font-size: clamp(26px, 8vw, 42px);
  }
  .page-title { margin: 24px 0 36px; }
  .page-lead { font-size: 16px; margin-bottom: 40px; }
  .page-section { margin-bottom: 42px; }
  .cat-title, .cart-title { margin: 24px 0 40px; }

  /* -- Catalogue -- */
  .grid { gap: 26px 12px; }
  /* Le nom et le prix se partageaient une ligne, aux deux bouts. Dans 164 px,
     « VESTE TAILLEUR 90S » et « 74 € 52 € » se chevauchaient et se coupaient en
     escalier. Ils se rangent l'un sous l'autre. */
  .card-caption {
    flex-direction: column;
    align-items: flex-start;
    gap: 3px;
    font-size: 13px;
    margin-top: 10px;
  }
  .card .card-price { font-size: 15px; }

  /* -- Fiche produit -- */
  .product-strip { margin-top: 64px; }
  body.defile .product-strip { margin-top: 40px; }
  /* Une vignette à 30 vw faisait 112 px : on n'y voyait rien. */
  .product-strip .shot { width: 66vw; }
  .product-body { padding: 28px 18px 0; }
  .product-name { font-size: 21px; margin-bottom: 18px; }
  .product-desc { font-size: 15.5px; }
  .spec { gap: 12px 26px; margin-top: 24px; }
  .product-price { font-size: 24px; margin: 30px 0 8px; }
  .measure td { padding: 5px 26px 5px 0; }
  .btn-line { padding: 14px 28px; letter-spacing: 0.08em; }

  /* -- Panier -- */
  /* Le grand lecteur CD n'a pas sa place sur un téléphone : posé en haut à
     droite, il recouvrait le titre de la page. Il reprend la taille qu'il a
     partout ailleurs — l'écran est trop étroit pour ce morceau de décor, et le
     panier a plus besoin de son titre. */
  .cart-player,
  .cart-player.small { width: 62px; top: 12px; right: 14px; }
  /* Le bouton et le total se suivaient sur une ligne de 60 px d'écart. */
  .cart-footer { gap: 26px; margin: 54px 0 30px; flex-direction: column; }
  .pay-btn { font-size: clamp(26px, 9vw, 38px); padding: 14px 46px 17px; }
  .total-lcd { font-size: clamp(22px, 7vw, 30px); }

  /* -- Pop-up -- */
  .popup-carte { padding: 20px 18px 22px; margin-bottom: 24px; }
  .popup-nom { font-size: clamp(19px, 6.4vw, 26px); }
  .popup-quand { font-size: 11.5px; letter-spacing: 0.12em; }
  .popup-lieu { font-size: 15.5px; }
  .popup-notes { font-size: 15px; }

  /* -- Administration -- */
  .admin-wrap { padding: 18px 12px 60px; }
  .admin-head { gap: 12px; margin-bottom: 22px; }
  .admin-logo { width: 116px; }
  .admin-tabs { gap: 6px; margin-bottom: 20px; }
  .admin-tabs button { font-size: 10px; padding: 8px 11px; letter-spacing: 0.03em; }
  .admin-card { padding: 16px 14px; overflow-x: auto; }
  .admin-card h3 { font-size: 15px; }
  .admin-form { grid-template-columns: 1fr; }
  /* Les tableaux ne se plient pas : on les fait glisser horizontalement dans
     leur carte plutôt que de les laisser pousser la page entière. */
  .admin-table { min-width: 520px; }
  .admin-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
  .stat .v { font-size: 24px; }
}

/* La grille du catalogue garde DEUX colonnes sur téléphone. Une seule montrait
   certes mieux chaque pièce, mais imposait un écran entier par article : on ne
   pouvait plus parcourir un drop, seulement le feuilleter. Deux vignettes de
   163 px laissent voir la coupe et la couleur, et c'est l'usage partout ailleurs.

   La grille de l'admin, elle, passe bien à une colonne : on y règle des articles
   un par un, on ne les parcourt pas. */
@media (max-width: 400px) {
  .admin-grid { grid-template-columns: 1fr; }
}

/* Bandeau d'état du stockage, en tête de l'admin. Rouge tant que les fichiers
   vivent sur un disque qui les perdra ; discret une fois R2 en place. */
#stockage-etat:empty { display: none; }
.stockage-ok, .stockage-alerte {
  font-family: var(--font-body);
  font-size: 13px;
  padding: 9px 14px;
  margin-bottom: 16px;
  border-radius: 2px;
}
.stockage-ok { background: rgba(255, 255, 255, 0.05); opacity: 0.6; }
.stockage-alerte {
  background: rgba(176, 0, 16, 0.22);
  border-left: 3px solid var(--red);
  color: #ffd9d2;
}

/* ============ PIED DE PAGE ============ */
/* Les pages légales, en bas à gauche de chaque page. Elles ont quitté le menu :
   elles y allongeaient une liste qu'on veut courte, et il fallait l'ouvrir pour
   les trouver. Obligation légale oblige, elles restent atteignables partout —
   mais discrètement, ce qui est exactement leur statut.

   DANS LE FLUX, et non plus flottant. Il était fixé au bas de la fenêtre : sur
   une page qui défile, il restait donc posé par-dessus le texte qu'on était en
   train de lire, à suivre le regard sans jamais s'écarter. Un pied de page n'a
   rien à faire là : sa place est à la FIN du document, qu'on y arrive en
   descendant.
   C'est le corps en colonne (plus bas) qui le garde collé en bas de fenêtre sur
   les pages trop courtes pour défiler — l'accueil notamment. */
.site-pied {
  /* Fond et couleur POSÉS, et non hérités. Le corps change de teinte selon la
     page — brun sur les pages de contenu, gris sur l'accueil —, si bien qu'un
     pied en blanc translucide se lisait très bien sur l'une et disparaissait sur
     l'autre, gris pâle sur gris. Une bande à la couleur du site règle la
     question une fois pour toutes : le contraste est le même partout. */
  background: var(--ground);
  color: var(--on-ground);
  padding: 34px 20px 16px;
  display: flex;
  gap: 18px;
  font-size: 11px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
}
.site-pied a {
  color: inherit;
  opacity: 0.42;
  transition: opacity 0.2s ease;
}
.site-pied a:hover { opacity: 1; text-decoration: underline; }

/* Corps en colonne : le contenu prend la place qu'il peut, le pied se range
   dessous. `min-height: 100vh` le maintient en bas de l'écran même quand la
   page est trop courte pour défiler, sans jamais le faire flotter par-dessus.
   L'admin est exclue : elle a sa propre mise en page et n'affiche pas de pied. */
body:not(.theme-admin) {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}
body:not(.theme-admin) > main { flex: 1 0 auto; }

/* Sur les pages au fond crème, le texte est sombre : l'héritage de `color`
   suffit, mais l'opacité doit remonter un peu, un gris pâle sur crème se lisant
   moins bien qu'un blanc pâle sur brun. */
.theme-cream .site-pied a { opacity: 0.5; }

/* L'admin a sa propre mise en page et n'est pas une page publique : le pied n'y
   a rien à faire. */
.theme-admin .site-pied { display: none; }

/* ============ L'ACCUEIL, UN CAS À PART ============ */
/* Ailleurs, le pied se range à la fin du document : la page défile, et il n'a
   rien à faire par-dessus le texte qu'on lit.

   L'accueil ne défile pas. Le ranger sous les deux moitiés y découpait une
   bande, et rognait d'autant des images qui doivent occuper tout l'écran. Il y
   redevient donc flottant — ce qui n'a aucun des inconvénients vus ailleurs,
   puisqu'il n'y a rien à recouvrir. */
@media (hover: hover) and (pointer: fine) {
  body.accueil { display: block; }
  .accueil .home-split { height: 100vh; }
  .accueil .site-pied {
    position: fixed;
    left: 20px;
    bottom: 14px;
    z-index: 40;
    background: none;
    padding: 0;
  }
  /* Posé sur les images, le texte doit tenir sur un plan clair comme sur un
     plan sombre : l'ombre portée lui donne un bord en toutes circonstances,
     là où la bande de couleur s'en chargeait. */
  .accueil .site-pied a {
    opacity: 0.55;
    text-shadow: 0 1px 4px rgba(0, 0, 0, 0.75);
  }
  /* Le panneau du menu est translucide : le pied transparaissait dessous,
     lisible à moitié et pas cliquable. Il s'efface le temps qu'il est ouvert. */
  .accueil .menu-overlay.open ~ .site-pied { opacity: 0; pointer-events: none; }
  .accueil .site-pied { transition: opacity 0.25s ease; }
}
