/* ==========================================================================
   Quiet Puzzle — prototype
   Identité : rose pastel, sobre, épuré. Toute la chromie de l'habillage dérive
   d'une seule teinte `--h`, qui glisse du rose au vert d'eau à mesure que le
   joueur descend dans les niveaux (voir src/ui/theme.js).
   ========================================================================== */

:root {
  /* Teinte de départ. src/ui/theme.js la remplace à chaque niveau, sur #app et
     sur chaque section de la carte. */
  --h: 345;

  /* Cœurs (vies) : toujours roses, quel que soit le monde — c'est la seule
     couleur du jeu qui ne suit pas `--h`. */
  --heart-a: #f7bccb;
  --heart-b: #ec93ab;
  --heart-empty: #efe3e8;
  --heart-hollow: #fff;

  /* Blocs : repli avant le premier niveau chargé. src/ui/theme.js pose ensuite
     la palette du monde. Les glyphes (●◆▲★■⬢), eux, ne changent jamais : ce
     sont eux qui portent la règle du jeu. */
  --c0: #eb9aad;  /* ● */
  --c1: #93bde4;  /* ◆ */
  --c2: #97cfb6;  /* ▲ */
  --c3: #e9cd8c;  /* ★ */
  --c4: #bdaadd;  /* ■ */
  --c5: #f0b18b;  /* ⬢ */
  --sealed: #f2eef4;

  --award: #d9a441;   /* étoiles : un ambre discret, stable quelle que soit --h */
  --alert: #d4697e;

  --font-display: 'Fraunces', 'Iowan Old Style', Georgia, serif;
  --font-ui: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --radius: 18px;
  --cell: 40px;
}

/* Tokens dérivés de la teinte.
   Ils sont redéclarés sur CHAQUE élément susceptible de porter un `--h` propre,
   et pas seulement sur :root — c'est indispensable, et la raison n'est pas
   évidente : une propriété personnalisée substitue ses `var()` là où elle est
   DÉCLARÉE, pas là où elle est employée. Un `--ground` défini sur :root fige
   donc le `--h` de :root, et les descendants héritent d'une couleur déjà
   calculée. Le fond de l'application restait ainsi rose à tous les niveaux,
   alors que le plateau, qui écrit `hsl(var(--h) …)` en direct, virait bien. */
:root, .app, .realm {
  /* Surfaces — un blanc très légèrement teinté, jamais un blanc pur : sur un
     fond pastel, le blanc pur fait un trou. */
  --ground: hsl(var(--h) 62% 96.5%);
  --ground-deep: hsl(var(--h) 56% 89%);
  --surface: hsl(var(--h) 60% 99.2%);
  --line: hsl(var(--h) 28% 88%);
  --line-soft: hsl(var(--h) 30% 92%);

  /* Encre — un gris profond teinté, pas du noir : le noir durcit le pastel. */
  --ink: hsl(var(--h) 22% 24%);
  --ink-soft: hsl(var(--h) 14% 46%);
  --ink-faint: hsl(var(--h) 16% 66%);

  --accent: hsl(var(--h) 58% 66%);
  --accent-deep: hsl(var(--h) 52% 54%);
  --accent-wash: hsl(var(--h) 72% 94%);
}

/* Mode nuit : mêmes jetons, luminosité inversée — `--h` ne change pas, donc
   la nuit reste teintée par le monde en cours, juste éclairée autrement. */
html.night, html.night .app, html.night .realm {
  --heart-empty: hsl(340 18% 30%);
  --heart-hollow: hsl(340 20% 24%);
  --ground: hsl(var(--h) 30% 14%);
  --ground-deep: hsl(var(--h) 34% 9%);
  --surface: hsl(var(--h) 26% 19%);
  --line: hsl(var(--h) 24% 30%);
  --line-soft: hsl(var(--h) 22% 25%);

  --ink: hsl(var(--h) 20% 93%);
  --ink-soft: hsl(var(--h) 14% 74%);
  --ink-faint: hsl(var(--h) 12% 55%);

  --accent: hsl(var(--h) 55% 62%);
  --accent-deep: hsl(var(--h) 55% 72%);
  --accent-wash: hsl(var(--h) 40% 25%);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* `hidden` doit gagner contre les display: grid/flex des composants. */
[hidden] { display: none !important; }

html, body {
  height: 100%;
  background: #ece7ec;
  color: var(--ink);
  font: 400 15px/1.5 var(--font-ui);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior: none;
}

body { display: grid; place-items: center; }

/* Cadre 9:16. La largeur est aussi bornée par la hauteur disponible : fixer
   largeur ET hauteur annulerait l'aspect-ratio et rognerait le plateau. */
.device {
  position: relative;
  width: min(100vw, 430px, calc(100vh * 9 / 16));
  aspect-ratio: 9 / 16;
  max-height: 100vh;
  overflow: hidden;
  border-radius: clamp(0px, calc((100vw - 430px) * 999), 26px);
  box-shadow: 0 24px 60px rgba(60, 40, 55, 0.16);
}

/* Packaged app: no page to frame a phone mockup on, so the board fills the
   real device screen instead of being letterboxed to a fixed 9:16 card. */
.native-app .device {
  width: 100vw;
  height: 100vh;
  aspect-ratio: auto;
  max-height: none;
  border-radius: 0;
  box-shadow: none;
}

.app {
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, var(--ground) 0%, var(--ground-deep) 100%);
  overflow: hidden;
  touch-action: manipulation;
  -webkit-user-select: none;
  user-select: none;
  /* La teinte glisse doucement d'un niveau à l'autre plutôt que de sauter. */
  transition: background 700ms ease;
}

/* Premier lancement (pas de teinte mémorisée, voir index.html) : un voile
   calme couvre le menu le temps que la base, la session et la synchro
   répondent, plutôt que de le montrer rose puis de le repeindre. Neutre, sans
   teinte : on ne sait pas encore celle du joueur. */
.boot-veil {
  position: absolute; inset: 0; z-index: 200;
  display: grid; place-items: center;
  background: #f1ecf0; /* = le splash Android (mobile/assets-src/splash.svg) */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 600ms ease, visibility 0s linear 600ms;
}
html.booting .boot-veil {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: none;
}
html.night .boot-veil { background: linear-gradient(180deg, #2a2530 0%, #1c1820 100%); }

/* Une barre fine où une lueur passe et revient, sans jamais se presser. */
.boot-veil-bar {
  position: relative; overflow: hidden;
  width: 96px; height: 3px; border-radius: 999px;
  background: rgba(120, 100, 115, 0.14);
}
.boot-veil-bar::after {
  content: ''; position: absolute; inset: 0; width: 40%;
  border-radius: inherit;
  background: rgba(120, 100, 115, 0.45);
  animation: boot-glide 2.4s ease-in-out infinite alternate;
}
html.night .boot-veil-bar { background: rgba(230, 220, 235, 0.12); }
html.night .boot-veil-bar::after { background: rgba(230, 220, 235, 0.45); }
@keyframes boot-glide {
  from { transform: translateX(-10%); }
  to { transform: translateX(160%); }
}

/* ---------- Écrans ---------- */

.screen {
  position: absolute;
  inset: 0;
  display: none;
  flex-direction: column;
  padding: max(18px, env(safe-area-inset-top)) 20px 20px;
}
.screen.active { display: flex; animation: screen-in 0.34s cubic-bezier(0.2, 0.8, 0.3, 1) both; }

@keyframes screen-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

.topbar {
  display: grid;
  grid-template-columns: 38px 1fr auto;
  align-items: center;
  gap: 10px;
  margin-bottom: 16px;
}
.topbar-title {
  text-align: center;
  font: 400 15px var(--font-ui);
  letter-spacing: 0.06em;
  color: var(--ink-soft);
}
.topbar-title b { color: var(--ink); font-weight: 500; }
/* Win streak (≥ 3) in the HUD: a small flickering flame and the count. */
.hud-streak {
  display: inline-flex; align-items: center; gap: 2px; margin-left: 8px;
  padding: 1px 7px 1px 5px; border-radius: 999px; vertical-align: 2px;
  background: hsl(24 80% 94%); color: hsl(20 60% 44%);
  font: 500 12px var(--font-ui); font-variant-numeric: tabular-nums;
}
.hud-streak[hidden] { display: none; }
.hud-streak b { color: inherit; }
.hud-flame {
  width: 9px; height: 12px; fill: hsl(20 85% 62%);
  transform-origin: 50% 90%; animation: flame-flicker 1.4s ease-in-out infinite;
}

.icon-btn {
  width: 38px; height: 38px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 19px; line-height: 1;
  cursor: pointer;
  transition: border-color 0.15s ease;
}
.icon-btn:hover { border-color: var(--accent); }
.icon-btn:active { transform: scale(0.95); }

.pill {
  justify-self: end;
  white-space: nowrap;
  padding: 6px 13px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}

/* ---------- Boutons ---------- */

.btn {
  border: 0;
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  font: 400 15px var(--font-ui);
  padding: 13px 20px;
  cursor: pointer;
  transition: transform 0.14s ease, background 0.16s ease, border-color 0.16s ease;
}
.btn:active { transform: scale(0.975); }
.btn-primary {
  background: var(--accent);
  color: #fff;
  font-weight: 500;
  letter-spacing: 0.01em;
}
.btn-primary:hover { background: var(--accent-deep); }
.btn-ghost { background: var(--surface); border: 1px solid var(--line); color: var(--ink-soft); }
.btn-ghost:hover { border-color: var(--accent); color: var(--ink); }
.btn-lg { width: 100%; padding: 16px; font-size: 16px; }
.btn-sm { padding: 8px 12px; font-size: 12px; }
.btn[disabled] { opacity: 0.42; pointer-events: none; }

/* ---------- Menu ---------- */

#screen-menu { justify-content: center; align-items: center; gap: 14px; text-align: center; isolation: isolate; }
/* Behind the content, not over it: the screen is its own stacking context. */
#screen-menu .menu-art { z-index: -1; }

/* Petals: few, slow, transform/opacity only — cheap on a phone battery. */
.menu-art .petal, .brief-art .petal {
  position: absolute; top: -6%; left: var(--x);
  width: var(--size); height: calc(var(--size) * 0.72);
  border-radius: 80% 0 80% 0;
  background: hsl(var(--h) 62% 82%);
  opacity: 0;
  animation: petal-fall var(--d) linear var(--delay) infinite;
}
@keyframes petal-fall {
  0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
  10% { opacity: 0.55; }
  30% { transform: translate(var(--sway), 30vh) rotate(90deg); }
  60% { transform: translate(calc(var(--sway) * -0.6), 62vh) rotate(200deg); }
  88% { opacity: 0.5; }
  100% { transform: translate(var(--sway), 108vh) rotate(320deg); opacity: 0; }
}

/* Petals shaken loose by a tap around the buttons (confetti.petalBurst): they
   spring from the finger, then drift down swaying and fade. Behind the
   buttons, like the others (`z-index: -1` in the menu's stacking context). */
.petal-pop {
  position: absolute; z-index: -1; pointer-events: none;
  width: var(--size); height: calc(var(--size) * 0.72);
  margin: calc(var(--size) * -0.36) 0 0 calc(var(--size) * -0.5);
  border-radius: 80% 0 80% 0;
  background: hsl(var(--h) 64% var(--light, 80%));
  opacity: 0;
  /* ease-in-out on every step: each sway eases in and out, like a leaf. */
  animation: petal-pop 6s ease-in-out both;
}
@keyframes petal-pop {
  0%   { transform: translate(0, 0) rotate(0) scale(0.5); opacity: 0; }
  14%  { transform: translate(var(--px), var(--py)) rotate(calc(var(--spin) * 0.1)) scale(1); opacity: 0.85; }
  36%  { transform: translate(calc(var(--px) + var(--sway)), calc(var(--py) + var(--drop) * 0.25)) rotate(calc(var(--spin) * 0.35)); }
  58%  { transform: translate(calc(var(--px) - var(--sway)), calc(var(--py) + var(--drop) * 0.5)) rotate(calc(var(--spin) * 0.6)); opacity: 0.8; }
  80%  { transform: translate(calc(var(--px) + var(--sway) * 0.7), calc(var(--py) + var(--drop) * 0.76)) rotate(calc(var(--spin) * 0.85)); opacity: 0.7; }
  100% { transform: translate(calc(var(--px) - var(--sway) * 0.3), calc(var(--py) + var(--drop))) rotate(var(--spin)); opacity: 0; }
}

/* "Play" breathes, slowly: the one thing to do on this screen. */
#btn-play { animation: play-breathe 3.2s ease-in-out infinite; display: flex; flex-direction: column; align-items: center; gap: 1px; }
#btn-play small { font-size: 11px; font-weight: 400; opacity: 0.82; letter-spacing: 0.04em; }
#btn-play small:empty { display: none; }
@keyframes play-breathe { 50% { scale: 1.03; } }

/* Formes de fond : à peine perceptibles, elles donnent de la profondeur sans
   charger la page. */
.menu-art { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
.menu-art span {
  position: absolute; width: 260px; height: 260px; border-radius: 50%;
  filter: blur(48px); opacity: 0.4;
}
.menu-art span:nth-child(1) { background: hsl(var(--h) 70% 84%); top: -6%; left: -22%; }
.menu-art span:nth-child(2) { background: hsl(calc(var(--h) - 40) 62% 86%); top: 18%; right: -26%; }
.menu-art span:nth-child(3) { background: hsl(calc(var(--h) + 30) 60% 88%); bottom: 6%; left: -18%; }
.menu-art span:nth-child(4) { background: hsl(var(--h) 55% 90%); bottom: -8%; right: -14%; }

.logo {
  font: 300 46px/1.05 var(--font-display);
  font-variation-settings: 'SOFT' 40, 'WONK' 1;
  letter-spacing: -0.015em;
  color: var(--ink);
}
.logo em { display: block; font-style: normal; font-weight: 500; color: var(--accent-deep); }
.tagline { color: var(--ink-soft); font-size: 14px; margin-bottom: 12px; }

.menu-stats { display: flex; gap: 10px; margin: 6px 0 20px; }
.stat {
  min-width: 88px; padding: 14px 10px;
  background: var(--surface); border: 1px solid var(--line-soft); border-radius: var(--radius);
}
.stat b {
  display: block; font: 400 24px var(--font-ui); color: var(--ink);
  font-variant-numeric: tabular-nums;
  /* « 121/160 » est deux fois plus long qu'un nombre seul : la tuile rétrécit
     le texte plutôt que de déborder ou de couper. */
  white-space: nowrap;
}
.stat b:has(+ small) { font-size: clamp(17px, 5.4vw, 24px); }
.stat small { color: var(--ink-faint); font-size: 11px; letter-spacing: 0.04em; }

#screen-menu .btn-lg { max-width: 268px; }
/* Daily quests and daily puzzle: secondary, so small and side by side. */
.menu-secondary { display: flex; gap: 8px; width: 100%; max-width: 268px; }
.btn-mini {
  flex: 1 1 0; min-width: 0; display: grid; justify-items: center; gap: 1px;
  padding: 8px 6px; border-radius: 14px; font-size: 13px; line-height: 1.2;
}
.btn-mini .mini-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
/* Something waiting (a quest reward to collect, today's puzzle not played):
   the button glows softly, breathing like "Play" — an invitation, not an
   alarm, so no red dot. */
.btn-mini.ready {
  border-color: var(--accent);
  animation: mini-glow 2.8s ease-in-out infinite;
}
@keyframes mini-glow {
  0%, 100% { scale: 1; box-shadow: 0 0 0 0 hsl(var(--h) 60% 65% / 0), 0 4px 14px hsl(var(--h) 60% 60% / 0.12); }
  50% { scale: 1.025; box-shadow: 0 0 0 5px hsl(var(--h) 60% 70% / 0.2), 0 4px 18px hsl(var(--h) 60% 60% / 0.28); }
}
.btn-mini small { font-size: 11px; opacity: 0.7; font-weight: 400; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.menu-liens { display: flex; gap: 8px; }

/* ---------- Étoiles ---------- */

.stars { display: flex; gap: 3px; }
.stars i { font-style: normal; color: var(--line); font-size: 14px; }
.stars i.on { color: var(--award); }
.stars-lg { justify-content: center; align-items: center; gap: 10px; }
.stars-lg i { font-size: 30px; }
.stars-lg i.on { animation: star-pop 0.5s cubic-bezier(0.2, 1.5, 0.4, 1) both; }
.stars-lg i.on:nth-child(2) { animation-delay: 0.12s; }
.stars-lg i.on:nth-child(3) { animation-delay: 0.24s; }
@keyframes star-pop { from { transform: scale(0.3); opacity: 0; } }

/* ---------- Carte des niveaux ---------- */

.map-scroll { flex: 1; overflow-y: auto; padding: 4px 0 24px; scrollbar-width: none; position: relative; }
.map-scroll::-webkit-scrollbar { display: none; }

/* Chaque monde porte sa propre teinte : en faisant défiler, on voit la
   gradation chromatique de la progression. */
.realm {
  padding: 6px 0 18px;
  border-radius: 22px;
  position: relative;
  z-index: 0;
  background: linear-gradient(180deg, transparent, hsl(var(--h) 60% 96%) 40%, transparent);
}

/* Le décor est porté par le monde et non par le conteneur : le changement de
   branche tombe ainsi sur la frontière entre deux mondes, là où se trouve le
   titre. Le masque le fait naître et mourir en fondu autour de ce titre, sinon
   le raccord se lit comme une ligne nette juste au-dessus.

   La branche est posée une seule fois, sans répétition : elle est plus courte
   que le monde, et c'est `--papier` — sa propre couleur de fond — qui complète
   la hauteur restante, si bien que la fin de l'image ne se voit pas.

   L'opacité tient lieu de voile : les numéros restent lisibles, et la couleur
   de papier propre à chaque monde n'est pas repeinte par la teinte du thème. */
.realm::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--papier, transparent);
  background-image: var(--branche, none);
  background-repeat: no-repeat;
  background-position: top center;
  background-size: 100% auto;
  opacity: 0.5;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 96px,
                                      #000 calc(100% - 96px), transparent);
          mask-image: linear-gradient(180deg, transparent, #000 96px,
                                      #000 calc(100% - 96px), transparent);
}

/* Une branche par monde. La couleur change à chaque monde sur un cycle court
   de dix teintes — rose, violet, bleu, bleu ciel, vert, jaune, doré, orange,
   rouge, blanc — soit trois tours pour les trente mondes, le dernier
   revenant sur le rose du premier. `--papier` complète la hauteur sous
   l'image ; la teinte `--h` du monde suit le même cycle, réglée dans REALMS.
   La table de référence des trois est tools/mondes.py. */
.realm:nth-child(1) { --branche: url('../images/branches/branche-01.webp'); --papier: #f8eaee; }  /* Peaceful Sakura */
.realm:nth-child(2) { --branche: url('../images/branches/branche-02.webp'); --papier: #f0e8f6; }  /* Wisteria Veil */
.realm:nth-child(3) { --branche: url('../images/branches/branche-03.webp'); --papier: #e4e9f6; }  /* Blue Lys */
.realm:nth-child(4) { --branche: url('../images/branches/branche-04.webp'); --papier: #e2eff7; }  /* Forget Me Not */
.realm:nth-child(5) { --branche: url('../images/branches/branche-05.webp'); --papier: #e6f2e9; }  /* Eucalyptus Calm */
.realm:nth-child(6) { --branche: url('../images/branches/branche-06.webp'); --papier: #f7f3db; }  /* Mimosa Sun */
.realm:nth-child(7) { --branche: url('../images/branches/branche-07.webp'); --papier: #f6ecd6; }  /* Golden Ginkgo */
.realm:nth-child(8) { --branche: url('../images/branches/branche-08.webp'); --papier: #fae8da; }  /* Autumn Elm */
.realm:nth-child(9) { --branche: url('../images/branches/branche-09.webp'); --papier: #fae6e4; }  /* Winter Maple */
.realm:nth-child(10) { --branche: url('../images/branches/branche-10.webp'); --papier: #f6f2ec; }  /* Apple Blossom */
.realm:nth-child(11) { --branche: url('../images/branches/branche-11.webp'); --papier: #f8eaee; }  /* Quiet Camellia */
.realm:nth-child(12) { --branche: url('../images/branches/branche-12.webp'); --papier: #f0e8f6; }  /* Lilac Drift */
.realm:nth-child(13) { --branche: url('../images/branches/branche-13.webp'); --papier: #e4e9f6; }  /* Cornflower Blue */
.realm:nth-child(14) { --branche: url('../images/branches/branche-14.webp'); --papier: #e2eff7; }  /* Morning Glory */
.realm:nth-child(15) { --branche: url('../images/branches/branche-15.webp'); --papier: #e6f2e9; }  /* Sage Fern */
.realm:nth-child(16) { --branche: url('../images/branches/branche-16.webp'); --papier: #f7f3db; }  /* Honey Acacia */
.realm:nth-child(17) { --branche: url('../images/branches/branche-17.webp'); --papier: #f6ecd6; }  /* Amber Birch */
.realm:nth-child(18) { --branche: url('../images/branches/branche-18.webp'); --papier: #fae8da; }  /* Apricot Branch */
.realm:nth-child(19) { --branche: url('../images/branches/branche-19.webp'); --papier: #fae6e4; }  /* Red Berry */
.realm:nth-child(20) { --branche: url('../images/branches/branche-20.webp'); --papier: #f6f2ec; }  /* Snow Willow */
.realm:nth-child(21) { --branche: url('../images/branches/branche-21.webp'); --papier: #f8eaee; }  /* Blush Plum */
.realm:nth-child(22) { --branche: url('../images/branches/branche-22.webp'); --papier: #f0e8f6; }  /* Lavender Field */
.realm:nth-child(23) { --branche: url('../images/branches/branche-23.webp'); --papier: #e4e9f6; }  /* Blue Hydrangea */
.realm:nth-child(24) { --branche: url('../images/branches/branche-24.webp'); --papier: #e2eff7; }  /* Winter Frost */
.realm:nth-child(25) { --branche: url('../images/branches/branche-25.webp'); --papier: #e6f2e9; }  /* Bamboo Breeze */
.realm:nth-child(26) { --branche: url('../images/branches/branche-26.webp'); --papier: #f7f3db; }  /* Yellow Broom */
.realm:nth-child(27) { --branche: url('../images/branches/branche-27.webp'); --papier: #f6ecd6; }  /* Wheat Field */
.realm:nth-child(28) { --branche: url('../images/branches/branche-28.webp'); --papier: #fae8da; }  /* Copper Beech */
.realm:nth-child(29) { --branche: url('../images/branches/branche-29.webp'); --papier: #fae6e4; }  /* Crimson Vine */
.realm:nth-child(30) { --branche: url('../images/branches/branche-30.webp'); --papier: #f6f2ec; }  /* Silver Mist */
.realm:nth-child(31) { --branche: url('../images/branches/branche-31.webp'); --papier: #f8eaee; }  /* Ivory Magnolia */
.realm:nth-child(32) { --branche: url('../images/branches/branche-32.webp'); --papier: #f0e8f6; }  /* Dusk Iris */
.realm:nth-child(33) { --branche: url('../images/branches/branche-33.webp'); --papier: #e4e9f6; }  /* Deep Cornflower */
.realm:nth-child(34) { --branche: url('../images/branches/branche-34.webp'); --papier: #e2eff7; }  /* Pale Larkspur */
.realm:nth-child(35) { --branche: url('../images/branches/branche-35.webp'); --papier: #e6f2e9; }  /* Moss Cedar */
.realm:nth-child(36) { --branche: url('../images/branches/branche-36.webp'); --papier: #f7f3db; }  /* Late Mimosa */
.realm:nth-child(37) { --branche: url('../images/branches/branche-37.webp'); --papier: #f6ecd6; }  /* Bronze Ginkgo */
.realm:nth-child(38) { --branche: url('../images/branches/branche-38.webp'); --papier: #fae8da; }  /* Ember Maple */
.realm:nth-child(39) { --branche: url('../images/branches/branche-39.webp'); --papier: #fae6e4; }  /* Garnet Sorrel */
.realm:nth-child(40) { --branche: url('../images/branches/branche-40.webp'); --papier: #f6f2ec; }  /* Frost Aster */
.realm:nth-child(41) { --branche: url('../images/branches/branche-41.webp'); --papier: #f8eaee; }  /* Winter Peony */
.realm:nth-child(42) { --branche: url('../images/branches/branche-42.webp'); --papier: #f0e8f6; }  /* Violet Thistle */
.realm:nth-child(43) { --branche: url('../images/branches/branche-43.webp'); --papier: #e4e9f6; }  /* Indigo Flax */
.realm:nth-child(44) { --branche: url('../images/branches/branche-44.webp'); --papier: #e2eff7; }  /* Glacier Sage */
.realm:nth-child(45) { --branche: url('../images/branches/branche-45.webp'); --papier: #e6f2e9; }  /* Pine Shadow */
.realm:nth-child(46) { --branche: url('../images/branches/branche-46.webp'); --papier: #f7f3db; }  /* Amber Reed */
.realm:nth-child(47) { --branche: url('../images/branches/branche-47.webp'); --papier: #f6ecd6; }  /* Rust Alder */
.realm:nth-child(48) { --branche: url('../images/branches/branche-48.webp'); --papier: #fae8da; }  /* Cinder Rowan */
.realm:nth-child(49) { --branche: url('../images/branches/branche-49.webp'); --papier: #fae6e4; }  /* Crimson Yew */
.realm:nth-child(50) { --branche: url('../images/branches/branche-50.webp'); --papier: #f6f2ec; }  /* Last Light */

.realm-label {
  display: flex; align-items: center; gap: 12px;
  margin: 14px 0 14px; color: var(--ink-faint);
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.18em;
}
.realm-label::before, .realm-label::after {
  content: ''; flex: 1; height: 1px; background: var(--line);
}

.map-path { position: relative; display: flex; flex-direction: column; align-items: center; gap: 12px; }

.map-node {
  position: relative;
  width: 60px; padding: 9px 0 7px;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 18px;
  color: var(--ink);
  cursor: pointer;
  transition: transform 0.14s ease, border-color 0.16s ease;
}
.map-node:hover { border-color: var(--accent); }
.map-node:active { transform: scale(0.95); }
.map-node b { font: 400 18px var(--font-ui); font-variant-numeric: tabular-nums; }
.map-node.current {
  background: var(--accent); border-color: transparent; color: #fff;
}
.map-node.current .stars i { color: rgba(255, 255, 255, 0.45); }
.map-node.current .stars i.on { color: #fff; }
.map-node.current::after {
  content: attr(data-label); position: absolute; top: -9px;
  font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
  background: var(--ink); color: var(--ground); padding: 2px 8px; border-radius: 999px;
}
/* Niveaux verrouillés : de plus en plus sombres jusqu'à l'horizon de la carte
   (mapScreen.js, LOOKAHEAD) — la suite se devine, elle ne se lit pas. */
/* Registered so it can TRANSITION: when a win pushes the frontier on, every
   locked level eases one notch out of the dark (mapScreen.js, playReveal). */
@property --fog { syntax: '<number>'; inherits: false; initial-value: 0; }
.map-node.locked {
  transition: --fog 0.9s ease, transform 0.14s ease;
  cursor: not-allowed;
  opacity: calc(0.7 - var(--fog, 0) * 0.3);
  filter: brightness(calc(0.92 - var(--fog, 0) * 0.55)) saturate(calc(0.8 - var(--fog, 0) * 0.6));
}
/* Le crépuscule de la carte (mapScreen.js) : du prochain niveau jusqu'à
   l'horizon, tout s'assombrit — cases, branches, fleurs. */
.map-dusk.moving { transition: top 0.9s cubic-bezier(0.4, 0, 0.2, 1), height 0.9s cubic-bezier(0.4, 0, 0.2, 1); }
/* The line of light from the level just won to the one it opens. */
.map-trail { position: absolute; top: 0; left: 0; pointer-events: none; z-index: 3; overflow: visible; }
.map-trail path {
  fill: none; stroke: #fff; stroke-width: 4; stroke-linecap: round;
  filter: drop-shadow(0 0 5px hsl(var(--h, 330) 80% 75%)) drop-shadow(0 0 2px #fff);
}
/* The run drawn (mapScreen.js `glowLine`): the light stays, and breathes. */
.map-trail.settled path { animation: trail-breathe 3.2s ease-in-out infinite; }
@keyframes trail-breathe { 50% { opacity: 0.6; } }
/* A soft ring spreading from a level that has just unlocked. */
.unlock-ring {
  position: absolute; inset: -2px; border-radius: 20px; pointer-events: none;
  border: 2px solid var(--accent);
  animation: unlock-ring 0.75s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
@keyframes unlock-ring { from { scale: 1; opacity: 0.9; } to { scale: 1.6; opacity: 0; } }
/* "Next" breathes: this is where to tap. `scale`, not `transform`, which
   carries the node's place on the winding path. */
.map-node.current { animation: next-breathe 2.6s ease-in-out infinite; }
@keyframes next-breathe {
  0%, 100% { scale: 1; box-shadow: 0 0 0 0 hsl(var(--h) 60% 65% / 0); }
  50% { scale: 1.06; box-shadow: 0 0 0 7px hsl(var(--h) 60% 65% / 0.16); }
}
.map-dusk {
  position: absolute; left: 0; right: 0; pointer-events: none; z-index: 2;
  background: linear-gradient(to bottom,
    transparent 0%, hsl(var(--h, 330) 25% 20% / 0.18) 25%,
    hsl(var(--h, 330) 25% 16% / 0.5) 70%, hsl(var(--h, 330) 25% 12% / 0.78) 100%);
}
.map-node.locked b::after { content: '·'; }
.map-node.done { border-color: hsl(var(--h) 40% 82%); }

/* Dernier niveau d'un monde : sensiblement plus dur, la case le porte en
   pleine teinte du monde plutôt qu'en simple liséré — le même traitement que
   `.current`, à la couleur du monde près. `:not(.current)` laisse la place à
   l'accent d'usage tant que ce niveau est aussi le prochain à jouer —
   l'information « à jouer » prime sur « c'est le corsé ». */
.map-node.boss:not(.current) {
  background: hsl(var(--h) 55% 58%); border-color: transparent; color: #fff;
  box-shadow: 0 6px 16px hsl(var(--h) 50% 45% / 0.35);
}
.map-node.boss:not(.current) .stars i { color: rgba(255, 255, 255, 0.4); }
.map-node.boss:not(.current) .stars i.on { color: #fff; }
/* …et il luit : seul son HALO bouge — il respire et vacille par à-coups
   irréguliers, comme une chandelle. La case elle-même reste immobile. */
.map-node.boss { animation: boss-halo 4.6s ease-in-out infinite; }
@keyframes boss-halo {
  0%, 100% { box-shadow: 0 6px 16px hsl(var(--h) 50% 45% / 0.28), 0 0 10px 2px hsl(var(--h) 70% 72% / 0.35); }
  18% { box-shadow: 0 6px 18px hsl(var(--h) 50% 45% / 0.32), 0 0 20px 7px hsl(var(--h) 70% 74% / 0.5); }
  24% { box-shadow: 0 6px 17px hsl(var(--h) 50% 45% / 0.3), 0 0 14px 4px hsl(var(--h) 70% 74% / 0.38); }
  31% { box-shadow: 0 6px 18px hsl(var(--h) 50% 45% / 0.32), 0 0 22px 8px hsl(var(--h) 70% 74% / 0.52); }
  55% { box-shadow: 0 6px 16px hsl(var(--h) 50% 45% / 0.28), 0 0 12px 3px hsl(var(--h) 70% 72% / 0.36); }
  72% { box-shadow: 0 6px 19px hsl(var(--h) 50% 45% / 0.34), 0 0 26px 9px hsl(var(--h) 70% 76% / 0.48); }
  80% { box-shadow: 0 6px 17px hsl(var(--h) 50% 45% / 0.3), 0 0 16px 5px hsl(var(--h) 70% 74% / 0.4); }
}
@media (prefers-reduced-motion: reduce) { .map-node.boss { animation: none; } }
/* Verrouillé et dans l'ombre, il garde sa lueur : c'est lui qu'on devine au loin. */
.map-node.boss.locked { animation-duration: 5.8s; }
/* Les autres pics des dents de scie (core/sawtooth.js) : une petite flamme
   dessinée à la couleur du monde, posée sur le coin de la case — repérable
   sans voler la vedette au niveau final, qui lui prend la pleine teinte. */
.map-node.hard::before {
  content: ''; position: absolute; top: -11px; right: -7px;
  width: 16px; height: 22px;
  background: hsl(var(--h) 56% 66%);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath fill-rule='evenodd' d='M12 1C13 7 20 10.5 20 20.5C20 26.5 16.5 31 12 31C7.5 31 4 26.5 4 21C4 16.5 6.5 13.5 8.5 10.5C8.8 13.8 10.2 16 12 17C10.8 12 10.6 6 12 1ZM12 29.5C9.9 29.5 8.6 27.7 8.6 25.4C8.6 22.6 10.6 21 12 18.8C13.4 21 15.4 22.6 15.4 25.4C15.4 27.7 14.1 29.5 12 29.5Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 32'%3E%3Cpath fill-rule='evenodd' d='M12 1C13 7 20 10.5 20 20.5C20 26.5 16.5 31 12 31C7.5 31 4 26.5 4 21C4 16.5 6.5 13.5 8.5 10.5C8.8 13.8 10.2 16 12 17C10.8 12 10.6 6 12 1ZM12 29.5C9.9 29.5 8.6 27.7 8.6 25.4C8.6 22.6 10.6 21 12 18.8C13.4 21 15.4 22.6 15.4 25.4C15.4 27.7 14.1 29.5 12 29.5Z'/%3E%3C/svg%3E") center / contain no-repeat;
  filter: drop-shadow(0 1px 2px hsl(var(--h) 40% 50% / 0.18));
  transform-origin: 50% 90%;
  animation: flame-flicker 1.9s ease-in-out infinite;
}
/* Each flame at its own pace, so a map full of them does not beat in step. */
/* On the "Next" level the flame used to sit under the label and vanish, pink
   on the node's own pink: it moves to the node's right flank, above the label,
   in a deeper tone with a light outline so it reads against the accent. */
.map-node.current.hard::before {
  top: 2px; right: -12px; z-index: 1;
  background: hsl(var(--h) 60% 50%);
  filter: drop-shadow(0 0 1.5px #fff) drop-shadow(0 1px 2px hsl(var(--h) 40% 40% / 0.25));
}
.map-node.hard:nth-child(3n)::before { animation-duration: 2.3s; animation-delay: -0.7s; }
.map-node.hard:nth-child(3n+1)::before { animation-duration: 1.7s; animation-delay: -1.1s; }

/* ---------- Pré-niveau ---------- */

#screen-brief { justify-content: flex-start; isolation: isolate; }
/* Scenery behind the brief card. `--brief-branch` is the realm's own branch
   (the map's), set by main.js; `data-decor` says what to show. */
.brief-art { position: absolute; inset: 0; z-index: -1; overflow: hidden; pointer-events: none; }
.brief-art::before {
  content: ''; position: absolute; inset: 0; display: none;
  /* The branch runs the full height of the screen (its image is tall and
     narrow), so it frames the card above and below instead of hiding under it. */
  background: var(--brief-branch, none) center 30% / 115% auto no-repeat;
  opacity: 0.55;
  -webkit-mask-image: linear-gradient(180deg, transparent, #000 12%, #000 80%, transparent);
          mask-image: linear-gradient(180deg, transparent, #000 12%, #000 80%, transparent);
}
.brief-art .petal { display: none; }
[data-decor~="branch"] > .brief-art::before { display: block; }
[data-decor~="petals"] > .brief-art .petal { display: block; }
/* The result screen wears the same scenery (main.js, applyScenery): a veil
   that hides the board, the branch and the petals behind the card. */
#overlay-result[data-decor] { isolation: isolate; background: hsl(var(--h) 34% 96% / 0.97); }
.brief-card {
  margin: auto 0; padding: 30px 22px 22px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 26px;
  text-align: center;
}
.brief-badge {
  width: 62px; height: 62px; margin: -52px auto 16px;
  display: grid; place-items: center;
  background: var(--accent); color: #fff; border-radius: 20px;
  font: 400 25px var(--font-ui);
  box-shadow: 0 8px 20px hsl(var(--h) 50% 70% / 0.4);
}

/* Dernier niveau d'un monde : le badge passe à la couleur du monde et respire
   d'un anneau qui pulse — le même signal qu'on vient de voir sur la carte,
   repris ici pour que le joueur comprenne où il met les pieds avant de jouer. */
.brief-card.final .brief-badge {
  background: hsl(var(--h) 55% 55%);
  animation: brief-final-glow 1.8s ease-in-out infinite;
}
/* Série de victoires : ce qu'elle offre au niveau qui s'ouvre (ou ce qu'une
   victoire de plus offrirait, en plus discret). */
.brief-streak {
  margin: 0 0 14px; padding: 7px 14px; border-radius: 14px;
  background: var(--line); color: var(--ink-soft); font-size: 13px;
}
.brief-streak.on { background: #fff1dc; color: #a2601a; font-weight: 600; }
.brief-card.hard .brief-badge { background: hsl(var(--h) 55% 55%); }
.brief-final {
  display: inline-block; margin: 0 0 14px; padding: 5px 14px;
  border-radius: 999px; background: hsl(var(--h) 55% 93%); color: hsl(var(--h) 55% 36%);
  font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase;
  animation: brief-final-pop 0.5s 0.1s cubic-bezier(0.2, 1.4, 0.4, 1) both;
}
@keyframes brief-final-pop { from { transform: scale(0.6); opacity: 0; } }
@keyframes brief-final-glow {
  0%, 100% { box-shadow: 0 8px 20px hsl(var(--h) 50% 70% / 0.4), 0 0 0 0 hsl(var(--h) 60% 60% / 0.5); }
  50% { box-shadow: 0 8px 20px hsl(var(--h) 50% 70% / 0.4), 0 0 0 9px hsl(var(--h) 60% 60% / 0); }
}

/* Nouveau bloc de la partie : un pion qu'on ne présente qu'une fois par monde,
   et que le joueur doit garder en tête pour tout le reste du jeu. Un simple
   texte se serait fondu dans le brief — ce badge respire en continu (léger
   avant-arrière + éclat qui monte et redescend) pour rester accroché à l'œil
   sans jamais devenir criard : la pulsation est lente et l'amplitude reste
   petite, dans l'esprit pastel du reste de l'habillage. Coupé net par la
   règle prefers-reduced-motion globale, plus haut dans ce fichier. */
.brief-novelty {
  display: inline-block;
  margin: 0 0 14px; padding: 8px 18px;
  border-radius: 999px;
  background: var(--accent-wash);
  border: 1px solid hsl(var(--h) 50% 78%);
  color: var(--accent-deep);
  font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
  animation:
    brief-novelty-pop 0.5s 0.1s cubic-bezier(0.2, 1.4, 0.4, 1) both,
    brief-novelty-shimmer 2.2s ease-in-out 0.6s infinite;
}
@keyframes brief-novelty-pop { from { transform: scale(0.6); opacity: 0; } }
@keyframes brief-novelty-shimmer {
  0%, 100% { transform: translateX(0) scale(1); filter: brightness(1); }
  50% { transform: translateX(2px) scale(1.035); filter: brightness(1.2); }
}

.brief-objective { font-size: 17px; margin: 18px 0 20px; color: var(--ink); }
.brief-meta { display: flex; justify-content: space-around; margin-bottom: 24px; }
.brief-meta dt { color: var(--ink-faint); font-size: 10px; text-transform: uppercase; letter-spacing: 0.1em; }
.brief-meta dd { font: 400 18px var(--font-ui); margin-top: 4px; font-variant-numeric: tabular-nums; }

/* ---------- HUD ---------- */

.hud {
  padding: 12px 16px 14px;
  margin-bottom: 14px;
  background: var(--surface);
  border: 1px solid var(--line-soft);
  border-radius: 16px;
}
.hud-row { display: flex; align-items: flex-end; justify-content: space-between; gap: 10px; }
.hud-stat { display: flex; flex-direction: column; gap: 3px; }
.hud-stat-stars { align-items: flex-end; }
.hud-label { font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-faint); }
.hud-stat b {
  font: 400 23px var(--font-ui); font-variant-numeric: tabular-nums; line-height: 1;
  transition: transform 0.2s cubic-bezier(0.2, 1.6, 0.4, 1);
}
.hud-stat b.pop { transform: scale(1.32); }
.hud-stat b.over { color: var(--ink-faint); opacity: 0.6; transition: color 0.6s ease, opacity 0.6s ease; }
.hud-stat .stars { padding-bottom: 5px; }

.time-track {
  height: 3px; margin-top: 12px; border-radius: 999px;
  background: var(--line-soft); overflow: hidden;
}
.time-track i {
  display: block; height: 100%; width: 100%;
  background: var(--accent);
  transition: width 1s linear, background 0.3s ease;
}

/* ---------- Plateau ---------- */

.board-wrap {
  position: relative; flex: 1;
  display: flex; align-items: center; justify-content: center;
  min-height: 0;
}

/* Le mur est un vrai cadre : les portes se posent dessus, en débord. */
.board {
  position: relative;
  box-sizing: content-box;
  border: var(--wall) solid hsl(var(--h) 34% 90%);
  border-radius: calc(var(--wall) + 8px);
  background: hsl(var(--h) 40% 98%);
  box-shadow: 0 10px 28px hsl(var(--h) 30% 60% / 0.13);
}
.grid-layer, .gate-layer, .block-layer { position: absolute; inset: 0; }
.gate-layer { pointer-events: none; }

/* Le quadrillage se devine, il ne se lit pas : il sert à situer une case vide,
   pas à découper la grille. Plus il est net, plus le plateau paraît chargé. */
.grid-cell {
  position: absolute; top: 0; left: 0;
  width: var(--cell); height: var(--cell);
  border-right: 1px solid hsl(var(--h) 24% 96.5%);
  border-bottom: 1px solid hsl(var(--h) 24% 96.5%);
}

/* ---------- Portes ---------- */

.gate {
  position: absolute;
  display: grid; place-items: center;
  background: var(--tile);
  color: rgba(255, 255, 255, 0.9);
  font-size: calc(var(--wall) * 0.72);
  line-height: 1;
}
/* Une flèche, au centre de l'ouverture, qui dit le SENS de sortie. (Avec
   l'option « motifs », la porte porte en plus la texture de sa couleur.) */
.gate-arrow {
  display: flex; align-items: center; justify-content: center; gap: 0.25em;
  width: 100%; height: 100%;
  font-size: 9px; line-height: 1;
}
.gate-left .gate-arrow, .gate-right .gate-arrow { flex-direction: column; }
.gate-top    { top: calc(var(--wall) * -1); height: var(--wall); border-radius: 7px 7px 0 0; }
.gate-bottom { bottom: calc(var(--wall) * -1); height: var(--wall); border-radius: 0 0 7px 7px; }
.gate-left   { left: calc(var(--wall) * -1); width: var(--wall); border-radius: 7px 0 0 7px; }
.gate-right  { right: calc(var(--wall) * -1); width: var(--wall); border-radius: 0 7px 7px 0; }
.gate-cap {
  position: absolute;
  min-width: 15px; padding: 0 3px;
  border-radius: 999px;
  background: var(--ink); color: var(--ground);
  font-size: 9px; font-weight: 500; line-height: 15px; text-align: center;
  font-variant-numeric: tabular-nums;
}
.gate-top .gate-cap    { top: calc(var(--wall) * -1 - 8px); }
.gate-bottom .gate-cap { bottom: calc(var(--wall) * -1 - 8px); }
.gate-left .gate-cap   { left: calc(var(--wall) * -1 - 6px); }
.gate-right .gate-cap  { right: calc(var(--wall) * -1 - 6px); }
.gate-full { opacity: 0.4; filter: grayscale(0.7); }

@keyframes gate-flash {
  40% { filter: brightness(1.35) saturate(1.2); box-shadow: 0 0 16px 3px var(--tile); }
}

/* ---------- Blocs ---------- */

.block {
  position: absolute; top: 0; left: 0;
  --bevel: calc(var(--cell) * 0.4);

  /* Matière du bloc : une pâte mate et bien ronde, éclairée de haut, sans
     éclat de verre. Le reflet est ici, en un seul endroit, parce que les blocs
     doubles et jokers le reprennent tel quel par-dessus leur propre teinte.

     `--reflet` ne doit contenir AUCUN `var()` : une propriété personnalisée
     substitue ses `var()` là où elle est DÉCLARÉE. Un `calc(var(--cell) …)`
     écrit ici se figerait sur le `--cell` de repli au lieu de suivre la taille
     réelle des cases. */
  --reflet:
    radial-gradient(112% 74% at 32% 6%, rgba(255, 255, 255, 0.58), rgba(255, 255, 255, 0) 60%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0) 40%,
      rgba(96, 74, 102, 0.13) 100%),
    linear-gradient(0deg, rgba(255, 255, 255, 0.14), rgba(255, 255, 255, 0.14));

  /* `scale` is separate from `transform` (which carries the position): the
     lift when grabbed and the squash against a wall never fight the move. */
  transition: transform 80ms cubic-bezier(0.25, 0.8, 0.3, 1),
              scale 180ms cubic-bezier(0.3, 1.5, 0.5, 1);
  cursor: grab;
  touch-action: none;
  will-change: transform;
  /* L'ombre portée suit la SILHOUETTE réelle du polyomino, pas sa boîte : c'est
     ce qui donne le relief sans dessiner de contour case par case. Deux
     couches : un trait de contact net qui pose le bloc, et un halo large qui
     l'adoucit. */
  filter:
    drop-shadow(0 calc(var(--cell) * 0.04) 0 rgba(94, 72, 102, 0.15))
    drop-shadow(0 calc(var(--cell) * 0.12) calc(var(--cell) * 0.17) rgba(94, 72, 102, 0.16));
}

/*
 * Le reflet est un dégradé unique calé sur la BOÎTE DU BLOC
 * (backgroundSize/Position posés en JS), donc continu d'une case à l'autre.
 * Un reflet par case aurait dessiné une grille de petits carrés éclairés.
 */
.block-cell {
  position: absolute;
  width: var(--cell); height: var(--cell);
  background-color: var(--tile);
  background-image: var(--reflet);
  background-repeat: no-repeat;
}

/*
 * Relief de silhouette. Contrairement au reflet, qui est calé sur la boîte du
 * bloc, celui-ci se dessine CASE PAR CASE — mais uniquement sur les arêtes
 * marquées `e-t/e-r/e-b/e-l` par boardView, c'est-à-dire celles qui donnent
 * vraiment sur l'extérieur. Une arête intérieure ne dessine rien : c'est ce qui
 * permet à un L ou à un T de paraître gonflé d'un seul tenant au lieu de se
 * recouper en petits carrés.
 */
.block-cell::after {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background-image:
    linear-gradient(to bottom, var(--lum-t, transparent), rgba(255, 255, 255, 0) 48%),
    linear-gradient(to right,  var(--lum-l, transparent), rgba(255, 255, 255, 0) 48%),
    linear-gradient(to top,    var(--omb-b, transparent), rgba(255, 255, 255, 0) 44%),
    linear-gradient(to left,   var(--omb-r, transparent), rgba(255, 255, 255, 0) 44%);
}
.block-cell.e-t { --lum-t: rgba(255, 255, 255, 0.32); }
.block-cell.e-l { --lum-l: rgba(255, 255, 255, 0.18); }
.block-cell.e-b { --omb-b: rgba(98, 74, 106, 0.15); }
.block-cell.e-r { --omb-r: rgba(98, 74, 106, 0.08); }
/* Marque d'un bloc : cadenas, décompte, « ×2 », glyphe de famille. Ses
   dimensions viennent de boardView (`_boiteMarque`) — la boîte du bloc entier
   quand son centre tombe dans la forme, une case sinon. Elle ne dessine rien
   par elle-même : elle centre ce qu'elle contient. */
.block-mark {
  position: absolute;
  display: grid; place-items: center;
  font-size: calc(var(--cell) * 0.36);
  color: rgba(255, 255, 255, 0.6);
  pointer-events: none;
  line-height: 1;
}

/* Pendant la saisie, aucune transition : le bloc doit coller au doigt.
   La transition de 95 ms le faisait traîner derrière et donnait une impression
   de lourdeur. */
.block.grabbed { z-index: 5; transition: scale 120ms ease-out; }
.block.grabbed .block-cell { filter: brightness(1.06); }
/* Held, the block LIFTS: a touch bigger, its shadow further down and softer —
   and it settles back on release (the `scale` transition above). */
.block.grabbed {
  scale: 1.04;
  filter:
    drop-shadow(0 calc(var(--cell) * 0.07) 0 rgba(94, 72, 102, 0.12))
    drop-shadow(0 calc(var(--cell) * 0.3) calc(var(--cell) * 0.36) rgba(94, 72, 102, 0.22));
}
.block.exiting { pointer-events: none; transition-property: transform, opacity; }
.block.bumping { animation: bump 0.13s ease-in-out 2 alternate; }
@keyframes bump { to { filter: brightness(1.12) saturate(1.3); } }

.block.k-anchor, .block.k-bulky { cursor: grab; }
.block.k-wall { cursor: not-allowed; }
/* Le bloc scellé : mat, plus froid, sans reflet. Il ne bouge pas, il ne porte
   rien — c'est un morceau de décor, et il doit se lire comme tel sans pour
   autant se confondre avec un bloc verrouillé, qui garde sa couleur. */
.block.k-wall .block-cell {
  background-color: var(--sealed);
  /* Les rayures disaient « scellé », mais sur cinq ou six blocs elles faisaient
     autant de lignes que le reste réuni. La matière mate suffit : ce bloc-ci
     n'a ni éclat ni symbole, et c'est bien ce qui le distingue. */
  background-image:
    linear-gradient(178deg, rgba(255,255,255,.34) 0%, rgba(255,255,255,0) 46%, rgba(70,60,80,.14) 100%);
}
.block.k-locked { cursor: not-allowed; }
.block.k-locked .block-cell { filter: saturate(0.42) brightness(1.08); }
.block.unlocking .block-cell { animation: unlock 0.44s ease-out; }
@keyframes unlock {
  0% { filter: saturate(0.2) brightness(1.06); }
  45% { filter: saturate(1.25) brightness(1.14); }
  100% { filter: none; }
}

/* Glissière : une double flèche ↔ sur l'axe, tracée par boardView
   (`_axisArrow`) en SVG dont l'unité est la case. La barre translucide qu'elle
   remplace passait inaperçue, et sur un L elle tombait dans la case vide :
   la flèche suit désormais le bras de la forme qui porte l'axe. Deux pointes
   là où l'ancre en a une — un axe, pas un sens. */
.block-rail {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  overflow: visible; pointer-events: none;
}
.block-rail line { stroke: rgba(255, 255, 255, 0.52); stroke-linecap: round; }
.block-rail g { fill: rgba(255, 255, 255, 0.52); stroke: rgba(255, 255, 255, 0.52); stroke-linejoin: round; }

/* Ancre : la même flèche que le rail (`.block-rail`, tracée par boardView),
   avec une seule pointe, vers sa porte. Quand l'ancre est aussi la clé du
   niveau, le losange occupe le centre : la flèche se réduit alors à un glyphe
   dans un coin. */
.block-arrow.arrow-corner {
  position: absolute;
  inset: auto 6% 3% auto;
  font-size: calc(var(--cell) * 0.34); line-height: 1;
  color: rgba(255, 255, 255, 0.55);
  pointer-events: none;
  /* C'est un `<u>` : sans cette ligne le navigateur souligne la flèche. */
  text-decoration: none;
}
/* L'ancre se reconnaît à sa flèche. Le liseré qu'elle portait en plus ajoutait
   un contour à chaque bloc, et c'est la somme de ces contours qui alourdissait
   la grille. */

/* Encombrant : coûte le double à sa porte. Le liseré doublé le dit de loin, le
   « ×2 » porté sur le bloc le dit exactement. */
/* L'encombrant se reconnaît à son « ×2 » : le double liseré faisait double
   emploi, et deux traits de plus par bloc. */
/* Décompte d'un verrou : une pastille claire, lisible sans crier. Elle est
   portée par le décompte et non par la marque qui l'entoure — celle-ci couvre
   désormais tout le bloc, et le fond aurait recouvert le bloc entier. */
.lock-count {
  display: grid; place-items: center;
  min-width: 1.5em; padding: 0.1em 0.35em;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.42);
  color: rgba(70, 55, 75, 0.62);
  font-size: calc(var(--cell) * 0.28);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
/* Rien à poser derrière un verrou ouvert, un cadenas ou une pastille de
   couleur : chacun porte déjà sa propre forme. */
.lock-count:empty, .lock-count.lock-padlock, .lock-count.lock-color {
  background: none; padding: 0; min-width: 0;
}

/* Cadenas d'un verrou à clé : blanc laiteux, à peine gris, posé au centre du
   bloc. L'ombre portée le détache aussi bien d'un bloc pâle que d'un foncé. */
.padlock {
  width: calc(var(--cell) * 0.46); height: calc(var(--cell) * 0.46);
  filter: drop-shadow(0 1px 1.5px rgba(72, 56, 80, 0.3));
}
/* Softened (2026-09-30): the marks are guessed, not shouted. */
.padlock rect { fill: rgba(246, 243, 247, 0.62); }
.padlock path { stroke: rgba(246, 243, 247, 0.56); }
.padlock circle { fill: rgba(126, 110, 132, 0.45); }

.weight-mark b {
  font-size: calc(var(--cell) * 0.3);
  font-variant-numeric: tabular-nums;
  opacity: 0.62;
}

/* Scellé de couleur : une pastille de la couleur qu'il attend. */
.lock-count.lock-color {
  width: calc(var(--cell) * 0.26); height: calc(var(--cell) * 0.26);
  border-radius: 50%;
  background: var(--expected);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, 0.6);
}

/* Bloc double : deux couleurs, donc deux moitiés. La diagonale se lit d'un coup
   d'œil et ne se confond pas avec le joker, qui balaie quatre teintes. */
.block.k-dual .block-cell {
  background-color: transparent;
  background-image: var(--reflet), linear-gradient(118deg, var(--tile) 0 47%, var(--c-alt) 53% 100%);
}

/* Porte partagée : elle sert deux familles, et le dit de la même façon. */
.gate.gate-shared { background-image: linear-gradient(118deg, var(--tile) 0 47%, var(--c-alt) 53% 100%); }

/* La clé porte déjà son symbole ; un liseré de plus n'apprenait rien. */

/* Joker : sort par n'importe quelle porte. */
.block.k-joker .block-cell {
  background-color: transparent;
  background-image: var(--reflet),
    linear-gradient(135deg, var(--c0), var(--c3) 34%, var(--c2) 62%, var(--c1));
}

.block.smashed { pointer-events: none; animation: smash 0.32s ease-in forwards; }
@keyframes smash { to { transform: scale(0.3) rotate(14deg); opacity: 0; } }

/* L'indice : un fantôme du bloc qui fait le geste (BoardView.ghost). Son trajet
   est libre, il passe donc au-dessus de la grille sans rien cacher, et ne capte
   aucun toucher. */
.block.ghost {
  z-index: 3; pointer-events: none; opacity: 0;
  /* Un halo de sa propre couleur sur la silhouette (drop-shadow suit la forme,
     pas la boîte) : le fantôme de CE bloc. Le joker, sans couleur, prend l'encre. */
  --ghost-ink: var(--tile, var(--ink));
  filter: drop-shadow(0 0 calc(var(--cell) * 0.1) var(--ghost-ink)) drop-shadow(0 0 calc(var(--cell) * 0.22) var(--ghost-ink));
}

/* ---------- Sortie d'un bloc : la récompense ---------- */

.fx-layer { position: absolute; inset: 0; pointer-events: none; z-index: 6; overflow: visible; }

/* Realm-complete celebration: a screen-sized version of the spark above,
   scaled up and freed from any one block's colour (see render/confetti.js). */
.confetti-layer { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 2; }
.confetti-piece {
  position: absolute; top: -12px; left: var(--x);
  width: 8px; height: 12px; border-radius: 2px;
  transform: rotate(0deg);
  animation: confetti-fall var(--fall) cubic-bezier(0.3, 0.1, 0.4, 1) var(--delay) forwards;
}
@keyframes confetti-fall {
  0% { transform: translate(0, -10%) rotate(0deg); opacity: 0; }
  10% { opacity: 1; }
  100% { transform: translate(var(--drift), 115vh) rotate(var(--rot)); opacity: 0.9; }
}

/* Easter egg snowfall (see render/confetti.js's `snow()`) — fixed to the
   viewport, since it can be triggered from the map rather than from inside
   one card like the confetti burst above. */
.snow-layer { position: fixed; inset: 0; overflow: hidden; pointer-events: none; z-index: 40; }
.snow-piece {
  position: absolute; top: -10%; left: var(--x);
  animation: snow-fall var(--fall) linear var(--delay) forwards;
}
@keyframes snow-fall {
  0% { transform: translate(0, -10vh) rotate(0deg); opacity: 0; }
  8% { opacity: 1; }
  100% { transform: translate(var(--drift), 115vh) rotate(var(--rot)); opacity: 0.9; }
}

.spark {
  position: absolute; border-radius: 50%;
  background: var(--tile);
  margin: calc(var(--cell) * -0.07) 0 0 calc(var(--cell) * -0.07);
  animation: spark 620ms cubic-bezier(0.15, 0.75, 0.35, 1) both;
}
@keyframes spark {
  0% { transform: translate3d(0, 0, 0) scale(0.5); opacity: 0; }
  18% { opacity: 1; }
  100% { transform: translate3d(var(--dx), var(--dy), 0) scale(0.18); opacity: 0; }
}

.ring {
  position: absolute; border-radius: 50%;
  border: 2px solid var(--tile);
  transform: translate(-50%, -50%);
  animation: ring 560ms cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

/* The grid is empty (boardView.finale): a wave of light sweeps across it. */
.sweep {
  position: absolute; inset: 0; border-radius: 10px; overflow: hidden;
  background: linear-gradient(105deg, transparent 35%, rgba(255, 255, 255, 0.8) 50%, transparent 65%);
  background-size: 300% 100%;
  animation: sweep 0.75s cubic-bezier(0.4, 0, 0.3, 1) both;
}
@keyframes sweep {
  from { background-position: 100% 0; opacity: 1; }
  80% { opacity: 1; }
  to { background-position: 0 0; opacity: 0; }
}
@keyframes ring {
  0% { transform: translate(-50%, -50%) scale(0.25); opacity: 0.85; }
  100% { transform: translate(-50%, -50%) scale(2.6); opacity: 0; }
}

.gate.flash { animation: gate-flash 0.5s ease-out; }

/* Couleurs des blocs : posées par ui/theme.js, une palette par monde. Les
   valeurs de :root ne servent que de repli avant le premier niveau chargé. */
.c0 { --tile: var(--c0); }
.c1 { --tile: var(--c1); }
.c2 { --tile: var(--c2); }
.c3 { --tile: var(--c3); }
.c4 { --tile: var(--c4); }
.c5 { --tile: var(--c5); }

/* ---------- Colour patterns (accessibility option) ----------
   With the option on (`.with-glyphs` on the app), every colour id carries a
   texture over the whole block and over its gates, so a block and its gate
   can be matched without telling hues apart:
     c0 fine diagonal stripes · c1 polka dots · c2 checks · c3 zebra bands ·
     c4 leopard rings · c5 grid.
   The ids are stable across worlds (only the palette changes), so each
   texture keeps its meaning all game long.
   Seamless across a polyomino: the tile `--pat-t` is a whole number of pixels
   (boardView.layout) and each cell shifts the pattern by its offset in the
   block (`--pat-pos`), like the highlight — one texture over the block, not
   one per cell. Stripes and zebra are square tiles whose stops repeat at half
   the diagonal, so they tile exactly. The ink is a translucent dark plum: it
   reads on every pastel, light or deep. */
.c0 { --pat: linear-gradient(45deg, var(--pat-ink) 0 18%, transparent 18% 50%, var(--pat-ink) 50% 68%, transparent 68%);
      --pat-size: var(--pat-t) var(--pat-t); }
.c1 { --pat: radial-gradient(circle, var(--pat-ink) 0 28%, transparent 30%);
      --pat-size: var(--pat-t) var(--pat-t); }
.c2 { --pat: conic-gradient(var(--pat-ink) 0 25%, transparent 0 50%, var(--pat-ink) 0 75%, transparent 0);
      --pat-size: calc(var(--pat-t) * 2) calc(var(--pat-t) * 2); }
.c3 { --pat: linear-gradient(-45deg, var(--pat-ink) 0 19%, transparent 19% 28%, var(--pat-ink) 28% 33%, transparent 33% 50%,
        var(--pat-ink) 50% 69%, transparent 69% 78%, var(--pat-ink) 78% 83%, transparent 83%);
      --pat-size: calc(var(--pat-t) * 2) calc(var(--pat-t) * 2); }
.c4 { --pat: radial-gradient(circle at 30% 30%, transparent 0 9%, var(--pat-ink) 10% 17%, transparent 18%),
             radial-gradient(circle at 78% 72%, var(--pat-ink) 0 10%, transparent 11%);
      --pat-size: calc(var(--pat-t) * 2) calc(var(--pat-t) * 2); }
.c5 { --pat: linear-gradient(var(--pat-ink) 0 14%, transparent 0),
             linear-gradient(90deg, var(--pat-ink) 0 14%, transparent 0);
      --pat-size: var(--pat-t) var(--pat-t); }
.app.with-glyphs { --pat-ink: rgba(70, 40, 80, 0.26); }
.app.with-glyphs .block[class*=" c"]:not(.k-joker) .block-cell::before,
.app.with-glyphs .gate[class*=" c"]::before {
  content: ''; position: absolute; inset: 0;
  border-radius: inherit; pointer-events: none;
  background-image: var(--pat, none);
  background-size: var(--pat-size, auto);
  background-position: var(--pat-pos, 0 0);
}
/* The gate is only a few pixels thick: its texture is drawn a notch finer. */
.app.with-glyphs .gate { --pat-t: calc(var(--wall) * 0.6); }

/* Annonce d'entrée dans un monde : ce que le joueur va rencontrer pour la
   première fois. Ne s'affiche qu'au premier niveau du monde — répétée vingt
   fois, elle ne serait plus lue. */
.brief-novelty {
  margin: 12px auto 0; padding: 9px 14px; max-width: 260px;
  background: var(--accent-wash); border-radius: 14px;
  color: var(--accent-deep); font: 400 13px/1.35 var(--font-ui);
}

/* Choix de la langue, dans le menu utilisateur. */
.user-langue { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 10px; }
.user-langue > span { color: var(--ink-soft); font-size: 14px; }
.language-choice { display: flex; }
.language-select {
  appearance: none;
  padding: 6px 30px 6px 12px;
  background: var(--ground) no-repeat right 11px center / 9px;
  /* Chevron dessiné en data:URI — la politique de sécurité d'un Artifact
     interdit de charger une image depuis un autre hôte. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a7d88' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink); font: 400 13px var(--font-ui);
  cursor: pointer;
  transition: border-color 0.16s ease;
}
.language-select:hover, .language-select:focus-visible { border-color: var(--accent); outline: none; }

/* La tuile « pièces » du menu est un bouton : elle ouvre la boutique. Elle garde
   l'allure des deux autres tuiles, avec juste ce qu'il faut pour qu'on devine
   qu'elle répond — une tuile qui réagit sans le montrer ne se clique jamais. */
.stat-action {
  border: 1px solid var(--line); cursor: pointer;
  font: inherit; text-align: center;
  transition: border-color 0.16s ease, transform 0.14s ease;
}
.stat-action:hover { border-color: var(--accent); }
.stat-action:active { transform: scale(0.97); }

/* Le temps mis, sous le nombre de glissés : présent sans concurrencer les
   étoiles, qui restent la note du niveau. */
.result-time {
  margin: -8px 0 10px;
  color: var(--ink-faint); font: 400 13px var(--font-ui);
  font-variant-numeric: tabular-nums;
}

/* Home screen's top-left corner: the lives, then the streak badge. Only on
   the home screen (see index.html), so nothing sits on the other screens'
   back arrows. */
.menu-corner {
  position: absolute; top: max(18px, env(safe-area-inset-top)); left: 18px; z-index: 18;
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; max-width: calc(100% - 150px);
}
.streak-badge {
  padding: 7px 12px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink-soft); font: 500 13px var(--font-ui);
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  transition: transform 0.2s ease, background 0.2s ease;
}
/* Today's gift waiting in the badge: accent, a slow breath, the amount in gold. */
.streak-badge.gift {
  background: var(--accent-wash); border-color: var(--accent); color: var(--accent-deep);
  animation: gift-breathe 2.4s ease-in-out infinite, wiggle 3.6s ease-in-out 1s infinite;
}
@keyframes gift-breathe { 50% { box-shadow: 0 0 0 5px hsl(var(--h) 60% 70% / 0.18); } }
.streak-badge.claimed { animation: gift-pop 0.42s cubic-bezier(0.2, 1.5, 0.4, 1) both; }
@keyframes gift-pop { 40% { transform: scale(1.12); } }
@media (prefers-reduced-motion: reduce) { .streak-badge.gift, .streak-badge.claimed { animation: none; } }

/* "Bonjour Prénom" — only for a real account, on the opposite corner from the
   streak badge so the two never fight for the same space. `right` clears the
   38px `.user-btn` circle plus its own gap: both used to sit at the same
   `right: 18px` and overlapped outright. */
.menu-greeting {
  position: absolute; top: max(18px, env(safe-area-inset-top)); right: 64px; z-index: 18;
  display: flex; align-items: center; gap: 7px;
  padding: 5px 12px 5px 5px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--line);
  color: var(--ink-soft); font: 500 13px var(--font-ui);
  cursor: pointer; max-width: 38vw;
}
.menu-greeting-avatar {
  width: 24px; height: 24px; border-radius: 50%; object-fit: cover;
  background: var(--accent-wash); flex-shrink: 0;
}
.menu-greeting-text {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}


/* Écran de défaite : UNE offre principale, une option discrète, une sortie.
   Trois boutons de même poids ne se choisissent pas, ils se subissent. */
.offer-oups { font: 500 26px var(--font-display); margin-bottom: 2px; }
.offer-emoji { font-size: 40px; line-height: 1.1; margin-bottom: 6px; }
.offer-lead { color: var(--ink-soft); font: 400 15px var(--font-ui); margin-bottom: 16px; }

.offer-main {
  width: 100%; display: grid; gap: 2px; padding: 14px 12px;
  background: linear-gradient(160deg, hsl(150 46% 92%), hsl(150 42% 86%));
  border: 1px solid hsl(150 38% 74%); border-radius: 20px;
  color: hsl(150 40% 26%);
  box-shadow: 0 8px 20px hsl(150 30% 60% / 0.25);
}
.offer-main-title { font: 500 18px var(--font-ui); }
.offer-main-sub { font-size: 13px; opacity: 0.8; }
.offer-main-free { font: 500 13px var(--font-ui); letter-spacing: 0.02em; }

.offer-second { margin-top: 10px; font-size: 13px; }
.offer-second b { font-variant-numeric: tabular-nums; }
.offer-quit { margin-top: 4px; }

/* ---------- Vies ---------- */

/* La pastille : même gabarit que le compteur de coups. Le minuteur n'apparaît
   que s'il manque un cœur (`:empty` sinon) — plein, il n'y a rien à attendre. */
.lives-pill { display: inline-flex; align-items: center; gap: 5px; font-variant-numeric: tabular-nums; }
.lives-pill .heart { width: 15px; height: 15px; flex: none; }
.lives-pill .heart path { fill: var(--heart-b); }
.lives-pill.empty .heart path { fill: var(--heart-empty); }
.lives-n { font-weight: 500; color: var(--ink); }
.lives-timer { color: var(--ink-faint); }
.lives-timer:not(:empty)::before { content: '· '; }
.menu-lives { margin: 0; }
.topbar-pills { display: inline-flex; gap: 6px; justify-self: end; }

button.lives-pill { font-family: inherit; cursor: pointer; }

/* La carte des vies (et « Petite pause ») : rien de rouge, un minuteur
   discret, la pub et les pièces proposées. Chaque case superpose un cœur
   blanc cerclé de rose et un cœur plein, découpé à ce qu'il en reste. */
.lives-row { display: flex; justify-content: center; gap: 6px; margin-bottom: 6px; }
.lives-slot { position: relative; width: 38px; height: 38px; }
.lives-slot .heart { position: absolute; inset: 0; width: 100%; height: 100%; }
.lives-slot .heart:first-child path { fill: var(--heart-hollow); stroke: var(--heart-a); stroke-width: 1.3; }
.lives-slot .heart:last-child path { fill: var(--heart-b); }
.lives-slot .heart:last-child { filter: drop-shadow(0 2px 5px rgb(232 142 166 / 0.3)); transition: clip-path 0.4s ease; }
.lives-slot[data-fill='half'] .heart:last-child { clip-path: inset(0 50% 0 0); }
.lives-slot[data-fill='none'] .heart:last-child { clip-path: inset(0 100% 0 0); }
.lives-card h2 { margin: 10px 0 4px; }
.lives-card .offer-second { width: 100%; margin-top: 8px; }
.lives-clock {
  display: inline-block; margin: 8px 0 18px; padding: 5px 14px; border-radius: 999px;
  background: color-mix(in srgb, var(--heart-a) 22%, var(--surface));
  color: var(--heart-b); font: 500 15px var(--font-ui); font-variant-numeric: tabular-nums;
}
.lives-ad {
  width: 100%; padding: 14px 12px; border-radius: 20px;
  background: linear-gradient(160deg, var(--heart-a), var(--heart-b));
  border: 0; color: #fff; font-weight: 600;
  box-shadow: 0 8px 20px rgb(236 147 171 / 0.3);
}

/* Signalement. */
.feedback-card { max-width: 340px; text-align: left; }
.feedback-card h2 { text-align: center; }
.fb-cats { display: flex; gap: 6px; margin: 10px 0; }
.fb-cat {
  flex: 1; padding: 7px 4px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 999px;
  color: var(--ink-soft); font: 400 13px var(--font-ui); cursor: pointer;
  transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
}
.fb-cat.sel { background: var(--accent-wash); border-color: var(--accent); color: var(--accent-deep); }
.fb-message {
  width: 100%; padding: 10px 12px; resize: vertical;
  background: var(--ground); border: 1px solid var(--line); border-radius: 14px;
  color: var(--ink); font: 400 14px/1.4 var(--font-ui);
}
.fb-message:focus-visible { border-color: var(--accent); outline: none; }
.fb-add {
  display: block; margin: 8px 0; padding: 8px;
  border: 1px dashed var(--line); border-radius: 12px;
  color: var(--ink-soft); font: 400 13px var(--font-ui); text-align: center; cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.fb-add:hover { border-color: var(--accent); color: var(--accent-deep); }
.fb-shots { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.fb-shot {
  width: 54px; height: 54px; padding: 0; overflow: hidden;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--ground); cursor: pointer;
}
.fb-shot img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fb-shot:hover { border-color: var(--alert); }
.fb-actions { display: flex; gap: 6px; margin: 10px 0 4px; }
.fb-actions .btn { flex: 1; }

/* Historique des grilles créées. */
.mine-list { list-style: none; display: grid; gap: 6px; margin: 10px 0 14px; text-align: left; }
.mine-list li { display: flex; gap: 6px; align-items: stretch; }
.mine-open {
  flex: 1; display: grid; gap: 1px; padding: 9px 12px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 12px;
  color: var(--ink); font: inherit; text-align: left; cursor: pointer;
  transition: border-color 0.16s ease;
}
.mine-open:hover { border-color: var(--accent); }
.mine-open b { font: 500 14px var(--font-ui); }
.mine-open small { color: var(--ink-faint); font-size: 12px; }
.mine-del {
  width: 36px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--ground); color: var(--ink-faint);
  font-size: 17px; line-height: 1; cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.mine-del:hover { border-color: var(--alert); color: var(--alert); }

/* Boutique. */
.shop-card { max-width: 340px; }
.shop-balance { margin: 4px 0 14px; color: var(--ink-soft); font: 400 15px var(--font-ui); }
.shop-balance b { color: var(--ink); font-size: 22px; font-variant-numeric: tabular-nums; }
.shop-ad { width: 100%; }
.shop-ad[disabled] { opacity: 0.45; pointer-events: none; }
#shop-ad-note { margin: 6px 0 18px; }

/* Puzzle du jour : l'entrée du menu porte son titre en second niveau. */

/* Classement du jour. */
.rank-mine { margin: 6px 0 12px; color: var(--accent-deep); font: 500 15px var(--font-ui); }
.rank-reward { margin: -4px 0 12px; padding: 8px 12px; border-radius: 12px; background: var(--accent-wash); color: var(--accent-deep); font: 500 14px var(--font-ui); }
.rank-list { list-style: none; display: grid; gap: 4px; margin: 0 0 12px; text-align: left; }
.rank-list li {
  display: flex; justify-content: space-between; align-items: baseline; gap: 10px;
  padding: 7px 12px; border-radius: 12px;
  background: var(--ground); color: var(--ink-soft);
  font: 400 14px var(--font-ui);
}
.rank-list li.me { background: var(--accent-wash); color: var(--accent-deep); font-weight: 500; }
.rank-list b { font-variant-numeric: tabular-nums; }
/* Carte « ce que fait ce bloc » (toucher un bloc spécial) : le niveau reste
   visible derrière — on regarde le bloc dont on lit l'explication. Voile léger,
   à peine flouté, et carte légèrement translucide. */
#overlay-info { background: hsl(var(--h) 30% 96% / 0.35); backdrop-filter: blur(1.5px); align-items: end; padding-bottom: 110px; }
#overlay-info .info-card { background: hsl(var(--h) 40% 99% / 0.9); box-shadow: 0 12px 34px hsl(var(--h) 30% 30% / 0.18); }
.info-card { max-width: 340px; }
.info-icon {
  width: 56px; height: 56px; margin: 0 auto 10px; display: grid; place-items: center;
  border-radius: 18px; background: var(--accent-wash); color: var(--accent-deep); font: 600 24px var(--font-ui);
}
.info-card { position: relative; }
.info-close {
  position: absolute; top: 10px; right: 10px; width: 32px; height: 32px;
  display: grid; place-items: center; border: 0; border-radius: 50%;
  background: transparent; color: var(--ink-soft); font: 300 24px/1 var(--font-ui); cursor: pointer;
}
.info-close:hover { background: var(--line); color: var(--ink); }
.info-icon:has(.info-preview) { width: fit-content; height: auto; min-width: 64px; padding: 12px; border-radius: 16px; background: var(--ground); }
.info-preview { position: relative; pointer-events: none; }
.info-text { margin: 6px 0 4px; color: var(--ink-soft); font: 400 15px/1.5 var(--font-ui);; white-space: pre-line; }
.info-icon .action-icon { width: 30px; height: 30px; }
/* Quêtes du jour : icône, intitulé + barre, bouton de récolte. */
/* A little shake every few seconds — the streak badge while its gift waits. */
@keyframes wiggle {
  0%, 82%, 100% { transform: rotate(0); }
  85% { transform: rotate(-14deg) scale(1.12); }
  88% { transform: rotate(11deg) scale(1.12); }
  91% { transform: rotate(-7deg); }
  94% { transform: rotate(4deg); }
}
.quest-list { list-style: none; display: grid; gap: 8px; margin: 8px 0 14px; padding: 0; text-align: left; }
.quest { display: grid; grid-template-columns: 30px 1fr auto; gap: 10px; align-items: center; padding: 10px 12px; border-radius: 14px; background: var(--ground); }
.quest.claimed { opacity: 0.55; }
.quest-icon { font-size: 20px; text-align: center; }
.quest-body b { display: block; font: 500 14px var(--font-ui); color: var(--ink); }
.quest-body small { color: var(--ink-soft); font-size: 11px; }
.quest-bar { height: 6px; margin: 6px 0 3px; border-radius: 3px; background: var(--line); overflow: hidden; }
.quest-bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; transition: width 0.4s; }
.quest-claim { min-width: 64px; }
.quest-chest { width: 100%; margin-bottom: 10px; }
/* Classement des joueurs : rang, pseudo, niveau atteint, étoiles. */
.board-card { max-height: 86vh; display: flex; flex-direction: column; }
.board-list { overflow-y: auto; min-height: 0; }
.board-list li { display: grid; grid-template-columns: 2.2em 1fr auto auto; gap: 10px; align-items: center; }
.board-list i { font-style: normal; color: var(--ink-faint); font-variant-numeric: tabular-nums; }
.board-list small { color: var(--ink-soft); font-size: 11px; }

/* Bouton pleine largeur du menu utilisateur (éditeur). */
.user-large { width: 100%; }

/* Échec de démarrage : la base de niveaux n'a pas pu être lue. */
.boot-error {
  position: absolute; inset: 0;
  display: grid; align-content: center; gap: 10px; padding: 32px; text-align: center;
  color: var(--ink-soft); font: 400 14px/1.5 var(--font-ui);
}
.boot-error h1 { color: var(--ink); font: 500 20px var(--font-display); }
.boot-error code {
  padding: 2px 6px; border-radius: 6px;
  background: var(--accent-wash); color: var(--accent-deep); font-size: 13px;
}

/* ---------- Actions en partie ---------- */

.game-actions { display: flex; gap: 7px; margin-top: 14px; }
.action {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 9px 4px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
  color: var(--ink-soft); font: 400 13px var(--font-ui);
  cursor: pointer;
  transition: border-color 0.16s ease, color 0.16s ease;
}
.action:hover { border-color: var(--accent); color: var(--ink); }
.action:active { transform: scale(0.97); }
.action[disabled] { opacity: 0.4; pointer-events: none; }
/* The bulb thinking (an exact search, up to a couple of seconds on a slow phone). */
.action[aria-busy="true"] { opacity: 0.5; pointer-events: none; }
.action-icon {
  width: 24px; height: 24px;
  fill: none; stroke: var(--accent-deep); stroke-width: 1.3;
  stroke-linecap: round; stroke-linejoin: round;
}
.action:hover .action-icon { stroke: var(--ink); }
.action-plain { flex: 0 0 46px; }
.action-cost {
  padding: 1px 7px; border-radius: 999px;
  background: var(--accent-wash); color: var(--accent-deep);
  font-size: 10px; font-variant-numeric: tabular-nums;
}
/* Un bonus obtenu par pub est signalé comme tel : le joueur doit savoir ce
   qu'il déclenche avant d'appuyer. */
.action-cost.ad { background: var(--accent-wash); color: var(--ink-soft); }
/* Before level 5 the bar holds Restart alone, on the right as usual. */
.game-actions.no-boosters .action-booster { display: none; }
.game-actions.no-boosters { justify-content: flex-end; }

/* Mode marteau : on désigne la cible, le plateau le dit clairement. */
.app.hammer .board { box-shadow: 0 0 0 3px var(--alert), 0 10px 28px hsl(var(--h) 30% 60% / 0.13); }
.app.hammer .block:not(.k-wall) { cursor: crosshair; }
/* Offert par la série de victoires : orange « flamme », pour ne pas le
   confondre avec le vert de la pub. */
.action-cost.free { background: var(--accent-wash); color: var(--accent-deep); font-weight: 600; }

/* ---------- Superpositions ---------- */

.overlay {
  position: absolute; inset: 0; z-index: 20;
  display: grid; place-items: center; padding: 26px;
  background: hsl(var(--h) 30% 96% / 0.86);
  backdrop-filter: blur(8px);
  animation: screen-in 0.26s ease both;
}
.result-card, .offer-card, .realm-card {
  width: 100%; padding: 30px 24px 24px;
  background: var(--surface);
  border: 1px solid var(--line); border-radius: 26px;
  box-shadow: 0 18px 44px hsl(var(--h) 30% 50% / 0.14);
  text-align: center;
  animation: card-in 0.42s cubic-bezier(0.2, 1.3, 0.4, 1) both;
}
@keyframes card-in { from { transform: scale(0.94) translateY(14px); opacity: 0; } }

.result-card h2, .offer-card h2, .realm-card h2 {
  font: 300 26px var(--font-display);
  font-variation-settings: 'SOFT' 40;
  margin-bottom: 20px;
}

.realm-card { position: relative; z-index: 1; }
.realm-done-name {
  font: 400 17px var(--font-ui); color: var(--accent-deep); margin-bottom: 14px;
}
.realm-reward { color: var(--award); margin: 14px 0 22px; }
.realm-next {
  margin: 6px 0 24px; padding: 16px; border-radius: 16px;
  background: var(--ground); border: 1px solid var(--line);
}
.realm-next-label {
  display: block; font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-faint); margin-bottom: 6px;
}
.realm-next-name { font: 400 19px var(--font-display); margin-bottom: 4px; }
.realm-next-intro[hidden] { display: none; }
.result-score { margin: 20px 0 6px; }
.result-score span { font: 400 40px var(--font-ui); font-variant-numeric: tabular-nums; }
.result-score small {
  display: block; color: var(--ink-faint); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
}
.result-near {
  margin: 8px 0 0; padding: 9px 14px;
  background: var(--accent-wash); border-radius: 12px;
  color: var(--accent-deep); font-size: 13px;
}
.result-reward { color: var(--award); margin: 14px 0 18px; min-height: 21px; }

/* Levels won in a row. Three tiers, each a notch bigger, warmer and livelier
   than the last: the flame grows and flickers faster, the count rolls from
   N-1 to N (resultScreen.js adds `.rolled`). */
.result-streak {
  display: inline-flex; align-items: center; gap: 2px;
  margin: -8px 0 18px; padding: 6px 14px 6px 10px;
  border-radius: 999px; font: 500 13px var(--font-ui);
  font-variant-numeric: tabular-nums;
  background: hsl(28 70% 93%); color: hsl(28 60% 40%);
}
.result-streak[hidden] { display: none; }
.streak-flame {
  width: 13px; height: 17px; margin-right: 5px; fill: hsl(24 85% 62%);
  transform-origin: 50% 90%;
  animation: flame-flicker 1.6s ease-in-out infinite;
}
.result-streak.streak-2 { font-size: 14px; background: hsl(20 75% 90%); color: hsl(20 65% 42%); }
.result-streak.streak-2 .streak-flame { width: 16px; height: 21px; fill: hsl(16 88% 60%); animation-duration: 1.2s; }
.result-streak.streak-3 { font-size: 16px; font-weight: 600; background: hsl(12 80% 88%); color: hsl(12 70% 42%); }
.result-streak.streak-3 .streak-flame {
  width: 19px; height: 25px; fill: hsl(8 90% 60%); animation-duration: 0.9s;
  filter: drop-shadow(0 0 4px hsl(30 95% 65% / 0.7));
}
@keyframes flame-flicker {
  0%, 100% { transform: scale(1, 1) rotate(0); }
  25% { transform: scale(0.94, 1.08) rotate(-3deg); }
  50% { transform: scale(1.04, 0.95) rotate(2deg); }
  75% { transform: scale(0.97, 1.05) rotate(-1deg); }
}
/* The count's window: N-1 rolls up and out, N comes in from below. */
.streak-num {
  display: inline-grid; overflow: hidden; height: 1.25em; line-height: 1.25em;
  vertical-align: middle;
}
.streak-num i {
  grid-area: 1 / 1; font-style: normal; text-align: center;
  transition: transform 0.36s cubic-bezier(0.3, 1.3, 0.5, 1), opacity 0.3s ease;
}
.streak-num .new { transform: translateY(100%); opacity: 0; }
.result-streak.rolled .streak-num .old { transform: translateY(-100%); opacity: 0; }
.result-streak.rolled .streak-num .new { transform: none; opacity: 1; }

/* ---------- Win sequence (resultScreen.js) ---------- */

/* Played by the Web Animations API: the CSS entrances stand aside. */
.result-card.seq { animation: none; }
.result-card.seq .stars-lg i.on { animation: none; }
/* Waiting for its turn: invisible, but holding its place — the card never
   changes size as the sequence fills it. */
.result-card .pending { opacity: 0; pointer-events: none; }
/* "Next", once the rest has landed: a slow, soft breath. */
.btn.pulse { animation: soft-pulse 2.2s ease-in-out infinite; }
@keyframes soft-pulse {
  0%, 100% { scale: 1; box-shadow: 0 0 0 0 hsl(var(--h) 60% 65% / 0); }
  50% { scale: 1.035; box-shadow: 0 0 0 6px hsl(var(--h) 60% 65% / 0.16); }
}

/* The purse (resultScreen.js `flyShards`), in the middle of the card, with
   "Double" beside it. The card sits above the flying shards' first leg, so
   they come out from behind it. */
.result-card { position: relative; z-index: 1; }
.result-gain {
  display: flex; justify-content: center; align-items: center; gap: 8px;
  margin: -8px 0 18px;
}
.result-gain[hidden] { display: none; }
.result-purse {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 6px 14px; font-size: 15px;
  color: var(--ink); font-weight: 500;
}
/* "Double", in the shards' amber, breathing a soft glow like "Next". */
.btn-double {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 14px; border-radius: 999px; cursor: pointer;
  font: 600 12px var(--font-ui); color: #fff;
  background: linear-gradient(135deg, hsl(44 88% 66%) 0%, hsl(38 78% 55%) 100%);
  border: 1px solid hsl(40 70% 58%);
  text-shadow: 0 1px 1px hsl(34 60% 38% / 0.35);
  animation: double-glow 2.2s ease-in-out infinite;
}
@keyframes double-glow {
  0%, 100% { scale: 1; box-shadow: 0 2px 6px hsl(40 80% 50% / 0.25), 0 0 0 0 hsl(42 90% 62% / 0); }
  50% { scale: 1.04; box-shadow: 0 2px 8px hsl(40 80% 50% / 0.3), 0 0 0 6px hsl(42 90% 62% / 0.2); }
}
.btn-double span:first-child { font-size: 9px; }
.btn-double[hidden] { display: none; }
.btn-double[disabled] { opacity: 0.5; pointer-events: none; }
.result-purse.bump { animation: purse-bump 0.32s cubic-bezier(.3,1.6,.5,1); }
@keyframes purse-bump { 40% { scale: 1.12; } }
.result-purse.bump .shard { animation: shard-glint 0.32s ease-out; }
@keyframes shard-glint { 40% { filter: brightness(1.35) drop-shadow(0 0 4px hsl(42 90% 70%)); } }

/* A shard: a small faceted gem in the award's amber. */
.shard {
  display: inline-block; width: 13px; height: 15px; flex: none;
  background:
    linear-gradient(115deg, hsl(45 95% 82%) 0 38%, transparent 38%),
    linear-gradient(245deg, hsl(36 75% 52%) 0 40%, transparent 40%),
    var(--award);
  clip-path: polygon(50% 0, 100% 32%, 82% 100%, 18% 100%, 0 32%);
}
.shard-flying {
  position: absolute; z-index: 0; pointer-events: none;
  width: 12px; height: 14px;
  filter: drop-shadow(0 0 4px hsl(42 90% 72% / 0.8));
}
.shard-flying.over { z-index: 30; }

/* A replay's new best, or the 3-star target when the player fell short. */
.result-goal {
  margin: 6px 0 0; font-size: 12px; color: var(--ink-faint);
  letter-spacing: 0.02em;
}
.result-goal[hidden] { display: none; }
.result-goal.best { color: var(--award); font-weight: 500; }

/* The flower (meta/bloom.js): a petal per new level won, a gift at five.
   A small pill, no wider than the purse and "Double" above it. */
.result-bloom {
  display: flex; align-items: center; gap: 8px;
  width: fit-content; max-width: 100%; margin: -8px auto 14px; padding: 4px 13px 4px 5px;
  border-radius: 999px; background: var(--ground); border: 1px solid var(--line-soft);
  text-align: left;
}
.result-bloom[hidden] { display: none; }
/* Still growing, a tap explains it (main.js `explainBloom`); opened, it is a gift, not a question. */
.result-bloom.explains { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.result-bloom.explains:active { scale: 0.98; }
.info-icon .bloom-flower { flex-basis: 42px; width: 42px; height: 42px; }
/* Opened and not yet picked: it calls for the finger with a little sway. */
.result-bloom.ready { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.result-bloom.ready:active { scale: 0.97; }
.result-bloom.ready .bloom-flower { animation: bloom-call 1.6s ease-in-out infinite; }
@keyframes bloom-call {
  0%, 55%, 100% { transform: rotate(0); }
  65% { transform: rotate(-12deg); } 75% { transform: rotate(10deg); } 85% { transform: rotate(-5deg); }
}
/* A hint or a hammer the flower gave: it rises out of it and floats away. */
.bloom-present {
  position: absolute; z-index: 30; pointer-events: none;
  display: inline-flex; align-items: center; gap: 4px;
  padding: 7px 11px 7px 8px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--accent);
  color: var(--accent-deep); font: 600 14px var(--font-ui);
  box-shadow: 0 6px 18px hsl(var(--h) 50% 50% / 0.22), 0 0 0 6px hsl(var(--h) 70% 80% / 0.25);
}
.bloom-present svg { width: 22px; height: 22px; }
.bloom-flower { flex: 0 0 26px; height: 26px; }
.bloom-flower svg { width: 100%; height: 100%; overflow: visible; }
.bloom-flower .petal, .bloom-flower .lit, .bloom-flower .spark {
  transform-box: view-box; transform-origin: 20px 20px;
}
.bloom-flower .spark { transform-origin: 20px 3.2px; fill: #fff; opacity: 0; }
.bloom-flower .bed { fill: var(--line-soft); }
.bloom-flower .lit { fill: url(#bloom-petal); opacity: 0; }
.bloom-flower .gloss { fill: #fff; opacity: 0; }
.bloom-flower .on .lit { opacity: 1; }
.bloom-flower .on .gloss { opacity: 0.55; }
.bloom-flower .stop-base { stop-color: hsl(var(--h) 90% 93%); }
.bloom-flower .stop-mid { stop-color: hsl(var(--h) 70% 78%); }
.bloom-flower .stop-tip { stop-color: hsl(var(--h) 58% 62%); }
.bloom-flower .heart { fill: url(#bloom-heart); }
.bloom-body { min-width: 0; line-height: 1.25; }
.bloom-text { font: 500 12px var(--font-ui); color: var(--ink-soft); }
.bloom-gift { font-size: 12px; color: var(--award); font-weight: 500; }
.bloom-gift[hidden] { display: none; }
/* Opened: the row warms up, the flower breathes. */
.result-bloom.open { border-color: var(--accent); background: var(--accent-wash); }
.result-bloom.open .bloom-text { color: var(--accent-deep); }
.result-bloom.open .bloom-flower svg { animation: bloom-breathe 2.4s ease-in-out infinite; }
@keyframes bloom-breathe {
  0%, 100% { filter: drop-shadow(0 0 2px hsl(var(--h) 80% 75% / 0.4)); }
  50% { scale: 1.1; filter: drop-shadow(0 0 6px hsl(var(--h) 85% 72% / 0.8)) brightness(1.06); }
}

/* "Next novelty in N levels" */
.result-novelty {
  display: flex; align-items: center; gap: 12px;
  margin: -6px 0 18px; padding: 10px 14px;
  border-radius: 16px; background: var(--ground); border: 1px solid var(--line-soft);
  text-align: left;
}
.result-novelty[hidden] { display: none; }
.novelty-icon {
  flex: 0 0 38px; height: 38px; display: grid; place-items: center;
  border-radius: 12px; background: var(--accent); color: #fff;
  font: 600 17px var(--font-ui);
  box-shadow: inset 0 -3px 0 hsl(var(--h) 40% 40% / 0.18);
}
.novelty-body { flex: 1; min-width: 0; }
.novelty-text { font: 500 13px var(--font-ui); color: var(--ink); }
.novelty-name { font-size: 11px; color: var(--ink-faint); margin: 1px 0 6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.novelty-track { height: 5px; border-radius: 999px; background: var(--line-soft); overflow: hidden; }
.novelty-track i {
  display: block; height: 100%; border-radius: inherit; background: var(--accent);
  transition: width 0.55s cubic-bezier(0.3, 0.8, 0.3, 1);
}
/* Reached: the next level brings it — the box lights up and the icon beams. */
.result-novelty.reached { border-color: var(--accent); background: var(--accent-wash); }
.result-novelty.reached .novelty-text { color: var(--accent-deep); }
.result-novelty.reached .novelty-icon { animation: novelty-beam 1.8s ease-in-out infinite; }
@keyframes novelty-beam {
  0%, 100% { box-shadow: inset 0 -3px 0 hsl(var(--h) 40% 40% / 0.18), 0 0 0 0 hsl(var(--h) 70% 70% / 0.5); }
  50% { box-shadow: inset 0 -3px 0 hsl(var(--h) 40% 40% / 0.18), 0 0 0 7px hsl(var(--h) 70% 70% / 0); scale: 1.06; }
}

.result-actions { display: flex; gap: 9px; flex-wrap: wrap; }
.result-actions .btn { flex: 1; min-width: 84px; }

.btn-reward {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  width: 100%; margin-bottom: 10px;
  background: hsl(150 40% 92%); color: hsl(150 38% 32%);
  border: 1px solid hsl(150 34% 82%);
}
.btn-reward:hover { background: hsl(150 44% 89%); }
.btn-reward span { font-size: 11px; }

.offer-lead { margin: 12px 0 2px; color: var(--ink-soft); font-size: 14px; }
.offer-lead b { color: var(--ink); font-size: 17px; }
.offer-bonus { margin: 14px 0 22px; font-size: 14px; color: var(--ink-soft); }
.offer-bonus b { color: var(--accent-deep); }
.offer-actions { display: flex; flex-direction: column; gap: 9px; }

/* ---------- Publicité simulée ---------- */

.ad-overlay { background: hsl(var(--h) 20% 20% / 0.9); }
.ad-card {
  width: 100%; padding: 26px 22px 22px;
  border: 1px dashed hsl(var(--h) 15% 55%); border-radius: 22px;
  background: hsl(var(--h) 18% 26%);
  color: hsl(var(--h) 20% 92%);
  text-align: center;
}
.ad-tag {
  display: inline-block; margin-bottom: 14px; padding: 3px 11px;
  border-radius: 999px; background: hsl(var(--h) 14% 34%);
  font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase;
  color: hsl(var(--h) 15% 72%);
}
.ad-title { font: 300 20px var(--font-display); margin-bottom: 18px; }
.ad-visual {
  display: grid; place-items: center;
  aspect-ratio: 4 / 3; margin-bottom: 18px;
  border-radius: 16px;
  background: repeating-linear-gradient(45deg, hsl(var(--h) 12% 31%) 0 10px, hsl(var(--h) 12% 29%) 10px 20px);
}
.ad-count { font: 400 44px var(--font-ui); color: hsl(var(--h) 14% 62%); font-variant-numeric: tabular-nums; }
.ad-note { margin-bottom: 18px; color: hsl(var(--h) 14% 68%); font-size: 12px; }
.ad-close { background: var(--accent); color: #fff; }

/* ---------- Bannière ---------- */

/* Jamais pendant une partie : elle volerait de la place au plateau et
   provoquerait des clics accidentels en plein glissé. */
.banner {
  position: absolute; left: 50%; bottom: 0; z-index: 15;
  transform: translateX(-50%);
  width: 320px; max-width: 100%; height: 50px;
  display: flex; align-items: center; justify-content: center; gap: 9px;
  background: var(--surface);
  border-top: 1px solid var(--line);
  color: var(--ink-faint); font-size: 12px;
}
.banner-tag {
  padding: 2px 7px; border-radius: 4px;
  background: var(--accent-wash); color: var(--accent-deep);
  font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase;
}
.app.with-banner .screen { padding-bottom: 62px; }
/* Native app: this placeholder still drives `.with-banner` above (space for
   the banner), but the banner itself is a native view AdMob draws on top of
   the WebView — this `<div>` must stay invisible so its dev-only placeholder
   text never shows underneath or alongside the real ad. */
.native-app .banner { visibility: hidden; }
.banner-result {
  position: static; transform: none; width: auto; margin-top: 18px;
  border: 1px solid var(--line); border-radius: 12px;
}

/* ---------- Toast ---------- */

.toast {
  position: absolute; left: 50%; bottom: 84px; z-index: 25;
  transform: translateX(-50%);
  padding: 10px 18px; border-radius: 999px;
  background: var(--ink); color: var(--ground);
  font-size: 13px;
  animation: toast 1.4s ease both;
  pointer-events: none; white-space: nowrap;
}
@keyframes toast { 0%, 100% { opacity: 0; } 15%, 75% { opacity: 1; } }

/* Directional refusal (rail/anchor/one-way): same pill, the muted pastel red
   already used for urgent/danger elsewhere (--alert) rather than a new hue —
   still soft, never a harsh warning colour. */
.toast.alert { background: var(--alert); color: var(--ground); }

/* ---------- Focus clavier ---------- */

.btn:focus-visible,
.icon-btn:focus-visible,
.map-node:focus-visible,
.action:focus-visible,
.debug-toggle:focus-visible,
.debug-panel input:focus-visible {
  outline: 2px solid var(--accent-deep);
  outline-offset: 3px;
}

/* ---------- Éditeur (src/ui/editor.js) ----------
   Built for a phone: modes on top, the grid drawn by the game's own renderer
   in the middle, the settings of the current mode below, actions at the
   bottom. Nothing scrolls but the chip rows. */

#screen-editor { gap: 6px; }
.ed-mine { font: 500 12px var(--font-ui); cursor: pointer; }

/* The four modes: a segmented control, the current one filled. */
.ed-modes {
  flex: 0 0 auto; display: grid; grid-template-columns: repeat(4, 1fr); gap: 3px; padding: 3px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px;
}
.ed-modes button {
  padding: 8px 2px; border: 0; border-radius: 11px;
  background: transparent; color: var(--ink-soft);
  font: 500 13px var(--font-ui); cursor: pointer;
  transition: background 0.16s ease, color 0.16s ease;
}
.ed-modes button.sel { background: var(--accent); color: #fff; }
/* A tap the current mode cannot serve points at the one that can. */
.ed-modes button.flash { animation: mode-flash 0.9s ease-out; }
@keyframes mode-flash { 30% { background: var(--accent-wash); color: var(--accent-deep); scale: 1.06; } }

.ed-stage {
  /* The grid keeps its room whatever the panel holds: the panel scrolls. */
  /* Zero basis: it takes the room LEFT, then BoardView sizes the grid to it
     (with `auto`, the drawn grid set the stage's size, which set the grid's…). */
  flex: 1 1 0; min-height: 32vh;
  display: flex; align-items: center; justify-content: center;
}
.ed-board { touch-action: none; }

/* Touch layer over the grid, and one slot per edge cell over the wall. */
.ed-hit { position: absolute; inset: 0; z-index: 8; cursor: pointer; }
.ed-slots { position: absolute; inset: 0; z-index: 9; pointer-events: none; }
.ed-slot {
  position: absolute; padding: 0; border: 0; background: transparent;
  pointer-events: none; cursor: pointer;
}
.ed-slot-top, .ed-slot-bottom { width: var(--cell); height: calc(var(--wall) + 14px); left: calc(var(--i) * var(--cell)); }
.ed-slot-top { top: calc(var(--wall) * -1 - 10px); }
.ed-slot-bottom { bottom: calc(var(--wall) * -1 - 10px); }
.ed-slot-left, .ed-slot-right { height: var(--cell); width: calc(var(--wall) + 14px); top: calc(var(--i) * var(--cell)); }
.ed-slot-left { left: calc(var(--wall) * -1 - 10px); }
.ed-slot-right { right: calc(var(--wall) * -1 - 10px); }
/* In Gates mode every edge cell shows where a gate can go. */
.ed-board[data-mode="gates"] .ed-slot, .ed-board[data-mode="erase"] .ed-slot.open { pointer-events: auto; }
.ed-board[data-mode="gates"] .ed-slot::after {
  content: ''; position: absolute; inset: 9px 4px; border-radius: 6px;
  border: 1.5px dashed hsl(var(--h) 40% 70%);
}
.ed-board[data-mode="gates"] .ed-slot-left::after, .ed-board[data-mode="gates"] .ed-slot-right::after { inset: 4px 9px; }
.ed-board[data-mode="gates"] .ed-slot.open::after { border-color: transparent; }
.ed-board[data-mode="gates"] .ed-hit, .ed-board[data-mode="erase"] .ed-hit { cursor: crosshair; }
.ed-board[data-mode="erase"] { outline: 2px dashed hsl(0 60% 72%); outline-offset: calc(var(--wall) + 4px); }

/* The block under the finger before it is placed: green fits, red does not. */
.ed-ghost { position: absolute; inset: 0; z-index: 7; pointer-events: none; }
.ed-ghost i {
  position: absolute; width: var(--cell); height: var(--cell);
  border-radius: calc(var(--cell) * 0.28);
  background: var(--tile); opacity: 0.55;
  box-shadow: inset 0 0 0 3px hsl(150 55% 45%);
}
.ed-ghost.ko i { opacity: 0.35; box-shadow: inset 0 0 0 3px var(--alert); }

.ed-status {
  flex: 0 0 auto; min-height: 34px; display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 2px 6px;
  font: 400 12px/1.3 var(--font-ui); color: var(--ink-soft); text-align: center;
}
.ed-status.ok { color: hsl(150 40% 32%); }
.ed-status.ko { color: var(--alert); }
.ed-status .btn { flex: 0 0 auto; padding: 6px 12px; font-size: 12px; }

/* The current mode's settings, one labelled row each. */
.ed-panel {
  flex: 0 1 auto; max-height: 31vh; overflow-y: auto; overscroll-behavior: contain;
  display: grid; align-content: start; gap: 6px; padding: 8px 10px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 18px;
}
.ed-row { display: grid; grid-template-columns: 52px 1fr; align-items: center; gap: 6px; min-width: 0; }
.ed-legend {
  font-size: 9px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint);
}
.ed-line { display: flex; gap: 6px; flex-wrap: wrap; min-width: 0; }
.ed-scroll-row .ed-line { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; padding: 2px 1px 4px; }
.ed-scroll-row .ed-line::-webkit-scrollbar { display: none; }
.ed-note { font-size: 13px; color: var(--ink-soft); text-align: center; }

.ed-chip {
  flex: 0 0 auto; display: grid; place-items: center; gap: 3px;
  min-width: 38px; min-height: 36px; padding: 5px 10px;
  background: var(--ground); border: 1.5px solid var(--line); border-radius: 12px;
  color: var(--ink-soft); font: 500 13px var(--font-ui); cursor: pointer;
  transition: border-color 0.14s ease, background 0.14s ease;
}
.ed-chip.sel { border-color: var(--accent-deep); background: var(--accent-wash); color: var(--accent-deep); }
.ed-type { padding: 5px 5px 3px; min-width: 54px; }
.ed-type small { font-size: 10px; white-space: nowrap; }

/* A block type, as a small tile in its colour with its mark. */
.ed-swatch {
  width: 24px; height: 24px; display: grid; place-items: center;
  border-radius: 8px; background: var(--tile, var(--line));
  color: rgba(255, 255, 255, 0.85); font: 600 12px var(--font-ui);
  box-shadow: inset 0 -3px 0 rgba(94, 72, 102, 0.12);
}
.ed-swatch.t-wall { background: hsl(var(--h) 10% 82%); }
.ed-swatch.t-joker { background: conic-gradient(var(--c0), var(--c1), var(--c2), var(--c3), var(--c4), var(--c5), var(--c0)); }
.ed-swatch.t-dual { background: linear-gradient(118deg, var(--tile) 0 47%, var(--c-alt) 53% 100%); }
.ed-swatch.t-locked { filter: saturate(0.5); }

.ed-colors .ed-line { flex-wrap: nowrap; gap: 5px; }
.ed-color {
  flex: 0 0 auto; width: 30px; height: 30px; padding: 0; border-radius: 50%;
  background: var(--tile); border: 3px solid var(--surface);
  box-shadow: 0 0 0 1.5px var(--line); cursor: pointer;
}
.ed-color.sel { box-shadow: 0 0 0 2.5px var(--accent-deep); }
.ed-color:disabled { opacity: 0.25; cursor: not-allowed; }
.app.with-glyphs .ed-color { background-image: var(--pat); background-size: 8px 8px; --cell: 32px; --pat-t: 8px; }

.ed-shape { padding: 6px; min-height: 34px; }
.ed-shape-cells { display: grid; gap: 1.5px; }
.ed-shape-cells i { background: transparent; border-radius: 2px; }
.ed-shape-cells i.on { background: var(--tile, var(--ink-soft)); }
.ed-shape.t-wall .ed-shape-cells i.on { background: hsl(var(--h) 10% 78%); }
.ed-shape.t-joker .ed-shape-cells i.on { background: var(--c3); }

.ed-stepper { display: inline-flex; align-items: center; gap: 10px; }
.ed-stepper b { min-width: 20px; text-align: center; font: 500 16px var(--font-ui); color: var(--ink); font-variant-numeric: tabular-nums; }
.ed-step {
  width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--ground); color: var(--ink);
  font-size: 18px; line-height: 1; cursor: pointer;
}
.ed-step:disabled { opacity: 0.35; cursor: not-allowed; }

/* Bottom actions: undo and grid size as icons, check and test as words. */
.ed-bar { flex: 0 0 auto; display: grid; grid-template-columns: 46px auto 1fr 1.3fr; gap: 6px; }
.ed-bar .btn { min-height: 44px; }
.ed-icon { padding: 0 10px; display: grid; place-items: center; }
.ed-icon svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
.ed-size { font: 500 13px var(--font-ui); white-space: nowrap; }
.ed-bar .btn:disabled { opacity: 0.4; }

/* ---------- Menu utilisateur ---------- */

/* Flottant plutôt qu'intégré aux barres de titre : il doit être au même
   endroit sur tous les écrans, y compris le menu qui n'a pas de barre. */
.user-btn {
  position: absolute; top: max(18px, env(safe-area-inset-top)); right: 18px; z-index: 18;
  width: 38px; height: 38px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink); cursor: pointer;
  display: grid; place-items: center;
  transition: border-color 0.16s ease, transform 0.14s ease;
}
.user-btn:hover { border-color: var(--accent); }
.user-btn:active { transform: scale(0.94); }
.user-btn.open { border-color: var(--accent-deep); }
/* Trois barres : le bouton doit dire « menu ». Le niveau du joueur y figurait,
   mais un chiffre se lit comme une information, pas comme une commande — et
   personne ne pensait à cliquer dessus pour régler le son. Le niveau reste
   affiché dans le panneau, où il a sa place. */
.user-burger { display: grid; gap: 3px; width: 15px; }
.user-burger i {
  display: block; height: 2px; border-radius: 2px;
  background: var(--ink-soft);
  transition: background 0.16s ease;
}
.user-btn:hover .user-burger i, .user-btn.open .user-burger i { background: var(--accent-deep); }
/* Pastille discrète quand le son est coupé : l'état doit se voir sans ouvrir. */
.user-btn.muted::after {
  content: '🔇'; position: absolute; right: -3px; bottom: -3px;
  font-size: 11px; line-height: 1;
  background: var(--surface); border-radius: 50%; padding: 1px;
}

/* Les barres de titre laissent la place au bouton flottant. */
.topbar { padding-right: 46px; }

.user-panel {
  position: absolute; top: calc(max(18px, env(safe-area-inset-top)) + 46px); right: 14px;
  z-index: 19; width: min(280px, calc(100% - 28px));
  max-height: calc(100% - max(18px, env(safe-area-inset-top)) - 46px - 14px);
  overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none;
  padding: 16px; border-radius: 20px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 18px 44px hsl(var(--h) 30% 50% / 0.18);
  animation: panneau-in 0.22s cubic-bezier(0.2, 1.1, 0.4, 1) both;
}
@keyframes panneau-in { from { opacity: 0; transform: translateY(-8px) scale(0.97); } }

.user-head { display: flex; justify-content: flex-end; margin-bottom: 8px; }
.user-board { margin: -4px 0 8px; }
/* Leaderboard and editor: the panel's two actions, in the realm's hue — set
   apart from the settings below. */
.user-action {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--accent-wash); color: var(--accent-deep);
  border: 1px solid hsl(var(--h) 60% 84%); font-weight: 500;
}
.user-action:hover { border-color: var(--accent); }
html.night .user-action { border-color: hsl(var(--h) 34% 36%); }
.editor-logo { width: 30px; height: 20px; flex: none; }
.editor-logo .blk-a { fill: var(--c1); stroke: var(--c1); stroke-width: 0.6; }
/* The L is one path; a stroke of its own colour, joined round, rounds its corners. */
.editor-logo .blk-b { fill: var(--c0); stroke: var(--c0); stroke-width: 2.6; stroke-linejoin: round; }
.editor-logo .blk-shine { fill: none; stroke: #fff; stroke-opacity: 0.55; stroke-width: 1.4; stroke-linecap: round; }
.user-editor-note { margin: 6px 0 16px; }
.user-close { width: 30px; height: 30px; font-size: 17px; }


.user-stats { display: flex; gap: 8px; margin-bottom: 15px; }
.user-stats > div {
  flex: 1; padding: 9px 6px; text-align: center;
  background: var(--ground); border-radius: 12px;
}
.user-stats dt { font-size: 9px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); }
.user-stats dd { margin-top: 3px; font: 400 16px var(--font-ui); font-variant-numeric: tabular-nums; }

.user-section { margin-bottom: 14px; }
.user-legend {
  display: block; margin-bottom: 7px;
  font-size: 9px; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-faint);
}
.user-note { margin-top: 6px; font-size: 10px; color: var(--ink-faint); line-height: 1.4; }

/* Settings: four icon toggles in equal columns, each named underneath. On, the
   icon in the realm's accent; off, grey and crossed out (the checkbox stays
   the source of truth, main.js wires it as before). */
.sound-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.sound-toggle {
  position: relative; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  -webkit-tap-highlight-color: transparent;
}
.sound-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.sound-toggle svg {
  box-sizing: content-box; width: 22px; height: 22px; padding: 11px;
  border-radius: 14px; border: 1px solid hsl(var(--h) 60% 82%);
  background: var(--surface); color: var(--accent-deep);
  fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, scale 0.12s ease;
}
.sound-toggle:active svg { scale: 0.94; }
.sound-toggle input:not(:checked) ~ svg { background: var(--ground); border-color: var(--line); color: var(--ink-faint); }
.sound-toggle .ico-off, .sound-toggle input:not(:checked) ~ .ico-on { display: none; }
.sound-toggle input:not(:checked) ~ .ico-off { display: block; }
.sound-toggle input:focus-visible ~ svg { outline: 2px solid var(--accent-deep); outline-offset: 2px; }
.sound-name { font-size: 11px; line-height: 1.2; text-align: center; color: var(--ink-soft); }
.sound-toggle input:not(:checked) ~ .sound-name { color: var(--ink-faint); }
/* Night mode: sun by day, moon by night — two "on" states, never greyed. */
.theme-toggle input:not(:checked) ~ svg { background: var(--surface); border-color: hsl(var(--h) 60% 82%); color: var(--accent-deep); }
.theme-toggle input:not(:checked) ~ .sound-name { color: var(--ink-soft); }
.theme-toggle .name-off, .theme-toggle input:not(:checked) ~ .name-on { display: none; }
.theme-toggle input:not(:checked) ~ .name-off { display: block; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Interrupteur */
.switch {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 0; cursor: pointer; font-size: 13px;
}
.switch span { flex: 1; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch i {
  flex: 0 0 auto; width: 38px; height: 22px; border-radius: 999px;
  background: var(--line); position: relative; transition: background 0.18s ease;
}
.switch i::after {
  content: ''; position: absolute; top: 3px; left: 3px;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--surface); transition: transform 0.18s cubic-bezier(0.3, 1.3, 0.5, 1);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.18);
}
.switch input:checked + i { background: var(--accent); }
.switch input:checked + i::after { transform: translateX(16px); }
.switch input:focus-visible + i { outline: 2px solid var(--accent-deep); outline-offset: 2px; }

.user-danger { width: 100%; color: var(--alert); }
.user-danger:hover { border-color: var(--alert); color: var(--alert); }

/* ---------- Debug ---------- */

.debug-toggle {
  position: absolute; right: 8px; bottom: 8px; z-index: 30;
  width: 28px; height: 28px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface);
  color: var(--ink-faint); font-size: 13px; cursor: pointer; opacity: 0.55;
}
.debug-toggle:hover { opacity: 1; }
.debug-panel {
  position: absolute; right: 8px; bottom: 42px; z-index: 30; width: 222px;
  padding: 13px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 12px 30px hsl(var(--h) 30% 50% / 0.16);
  font-size: 12px;
}
.debug-panel label { display: block; color: var(--ink-faint); margin-bottom: 9px; }
.debug-panel input {
  width: 100%; margin-top: 5px; padding: 6px 9px;
  background: var(--ground); border: 1px solid var(--line); border-radius: 8px; color: var(--ink);
}
.debug-actions { display: flex; flex-wrap: wrap; gap: 5px; }
.debug-info { margin-top: 9px; color: var(--ink-faint); line-height: 1.4; white-space: pre-line; }
.debug-events {
  margin: 9px 0 0; padding-left: 16px; max-height: 160px; overflow-y: auto;
  color: var(--ink-faint); font-size: 10px; line-height: 1.5;
}
.debug-events b { color: var(--ink); font-weight: 500; }

@media (prefers-reduced-motion: reduce) {
  /* One iteration: a looping animation (breathing buttons, flames, petals)
     would otherwise restart every 0.01 ms and flicker. */
  *, *::before, *::after {
    animation-duration: 0.01ms !important; animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .menu-art .petal, .brief-art .petal { display: none; }
}

/* ---------------------------------------------------------------------------
   Sens unique — une case qui ne se quitte que dans un sens.
   
   Dessinée sur la grille, sous les blocs : la flèche appartient au plateau et
   non à ce qui s'y trouve, et le joueur doit pouvoir la lire AVANT de décider
   d'y entrer. Le chevron est en pur CSS : deux bords d'un carré tourné, ce qui
   suit la teinte du monde sans demander d'image à charger.
   --------------------------------------------------------------------------- */
.one-way {
  position: absolute;
  width: var(--cell);
  height: var(--cell);
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  /* Un fond très léger : la case se repère même quand un bloc la couvre à moitié. */
  background: hsl(var(--h, 345) 45% 62% / 0.10);
  border-radius: 3px;
}
.one-way::after {
  content: '';
  width: 34%;
  height: 34%;
  border-right: 2.5px solid hsl(var(--h, 345) 45% 46% / 0.62);
  border-bottom: 2.5px solid hsl(var(--h, 345) 45% 46% / 0.62);
  border-radius: 0 0 3px 0;
}
.one-way[data-dir="right"]::after { transform: rotate(-45deg) translate(-6%, -6%); }
.one-way[data-dir="left"]::after  { transform: rotate(135deg) translate(-6%, -6%); }
.one-way[data-dir="down"]::after  { transform: rotate(45deg)  translate(-6%, -6%); }
.one-way[data-dir="up"]::after    { transform: rotate(-135deg) translate(-6%, -6%); }

/* Porte fermée : elle s'ouvrira après N sorties. Elle reste lisible (couleur,
   glyphe) mais nettement en retrait — une porte invisible passerait pour une
   porte cassée. Le compteur dit combien de blocs doivent encore partir. */
.gate.gate-shut { opacity: .38; filter: saturate(.45); }
.gate-wait {
  position: absolute;
  font: 600 10px/1 system-ui, sans-serif;
  color: #fff;
  background: hsl(var(--h, 345) 30% 34% / .9);
  border-radius: 999px;
  min-width: 14px;
  height: 14px;
  padding: 0 3px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.gate-top .gate-wait    { bottom: -7px; left: 50%; transform: translateX(-50%); }
.gate-bottom .gate-wait { top: -7px;    left: 50%; transform: translateX(-50%); }
.gate-left .gate-wait   { right: -7px;  top: 50%;  transform: translateY(-50%); }
.gate-right .gate-wait  { left: -7px;   top: 50%;  transform: translateY(-50%); }

/* Bloc glissant : trois traînées, comme quelque chose qu'on vient de lâcher.
   Volontairement pas une flèche — la barre d'un rail dit quel axe, la flèche
   d'une ancre dit quel sens ; un glissant est libre dans les quatre. Ce qui le
   distingue, c'est qu'il ne s'arrête pas où on le lâche, donc la marque doit
   parler de mouvement et non de cap. */
.block-slide {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 46%;
  height: 26%;
  transform: translate(-50%, -50%);
  pointer-events: none;
  background:
    linear-gradient(to right, transparent 0 4%,  rgba(255,255,255,.4) 4% 34%,  transparent 34%) 0 12% / 100% 16% no-repeat,
    linear-gradient(to right, transparent 0 20%, rgba(255,255,255,.46) 20% 62%, transparent 62%) 0 50% / 100% 16% no-repeat,
    linear-gradient(to right, transparent 0 4%,  rgba(255,255,255,.4) 4% 34%,  transparent 34%) 0 88% / 100% 16% no-repeat;
  filter: drop-shadow(0 1px 0 rgba(40, 28, 34, .18));
}
