/* =================================================================
   QuizLive — système de design commun
   Une seule feuille de jetons pour les trois jeux et le portail.
   Chaque jeu garde sa teinte d'accent ; tout le reste est partagé.
   ================================================================= */

:root {
  /* Fonds */
  --bg: #07060d;
  --bg-2: #0d0c16;
  --panel: rgba(20, 19, 31, 0.86);
  --panel-solide: #14131f;
  --line: rgba(255, 255, 255, 0.07);
  --line-forte: rgba(255, 255, 255, 0.14);

  /* Texte */
  --txt: #eef0f8;
  --muted: #9298b3;
  --faint: #5d627b;

  /* Accents par jeu — chaque application redéfinit --accent */
  --or: #f0b429;
  --or-rgb: 240, 180, 41;
  --violet: #7c6cff;
  --violet-rgb: 124, 108, 255;
  --rouge: #ff4d6d;
  --rouge-rgb: 255, 77, 109;
  --accent: var(--or);
  --accent-rgb: var(--or-rgb);

  /* Typographie */
  --font-ui: "Bricolage Grotesque", "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-titre: "Fraunces", "Georgia", "Times New Roman", serif;
  --font-mono: "JetBrains Mono", ui-monospace, "SFMono-Regular", monospace;

  /* Rythme */
  --r-sm: 8px; --r-md: 14px; --r-lg: 22px; --r-xl: 30px;
  --ombre: 0 24px 60px rgba(0, 0, 0, 0.55);
  --lueur: 0 0 40px rgba(var(--accent-rgb), 0.22);

  /* Mouvement — courbes partagées, pour une cohérence de geste */
  --ease-sortie: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-rebond: cubic-bezier(0.34, 1.56, 0.64, 1);
  --duree-court: 180ms;
  --duree-moyen: 420ms;
  --duree-long: 720ms;
}

* { box-sizing: border-box; }
html, body, #root { height: 100%; margin: 0; }
body {
  background: var(--bg);
  color: var(--txt);
  font: 15px/1.6 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
button { font: inherit; cursor: pointer; }
h1, h2, h3 { font-family: var(--font-titre); font-weight: 600; letter-spacing: -0.01em; }

/* ---------- Le fond vivant ----------
   Deux halos qui dérivent lentement. Ils ne bougent qu'en transform,
   donc le compositeur s'en charge. Surtout PAS de filter: blur() ici :
   un flou sur une couche animée est recalculé à chaque image (deux
   gaussiennes plein écran à 60 Hz). Le fondu doux vient d'un dégradé
   radial à paliers, peint une seule fois. */
.fond-vivant {
  position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden;
}
.fond-vivant::before, .fond-vivant::after {
  content: ""; position: absolute; width: 60vmax; height: 60vmax; border-radius: 50%;
  opacity: 0.35; will-change: transform;
}
.fond-vivant::before {
  left: -20vmax; top: -20vmax;
  background: radial-gradient(circle,
    rgba(var(--accent-rgb), 0.55) 0%, rgba(var(--accent-rgb), 0.33) 25%,
    rgba(var(--accent-rgb), 0.14) 45%, transparent 65%);
  animation: derive-a 26s ease-in-out infinite alternate;
}
.fond-vivant::after {
  right: -22vmax; bottom: -24vmax;
  background: radial-gradient(circle,
    rgba(var(--violet-rgb), 0.45) 0%, rgba(var(--violet-rgb), 0.27) 25%,
    rgba(var(--violet-rgb), 0.11) 45%, transparent 65%);
  animation: derive-b 34s ease-in-out infinite alternate;
}
@keyframes derive-a { to { transform: translate3d(14vmax, 10vmax, 0) scale(1.15); } }
@keyframes derive-b { to { transform: translate3d(-12vmax, -14vmax, 0) scale(0.9); } }
/* Pendant une partie (classe posée sur <body> par le jeu), les halos
   se figent : rien ne doit disputer l'image au chrono et aux cascades. */
body.en-partie .fond-vivant::before, body.en-partie .fond-vivant::after { animation-play-state: paused; }

/* Grain fixe, très léger, qui unifie les aplats */
.fond-vivant .grain-fixe {
  position: absolute; inset: 0; opacity: 0.045;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/><feColorMatrix type='saturate' values='0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>");
}

/* ---------- Composants ---------- */

.carte {
  /* Pas de backdrop-filter : il refloute le fond animé à chaque image.
     Un fond un peu plus dense garde la lisibilité sans lui. */
  background: color-mix(in srgb, var(--panel-solide) 94%, transparent);
  border: 1px solid var(--line); border-radius: var(--r-lg);
  box-shadow: var(--ombre);
}

.bouton {
  display: inline-flex; align-items: center; justify-content: center; gap: 10px;
  padding: 14px 28px; border-radius: 999px; border: 1px solid transparent;
  font-weight: 700; font-size: 1rem; color: #16110a;
  background: linear-gradient(180deg, var(--accent), color-mix(in srgb, var(--accent) 70%, #000));
  box-shadow: 0 10px 30px rgba(var(--accent-rgb), 0.25), inset 0 1px 0 rgba(255,255,255,0.25);
  transition: transform var(--duree-court) var(--ease-sortie), box-shadow var(--duree-court);
  will-change: transform;
}
.bouton:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 16px 40px rgba(var(--accent-rgb), 0.35); }
.bouton:active:not(:disabled) { transform: translateY(0) scale(0.98); }
.bouton:disabled { background: #2a2a3d; color: var(--faint); box-shadow: none; cursor: default; }
.bouton.discret {
  background: transparent; color: var(--muted); border-color: var(--line-forte); box-shadow: none;
}
.bouton.discret:hover:not(:disabled) { color: var(--txt); border-color: var(--accent); }

.champ {
  width: 100%; padding: 14px 18px; font-size: 1rem;
  background: rgba(0,0,0,0.35); border: 1px solid var(--line-forte);
  border-radius: var(--r-md); color: var(--txt); font-family: inherit;
  transition: border-color var(--duree-court), box-shadow var(--duree-court);
}
.champ:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0.15); }
.champ::placeholder { color: var(--faint); }

.eyebrow {
  font-size: 0.72rem; letter-spacing: 0.24em; text-transform: uppercase;
  color: var(--faint); margin: 0;
}

/* ---------- Transitions d'écran ----------
   Sortie courte, entrée plus longue et légèrement décalée : le geste
   ressemble à un rideau, pas à un clignotement. Transform + opacité
   uniquement — jamais de flou animé. */
.ecran-entre { animation: ecran-entre var(--duree-moyen) var(--ease-sortie) both; }
.ecran-sort  { animation: ecran-sort var(--duree-court) ease-in both; }
@keyframes ecran-entre {
  from { opacity: 0; transform: translate3d(0, 18px, 0) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
@keyframes ecran-sort {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translate3d(0, -10px, 0) scale(0.99); }
}

/* Cascade : les enfants d'un écran arrivent l'un après l'autre */
.cascade > * { animation: monte var(--duree-long) var(--ease-sortie) backwards; }
.cascade > *:nth-child(1) { animation-delay: 0ms; }
.cascade > *:nth-child(2) { animation-delay: 60ms; }
.cascade > *:nth-child(3) { animation-delay: 120ms; }
.cascade > *:nth-child(4) { animation-delay: 180ms; }
.cascade > *:nth-child(5) { animation-delay: 240ms; }
.cascade > *:nth-child(6) { animation-delay: 300ms; }
.cascade > *:nth-child(n+7) { animation-delay: 360ms; }
@keyframes monte { from { opacity: 0; transform: translate3d(0, 16px, 0); } }

/* Impact : pour un chiffre qui tombe, un verdict, un jeton */
.impact { animation: impact var(--duree-moyen) var(--ease-rebond) both; }
@keyframes impact { from { opacity: 0; transform: scale(0.6); } 70% { transform: scale(1.06); } to { opacity: 1; transform: scale(1); } }

/* Pulsation d'attention, sans ombre animée */
.pulse { position: relative; }
.pulse::after {
  content: ""; position: absolute; inset: -3px; border-radius: inherit;
  border: 2px solid var(--accent); opacity: 0; pointer-events: none;
  animation: pulse-anneau 1.8s ease-out infinite;
}
@keyframes pulse-anneau { 0% { opacity: 0.7; transform: scale(1); } 100% { opacity: 0; transform: scale(1.12); } }

/* Confettis : des particules en transform seul */
.confetti { position: fixed; inset: 0; pointer-events: none; overflow: hidden; z-index: 50; contain: strict; }
.confetti span {
  position: absolute; top: -12px; width: 9px; height: 14px; border-radius: 2px;
  background: var(--c, var(--accent)); left: var(--x, 50%);
  animation: confetti-chute var(--d, 3s) linear var(--delai, 0s) both;
  /* Pas de will-change : Chrome promeut la couche le temps de l'animation
     et la libère ensuite, au lieu de garder 70 couches GPU hors écran. */
}
@keyframes confetti-chute {
  to { transform: translate3d(var(--dx, 0), 110vh, 0) rotate(var(--rot, 720deg)); opacity: 0.8; }
}

/* Respect du réglage système */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* PENDANT UNE TRANSITION DE PAGE, LE DÉCOR SE TAIT.
   Une transition de vue anime une PHOTO de la page : pendant ce temps, le
   décor vivant qui est derrière (projecteurs, confettis, lampes) continue
   pourtant de tourner et se dispute le temps de calcul avec le mouvement
   qu'on est en train de regarder — c'est ce qui rendait le passage saccadé
   alors même que la photo, elle, ne coûte presque rien. On met donc tout en
   pause le temps du voyage ; la classe est retirée au retour (`pageshow`).
   `animation-play-state` ne touche que les animations CSS : celles de la
   transition elle-même, créées en JavaScript sur les pseudo-éléments, ne
   sont pas concernées. */
html.ql-gel *, html.ql-gel *::before, html.ql-gel *::after { animation-play-state: paused !important; }

/* ET PENDANT LE VOYAGE, LA PAGE VIVANTE S'EFFACE.
   Mettre les animations en pause ne suffisait pas : une transition de vue
   anime une PHOTO de la page, mais le navigateur continue de FABRIQUER la
   page vivante derrière elle, à chaque trame — décor, faisceaux, dégradés,
   tout est recomposé pour rien puisque personne ne le voit. Mesuré sur le
   bouton JOUER : les trois quarts des trames étaient perdues.
   On RETIRE donc le contenu du rendu le temps du passage. `visibility:
   hidden` ne suffisait pas : la mise en page et les couches restaient, et
   avec elles l'essentiel du travail (mesuré : 179 repeints contre 62 une
   fois le contenu vraiment retiré). `display: none` coûte une remise en
   page au moment où on le pose — mais la photo est déjà prise, et la page
   est sur le point d'être remplacée : personne ne la verra sauter.
   On garde ce qui doit rester : le voile de couleur, et la sonde d'un
   pixel qui donne au navigateur son repère. La classe tombe au retour
   (`pageshow`). */
html.ql-envol body > *:not(.ql-garde) { display: none; }

/* ON PILOTE LA TRANSITION ; LE NAVIGATEUR NE DOIT PAS EN PILOTER UNE AUTRE.
   Par défaut, une transition de vue croise les deux photos avec son propre
   fondu ET un mélange `plus-lighter`. Le fondu est superflu — nos deux
   animations (l'avance de la caméra sur l'ancienne photo, l'apparition du
   voile sur la nouvelle) font déjà tout le travail. Le mélange, lui, coûte
   cher : il oblige le moteur à isoler les deux couches dans une passe de
   rendu séparée et à relire l'écran à chaque trame. Mesuré sur le bouton
   JOUER, décor pourtant déjà gelé : onze repeints plein écran et 343 ms de
   recalcul de style pendant le passage.
   `animation: none` ne touche que les animations CSS du navigateur : les
   nôtres, créées en JavaScript, continuent. */
::view-transition-old(root), ::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-image-pair(root) { isolation: auto; }
