/* === En Zistoir Design System v5.0 — « Kolaz Vakans » (La Réunion) ===
   Direction « carnet de vacances / scrapbook » : papier kraft #f3e9d7 à texture
   pointillée, encre brun chaud #33302a, accent rouge (rouge dominant du logo) #a02a1a + jaune washi #ffe082.
   Photos « scotchées » légèrement inclinées, ruban adhésif (washi tape), bords
   pointillés, notes manuscrites (Caveat), ombres DOUCES (pas d'ombre dure). Typo :
   Bricolage Grotesque (titres/corps) + Caveat (accents manuscrits : notes, CTA, scores).

   ┌─────────────────────────────────────────────────────────────────────┐
   │  FICHIER DE THÈME — ne pas éditer la copie active `css/app.css`.     │
   │  Source de vérité : `css/themes/kolaz-vakans/app.css`.               │
   │  Bascule / retour arrière :  scripts/switch-theme.sh <thème>         │
   │  Thèmes archivés : arcade-tropikal · zournal-kreol (Web) · tizor (Maui) │
   └─────────────────────────────────────────────────────────────────────┘

   Architecture de tokens conservée : les noms existants (--bg/--ink/--ember/--tz-*…)
   sont remis à la palette Kolaz Vakans → propagation automatique. Couleurs de marque
   brutes sous --tz-* ; tokens spécifiques Kolaz sous --kv-* ; traitements de surface
   des primitives sous --ui-*. */

/* ── Palette Kolaz Vakans (default) ── */
:root {
  /* Brand ramp — accent rouge (rouge dominant du logo) + jaune washi, secondaires pastel/sauge */
  --tz-teal-d:  #5b7a6e;   /* sauge profonde */
  --tz-teal:    #7a8f85;   /* sauge (tampons « VALIDÉ ») */
  --tz-green:   #2e5243;   /* vert profond (rareté « commune ») */
  --tz-green-d: #2e5243;
  --tz-coral:   #a02a1a;   /* ROUGE (logo) — accent primaire : CTA, liens, scores */
  --tz-coral-d: #7c1c14;
  --tz-gold:    #ffe082;   /* JAUNE washi tape / surligneur */
  --tz-amber:   #e0a54d;
  --tz-purple:  #5b3f6e;   /* prune (rareté « épique ») */
  --tz-sky:     #6aa9c0;

  /* Surfaces — papier kraft + cartes/photos blanches */
  --bg:         #f3e9d7;
  --bg-soft:    #ede1cb;
  --bg-card:    #ffffff;

  /* Ink / text — brun chaud */
  --ink:        #33302a;
  --ink-soft:   #6b6559;   /* corps */
  --muted:      #a39a87;   /* notes manuscrites, méta discrète */
  --muted-2:    #b8ae99;

  /* Lines — filets kraft & bords pointillés */
  --line:        #efe6d2;
  --line-strong: #d9cbb2;  /* couleur des bords pointillés */

  /* Semantic accents (legacy aliases) */
  --canopy:      #7a8f85;
  --canopy-soft: #e5ede9;
  --ember:       #a02a1a;  /* rouge (logo) — accent signature */
  --ember-soft:  #f4e0da;

  /* Typography — Kolaz Vakans */
  --font-sans:  'Bricolage Grotesque', system-ui, -apple-system, sans-serif; /* corps 500, titres 800 */
  --font-serif: 'Bricolage Grotesque', system-ui, sans-serif;                /* display = grotesque gras */
  --font-hand:  'Caveat', 'Bricolage Grotesque', cursive;                    /* accents manuscrits */
  --font-mono:  ui-monospace, 'Spline Sans Mono', SFMono-Regular, monospace; /* data légère */

  /* Radii — Kolaz est « papier découpé » : coins nets */
  --r-sm: 3px;
  --r:    4px;
  --r-lg: 5px;
  --r-xl: 6px;

  /* Elevation — ombres DOUCES (photo soulevée), teintées encre chaude */
  --tz-shadow:    0 14px 32px -16px rgba(51,48,42,.45);
  --tz-shadow-lg: 0 18px 44px -20px rgba(51,48,42,.55);
  --tz-grad-brand: linear-gradient(120deg,#a02a1a,#c04a34);
  --tz-grad-page:  #f3e9d7;  /* fond uni ; la texture pointillée est posée sur <body> */

  /* ── Tokens spécifiques Kolaz Vakans (--kv-*) ── */
  --kv-paper:   #f3e9d7;
  --kv-ink:     #33302a;
  --kv-rose:    #a02a1a;
  --kv-jaune:   #ffe082;
  --kv-sage:    #7a8f85;
  --kv-dash:    #d9cbb2;
  --kv-note:    #a39a87;   /* gris des notes manuscrites */
  /* Ruban adhésif (washi tape) — rectangles semi-transparents posés sur les cartes */
  --kv-tape-jaune: rgba(255,224,130,.85);
  --kv-tape-rose:  rgba(160,42,26,.40);
  --kv-tape-mint:  rgba(205,228,218,.90);
  /* Aplats pastel des vignettes/photos (dégradés hachurés posés en ligne dans les pages) */
  --kv-mint:    #cde4da;
  --kv-peach:   #f4d9c2;
  --kv-lavender:#e4d2ec;
  --kv-aqua:    #c9e6ef;
  /* Ombres */
  --kv-shadow-photo: 0 18px 38px -16px rgba(51,48,42,.55);
  --kv-shadow-soft:  0 12px 28px -12px rgba(51,48,42,.40);
  --kv-shadow-tab:   3px 3px 0 rgba(51,48,42,.20);   /* petite ombre nette des chips/icônes bordées */
  /* Texture pointillée du papier */
  --kv-dots: radial-gradient(rgba(51,48,42,.05) 1.2px, transparent 1.2px);

  /* ── Traitements de surface consommés par les primitives (--ui-*) ──
     En Kolaz, les éléments bordés portent une fine bordure encre + une petite
     ombre nette (3px), pas l'ombre dure décalée d'Arcade. Repli = rendu legacy. */
  --ui-stroke:            var(--kv-ink);
  --ui-btn-border:        2px solid var(--kv-ink);
  --ui-btn-shadow:        var(--kv-shadow-tab);
  --ui-btn-shadow-active: 1px 1px 0 rgba(51,48,42,.20);
  --ui-chip-border:       2px solid var(--kv-ink);
  --ui-chip-shadow:       3px 3px 0 rgba(51,48,42,.16);
  --ui-badge-border:      none;
  --ui-badge-shadow:      0 4px 10px -6px rgba(51,48,42,.4);
}

/* ── Palette Cirques (variante fraîche) ── */
[data-palette="cirques"] {
  --canopy:     #5b7a86;
  --canopy-soft:#e2edf0;
  --ember:      #6aa9c0;
  --ember-soft: #e4f1f5;
}

/* ── Palette Volcan (variante chaude) ── */
[data-palette="volcan"] {
  --canopy:     #c2694a;
  --canopy-soft:#f6e3d8;
  --ember:      #a02a1a;
  --ember-soft: #f4e0da;
}

/* ── Reset & Base ── */
*, *::before, *::after { box-sizing: border-box; }
html, body, #root { height: 100%; }

/* La barre de défilement ne doit JAMAIS changer la largeur utile de la page. Sans ce gutter,
   une page plus haute que la fenêtre (accueil, /tracks, /pro) affiche l'ascenseur et perd
   ~15 px de large, tandis qu'une page courte (/about, /collections) les garde : l'en-tête —
   donc le LOGO — se décale visiblement d'une page à l'autre, alors qu'il n'a pas bougé.
   Réserver la gouttière en permanence est le seul correctif qui ne dépende ni de la hauteur
   de chaque page ni de la plateforme (macOS masque l'ascenseur, Windows non). */
html { scrollbar-gutter: stable; }

body {
  background-color: var(--bg);
  background-image: var(--kv-dots);
  background-size: 22px 22px;
  background-attachment: fixed;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  margin: 0;
  padding: 0;
}

button { font: inherit; color: inherit; cursor: pointer; }
a { color: inherit; }
input, textarea, select { font: inherit; color: inherit; }
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--ember) !important; }

/* Focus scripté (FocusOnNavigate) — pas de cadre autour du titre de page.
   `<FocusOnNavigate Selector="h1">` (Routes.razor) déplace le focus sur le <h1>
   à chaque navigation, afin que les lecteurs d'écran annoncent la nouvelle page.
   Blazor pose pour cela un `tabindex="-1"` sur l'élément puis appelle `.focus()` :
   comme aucune interaction ne précède ce focus scripté, les navigateurs le classent
   « visible » (:focus-visible) et peignent leur anneau par défaut — d'où le cadre
   bleu autour du titre, qui ne s'efface qu'au premier clic ailleurs.
   Un élément `tabindex="-1"` n'est jamais atteignable au Tab : l'anneau n'y indique
   aucune position de navigation. On le retire donc pour ces seules cibles de focus
   scriptées ; liens, boutons et champs conservent le leur. */
[tabindex="-1"]:focus { outline: none; }

/* ── Typography ──
   .display rend Bricolage Grotesque (gras 800, serré). L'emphase <em> devient un
   surlignage jaune washi (façon feutre), sans italique. Un utilitaire .hand rend
   les accents manuscrits en Caveat. */
.display {
  font-family: var(--font-serif);
  font-weight: 800;
  letter-spacing: -0.02em;
  line-height: 1.03;
  color: var(--ink);
}
.display em {
  font-style: normal;
  background: var(--kv-jaune);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  padding: 0 .16em;
}

.hand { font-family: var(--font-hand); font-weight: 700; color: var(--kv-rose); }

.mono {
  font-family: var(--font-mono);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.eyebrow {
  font-family: var(--font-sans);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.hairline { background: var(--line); height: 1px; width: 100%; }

/* ── Responsive visibility ── */
.th-hide-desktop { display: none; }
.th-hide-mobile { display: initial; }
.th-brand-sub { display: inline; }
@media (max-width: 1180px) { .th-brand-sub { display: none; } }
@media (max-width: 1024px) { .th-conn-label { display: none; } }

/* ── Page sections fill the column-flex <main> ──
   MainLayout's <main> is a column flex container (display:flex; flex-direction:column).
   Page section wrappers use `max-width:…; margin:0 auto` to cap-and-center their content.
   In flexbox, horizontal `auto` margins disable the default `align-items:stretch`, so such
   a wrapper collapses to its content's max-content width instead of spanning the row — e.g.
   the home "À la une" flex card (flex bases 280px + 320px) shrank to ~600px and centered,
   while the 3 concept cards above it (a grid with wide text) stayed near full width, leaving
   them visibly misaligned. The same caused the Pro "formules" grid to shrink under its hero.
   Forcing width:100% restores full-width behavior — still capped by each wrapper's own
   max-width and centered by its margin:auto. Targets only MainLayout (AuthLayout uses a div). */
main > * { width: 100%; }

/* ── Layout utilities ── */
.th-row-stack { display: grid; gap: 64px; align-items: end; }
.th-row-stack.cols-58 { grid-template-columns: 1.4fr 1fr; }
.th-row-stack.cols-eq { grid-template-columns: 1fr 1fr; }

.th-stats { display: grid; gap: 24px; }
.th-stats.cols-5 { grid-template-columns: repeat(5, 1fr); }
.th-stats.cols-4 { grid-template-columns: repeat(4, 1fr); }

.th-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.th-grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
@media (max-width: 640px) { .th-grid-3 { grid-template-columns: 1fr; } }
.th-grid-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; }
.th-home-popular { display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px; }
@media (max-width: 900px) { .th-home-popular { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .th-home-popular { grid-template-columns: 1fr; } }
.th-editor-split { display: grid; grid-template-columns: 380px 1fr 320px; flex: 1; min-height: 0; }
.th-recap-grid { display: grid; grid-template-columns: 1.2fr 1fr 1fr 1fr; }
.th-recap-bottom { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; }
.th-pad-page { padding: 0 28px 80px; }

/* ── Fluid headline sizes ── */
.th-hero-headline { font-size: clamp(36px, 6.4vw, 64px); }
.th-detail-headline { font-size: clamp(32px, 5.5vw, 56px); }
.th-section-headline { font-size: clamp(26px, 3.6vw, 48px); }
.th-play-headline { font-size: clamp(26px, 3.6vw, 42px); }
.th-recap-headline { font-size: clamp(34px, 5.6vw, 64px); }
/* Page indice mobile : titre d'étape un peu plus discret que le clamp desktop ci-dessus. */
@media (max-width: 900px) {
  .th-play-headline { font-size: clamp(19px, 5.2vw, 24px); }
}

/* ── Shell row ── */
.th-shell-row {
  max-width: 1440px;
  margin: 0 auto;
  height: 60px;
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: 16px;
}

/* ── Chasse "mode jeu" (mobile ET desktop) ──
   /chasse/{id} une fois la session du joueur démarrée : le hero + le contenu de consultation
   cèdent la place au shell immersif dédié carte/collection/photo (voir GameModeState +
   ChasseDetail.razor), quelle que soit la largeur d'écran — comme le mode traversée, qui
   retire déjà son propre header sur toutes les tailles (IsImmersiveGameRoute). Les ajustements
   propres au desktop (coque plein écran, overlays agrandis) vivent dans le bloc
   .th-play2-chasse plus bas, scoped pour ne jamais déborder sur le mode traversée. */
.th-game-hide-mobile { display: none !important; }
.th-game-shell {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
/* Verrouillage de la racine à 100dvh (voir .th-game-root) : le header/footer/barre du bas
   globaux sont masqués pendant le jeu, le shell remplit donc tout l'écran. Une hauteur
   *définitive* remonte ainsi la chaîne flex (root → main → .th-game-shell → .th-play2-shell →
   conteneur carte), ce qui permet à la carte Leaflet de prendre MapHeight="100%" sans rester à
   hauteur 0.
   min-height:0 !important — MainLayout pose un `min-height:100vh` EN LIGNE dès que la route
   n'est pas une route de jeu, et /collection/{id} n'en est pas une. Sur mobile, 100vh (barre
   d'URL masquée) est PLUS GRAND que 100dvh (barre visible) : ce min-height l'emportait, la
   coque dépassait le bas de l'écran et il fallait faire défiler pour atteindre le déclencheur. */
.th-game-root { height: 100dvh; min-height: 0 !important; overflow: hidden; }

/* ── Menu mobile : bouton hamburger + tiroir latéral ──
   Remplace l'ancienne barre de navigation du bas. Hors jeu, la navigation mobile
   passe désormais par un tiroir classique qui s'ouvre/se ferme (menu à trois barres).
   Le bouton (.th-burger) et le tiroir (.th-drawer) n'apparaissent que sur mobile
   (≤ 900px) ; sur desktop la nav horizontale de l'en-tête reste seule (.th-hide-mobile). */
.th-burger {
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--bg-soft);
  border: 1px solid var(--line-strong);
  color: var(--ink);
  cursor: pointer;
  padding: 9px;
  border-radius: 10px;
  flex-shrink: 0;
}

/* Tiroir : toujours présent dans le DOM (pour animer l'ouverture/fermeture),
   masqué sur desktop, hors écran tant qu'il n'est pas ouvert (.th-drawer--open). */
.th-drawer { display: none; }

@media (max-width: 900px) {
  .th-burger { display: inline-flex; }

  .th-drawer { display: block; }

  .th-drawer__scrim {
    position: fixed;
    inset: 0;
    z-index: 300;
    background: color-mix(in oklch, var(--ink) 45%, transparent);
    opacity: 0;
    pointer-events: none;
    transition: opacity .2s ease;
  }

  /* position:fixed → le panneau hors écran (translateX(100%)) ne crée pas de
     débordement horizontal : il est borné par le viewport. */
  .th-drawer__panel {
    position: fixed;
    top: 0; right: 0; bottom: 0;
    z-index: 301;
    width: min(84vw, 340px);
    display: flex;
    flex-direction: column;
    background: var(--bg-card);
    border-left: 1px solid var(--line-strong);
    box-shadow: -18px 0 44px -22px rgba(36, 28, 18, .5);
    transform: translateX(100%);
    transition: transform .24s ease;
    padding-bottom: env(safe-area-inset-bottom);
    overflow-y: auto;
  }

  .th-drawer--open .th-drawer__scrim { opacity: 1; pointer-events: auto; }
  .th-drawer--open .th-drawer__panel { transform: translateX(0); }

  .th-drawer__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 16px 16px 12px;
    border-bottom: 1px solid var(--line);
  }
  .th-drawer__title {
    font-family: var(--font-serif);
    font-weight: 700;
    font-size: 17px;
    color: var(--ink);
  }
  .th-drawer__close {
    display: flex;
    background: var(--bg-soft);
    border: 1px solid var(--line-strong);
    color: var(--ink);
    cursor: pointer;
    padding: 8px;
    border-radius: 10px;
  }
  .th-drawer__links {
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding: 12px;
  }
  .th-drawer__link {
    display: flex;
    align-items: center;
    gap: 13px;
    width: 100%;
    text-align: left;
    background: transparent;
    border: none;
    cursor: pointer;
    padding: 13px 14px;
    border-radius: 12px;
    color: var(--ink-soft);
    font-family: var(--font-sans);
    font-weight: 800;
    font-size: 15px;
    transition: background .15s ease, color .15s ease;
  }
  .th-drawer__link:hover { background: var(--bg-soft); }
  .th-drawer__link.is-active {
    background: var(--ember-soft);
    color: var(--ember);
  }
}

/* ── Footer (liens légaux + copyright) ──
   Une seule rangée sur desktop : les liens « Informations » (mentions légales,
   CGU, confidentialité, cookies, hébergement) à gauche, le copyright manuscrit
   à droite. Sur mobile (≤ 640px) la rangée passe en COLONNE CENTRÉE : les liens
   s'enroulaient sur deux lignes collées au bord gauche, ce qui donnait un bloc
   visuellement bancal sous le contenu centré de la page.
   Ces règles vivent ici (et non en style inline dans MainLayout.razor) parce
   qu'un style inline `justify-content:space-between` l'emporterait sur la media
   query. */
.th-footer__inner {
  max-width: 1400px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px 30px;
  flex-wrap: wrap;
}
.th-footer__nav {
  display: flex;
  align-items: center;
  gap: 12px 26px;
  flex-wrap: wrap;
}
.th-footer__copy {
  font-family: var(--font-hand);
  font-weight: 700;
  font-size: 19px;
  color: var(--muted);
  flex-shrink: 0;
}
@media (max-width: 640px) {
  .th-footer__inner {
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
  }
  .th-footer__nav { justify-content: center; }
}

/* ── Tabbed editor mobile ── */
.th-editor-pane-map { min-height: 360px; }

@media (max-width: 900px) {
  .th-editor-split[data-active-tab="list"] .th-editor-pane-map,
  .th-editor-split[data-active-tab="list"] .th-editor-pane-inspect { display: none !important; }
  .th-editor-split[data-active-tab="map"] .th-editor-pane-list,
  .th-editor-split[data-active-tab="map"] .th-editor-pane-inspect { display: none !important; }
  .th-editor-split[data-active-tab="inspect"] .th-editor-pane-list,
  .th-editor-split[data-active-tab="inspect"] .th-editor-pane-map { display: none !important; }
  .th-editor-pane-list, .th-editor-pane-inspect { border: none !important; }
  .th-editor-pane-map { min-height: calc(100vh - 220px); }
}

/* ── Editor toolbar ── */
.th-editor-toolbar {
  padding: 12px 20px;
  border-bottom: 1px solid var(--line);
  background: var(--bg);
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
}

/* ── Editor tabs mobile ── */
.th-editor-tabs {
  display: flex;
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}

/* ── Modal bottom-sheet mobile ── */
@media (max-width: 640px) {
  .th-modal-overlay { align-items: flex-end !important; }
  .th-modal {
    width: 100% !important;
    max-width: 100% !important;
    border-radius: 16px 16px 0 0 !important;
    animation: th-slide-up 0.22s ease-out;
  }
}

/* ── Animations ── */
@keyframes th-pulse {
  0%, 100% { transform: scale(1); opacity: 0.18; }
  50% { transform: scale(1.8); opacity: 0; }
}
@keyframes th-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}
@keyframes th-slide-up {
  from { transform: translateY(24px); opacity: 0.5; }
  to { transform: translateY(0); opacity: 1; }
}
@keyframes th-pulse-ring {
  0%, 100% { box-shadow: 0 0 0 0 rgba(255, 59, 48, 0.45); }
  50% { box-shadow: 0 0 0 14px rgba(255, 59, 48, 0); }
}
/* Éclair de déclencheur : flash blanc bref au moment où l'on prend la photo,
   pour un retour visuel immédiat pendant que la capture haute qualité se traite. */
@keyframes th-shutter-flash {
  0% { opacity: 0.85; }
  100% { opacity: 0; }
}
@keyframes th-spin {
  to { transform: rotate(360deg); }
}

/* ── Scrollbar ── */
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: rgba(36,28,18,.22);
  border-radius: 9px;
}

/* ── Responsive breakpoints ── */
@media (max-width: 900px) {
  .th-row-stack.cols-58,
  .th-row-stack.cols-eq { grid-template-columns: 1fr; gap: 28px; }
  .th-stats.cols-5 { grid-template-columns: repeat(3, 1fr); }
  .th-stats.cols-4 { grid-template-columns: repeat(2, 1fr); }
  .th-grid-2 { grid-template-columns: 1fr; }
  .th-editor-split { grid-template-columns: 1fr; }
  .th-recap-grid { grid-template-columns: repeat(2, 1fr); }
  .th-recap-bottom { grid-template-columns: 1fr; }
  .th-pad-page { padding: 0 18px 96px; }
  .th-hide-mobile { display: none !important; }
  .th-hide-desktop { display: initial; }
}

@media (max-width: 640px) {
  .th-stats.cols-5,
  .th-stats.cols-4 { grid-template-columns: repeat(2, 1fr); gap: 16px; }
  .th-grid-cards { grid-template-columns: 1fr; gap: 16px; }
  .th-pad-page { padding: 0 14px 96px; }
}

/* ── Accessibility ── */
.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; }

/* ── Legacy compat shim (form-control for InputText in auth pages) ── */
.form-control { width: 100%; }

/* ── Play page — mode jeu (/jeu/{id}) : menu à 4 éléments fixes (boussole,
   points, temps + étapes, action de validation), en bas de page. Un seul menu,
   quelle que soit la taille d'écran — remplace l'ancienne sub-bar desktop et la
   barre du bas mobile à onglets (voir MainLayout.IsGameRoute, qui masque la nav
   globale sur cette route, et JeuSession.razor). ── */
.th-play-menu {
  display: flex;
  align-items: stretch;
  flex: 0 0 auto;
  border-top: 1px solid var(--line);
  background: var(--bg);
  /* Pas de padding-bottom safe-area ici : <main> (MainLayout) le fournit déjà
     via son propre padding-bottom, et ce menu est un enfant de flux normal
     (contrairement à l'ancienne .th-play-gamebar, position:fixed). */
}
.th-play-menu > *:not(:last-child) { border-right: 1px solid var(--line); }
.th-play-menu-item {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  min-width: 0;
  padding: 10px 6px;
  border: none;
  background: none;
  color: var(--ink);
  font-family: var(--font-sans);
  cursor: default;
}
button.th-play-menu-item { cursor: pointer; }
button.th-play-menu-item:disabled { cursor: default; opacity: .55; }
.th-play-menu-value {
  font-size: 15px;
  font-weight: 700;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.th-play-menu-label { font-size: 9.5px; }
.th-play-compass { display: flex; align-items: center; justify-content: center; color: var(--ember); }
.th-play-compass svg { transition: transform .25s ease; }
.th-play-menu-cta {
  flex: 1.15;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  padding: 10px 6px;
  border: none;
  background: var(--ember);
  color: #fff;
  font-family: var(--font-sans);
  cursor: pointer;
}
.th-play-menu-cta .th-play-menu-value { color: #fff; font-size: 12px; font-weight: 800; white-space: normal; text-align: center; }

/* ── Corps de jeu : carte (2/6 de la hauteur) fusionnée à l'indice (4/6) —
   toujours affichées ensemble, quelle que soit la taille d'écran (plus de
   bascule carte/indice mobile). ── */
.th-play-body {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}
.th-play-clue-panel {
  flex: 4 1 0%;
  min-height: 0;
  overflow-y: auto;
  padding: 20px clamp(16px, 4vw, 32px) 28px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

/* ── Titre de l'étape — au-dessus de la carte (voir JeuSession.razor). ── */
.th-play-title-bar {
  flex: 0 0 auto;
  padding: 16px clamp(16px, 4vw, 32px) 4px;
}

/* ── Badges (type/points/zone/hors-ligne) — overlay en haut de la carte, plutôt
   que dans l'indice (voir JeuSession.razor). Fond translucide flouté : certains
   badges (Tone="outline") ont un fond transparent, illisible sur les tuiles de
   la carte sans ce bandeau. Leaflet garde son control de zoom en bottomright et
   son attribution désactivée (voir leaflet-route-map.js) : rien en haut ne peut
   entrer en conflit avec ce bandeau. ── */
.th-play-map-badges {
  position: absolute;
  top: 0; left: 0; right: 0;
  padding: 12px 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  background: color-mix(in oklch, var(--bg-card) 85%, transparent);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  pointer-events: none;
}

/* ── Indice principal — pleine largeur, sans encadré ni libellé. ── */
.th-play-clue-text {
  font-family: var(--font-serif);
  font-style: italic;
  font-size: clamp(17px, 2.6vw, 20px);
  color: var(--ink);
  margin: 0;
  line-height: 1.55;
}

/* ── Bouton média — incite au clic (pulsation tz-pulse), ouvre le média en
   grand (lightbox plein écran, voir JeuSession.razor) plutôt que de l'intégrer
   directement dans l'indice. ── */
.th-play-media-btn {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border: none;
  border-radius: 999px;
  background: var(--ember);
  color: #fff;
  font-family: var(--font-sans);
  font-weight: 800;
  font-size: 13.5px;
  cursor: pointer;
  animation: tz-pulse 2.4s infinite;
}

/* ── Objectif de l'étape — fixe juste au-dessus du menu, ne défile jamais avec
   l'indice (contrairement au reste du contenu de .th-play-clue-panel). ── */
.th-play-objective {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px clamp(16px, 4vw, 32px);
  border-top: 1px solid var(--line);
  background: var(--bg-soft);
}
.th-play-objective-icon {
  width: 34px;
  height: 34px;
  border-radius: 10px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-card);
  border: 1px solid var(--line-strong);
  color: var(--tz-teal-d);
}
.th-play-objective-title { font-size: 12.5px; font-weight: 800; color: var(--ink); }
.th-play-objective-desc { font-size: 12.5px; color: var(--ink-soft); font-weight: 600; line-height: 1.45; margin-top: 3px; }

/* ══════════════════════════════════════════════════════════════════════
   RÈGLE GLOBALE — Leaflet ne doit JAMAIS passer au-dessus du menu/header
   ou des fenêtres modales. Ce bug est revenu à CHAQUE nouvelle intégration
   Leaflet (voir CLAUDE.md, section "Bugs UI résolus") car le correctif était
   appliqué au cas par cas sur certains composants (isolation:isolate en
   inline style) et oublié sur d'autres.
   `.leaflet-container` est la classe posée PAR Leaflet lui-même sur le div
   de la carte (L.map(...)) : cette règle protège donc AUTOMATIQUEMENT toute
   nouvelle carte Leaflet ajoutée à l'app, sans qu'il soit nécessaire d'y
   penser composant par composant. Ne pas retirer, ne pas la limiter à une
   classe de wrapper spécifique. ── */
.leaflet-container {
  isolation: isolate;
}

/* ── Bandeau RGPD masqué pendant une partie ────────────────────────────
   Les widgets flottants de tarteaucitron (#tarteaucitronIcon, et
   #tarteaucitronAlertSmall s'il venait à être réactivé) sont posés en
   position:fixed dans <body>, hors de l'arbre Blazor : aucun composant de
   jeu ne peut donc les cacher par son propre style.

   :has() résout ça SANS interop ni cycle de vie : la coque de jeu n'existe
   dans le DOM que pendant la partie (.th-play2-shell en mode parcours,
   .th-game-shell en mode collection), la règle s'applique et se retire donc
   d'elle-même à l'entrée et à la sortie. Une bascule pilotée depuis Blazor
   aurait dû défaire son effet dans Dispose — synchrone, sur un circuit
   parfois déjà coupé, donc peu fiable.

   Pourquoi : ces écrans sont des coques plein écran, tenues à une main sur
   le terrain, où chaque coin porte déjà une commande de jeu. Le consentement
   reste révocable — /cookies porte le même bouton. ── */
body:has(.th-play2-shell) #tarteaucitronIcon,
body:has(.th-play2-shell) #tarteaucitronAlertSmall,
body:has(.th-game-shell) #tarteaucitronIcon,
body:has(.th-game-shell) #tarteaucitronAlertSmall {
  display: none !important;
}

/* ── Icône flottante tarteaucitron masquée en mode connecté ────────────
   Une fois authentifié, le compte dispose d'un accès permanent au panneau
   de consentement dans l'onglet RGPD du profil (Pages/Player/Profil.razor,
   bouton « Gérer mes préférences de cookies » → window.thConsent.openPanel()) :
   l'icône ronde bas-droite (#tarteaucitronIcon) devient redondante et reste
   affichée en permanence sur toutes les pages pour un visiteur non connecté,
   qui n'a que /cookies pour y accéder. `.th-connecte` est posée par
   Shared/MainLayout.razor sur le conteneur racine dès que l'état
   d'authentification est résolu (AuthenticationStateProvider). ── */
body:has(.th-connecte) #tarteaucitronIcon,
body:has(.th-connecte) #tarteaucitronAlertSmall {
  display: none !important;
}

/* ══════════════════════════════════════════════════════════════════════
   Cartes d'ÉDITION (créateur) — les contrôles Leaflet des coins HAUT ne
   doivent jamais passer sous les pastilles flottantes de l'éditeur.
   Bug : sur l'éditeur de quête, la pastille « Vue parcours » (coin haut
   gauche) recouvrait le zoom +/- et la pastille « Étape N » / « Départ »
   (coin haut droit) recouvrait le sélecteur de calques (Plan / Satellite /
   Repères) — les deux contrôles devenaient inaccessibles au clic.
   Ces pastilles sont posées en position:absolute (inset 14px, z-index 500)
   PAR-DESSUS la carte, hors du contexte d'empilement de `.leaflet-container`
   (isolation:isolate ci-dessus) : elles gagnent donc toujours.
   Correctif : décaler les DEUX coins hauts de la carte sous la rangée de
   pastilles (pastille ≈ 33px + inset 14px ≈ 47px ; marge Leaflet 10px déjà
   comprise dans le total). Appliqué via la classe `.th-editor-map` posée par
   le composant de carte lui-même, donc valable pour tous ses usages
   (desktop étape 1 / étape 2, et carte plein écran mobile). ── */
.th-editor-map .leaflet-top.leaflet-left,
.th-editor-map .leaflet-top.leaflet-right {
  margin-top: 48px;
}

/* ── Roue crantée « main dominante » de l'appareil photo joueur (MediaValidator) ──
   Même principe que la roue crantée du mode chasse (.th-chasse-handed*, plus bas dans
   ce fichier), mais classes DÉDIÉES plutôt qu'un partage direct : ce bouton vit dans un
   flux flex (barre de titre de la caméra), pas en position:absolute par-dessus une
   carte, et MediaValidator n'est pas propre au mode chasse (quêtes, traversées…). */
.th-cam-handed {
  position: relative;
  flex-shrink: 0;
}
.th-cam-handed-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.4);
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  cursor: pointer;
}
.th-cam-handed-btn.is-open {
  background: var(--tz-teal-d);
  border-color: #fff;
}
/* Panneau ouvert SOUS le bouton et aligné sur son bord droit : il ne peut donc pas
   déborder de l'écran du côté opposé, quel que soit le côté choisi pour les réglages. */
.th-cam-handed-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 4;
  min-width: 178px;
  padding: 10px;
  border: 1.5px solid rgba(255, 255, 255, 0.25);
  border-radius: 14px;
  background: rgba(8, 20, 24, 0.92);
  box-shadow: 0 18px 40px -18px rgba(8, 20, 24, 0.8);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.th-cam-handed-title {
  font: 800 11px var(--font-sans);
  color: rgba(255, 255, 255, 0.75);
}
.th-cam-handed-seg {
  display: flex;
  border-radius: 999px;
  overflow: hidden;
  border: 1.5px solid rgba(255, 255, 255, 0.25);
}
.th-cam-handed-seg button {
  flex: 1;
  border: none;
  background: transparent;
  color: #fff;
  font: 800 12px var(--font-sans);
  padding: 7px 0;
  cursor: pointer;
}
.th-cam-handed-seg button.is-active {
  background: var(--tz-teal-d);
}

/* ── Note explicative du filigrane (Ghost Overlay) ── */
.th-mv-note {
  flex-shrink: 0;
  margin: 0 20px;
  padding: 9px 13px;
  border-radius: var(--r);
  background: rgba(255, 255, 255, 0.08);
  color: rgba(255, 255, 255, 0.82);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.4;
}

/* La note est CLIQUABLE : elle rouvre la fenêtre découverte (GhostOverlayIntro.razor).
   Sans cette porte de retour, « OK, j'ai compris » était sans appel — un joueur qui
   retombe sur une étape à filigrane des mois plus tard n'avait plus aucun moyen de
   revoir l'explication. Le reste du bloc ci-dessus conserve l'aspect qu'avait la note
   du temps où c'était un <div> ; on ne défait ici que les styles propres au <button>. */
.th-mv-note {
  display: block;
  width: calc(100% - 40px);
  text-align: left;
  border: none;
  font-family: var(--font-sans);
  cursor: pointer;
}
.th-mv-note:hover {
  background: rgba(255, 255, 255, 0.14);
}
.th-mv-note-more {
  color: var(--tz-teal);
  font-weight: 800;
  white-space: nowrap;
}

/* ══ Ghost Overlay — fenêtre découverte (GhostOverlayIntro.razor) ═══════════════
   Affichée une seule fois par joueur, par-dessus la capture plein écran (z-index 1500
   dans MediaValidator.razor) : d'où 1600 ici. Tout est en CSS — aucune image, aucune
   vidéo — pour que l'explication tourne aussi hors réseau, là où le filigrane sert le
   plus (montagne, sentier). */
.th-gi-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1600;
  background: rgba(6, 12, 18, 0.82);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom));
  animation: th-fade-in 0.2s ease both;
}
.th-gi-sheet {
  width: 100%;
  max-width: 420px;
  max-height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: #10161d;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--r);
  padding: 16px 18px 18px;
  color: #fff;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  animation: th-slide-up 0.24s ease both;
}

/* ── La scène animée ──
   Deux copies du MÊME tracé (<use href="#th-gi-shapes">) : en couleurs = le paysage vu
   par la caméra, en trait blanc = la photo de référence. Le trait blanc arrive décalé,
   pivoté et trop grand, puis vient coïncider — le geste exact demandé au joueur. La
   jauge, la consigne et la coche suivent le même minutage (6 s) : les quatre éléments
   racontent une seule et même chose, et la jauge du viseur devient lisible d'avance. */
.th-gi-stage {
  position: relative;
  aspect-ratio: 160 / 104;
  border-radius: 12px;
  overflow: hidden;
  /* Le ciel est ici, et non dans le SVG : un fill="url(#dégradé)" se résout contre le
     <base href> de l'application sur une partie des navigateurs, et disparaît alors sur
     les URL profondes — exactement celles d'une session de jeu. */
  background: linear-gradient(#8fdcea, #ffe6bd);
  border: 1px solid rgba(255, 255, 255, 0.16);
}
.th-gi-svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Le même tracé, distingué par la seule feuille de style : aplats colorés pour le paysage
   vu par la caméra, trait blanc pour la photo de référence posée par-dessus. */
.th-gi-real .th-gi-sun { fill: #ffd166; }
.th-gi-real .th-gi-far { fill: #2ec4b6; }
.th-gi-real .th-gi-near { fill: #12786f; }
.th-gi-ref .th-gi-sun,
.th-gi-ref .th-gi-far,
.th-gi-ref .th-gi-near {
  fill: none;
  stroke: #fff;
  stroke-width: 2.4;
  stroke-linejoin: round;
}
.th-gi-drift {
  transform-box: view-box;
  transform-origin: 50% 50%;
  animation: th-gi-align 6s cubic-bezier(0.45, 0.05, 0.25, 1) infinite;
}
@keyframes th-gi-align {
  0%, 10% { transform: translate(26px, -13px) scale(1.22) rotate(-7deg); opacity: 0.5; }
  48%, 76% { transform: translate(0, 0) scale(1) rotate(0deg); opacity: 0.95; }
  90%, 100% { transform: translate(26px, -13px) scale(1.22) rotate(-7deg); opacity: 0.5; }
}
.th-gi-tag {
  position: absolute;
  top: 8px;
  left: 8px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.5);
  color: #fff;
  font-size: 10.5px;
  font-weight: 800;
}
.th-gi-check {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: -19px 0 0 -19px;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--tz-teal);
  color: #fff;
  font-size: 20px;
  font-weight: 900;
  box-shadow: 0 0 0 6px rgba(46, 196, 182, 0.25);
  opacity: 0;
  animation: th-gi-check 6s ease infinite;
}
@keyframes th-gi-check {
  0%, 42% { opacity: 0; transform: scale(0.5); }
  52%, 76% { opacity: 1; transform: scale(1); }
  84%, 100% { opacity: 0; transform: scale(0.5); }
}
.th-gi-hud {
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 8px;
  padding: 7px 10px;
  border-radius: 10px;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
/* Les deux consignes occupent la MÊME ligne (superposées en absolu) et se relaient en
   fondu : la hauteur du bandeau ne bouge pas d'un cycle à l'autre, sinon toute la
   fenêtre tressauterait toutes les six secondes. */
.th-gi-cue {
  position: relative;
  display: block;
  min-height: 15px;
}
.th-gi-cue > span {
  position: absolute;
  inset: 0;
  color: #fff;
  font-size: 11px;
  font-weight: 800;
  line-height: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.th-gi-cue-search {
  animation: th-gi-cue-out 6s ease infinite;
}
.th-gi-cue-ok {
  opacity: 0;
  animation: th-gi-cue-in 6s ease infinite;
}
@keyframes th-gi-cue-out {
  0%, 38% { opacity: 1; }
  46%, 84% { opacity: 0; }
  92%, 100% { opacity: 1; }
}
@keyframes th-gi-cue-in {
  0%, 40% { opacity: 0; }
  50%, 78% { opacity: 1; }
  86%, 100% { opacity: 0; }
}
.th-gi-gauge {
  display: block;
  height: 6px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.2);
  overflow: hidden;
}
.th-gi-gauge-fill {
  display: block;
  height: 100%;
  width: 16%;
  border-radius: 999px;
  background: var(--tz-coral);
  animation: th-gi-gauge 6s cubic-bezier(0.45, 0.05, 0.25, 1) infinite;
}
@keyframes th-gi-gauge {
  0%, 10% { width: 16%; background: var(--tz-coral); }
  48%, 76% { width: 100%; background: var(--tz-teal); }
  90%, 100% { width: 16%; background: var(--tz-coral); }
}

/* ── Le texte ── */
.th-gi-title {
  margin: 14px 0 0;
  font-size: 17px;
  font-weight: 900;
  color: #fff;
}
.th-gi-lead {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.8);
}
.th-gi-steps {
  margin: 12px 0 0;
  padding-left: 20px;
  display: flex;
  flex-direction: column;
  gap: 7px;
  font-size: 12.5px;
  line-height: 1.45;
  color: rgba(255, 255, 255, 0.88);
}
.th-gi-steps li::marker {
  color: var(--tz-teal);
  font-weight: 900;
}
.th-gi-required {
  margin: 12px 0 0;
  padding: 9px 12px;
  border-radius: 10px;
  background: rgba(255, 107, 107, 0.16);
  border: 1px solid var(--tz-coral);
  color: #fff;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.4;
}
.th-gi-cta {
  margin-top: 16px;
  width: 100%;
  height: 48px;
  border: none;
  border-radius: var(--r);
  background: var(--tz-teal);
  color: #fff;
  font-family: var(--font-sans);
  font-size: 15px;
  font-weight: 800;
  cursor: pointer;
}
.th-gi-cta:hover {
  filter: brightness(1.06);
}

/* Paysage sur écran bas (téléphone tenu à l'horizontale, position courante en visée) :
   l'illustration passe À CÔTÉ du texte plutôt qu'au-dessus, sinon le bouton
   d'acquittement — la seule sortie de la fenêtre — tombe sous la ligne de flottaison.
   Chaque enfant est placé explicitement : le placement automatique autour d'un élément
   qui couvre plusieurs rangées dépend trop de l'ordre et du nombre d'enfants (le bloc
   « cadrage obligatoire » n'est pas toujours là). */
@media (min-width: 560px) and (max-height: 520px) {
  .th-gi-sheet {
    max-width: 660px;
    display: grid;
    grid-template-columns: 244px minmax(0, 1fr);
    column-gap: 18px;
    align-content: start;
  }
  .th-gi-stage {
    grid-column: 1;
    grid-row: 1 / span 5;
    align-self: start;
  }
  .th-gi-title,
  .th-gi-lead,
  .th-gi-steps,
  .th-gi-required,
  .th-gi-cta {
    grid-column: 2;
  }
  .th-gi-title { margin-top: 0; }
  .th-gi-steps { gap: 5px; }
}

@media (prefers-reduced-motion: reduce) {
  .th-gi-backdrop,
  .th-gi-sheet {
    animation: none;
  }
  /* Le mouvement PORTE ici l'explication : le supprimer sans rien mettre à la place
     laisserait une image incompréhensible. La scène est donc figée sur son état
     d'arrivée — les deux tracés superposés, jauge pleine, coche affichée : exactement
     le résultat que le joueur doit obtenir. */
  .th-gi-drift {
    animation: none;
    opacity: 0.95;
  }
  .th-gi-gauge-fill {
    animation: none;
    width: 100%;
    background: var(--tz-teal);
  }
  .th-gi-check {
    animation: none;
    opacity: 1;
    transform: scale(1);
  }
  .th-gi-cue-search {
    animation: none;
    opacity: 0;
  }
  .th-gi-cue-ok {
    animation: none;
    opacity: 1;
  }
}

/* ── Réglages (filigrane, qualité) + déclencheur/valider, du côté de la main
   dominante ── Défaut droitier : réglages empilés à GAUCHE, déclencheur à DROITE (les
   deux seuls enfants directs, dans cet ordre, d'où le flex-direction normal). En
   gaucher (.th-mv-left), la rangée est simplement inversée : le déclencheur passe à
   gauche, les réglages à droite — le même DOM sert les deux cas. */
.th-mv-controls {
  display: flex;
  align-items: center;
  gap: 22px;
}
.th-mv-controls.th-mv-left {
  flex-direction: row-reverse;
}
.th-mv-sliders {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.th-mv-quality {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.th-mv-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 18px;
}

/* ── Main dominante — SpotCaptureModal.razor (photomaton du mode collection).
   Défaut droitier : bouton « changer de caméra » en haut à DROITE, pastille GPS
   en haut à GAUCHE (pouce droit dégagé pour le déclencheur, centré en bas). En
   gaucher (racine .th-spotcam-left), les deux échangent leur coin : c'est le
   coin haut-droit qui doit rester libre pour le pouce. Réglage de compte (voir
   PlayerPreferencesService), partagé avec ChasseDetail.razor. ── */
.th-spotcam-left .th-spotcam-switch {
  right: auto !important;
  left: 16px !important;
}
.th-spotcam-left .th-spotcam-gps {
  left: auto !important;
  right: 16px !important;
  align-items: flex-end !important;
}

/* ══════════════════════════════════════════════════════════════════════
   Appareil photo plein écran (joueur) — mise en page PAYSAGE.
   Bug : sur mobile web, une fois le téléphone tourné en paysage, la barre
   de titre, le curseur « Qualité » et la barre de déclenchement s'empilent
   verticalement et écrasent l'aperçu caméra en une fine bande — on ne voit
   plus ce qu'on photographie, quelle que soit l'activité (traversée, quête
   ET collection).
   Correctif GLOBAL (comme la règle Leaflet ci-dessus) : en paysage sur écran
   bas (téléphones), on sort ces éléments de « chrome » du flux vertical et on
   les superpose en overlay. L'aperçu (flex:1, seul enfant restant dans le
   flux) reprend alors tout l'écran. Toute future page caméra qui réutilise
   ces classes en hérite automatiquement.
   Les styles inline des composants l'emportant sur la feuille de style, les
   surcharges sont marquées !important.
   Composants : MediaValidator.razor (photo/vidéo d'étape) et
   SpotCaptureModal.razor (collection). Seuil ~600px : les tablettes en
   paysage (hauteur > 600px) gardent la mise en page verticale normale. ── */
@media (orientation: landscape) and (max-height: 600px) {
  /* Barre de titre : overlay en haut, largeur réduite pour ne pas passer
     sous la colonne de contrôles ancrée à droite (défaut droitier). */
  .th-cam-head {
    position: absolute !important;
    top: 0;
    left: 0;
    right: 116px;
    z-index: 3;
    padding-top: calc(6px + env(safe-area-inset-top)) !important;
    padding-bottom: 8px !important;
    background: linear-gradient(to bottom, rgba(0, 0, 0, .6), transparent) !important;
  }

  /* Colonne de contrôles (déclencheur, valider, compteur…) : bande verticale
     ancrée à droite, plutôt qu'une barre horizontale en bas. */
  .th-cam-controls {
    position: absolute !important;
    top: 0;
    bottom: 0;
    right: 0;
    left: auto !important;
    flex-direction: column !important;
    gap: 18px !important;
    width: auto;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom)) !important;
    background: linear-gradient(to left, rgba(0, 0, 0, .55), transparent) !important;
    z-index: 3;
  }

  /* Curseur qualité (avant capture) et champs titre/commentaire (après
     capture, collection) : overlay en bas à gauche, dégagé de la colonne de
     droite. Les deux sont mutuellement exclusifs (qualité si aucune photo,
     champs sinon), ils peuvent donc partager la même zone.
     Ne concerne plus que SpotCaptureModal (collection) : la caméra d'étape
     (MediaValidator) a sa propre classe .th-mv-quality, plus bas, dont la
     position suit la colonne .th-mv-controls (qui peut passer à gauche). */
  .th-cam-quality,
  .th-cam-fields {
    position: absolute !important;
    left: 0;
    right: 116px;
    bottom: 0;
    z-index: 3;
    background: linear-gradient(to top, rgba(0, 0, 0, .6), transparent) !important;
  }

  .th-cam-fields {
    max-height: 42vh !important;
  }

  /* Bandeau bas de la caméra d'étape (MediaValidator) : verdict de cadrage ou note
     filigrane (mutuellement exclusifs, cf. MediaValidator.razor). Ancré à gauche par
     défaut (droitier — dégagé de la colonne de contrôles à droite) ; passe à droite en
     .th-mv-left (voir plus bas), pour rester dégagé quel que soit le côté choisi. */
  .th-cam-lower {
    position: absolute !important;
    left: 0;
    right: 116px;
    bottom: 0;
    z-index: 3;
    max-height: 52vh;
    overflow-y: auto;
    background: linear-gradient(to top, rgba(0, 0, 0, .65), transparent) !important;
  }

  /* Jauge de cadrage (Ghost Overlay) : déjà en overlay par construction (voir
     .th-ghost-hud dans MediaValidator.razor, posée DANS la zone d'aperçu
     position:relative), il ne reste qu'à la dégager de la colonne de contrôles
     ancrée à droite — comme les autres éléments de ce bloc. */
  .th-ghost-hud {
    right: 116px !important;
  }

  /* ── Réglages + déclencheur (MediaValidator) : même traitement que
     .th-cam-controls ci-dessus — bande verticale ancrée à droite par défaut —
     mais les deux curseurs (filigrane, qualité) y vivent maintenant AUSSI (ils ne
     sont plus dans .th-cam-lower), empilés au-dessus du déclencheur dans la même
     colonne étroite. */
  .th-mv-controls {
    position: absolute !important;
    top: 0;
    bottom: 0;
    right: 0;
    left: auto !important;
    flex-direction: column !important;
    width: auto;
    padding: 10px 14px calc(10px + env(safe-area-inset-bottom)) !important;
    background: linear-gradient(to left, rgba(0, 0, 0, .55), transparent) !important;
    z-index: 3;
  }
  .th-mv-sliders {
    width: 108px;
  }
  .th-mv-actions {
    flex-direction: column !important;
  }

  /* ── Main gauche : la colonne de réglages/déclencheur passe du côté opposé.
     Les bandeaux qui évitaient la colonne de droite (right:116px) doivent alors
     éviter la colonne de gauche à la place — sinon un joueur gaucher retrouverait
     son propre déclencheur caché sous le titre ou le bandeau de verdict. */
  .th-mv-controls.th-mv-left {
    right: auto !important;
    left: 0;
    background: linear-gradient(to right, rgba(0, 0, 0, .55), transparent) !important;
  }
  .th-cam-head.th-mv-left {
    left: 116px;
    right: 0;
  }
  .th-cam-lower.th-mv-left {
    left: 116px;
    right: 0;
  }
  .th-ghost-hud.th-mv-left {
    right: 16px !important;
    left: 116px !important;
  }
}

/* ── Lien e-mail protégé contre le moissonnage (spam / phishing) ──────────
   Composant EmailLink.razor. Le « @ » n'est pas dans le texte du DOM :
   il est injecté ici via ::before, donc absent du textContent lu par les
   robots collecteurs. Utiliser l'échappement Unicode \0040 pour ne pas
   écrire non plus le caractère « @ » littéral dans la feuille de style. ── */
.th-eml {
  cursor: pointer;
  white-space: nowrap;
}
.th-eml__at::before {
  content: "\0040"; /* @ */
}

/* ── Impression de la fiche QR Code (ParcoursEditor) ──────────────────────
   Isole #qr-fiche-print du reste de la page à l'impression via `visibility`
   (préserve la mise en page, contrairement à `display:none`). ── */
@media print {
  body * {
    visibility: hidden;
  }
  #qr-fiche-print,
  #qr-fiche-print * {
    visibility: visible;
  }
  #qr-fiche-print {
    position: fixed;
    inset: 0;
    margin: auto;
    box-shadow: none !important;
  }
  .qr-fiche-no-print {
    display: none !important;
  }
}

/* ── Impression de la fiche parcours (ParcoursPrintSheet) ─────────────────
   Même principe que la fiche QR Code ci-dessus : isole #parcours-print-sheet
   du reste de la page à l'impression via `visibility`. ── */
@media print {
  body * {
    visibility: hidden;
  }
  #parcours-print-sheet,
  #parcours-print-sheet * {
    visibility: visible;
  }
  #parcours-print-sheet {
    position: absolute;
    inset: 0;
    margin: 0;
  }
  /* La zone de défilement de l'écran ne doit plus rien rogner à l'impression. */
  .th-print-scroll {
    overflow: visible !important;
  }
  .th-print-no-print {
    display: none !important;
  }
}

/* La route /creator/parcours/{id}/imprimer est une « editor route » : le shell
   (MainLayout) fige la hauteur à 100dvh avec overflow:hidden. Sans conteneur de
   défilement dédié, une fiche plus haute que l'écran est tronquée et impossible à
   faire défiler. On reproduit donc le patron de l'éditeur : colonne flex, barre
   d'outils figée en haut, fiche dans une zone qui scrolle. */
.th-print-page {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.th-print-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.th-print-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 20px;
  border-bottom: 1.5px solid var(--line);
  background: var(--bg-card);
}

.th-print-mode-toggle {
  display: inline-flex;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--r);
  overflow: hidden;
}

.th-print-mode-toggle button {
  border: none;
  background: transparent;
  padding: 8px 16px;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 800;
  color: var(--muted);
  cursor: pointer;
}

.th-print-mode-toggle button.is-active {
  background: var(--tz-teal);
  color: #fff;
}

#parcours-print-sheet {
  max-width: 900px;
  margin: 0 auto;
  padding: 24px;
  background: #fff;
  color: #111;
}

.th-print-header h1 {
  font-size: 24px;
  margin: 0 0 4px;
}

.th-print-lieu {
  margin: 0 0 8px;
  color: #555;
  font-weight: 700;
}

.th-print-mode-banner {
  display: inline-block;
  padding: 4px 10px;
  border-radius: 8px;
  font-size: 12px;
  font-weight: 800;
  margin-bottom: 16px;
}

.th-print-mode-banner.is-organisateur {
  background: #fde8e4;
  color: #b3392a;
}

.th-print-mode-banner.is-joueur {
  background: #e2f4ef;
  color: #0b4f5a;
}

.th-print-map {
  border: 1.5px solid #ccc;
  border-radius: 12px;
  overflow: hidden;
  margin-bottom: 24px;
  break-inside: avoid;
}

.th-print-etape {
  display: flex;
  gap: 14px;
  padding: 16px 0;
  border-top: 1.5px solid #e5e5e5;
  break-inside: avoid;
}

.th-print-etape-num {
  flex-shrink: 0;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: #1a1a1a;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 14px;
}

.th-print-etape-body h2 {
  margin: 0 0 8px;
  font-size: 16px;
}

.th-print-media-badge {
  display: inline-block;
  padding: 5px 10px;
  border-radius: 8px;
  background: #fff6e0;
  color: #7a5a00;
  font-weight: 800;
  font-size: 12.5px;
  margin-bottom: 8px;
}

.th-print-photo {
  max-width: 260px;
  max-height: 200px;
  object-fit: cover;
  border-radius: 8px;
  border: 1.5px solid #ddd;
  display: block;
  margin-bottom: 8px;
}

.th-print-qr {
  width: 90px;
  height: 90px;
}

.th-print-qr-caption {
  font-size: 11.5px;
  color: #777;
  margin: 4px 0 8px;
}

.th-print-indice-texte {
  margin: 0 0 10px;
  font-size: 14px;
  line-height: 1.5;
}

.th-print-lettres {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
}

.th-print-mot {
  display: flex;
  gap: 5px;
}

.th-print-case {
  width: 28px;
  height: 34px;
  border: 1.5px solid #999;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 15px;
}

.th-print-consigne {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  color: #333;
}

/* ── Livret plié A4 (ParcoursBookletSheet) ────────────────────────────────────
   Une feuille A4 (210×297mm) recto/verso pliée en quatre = 8 panneaux A6
   (105×148,5mm). Chaque face A4 porte une grille 2×2 ; la rangée haute est
   pivotée de 180° pour se lire dans l'ordre une fois la feuille pliée.
   Le format de page A4 est confiné aux feuilles du livret via une page nommée
   (`@page booklet`), pour ne pas affecter les autres impressions de l'app. ── */
@page booklet {
  size: A4 portrait;
  margin: 0;
}

.th-bk-toolbar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 20px;
  border-bottom: 1.5px solid var(--line);
  background: var(--bg-card);
}

.th-bk-toolbar-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.th-bk-instructions {
  max-width: 900px;
  margin: 16px auto 0;
  padding: 14px 18px;
  background: #eef7f4;
  border: 1.5px solid #bfe3da;
  border-radius: 12px;
  color: #0b4f5a;
  font-size: 13.5px;
}

.th-bk-instructions p {
  margin: 0 0 6px;
}

.th-bk-instructions ol {
  margin: 0;
  padding-left: 20px;
}

.th-bk-instructions li {
  margin: 2px 0;
  line-height: 1.45;
}

#parcours-booklet {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 24px 12px 48px;
  background: var(--bg);
}

.th-bk-sheet {
  page: booklet;
  position: relative;
  box-sizing: border-box;
  width: 210mm;
  height: 297mm;
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr 1fr;
  background: #fff;
  color: #111;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
}

.th-bk-sheet-label {
  position: absolute;
  top: -22px;
  left: 0;
  font-size: 12px;
  font-weight: 800;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.th-bk-panel {
  box-sizing: border-box;
  width: 105mm;
  height: 148.5mm;
  overflow: hidden;
  border: 1px dashed #cfcfcf; /* repères de pli / découpe */
}

.th-bk-panel.is-rotated {
  transform: rotate(180deg);
}

/* Mode paysage : A4 couché (297 × 210 mm), zones A6 larges (148,5 × 105 mm). */
.th-bk-landscape .th-bk-sheet {
  width: 297mm;
  height: 210mm;
}

.th-bk-landscape .th-bk-panel {
  width: 148.5mm;
  height: 105mm;
}

/* Pages blanches : lignes espacées pour l'écriture (notes des enfants). */
.th-bk-notes-label {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #b8b8b8;
  margin-bottom: 4mm;
}

.th-bk-notes-lines {
  flex: 1;
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent 10mm,
    #d6d6d6 10mm,
    #d6d6d6 calc(10mm + 1px)
  );
}

/* Page « contenu débloqué » du livret : la mention identifie sans ambiguïté l'étape
   qui l'a débloquée, une fois le livret découpé, plié et relié. */
.th-bk-carnet-mention {
  font-size: 8pt;
  font-weight: 800;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #fff;
  background: #1a1a1a;
  border-radius: 1mm;
  padding: 2.5mm 3mm;
  margin-bottom: 3mm;
  line-height: 3.6mm;
}

.th-bk-carnet-texte {
  flex: 1;
  font-size: 9pt;
  line-height: 4.1mm;
  color: #1e1e1e;
  white-space: pre;
}

/* ── Livret escape — carnet d'équipe papier et billets de fragment ──────────
   Les hauteurs reproduisent les constantes de BookletLayout (EcritureLigneH 9 mm,
   EquipeRowGap 2.2 mm, SlipPad 3 mm, SlipGap 2.5 mm, fragment 13 pt / 5.8 mm) :
   l'aperçu doit occuper la même place que le PDF, au millimètre. Lignes en
   white-space: pre — le navigateur ne peut pas AJOUTER de ligne (bug nº16). */
.th-bk-esc-head {
  margin: 0 0 3mm;
  font-size: 11pt;
  line-height: 4.7mm;
  font-weight: 700;
  color: #111;
  white-space: pre;
}

.th-bk-esc-row {
  margin-bottom: 2.2mm;
}

.th-bk-esc-row-label {
  font-size: 9pt;
  line-height: 4.1mm;
  font-weight: 400;
  color: #1e1e1e;
  white-space: pre;
}

.th-bk-esc-row-label--combinaison {
  font-weight: 700;
  color: #111;
}

/* La ligne d'écriture au crayon : toute la réserve de 9 mm, le trait au bas. */
.th-bk-esc-ligne {
  height: 9mm;
  border-bottom: 0.25mm dotted #969696;
}

.th-bk-esc-slip {
  border: 0.25mm dashed #969696;
  border-radius: 1.5mm;
  padding: 3mm;
  margin-bottom: 2.5mm;
}

.th-bk-esc-slip-label {
  font-size: 8pt;
  line-height: 3.6mm;
  font-weight: 700;
  color: #7a7a7a;
  white-space: pre;
  margin-bottom: 1mm;
}

.th-bk-esc-frag {
  font-size: 13pt;
  line-height: 5.8mm;
  font-weight: 700;
  color: #111;
  white-space: pre;
}

.th-bk-esc-portee {
  font-size: 8pt;
  line-height: 3.6mm;
  color: #787878;
  white-space: pre;
}

.th-bk-fin-site {
  margin: 6px 0 0;
  font-size: 11px;
  color: #777;
  text-align: center;
  word-break: break-all;
}

/* L'aperçu reproduit les MÉTRIQUES DU PDF : mêmes tailles (en points), mêmes
   interlignes (en millimètres) et même famille de police (Helvetica/Arial, celle
   qu'embarque jsPDF). Les lignes arrivent déjà découpées par le moteur de mise en
   page (src/TreasureHunt.Web/Booklet/BookletLayout.cs) et `white-space: pre-wrap`
   les pose telles quelles : l'aperçu montre alors exactement ce qui s'imprimera,
   et une page ne peut plus se faire rogner faute de place. */
/* L'aperçu reproduit les MÉTRIQUES DU PDF : mêmes tailles (en points), mêmes
   interlignes (en millimètres), même famille de police (Helvetica/Arial, celle
   qu'embarque jsPDF) et même GRAISSE. Les lignes arrivent déjà découpées par le moteur
   de mise en page (src/TreasureHunt.Web/Booklet/BookletLayout.cs) et `white-space: pre`
   les pose telles quelles : l'aperçu montre alors exactement ce qui s'imprimera. */
.th-bk-page {
  box-sizing: border-box;
  width: 100%;
  height: 100%;
  /* 7 mm = BookletLayout.Pad. Le bas est légèrement plus court : ce jeu absorbe
     l'écart de rendu entre le moteur de texte du navigateur et celui du PDF. */
  padding: 7mm 7mm 5.5mm;
  display: flex;
  flex-direction: column;
  position: relative;
  font-family: Helvetica, Arial, sans-serif;
  /* CAPITAL : `body` impose 600 à toute l'application. Le livret en héritait, alors que
     la mesure se fait avec la table AFM NORMALE (400) — le demi-gras étant plus large,
     le navigateur repliait des lignes que le PDF n'a pas, et la page débordait. Les
     blocs qui se mesurent en gras (titre, bandeaux, consigne) posent leur propre poids. */
  font-weight: 400;
}

/* Bandeau de continuation (« Étape 3 — suite (2/3) »), en tête des pages 2, 3… d'une
   étape trop longue pour une seule case A6. Hauteur totale = BookletLayout.SuiteHeadH. */
.th-bk-suite {
  font-size: 8pt;
  font-weight: 800;
  line-height: 3.4mm;
  color: #8c8c8c;
  border-bottom: 0.2mm solid #dcdcdc;
  padding-bottom: 1mm;
  margin-bottom: 2.1mm;
}

/* Bandeau de continuation (« Étape 3 — suite (2/3) »), en tête des pages 2, 3… d'une
   étape trop longue pour une seule case A6. Hauteur totale = BookletLayout.SuiteHeadH. */
.th-bk-suite {
  font-size: 8pt;
  font-weight: 800;
  line-height: 3.4mm;
  color: #8c8c8c;
  border-bottom: 0.2mm solid #dcdcdc;
  padding-bottom: 1mm;
  margin-bottom: 2.1mm;
}

.th-bk-page-num {
  position: absolute;
  top: 4px;
  right: 6px;
  font-size: 10px;
  font-weight: 800;
  color: #b8b8b8;
}

/* Couverture & fin — centrées */
.th-bk-cover,
.th-bk-fin {
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0;
}

.th-bk-cover-logo {
  width: 52mm;
  max-width: 80%;
  height: auto;
  display: block;
  margin-bottom: 4mm;
}

.th-bk-fin-logo {
  width: 30mm;
  max-width: 60%;
  height: auto;
  display: block;
  margin-top: 3mm;
  opacity: 0.75;
}

.th-bk-cover-rule {
  width: 28mm;
  height: 0.4mm;
  background: #c8c8c8;
  margin: 2mm 0 3mm;
}

.th-bk-cover h1,
.th-bk-fin h1 {
  margin: 0;
  font-size: 17pt;
  line-height: 6.6mm;
  font-weight: 700;
  /* Lignes déjà découpées, comme partout ailleurs dans le livret. */
  white-space: pre;
}

.th-bk-cover-lieu {
  margin: 0;
  font-weight: 700;
  color: #555;
  font-size: 13px;
}

.th-bk-cover-lore {
  margin: 0;
  font-size: 10pt;
  line-height: 4.2mm;
  color: #444;
  white-space: pre;
}

.th-bk-cover-meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 12px;
  font-weight: 800;
  font-size: 13px;
  color: #0b4f5a;
}

.th-bk-fin-message {
  margin: 2mm 0 0;
  font-size: 9.5pt;
  line-height: 4mm;
  color: #444;
  white-space: pre;
}

.th-bk-fin-score {
  margin-top: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: 14px;
}

.th-bk-fin-box {
  width: 46px;
  height: 34px;
  border: 1.5px solid #999;
  border-radius: 6px;
}

.th-bk-fin-total {
  color: #777;
}

/* Étape */
.th-bk-etape-head {
  display: flex;
  align-items: center;
  gap: 2mm;
  min-height: 6.8mm;
  margin-bottom: 3.2mm;
}

.th-bk-etape-num {
  flex-shrink: 0;
  width: 6.8mm;
  height: 6.8mm;
  border-radius: 50%;
  background: #1a1a1a;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 10pt;
}

.th-bk-etape-head h2 {
  margin: 0;
  font-size: 11pt;
  line-height: 4.7mm;
}

.th-bk-media-badge {
  align-self: flex-start;
  padding: 0.2mm 1.6mm;
  border-radius: 2mm;
  background: #fff6e0;
  color: #7a5a00;
  font-weight: 800;
  font-size: 8pt;
  line-height: 3.6mm;
  margin-bottom: 1mm;
}

.th-bk-qr {
  width: 25mm;
  height: 25mm;
  align-self: center;
  margin-bottom: 1.5mm;
}

.th-bk-qr canvas,
.th-bk-qr img {
  width: 100%;
  height: 100%;
  display: block;
}

.th-bk-qr-caption {
  font-size: 7.5pt;
  line-height: 3mm;
  color: #777;
  text-align: center;
  margin: 0 0 1.5mm;
}

/* La photo d'indice occupe TOUTE sa page du livret : la hauteur est posée en style
   inline par ParcoursBookletView, depuis BookletLayout.PhotoPageH — c'est elle qui a
   été comptée dans la pagination, pas le rapport de l'image. */
.th-bk-photo-box {
  margin-bottom: 2mm;
  display: flex;
  align-items: center;
  justify-content: center;
}

.th-bk-photo {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 1.5mm;
  border: 0.3mm solid #ddd;
  display: block;
}

.th-bk-consigne {
  margin: 0 0 1.8mm;
  font-size: 9.5pt;
  line-height: 4.3mm;
  white-space: pre;
}

.th-bk-avertissement {
  margin: 0 0 1.8mm;
  font-size: 9pt;
  line-height: 4.1mm;
  font-weight: 700;
  color: #333;
  white-space: pre;
}

.th-bk-script {
  margin: 0 0 1.8mm;
  font-size: 8.5pt;
  line-height: 3.9mm;
  font-style: italic;
  color: #444;
  white-space: pre;
}

/* Pas de `margin-top: auto` : les cases suivent le texte, comme dans le PDF. Les
   pousser en bas de la case ferait diverger l'aperçu de la page imprimée. */
.th-bk-lettres {
  display: flex;
  flex-wrap: wrap;
  gap: 2mm 3mm;
  margin-top: 1mm;
}

.th-bk-mot {
  display: flex;
  gap: 1.3mm;
}

.th-bk-case {
  width: 5.6mm;
  height: 7.2mm;
  border: 0.3mm solid #999;
  border-radius: 1mm;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-weight: 800;
  font-size: 11pt;
}

/* Page « carte du parcours » du livret — voir ParcoursBookletView.CartePage. */
.th-bk-carte {
  gap: 6px;
}

.th-bk-carte-head {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-weight: 800;
  font-size: 12.5px;
  color: #1a1a1a;
}

.th-bk-carte-sub {
  font-size: 10.5px;
  font-weight: 700;
  color: #999;
}

.th-bk-carte-map {
  flex: 1;
  min-height: 0;
  border-radius: 6px;
  overflow: hidden;
  border: 1.5px solid #ddd;
}

.th-bk-carte-map .leaflet-control-container {
  display: none;
}

/* Impression : n'afficher que le livret ; chaque feuille A4 sur une page. */
@media print {
  body * {
    visibility: hidden;
  }
  #parcours-booklet,
  #parcours-booklet * {
    visibility: visible;
  }
  #parcours-booklet {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 0;
    gap: 0;
    background: #fff;
  }
  #parcours-booklet .th-print-no-print {
    display: none !important;
  }
  .th-bk-sheet {
    box-shadow: none;
    margin: 0;
    break-after: page;
  }
  .th-bk-sheet:last-child {
    break-after: auto;
  }
  .th-bk-panel {
    border-color: #e6e6e6;
  }
}

/* ── Map panel (mode jeu) — 2/6 de la hauteur du corps, voir .th-play-body. ── */
.th-play-map-panel {
  flex: 2 1 0%;
  /* Filet de sécurité : garantit une hauteur visible même si un ancêtre venait à
     perdre sa hauteur définitive (voir MainLayout.IsGameRoute → height:100dvh),
     sans quoi ce panneau flex (flex-basis:0%, sans contenu en flux normal —
     PlayerPlayMap est en position:absolute) s'effondre à 0px et la carte disparaît. */
  min-height: 140px;
  position: relative;
  overflow: hidden;
  border-bottom: 1px solid var(--line);
  /* Confine les z-index internes de Leaflet (tuiles, marqueurs, contrôles ~1000)
     dans un contexte d'empilement local, sinon la carte passe AU-DESSUS des
     fenêtres modales (overlay z-index 1000+). */
  isolation: isolate;
}
/* Filet de sécurité : borne les z-index des panes/contrôles Leaflet afin
   qu'ils restent toujours sous les fenêtres modales du jeu. */
.th-play-map-panel .leaflet-pane,
.th-play-map-panel .leaflet-top,
.th-play-map-panel .leaflet-bottom,
.th-play-map-panel .leaflet-control { z-index: 1 !important; }

/* ══════════════════════════════════════════════════════════════════════
   Écran de jeu — template "2a" (import Claude Design, ADR-0004,
   docs/adr/0004-templates-ux-jeu-session.md). Colonne pleine hauteur :
   en-tête d'étape pleine largeur (aide « ? » + quitter), carte GPS sur la
   moitié du corps (boussole chaud/froid en overlay), corps défilant pour
   l'indice/consigne, barre d'actions fixe en bas (média, indice
   supplémentaire, CTA de validation).
   Classes distinctes (.th-play2-*) du template figé .th-play-* utilisé par
   JeuSessionInitial.razor (/jeu/{id}/initial) — ne jamais faire converger
   les deux jeux de classes, elles partagent la même feuille de style
   globale mais doivent pouvoir évoluer indépendamment.
   ══════════════════════════════════════════════════════════════════════ */

.th-play2-shell {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  background: var(--bg);
  display: flex;
  flex-direction: column;
}
@media (min-width: 640px) {
  .th-play2-shell {
    width: 100%;
    max-width: 560px;
    margin: 0 auto;
    box-shadow: 0 0 0 1px var(--line), 0 30px 70px -30px rgba(11, 79, 90, 0.35);
  }
}

/* Carte GPS : 52 % du corps (itération 2a « la carte respire ») ; overlays (chips, polaroid,
   boussole minimaliste) confinés */
.th-play2-map {
  position: relative;
  flex: 0 0 52%;
  min-height: 0;
  overflow: hidden;
  isolation: isolate; /* confine les z-index Leaflet, comme .th-play-map-panel */
}
.th-play2-map .leaflet-pane,
.th-play2-map .leaflet-top,
.th-play2-map .leaflet-bottom,
.th-play2-map .leaflet-control { z-index: 1 !important; }


/* ── En-tête d'étape — pleine largeur, en flux au-dessus de la carte ── */
.th-play2-objective {
  position: relative;
  z-index: 25;
  flex-shrink: 0;
  width: 100%;
  background: var(--bg-card);
  border-bottom: 1.5px solid var(--line);
  box-shadow: 0 14px 30px -20px rgba(11, 79, 90, 0.5);
  padding: 5px 12px 6px;
  animation: tz-pop 0.3s ease;
}
.th-play2-objective-btns {
  display: inline-flex;
  gap: 8px;
  margin-left: auto;
}
.th-play2-icon-btn {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 1.5px solid var(--line-strong);
  background: var(--bg-soft);
  color: var(--tz-teal-d);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  padding: 0;
}
.th-play2-objective-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
}
.th-play2-objective-meta {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font: 800 12px var(--font-sans);
  color: var(--ink);
  white-space: nowrap;
}
/* Titre de l'étape + boutons (aide « ? », quitter « × ») sur la même ligne,
   sans zone blanche : header compact. */
.th-play2-objective-titlerow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 2px;
}
.th-play2-objective-title {
  font-family: var(--font-serif);
  font-weight: 600;
  font-size: clamp(16px, 4vw, 19px);
  line-height: 1.15;
  color: var(--tz-teal-d);
  min-width: 0;
  flex: 1;
}

/* ── Légende zone / hors-ligne (chips flottants) ── */
.th-play2-zone-chip {
  position: absolute;
  left: 16px;
  z-index: 24;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 800 10px var(--font-sans);
  letter-spacing: 0.04em;
  color: var(--tz-teal-d);
  background: color-mix(in oklch, var(--bg-card) 92%, transparent);
  padding: 4px 9px;
  border-radius: 7px;
  box-shadow: 0 6px 14px -10px rgba(11, 79, 90, 0.5);
}

/* ── Boussole chaud/froid — en overlay au bas de la carte ── */
.th-play2-compass-float {
  position: absolute;
  bottom: 80px;
  left: 14px;
  z-index: 26;
  display: flex;
  align-items: center;
  gap: 10px;
}
.th-play2-dial {
  width: 96px;
  height: 96px;
  min-width: 96px;
  position: relative;
  display: block;
  filter: drop-shadow(0 12px 22px rgba(11, 79, 90, 0.35));
}
.th-play2-dial:disabled { cursor: default; }
.th-play2-dial-ring {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--bg-card); border: 2px solid var(--line);
}
.th-play2-dial-ring2 { position: absolute; inset: 11px; border-radius: 50%; border: 2px solid var(--line); }
.th-play2-dial-ring3 { position: absolute; inset: 23px; border-radius: 50%; border: 2px solid var(--line); }
.th-play2-dial-arc {
  position: absolute; inset: -1px; border-radius: 50%;
  -webkit-mask: radial-gradient(circle, transparent 44px, #000 46px);
  mask: radial-gradient(circle, transparent 44px, #000 46px);
  transition: background 0.4s ease;
}
.th-play2-dial-sweep {
  position: absolute;
  top: 2px; left: 50%;
  width: 2px; height: 46px;
  transform-origin: bottom center;
  animation: tz-spin 4s linear infinite;
  transition: opacity 0.4s ease;
}
.th-play2-dial-needle {
  position: absolute;
  left: 50%; top: 50%;
  transform-origin: bottom center;
  height: 38px;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  transition: transform 0.4s ease;
}
.th-play2-dial-center {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width: 11px; height: 11px;
  border-radius: 50%;
  border: 2px solid var(--bg-card);
  transition: background 0.4s ease;
}
.th-play2-proximity-card {
  background: var(--bg-card);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  box-shadow: 0 12px 24px -16px rgba(11, 79, 90, 0.5);
  padding: 9px 13px;
  min-width: 0;
  transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
}
/* Signalisation d'approche « Tu brûles » : le bloc « Proximité » remplace l'ancien
   bandeau — fond dégradé corail/ambre + textes blancs (voir ProximityAccentColor). */
.th-play2-proximity-card--hot {
  background: linear-gradient(120deg, var(--tz-coral), var(--tz-amber));
  border-color: transparent;
  box-shadow: 0 14px 28px -14px rgba(242, 84, 61, 0.7);
  animation: tz-pop 0.3s ease;
}

/* Boussole matérielle : bouton d'activation (permission device) + état actif.
   L'aiguille pointe alors la cible relativement à l'orientation réelle du téléphone. */
.th-play2-compass-enable {
  margin-top: 6px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 9px;
  border: 1.5px solid var(--line);
  border-radius: 999px;
  background: var(--bg-soft);
  color: var(--ink-soft);
  font: 800 10px var(--font-sans);
  cursor: pointer;
  transition: background 0.2s ease, border-color 0.2s ease;
}
.th-play2-compass-enable:hover {
  background: var(--bg-card);
  border-color: var(--line-strong);
}
/* Boussole matérielle active : simple icône accolée au libellé « Proximité », DANS la
   pilule. Elle vivait auparavant sous le cluster, où `--pill` (flex-direction: column) lui
   donnait une seconde ligne flottante mordant sur le bas de la carte. Aucune marge ni
   hauteur propre : le badge ne doit rien coûter à la carte. */
.th-play2-compass-state {
  display: inline-flex;
  align-items: center;
  line-height: 0;
}
.th-play2-compass-state--on {
  color: var(--canopy);
}

/* ── Corps défilant (indice, consigne, feedback) — sous la carte ── */
.th-play2-sheet {
  position: relative;
  z-index: 30;
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  background: var(--bg-card);
  box-shadow: 0 -14px 34px -22px rgba(11, 79, 90, 0.5);
  padding: 14px 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.th-play2-clue-box {
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--ember);
  background: var(--bg-soft);
  border-radius: var(--r);
  box-shadow: 0 10px 24px -20px rgba(11, 79, 90, 0.4);
}
.th-play2-clue-box--clickable { cursor: pointer; }
.th-play2-clue-box p {
  font-family: var(--font-serif);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
  /* Tronque au-delà de 4 lignes — la zone devient alors cliquable et ouvre
     l'indice complet en modale (voir th.isTextClamped / JeuSession.razor). */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
  line-clamp: 4;
  overflow: hidden;
}
.th-play2-clue-more {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  font: 800 11px var(--font-sans);
  color: var(--ember);
}

.th-play2-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.th-play2-action-btn {
  flex: 1 1 140px;
  min-height: 42px;
  border-radius: 12px;
  border: 1.5px solid var(--line-strong);
  background: var(--bg-card);
  color: var(--tz-teal-d);
  font: 800 12px var(--font-sans);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  cursor: pointer;
  padding: 8px 10px;
}
/* Onglet actif (Carte / Collection) de la barre d'actions du mode jeu chasse : seul le
   contour est mis en évidence (liseré teal + texte teal), le fond reste clair — un remplissage
   plein contrastait trop avec le bouton inactif. */
.th-play2-action-btn--active {
  border-color: var(--tz-teal-d);
  color: var(--tz-teal-d);
  box-shadow: inset 0 0 0 1px var(--tz-teal-d);
}
.th-play2-hint-btn {
  border-style: dashed;
  background: var(--bg-soft);
  color: var(--ink-soft);
}
.th-play2-media-btn { animation: tz-pulse 2.4s infinite; }

.th-play2-cta {
  width: 100%;
  min-height: 52px;
  border: none;
  border-radius: 16px;
  background: var(--ember);
  color: #fff;
  font: 800 15.5px var(--font-sans);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-shadow: 0 14px 26px -12px rgba(242, 84, 61, 0.55);
  cursor: pointer;
}

/* ── Barre d'actions — fixe en bas de l'écran, jamais sous le pli ── */
.th-play2-actionbar {
  flex-shrink: 0;
  z-index: 31;
  background: var(--bg-card);
  border-top: 1.5px solid var(--line);
  box-shadow: 0 -14px 34px -22px rgba(11, 79, 90, 0.5);
  padding: 10px 16px calc(12px + env(safe-area-inset-bottom));
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* ── Lightbox audio : gros bouton lecture/pause centré ── */
.th-play2-audio-toggle {
  width: 92px;
  height: 92px;
  border-radius: 50%;
  border: none;
  cursor: pointer;
  background: var(--ember);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 18px 40px -14px rgba(242, 84, 61, 0.7);
}

/* ══════════════════════════════════════════════════════════════════════
   Écran de jeu — itération 2a « Polaroid + boussole minimaliste » (ADR-0004).
   Le cadran à 3 anneaux + la grosse carte « Proximité » sont remplacés par une
   boussole minimaliste (pastille flèche + pilule distance) ; l'indice photo est
   « scotché » en polaroid sur la carte ; l'indice texte défile dans sa carte
   (fondu de débordement) au lieu d'ouvrir une modale.
   Classes ADDITIVES : les anciennes (.th-play2-dial*, .th-play2-proximity-card*)
   restent définies — encore utilisées par la coque Maui et les thèmes archivés.
   ══════════════════════════════════════════════════════════════════════ */

/* Conteneur boussole : pile verticale (pastille au-dessus, pilule distance dessous) */
.th-play2-compass-float--mini {
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}

/* Pastille flèche — 58 px, fond clair, repère N discret, flèche orientée vers la cible */
.th-play2-mini-pastille {
  width: 58px;
  height: 58px;
  min-width: 58px;
  border-radius: 50%;
  background: color-mix(in oklch, var(--bg-card) 96%, transparent);
  border: 1.5px solid var(--line);
  box-shadow: 0 10px 20px -10px rgba(11, 79, 90, 0.45);
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  cursor: pointer;
  transition: border-color 0.4s ease, box-shadow 0.4s ease;
}
.th-play2-mini-pastille:disabled { cursor: default; }
.th-play2-mini-pastille--hot {
  border-color: transparent;
  box-shadow: 0 12px 24px -10px rgba(242, 84, 61, 0.6);
}
.th-play2-mini-n {
  position: absolute;
  top: 3px;
  left: 50%;
  transform: translateX(-50%);
  font: 800 8px var(--font-sans);
  color: var(--muted);
}
.th-play2-mini-needle {
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.4s ease;
}

/* Pilule distance — libellé « Proximité » + valeur + précision GPS + tendance chaud/froid */
.th-play2-dist-pill {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--bg-card);
  border: 1.5px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 10px 20px -10px rgba(11, 79, 90, 0.45);
  padding: 5px 12px 6px;
  white-space: nowrap;
  transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
}
/* Approche « Tu brûles » : la pilule passe au dégradé corail/ambre, textes en blanc. */
.th-play2-dist-pill--hot {
  background: linear-gradient(120deg, var(--tz-coral), var(--tz-amber));
  border-color: transparent;
  box-shadow: 0 14px 28px -14px rgba(242, 84, 61, 0.7);
  animation: tz-pop 0.3s ease;
}
.th-play2-dist-eyebrow { font-size: 8.5px; margin: 0; }
.th-play2-dist-row { display: inline-flex; align-items: baseline; gap: 5px; }
.th-play2-dist-value { font-size: 15px; line-height: 1; }
.th-play2-dist-unit { font-size: 10px; }
.th-play2-dist-acc { font: 700 10px var(--font-sans); color: var(--ink-soft); }
.th-play2-dist-trend { font: 700 10px var(--font-sans); color: var(--muted); }
.th-play2-dist-pill--hot .th-play2-dist-eyebrow,
.th-play2-dist-pill--hot .th-play2-dist-acc,
.th-play2-dist-pill--hot .th-play2-dist-trend { color: rgba(255, 255, 255, 0.9); }

/* Polaroid « scotché » — indice photo épinglé en haut à droite de la carte, incliné,
   ruban washi jaune, légende manuscrite ; un toucher l'agrandit (lightbox). */
.th-play2-polaroid {
  position: absolute;
  top: 12px;
  right: 14px;
  /* Largeur de la photo = source unique : le cadre (photo + 2×8px de padding) et la légende
     en dérivent, pour qu'un agrandissement responsive (desktop → 180px) ne puisse pas
     désynchroniser les trois largeurs et faire déborder la photo hors du cadre photomaton. */
  --th-polaroid-photo-w: 128px;
  width: calc(var(--th-polaroid-photo-w) + 16px);
  box-sizing: border-box;
  z-index: 26;
  transform: rotate(-4deg);
  background: #fff;
  padding: 8px 8px 6px;
  border: none;
  cursor: pointer;
  box-shadow: 0 18px 38px -16px rgba(51, 48, 42, 0.55);
  transition: transform 0.18s ease, box-shadow 0.18s ease;
}
.th-play2-polaroid:hover { transform: rotate(-2deg) scale(1.02); }
.th-play2-polaroid-tape {
  position: absolute;
  top: -9px;
  left: 50%;
  transform: translateX(-50%) rotate(3deg);
  width: 64px;
  height: 18px;
  background: color-mix(in oklch, var(--tz-gold) 85%, transparent);
  box-shadow: 0 1px 3px rgba(51, 48, 42, 0.15);
}
.th-play2-polaroid-photo {
  display: block;
  position: relative;
  width: var(--th-polaroid-photo-w);
  height: 104px;
  overflow: hidden;
  background: var(--bg-soft);
}
.th-play2-polaroid-photo img,
.th-play2-polaroid-photo video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Tuile média (audio, ou vidéo embarquée sans vignette exploitable) : icône centrée sur fond papier. */
.th-play2-polaroid-mediaicon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  color: var(--ember);
  background: var(--bg-soft);
}
/* Pastille « lecture » en superposition (coin bas-droit) pour l'indice audio/vidéo. */
.th-play2-polaroid-playbadge {
  position: absolute;
  right: 6px;
  bottom: 6px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ember);
  color: #fff;
  box-shadow: 0 2px 6px rgba(51, 48, 42, 0.35);
}
.th-play2-polaroid-caption {
  display: block;
  width: var(--th-polaroid-photo-w);
  text-align: center;
  padding-top: 4px;
  font: 700 16px var(--font-hand, cursive);
  color: var(--ink);
}
/* Photomaton SANS légende (mode joueur parcours, quel que soit le type de média) : le bandeau
   blanc du bas — ce qui fait lire la vignette comme un polaroid — n'était dessiné que par la
   légende manuscrite. Le texte retiré, il faut le reposer explicitement, sinon il ne reste
   qu'un liseré blanc uniforme. L'alerte de proximité du mode chasse, elle, garde sa légende. */
.th-play2-polaroid--nocaption { padding-bottom: 26px; }

/* Carte d'indice — en-tête « Indice », corps défilant, fondu de débordement en bas */
.th-play2-clue-card {
  position: relative;
  flex: 1 1 auto;
  min-height: 96px;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--line);
  border-left: 3px solid var(--ember);
  background: var(--bg-soft);
  border-radius: var(--r);
  box-shadow: 0 10px 24px -20px rgba(11, 79, 90, 0.4);
  overflow: hidden;
}
.th-play2-clue-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 12px 16px 0;
  font: 800 10px var(--font-sans);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tz-coral-d);
}
.th-play2-clue-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 8px 16px 12px;
}
.th-play2-clue-scroll p {
  font-family: var(--font-serif);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.5;
  color: var(--ink);
  margin: 0;
}
.th-play2-clue-fade {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 22px;
  background: linear-gradient(to bottom, transparent, var(--bg-soft));
  pointer-events: none;
}

/* ══════════════════════════════════════════════════════════════════════
   Parcours editor — TI'ZOR (assistant 2 étapes · desktop 3-pane · mobile)
   Step 1 « Le parcours » (identité + départ) → Step 2 « Les étapes »
   ══════════════════════════════════════════════════════════════════════ */

/* Shell */
.tz-ed-root {
  flex: 1; min-height: 0;
  display: flex; flex-direction: column;
  background: var(--tz-grad-page);
}

/* Wizard top bar (shared by both steps) */
.tz-ed-bar {
  flex-shrink: 0;
  display: flex; align-items: center; gap: 14px;
  padding: 10px clamp(13px, 3vw, 24px);
  background: var(--bg-card);
  border-bottom: 1.5px solid var(--line);
  box-shadow: 0 8px 22px -20px rgba(11,79,90,.65);
  flex-wrap: wrap;
}
.tz-ed-back {
  flex-shrink: 0;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px 8px 11px;
  background: var(--bg-soft);
  border: 1.5px solid var(--line-strong); border-radius: 999px;
  color: var(--tz-teal-d); font-weight: 800; font-size: 12.5px;
  cursor: pointer; transition: background .15s ease, transform .15s ease;
}
.tz-ed-back:hover { background: var(--canopy-soft); transform: translateX(-2px); }
.tz-ed-titlewrap { flex: 1 1 220px; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.tz-ed-eyebrow {
  font-family: var(--font-mono); font-size: 9.5px; font-weight: 800;
  letter-spacing: .12em; text-transform: uppercase; color: var(--muted-2);
  display: inline-flex; align-items: center; gap: 5px;
}
.tz-ed-title {
  width: 100%;
  border: 1.5px solid transparent; background: transparent;
  border-radius: 11px; padding: 3px 9px; margin-left: -2px;
  font-family: var(--font-serif); font-weight: 600; font-size: 21px;
  color: var(--tz-teal-d); outline: none;
}
.tz-ed-title::placeholder { color: var(--muted-2); }
.tz-ed-title:hover { background: var(--bg-soft); }
.tz-ed-title:focus { background: var(--bg-soft); border-color: var(--line-strong); }
.tz-ed-actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
/* Rappel du titre dans la barre de l'éditeur : le titre ne se SAISIT que dans le formulaire
   (rubrique « Identité du parcours »). Deux champs pour la même valeur — la barre et le
   formulaire — laissaient croire à deux titres distincts. */
.tz-ed-titletext {
  display: block; width: 100%;
  padding: 3px 9px; margin-left: -2px;
  font-family: var(--font-serif); font-weight: 600; font-size: 21px;
  color: var(--tz-teal-d);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.tz-ed-titletext.is-empty { color: var(--muted-2); }


/* Stepper */
.tz-stepper { display: inline-flex; align-items: center; gap: 8px; }
.tz-step {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 6px 14px 6px 7px; border-radius: 999px;
  border: 1.5px solid var(--line-strong); background: var(--bg-card);
  color: var(--ink-soft); font-weight: 800; font-size: 12.5px;
  cursor: pointer; transition: all .15s ease; white-space: nowrap;
}
.tz-step:hover:not(:disabled):not(.is-active) { background: var(--bg-soft); }
.tz-step:disabled { opacity: .5; cursor: not-allowed; }
.tz-step.is-active { background: var(--tz-teal); border-color: var(--tz-teal); color: #fff; box-shadow: 0 11px 22px -13px rgba(12,122,114,.85); }
.tz-step-num {
  width: 23px; height: 23px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 12px; font-weight: 800;
  background: var(--canopy-soft); color: var(--tz-teal);
}
.tz-step.is-active .tz-step-num { background: rgba(255,255,255,.24); color: #fff; }
.tz-step.is-done .tz-step-num { background: #e7f7ee; color: var(--tz-green); }
.tz-step-sep { width: 22px; height: 2.5px; border-radius: 2px; background: var(--line-strong); }
@media (max-width: 760px) { .tz-step-label { display: none; } .tz-step { padding: 6px; } .tz-step-sep { width: 12px; } }

/* Frise de l'assistant — coque mobile (ParcoursEditor.RenderMobileStepper).
   Ses libellés restent visibles (contrairement à la frise desktop, masquée sous 760px) : c'est
   la seule navigation entre les pas. Les quatre à cinq pastilles ne tiennent donc pas sur une
   ligne de téléphone — elles s'ENROULENT au lieu de déborder. Sans cela (`flex:1` sur des
   pastilles en `white-space:nowrap`, qui ne peuvent pas rétrécir sous leur texte), la frise
   ressortait de l'écran et faisait défiler toute la page horizontalement. `min-width:0` +
   libellé ellipsé : une traduction longue rétrécit sa pastille plutôt que de la faire déborder. */
.tz-em-stepper { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; width: 100%; min-width: 0; }
.tz-em-stepper .tz-step {
  flex: 1 1 auto; min-width: 0; max-width: 100%;
  justify-content: center; gap: 6px; padding: 6px 11px 6px 6px;
}
.tz-em-steplabel { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Écrans étroits : la frise se resserre pour tenir sur deux lignes plutôt que trois. Pur
   confort — le débordement, lui, est déjà réglé par l'enroulement ci-dessus. */
@media (max-width: 385px) {
  .tz-em-stepper { gap: 5px; }
  .tz-em-stepper .tz-step { font-size: 11.5px; gap: 5px; padding: 5px 8px 5px 5px; }
  .tz-em-stepper .tz-step-num { width: 20px; height: 20px; font-size: 11px; }
}

/* ── Step 1 — identité & point de départ ── */
.tz-ed-step1 { flex: 1; min-height: 0; overflow-y: auto; }
.tz-ed-step1-inner {
  max-width: 1180px; margin: 0 auto;
  padding: clamp(16px,3vw,28px) clamp(14px,4vw,28px) 44px;
  display: grid; grid-template-columns: 1.02fr 0.98fr; gap: 22px; align-items: start;
}
.tz-ed-panel { padding: clamp(16px,2vw,22px); }
/* Pied d'action collant du formulaire d'étape 1 : « Créer le parcours » doit rester atteignable
   quelle que soit la hauteur du formulaire (le doublon qui vivait dans la barre du haut a été
   retiré, c'est désormais le seul emplacement de cette action). Le contexte de défilement est
   .tz-ed-step1 ; le fond opaque évite que le texte du formulaire transparaisse dessous. */
.tz-ed-formactions {
  position: sticky; bottom: 0; z-index: 5;
  margin: 20px -4px -6px; padding: 12px 4px;
  display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
  background: var(--bg-card);
  border-top: 1.5px solid var(--line);
}

.tz-ed-panel-map { padding: 0; overflow: hidden; display: flex; flex-direction: column; min-height: 480px; }
.tz-ed-panel-maphd { padding: 13px 16px; border-bottom: 1.5px solid var(--line); display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.tz-ed-maptall { position: relative; flex: 1; min-height: 420px; }
@media (max-width: 1024px) { .tz-ed-step1-inner { grid-template-columns: 1fr; } .tz-ed-panel-map { min-height: 360px; } .tz-ed-maptall { min-height: 300px; } }

/* ── Step 2 — desktop 3-pane ── */
.tz-ed-grid {
  flex: 1; min-height: 0;
  display: grid; grid-template-columns: 344px minmax(0,1fr) 398px; gap: 0;
}
@media (max-width: 1180px) { .tz-ed-grid { grid-template-columns: 300px minmax(0,1fr) 360px; } }
.tz-ed-col { min-height: 0; min-width: 0; display: flex; flex-direction: column; }
.tz-ed-col-list { background: var(--bg-card); border-right: 1.5px solid var(--line); }
.tz-ed-col-map  { background: var(--bg-soft); position: relative; }
.tz-ed-col-insp { background: var(--bg-card); border-left: 1.5px solid var(--line); }
.tz-ed-list { flex: 1; min-height: 0; overflow-y: auto; padding: 16px 14px; display: flex; flex-direction: column; gap: 8px; }
.tz-ed-row {
  display: flex; gap: 11px; align-items: center;
  padding: 11px 12px; width: 100%; text-align: left;
  background: var(--bg-card); border: 1.5px solid var(--line-strong); border-radius: var(--r);
  cursor: pointer; box-shadow: 0 8px 18px -17px rgba(11,79,90,.55);
  transition: background .12s ease, border-color .12s ease, transform .12s ease;
}
.tz-ed-row:hover { background: var(--bg-soft); transform: translateY(-1px); }
.tz-ed-row.is-active { border-color: var(--tz-teal); background: var(--canopy-soft); }

/* ── Éditeur de spots d'une collection (/creator/collections) ──────────────
   Desktop : carte à gauche, sélecteur d'affichage + liste à droite.
   Mobile  : la carte passe en PREMIER en pleine largeur (elle ne doit jamais
   sortir de l'écran ni être côte à côte avec la liste), suivie du sélecteur
   « Tous / Dans la carte » puis de la liste — cf. bug récurrent des cartes
   Leaflet et exigence UX mobile. */
.th-spots-editor {
  display: grid;
  grid-template-columns: minmax(300px, 1.4fr) minmax(260px, 1fr);
  gap: 16px;
  align-items: start;
}
.th-spots-editor-map { position: relative; height: 440px; }
.th-spots-editor-side {
  display: flex; flex-direction: column; gap: 10px; min-width: 0;
  /* Conteneur de requête : les lignes de spot s'adaptent à la largeur RÉELLE de
     la colonne liste (et non à celle de l'écran), donc le repli des boutons
     fonctionne aussi bien en portrait empilé qu'en paysage côte à côte. */
  container-type: inline-size;
}
.th-spots-filter {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
.th-spots-filter-label {
  font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.th-spots-editor-list {
  max-height: 386px; overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; gap: 8px; padding-right: 2px;
}
/* Quand la colonne liste est trop étroite pour tenir « nom + coordonnées +
   boutons » sur une ligne, on fait passer les boutons Modifier/Supprimer SOUS le
   texte (ligne dédiée, alignée à droite). Le texte récupère toute la largeur et
   la ligne ne déborde plus — vrai en portrait (empilé) comme en paysage. */
@container (max-width: 440px) {
  .th-spots-editor-list .tz-ed-row { flex-wrap: wrap; }
  .th-spots-editor-list .tz-ed-row > span:last-child {
    flex-basis: 100%; justify-content: flex-end;
  }
}
@media (max-width: 720px) {
  /* align-items: stretch est OBLIGATOIRE ici. La règle de base ci-dessus pose
     `align-items: start` (utile pour le layout grid desktop). En repassant en
     flex COLONNE, cet `align-items: start` s'applique à l'axe transversal
     (= largeur) et empêche les enfants de s'étirer : ils prennent alors la
     largeur de leur CONTENU. La carte, dont l'unique enfant est en
     position:absolute (donc 0 largeur intrinsèque), se réduisait à 0 px de
     LARGE → carte blanche (impossible de peindre des tuiles sur 0 px), et la
     colonne liste se tassait à la largeur des boutons. C'était LA cause du bug
     « pas de carte en portrait » (indépendant de toute logique JS Leaflet). */
  .th-spots-editor { display: flex; flex-direction: column; align-items: stretch; }
  .th-spots-editor-map { height: 320px; }
  .th-spots-editor-list { max-height: 340px; }
}
/* Écran très bas (téléphone en paysage) : la carte ne doit pas monopoliser toute
   la hauteur visible. */
@media (max-height: 520px) {
  .th-spots-editor-map { height: 240px; }
  .th-spots-editor-list { max-height: 240px; }
}

.tz-ed-quickadd {
  padding: 13px; border: 1.5px dashed var(--line-strong); border-radius: var(--r);
  background: transparent; color: var(--tz-teal); font-weight: 800; font-size: 13px; width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer;
  transition: background .15s ease;
}
.tz-ed-quickadd:hover { background: var(--canopy-soft); }

/* Map overlay pills */
.tz-ed-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 13px; border-radius: 999px;
  background: var(--bg-card); border: 1.5px solid var(--line-strong);
  color: var(--tz-teal-d); font-weight: 800; font-size: 12px;
  cursor: pointer; box-shadow: var(--tz-shadow);
}
.tz-ed-pill:hover { background: var(--bg-soft); }
.tz-ed-pill.is-on-teal  { background: var(--tz-teal);  border-color: var(--tz-teal);  color: #fff; }
.tz-ed-pill.is-on-coral { background: var(--tz-coral); border-color: var(--tz-coral); color: #fff; }

/* Inspector (step 2 right pane / mobile step) */
.tz-ed-insp-head { flex-shrink: 0; padding: 15px 18px 13px; border-bottom: 1.5px solid var(--line); display: flex; align-items: center; gap: 12px; }
.tz-ed-insp-body { flex: 1; overflow-y: auto; min-height: 0; padding: 17px 18px 26px; display: flex; flex-direction: column; gap: 16px; }
.tz-ed-insp-foot { flex-shrink: 0; padding: 12px 16px calc(12px + env(safe-area-inset-bottom)); border-top: 1.5px solid var(--line); background: var(--bg-card); display: flex; gap: 8px; align-items: center; }

/* ── Rubriques de l'éditeur d'étape : chaque bloc de données est nettement
      séparé, avec une couleur d'accent différente par rubrique. ── */
.th-ed-sec {
  border: 1.5px solid var(--th-sec-accent, var(--line-strong));
  border-left-width: 5px;
  border-radius: var(--r-lg);
  padding: 14px 16px;
  background: var(--th-sec-soft, var(--bg-card));
  display: flex;
  flex-direction: column;
  gap: 13px;
}
.th-ed-sec > .eyebrow { color: var(--th-sec-accent); margin: 0; }
/* Le trait de séparation interne (bordures top des sous-blocs) suit l'accent. */
.th-ed-sec .th-ed-subrule { border-top: 1.5px solid var(--th-sec-accent); opacity: .5; }

.th-ed-sec--capture    { --th-sec-accent: var(--tz-teal);   --th-sec-soft: var(--canopy-soft); border-style: dashed; }
.th-ed-sec--geo        { --th-sec-accent: var(--tz-coral);  --th-sec-soft: var(--ember-soft); }
.th-ed-sec--general    { --th-sec-accent: var(--tz-sky);    --th-sec-soft: #e6f5f9; }
.th-ed-sec--consigne     { --th-sec-accent: var(--tz-purple); --th-sec-soft: #f1ebfb; }
.th-ed-sec--validation { --th-sec-accent: var(--tz-green);  --th-sec-soft: #e6f5ec; }
.th-ed-sec--fin        { --th-sec-accent: var(--tz-amber);  --th-sec-soft: #fdf3e3; }

/* ── Replis d'une rubrique de l'éditeur d'étape ──
      Un même bouton sert pour l'en-tête d'une rubrique entière (« Fin d'étape », repliée
      tant qu'aucun contenu n'y est paramétré) et pour les sous-blocs « Options » /
      « Autres », qui rangent les réglages rarement touchés hors du chemin de saisie. */
.th-ed-collapse {
  display: inline-flex; align-items: center; gap: 6px;
  border: none; background: none; padding: 0; margin: 0;
  font: inherit; cursor: pointer; text-align: left;
  font-size: 11.5px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  color: var(--th-sec-accent, var(--muted));
}
.th-ed-collapse .th-ed-chev { display: inline-flex; transition: transform .16s ease; }
.th-ed-collapse.is-open .th-ed-chev { transform: rotate(90deg); }
.th-ed-collapse-hint { font-size: 11px; font-weight: 700; color: var(--muted); text-transform: none; letter-spacing: 0; }
.th-ed-collapse-body { display: flex; flex-direction: column; gap: 12px; }

/* ── Mode de création rapide (modale plein écran, saisie debout sur le terrain) ── */
.th-qc-overlay {
  position: fixed; inset: 0; z-index: 1400; background: rgba(0, 0, 0, .55);
  display: flex; align-items: flex-end; justify-content: center;
  animation: th-fade-in .16s;
}
.th-qc-sheet {
  width: min(560px, 100%); max-height: 92vh; display: flex; flex-direction: column;
  background: var(--bg-card); border-radius: var(--r-lg) var(--r-lg) 0 0;
  border: 1.5px solid var(--line); border-bottom: none; overflow: hidden;
}
.th-qc-head {
  flex-shrink: 0; display: flex; align-items: center; gap: 10px;
  padding: 14px 16px 12px; border-bottom: 1.5px solid var(--line);
}
.th-qc-body { flex: 1; min-height: 0; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 14px; }
.th-qc-foot {
  flex-shrink: 0; display: flex; gap: 8px; align-items: center;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  border-top: 1.5px solid var(--line); background: var(--bg-card);
}
.th-qc-choice {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 14px 16px; border-radius: var(--r-lg); cursor: pointer;
  border: 1.5px solid var(--line); background: var(--bg-card); font: inherit;
}
.th-qc-choice.is-active { border-color: var(--tz-teal); background: var(--bg-soft); }
.th-qc-coord {
  font-family: var(--font-mono); font-size: 19px; font-weight: 700; color: var(--tz-teal-d);
  background: var(--bg-soft); border-radius: var(--r-md, 10px); padding: 10px 14px;
  display: flex; flex-direction: column; gap: 3px;
}

/* Fenêtre « que faire du relevé GPS en cours ? » — posée PAR-DESSUS la feuille de création
   rapide (z-index > .th-qc-overlay), qui reste visible derrière : le créateur ne perd pas de
   vue l'étape qu'il est en train d'enregistrer. */
.th-qc-confirm {
  position: fixed; inset: 0; z-index: 1450; background: rgba(0, 0, 0, .55);
  display: flex; align-items: center; justify-content: center;
  padding: clamp(12px, 5vw, 28px); animation: th-fade-in .16s;
}
.th-qc-confirm-sheet {
  width: min(420px, 100%); max-height: 88vh; overflow-y: auto;
  display: flex; flex-direction: column; gap: 12px;
  background: var(--bg-card); border: 1.5px solid var(--line);
  border-radius: var(--r-lg); padding: 18px 16px;
}

@media (min-width: 720px) {
  .th-qc-overlay { align-items: center; }
  .th-qc-sheet { border-radius: var(--r-lg); border-bottom: 1.5px solid var(--line); }
}

/* ── Repérages : les captures d'une étape (photo, audio, vidéo, texte), en liste plate ──
   Le MÊME balisage est rendu par la modale de création rapide, par la rubrique « Général »
   de l'inspecteur et — en consultation — par la fenêtre « Gérer les captures ».
   Une seule feuille de style, donc, plutôt qu'un jeu de règles par écran.
   RAPPEL : toute règle ajoutée ici doit l'être AUSSI dans wwwroot/css/themes/*/app.css
   (copies complètes, cf. CLAUDE.md bug nᵒ8), sinon elle disparaît au changement de thème. */
.th-rep-item-del {
  margin-left: auto; border: none; background: none; color: var(--tz-coral-d);
  cursor: pointer; display: inline-flex; align-items: center; padding: 4px; flex: none;
}
.th-rep-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--bg-soft); border-radius: 11px; padding: 8px 10px;
}
.th-rep-icon { display: inline-flex; color: var(--tz-teal-d); flex: none; }
.th-rep-label { flex: 1; min-width: 0; font-size: 12px; font-weight: 700; color: var(--ink); overflow-wrap: anywhere; }
.th-rep-texte { flex: 1; min-width: 160px; font-size: 12.5px; }
.th-rep-voir {
  border: none; background: none; color: var(--tz-teal-d); text-decoration: underline;
  font: inherit; font-weight: 700; cursor: pointer; padding: 0; flex: none;
}
.th-rep-confirm {
  margin-left: auto; display: inline-flex; align-items: center; gap: 8px; flex-wrap: wrap;
  font-size: 11.5px; font-weight: 700; color: var(--tz-coral-d);
}
.th-rep-confirm-yes { border: none; background: none; color: var(--tz-coral-d); font: inherit; font-weight: 800; cursor: pointer; padding: 0; }
.th-rep-confirm-no { border: none; background: none; color: var(--muted); font: inherit; font-weight: 700; cursor: pointer; padding: 0; }
.th-rep-actions { display: flex; gap: 7px; flex-wrap: wrap; }

/* « Gérer les captures » : chaque élément est une cible de CHOIX, pas une ligne de liste.
   44 px de haut au minimum — sur le terrain comme au bureau, une case à cocher se manque. */
.th-rep-pickrow { display: flex; align-items: center; gap: 8px; }
.th-rep-pick {
  flex: 1; min-width: 0; min-height: 44px; display: flex; align-items: center; gap: 8px;
  text-align: left; border: 1.5px solid var(--line); background: var(--bg-card);
  border-radius: 11px; padding: 9px 12px; font: inherit; font-size: 12px; font-weight: 700;
  color: var(--ink); cursor: pointer;
}
.th-rep-pick:disabled { opacity: .45; cursor: not-allowed; }
.th-rep-pick.is-active { border-color: var(--tz-teal); background: var(--bg-soft); }
.th-rep-pick-label { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.th-rep-pick-mark { display: inline-flex; color: var(--canopy, #2a7); flex: none; }
.th-rep-recap {
  flex-shrink: 0; border-top: 1.5px solid var(--line); background: var(--bg-soft);
  padding: 10px 16px; display: flex; flex-direction: column; gap: 5px;
}
.th-rep-recap-title {
  font-size: 11px; font-weight: 800; letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.th-rep-recap-line { display: flex; align-items: center; gap: 7px; font-size: 12.5px; font-weight: 700; color: var(--ink); }
.th-rep-warn { display: flex; align-items: flex-start; gap: 6px; font-size: 11.5px; font-weight: 700; color: var(--tz-coral-d); }

/* Number pip */
.tz-pip {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 12.5px;
  background: var(--canopy-soft); color: var(--tz-teal);
}
.tz-pip.is-active { background: var(--tz-coral); color: #fff; }

/* Waypoint type selector chips */
.tz-wtype {
  height: 34px; padding: 0 12px; display: inline-flex; align-items: center; gap: 6px;
  border-radius: 999px; font-size: 12.5px; font-weight: 800; cursor: pointer;
  border: 1.5px solid var(--line-strong); background: var(--bg-card); color: var(--ink-soft);
  transition: all .14s ease;
}
.tz-wtype:hover { background: var(--bg-soft); }
.tz-wtype.is-active { border-color: var(--tz-teal); background: var(--tz-teal); color: #fff; }

/* Reorder buttons */
.tz-ed-ord {
  width: 30px; height: 30px; border-radius: 9px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--line-strong); background: var(--bg-card); color: var(--tz-teal-d);
}
.tz-ed-ord:disabled { opacity: .4; cursor: not-allowed; }
.tz-ed-ord:hover:not(:disabled) { background: var(--bg-soft); }

/* Inline message banner */
.tz-ed-msg { font-size: 12.5px; font-weight: 700; padding: 10px 13px; border-radius: 11px; }
.tz-ed-msg-ok  { background: #e7f7ee; color: #0b6b39; }
.tz-ed-msg-err  { background: var(--ember-soft); color: var(--tz-coral-d); }
.tz-ed-msg-warn { background: #fef9e7; color: #7a5c00; }

/* ── Mobile drill-down ── */
.tz-em-stage { position: relative; flex: 1; min-height: 0; width: 100%; overflow: hidden; display: flex; flex-direction: column; background: var(--bg); }
.tz-em-slide {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  background: var(--tz-grad-page); overflow: hidden;
  transition: transform .3s cubic-bezier(.32,.72,.36,1), opacity .22s ease;
}
.tz-em-slide.is-active { transform: translateX(0); opacity: 1; pointer-events: auto; }
.tz-em-slide.out-left  { transform: translateX(-12%); opacity: 0; pointer-events: none; }
.tz-em-slide.out-right { transform: translateX(100%); opacity: 0; pointer-events: none; }
.tz-em-head {
  flex-shrink: 0; min-height: 56px; padding: 10px 12px;
  display: flex; align-items: center; gap: 10px;
  background: var(--bg-card); border-bottom: 1.5px solid var(--line);
}
.tz-em-iconbtn {
  width: 38px; height: 38px; border-radius: 12px; flex-shrink: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--bg-soft); border: 1.5px solid var(--line-strong); color: var(--tz-teal-d);
}
.tz-em-iconbtn:disabled { opacity: .4; cursor: not-allowed; }
.tz-em-card {
  display: flex; gap: 12px; align-items: center; padding: 12px 14px; width: 100%; text-align: left;
  background: var(--bg-card); border: 1.5px solid var(--line-strong); border-radius: var(--r);
  cursor: pointer; box-shadow: 0 8px 18px -17px rgba(11,79,90,.55);
}
@keyframes tz-em-sheet-up { from { transform: translateY(16px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

/* ══════════════════════════════════════════════════════════════════════
   TI'ZOR — motion + reusable component layer
   ══════════════════════════════════════════════════════════════════════ */

@keyframes tz-pulse { 0%,100% { box-shadow: 0 0 0 0 rgba(242,84,61,.5); } 50% { box-shadow: 0 0 0 13px rgba(242,84,61,0); } }
@keyframes tz-pop   { from { transform: translateY(10px) scale(.99); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes tz-float { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-5px); } }
@keyframes tz-bar   { 0%,100% { transform: scaleY(.3); } 50% { transform: scaleY(1); } }
@keyframes tz-shine { 0% { background-position: -120% 0; } 100% { background-position: 220% 0; } }
@keyframes tz-fade  { from { opacity: 0; } to { opacity: 1; } }
@keyframes tz-spin  { to { transform: rotate(360deg); } }
@keyframes tz-banner-in { from { transform: translateY(-104%); opacity: 0; } to { transform: translateY(0); opacity: 1; } }

.tz-pop   { animation: tz-pop .3s ease; }
.tz-fade  { animation: tz-fade .2s ease; }
.tz-float { animation: tz-float 3s ease-in-out infinite; }

/* Page shell */
.tz-page { max-width: 1180px; margin: 0 auto; padding: clamp(24px,4vw,44px) clamp(16px,4vw,40px) 54px; }
.tz-page-narrow { max-width: 860px; }

/* Display heading helper (Spectral, éditorial) */
.tz-display { font-family: var(--font-serif); font-weight: 700; line-height: 1.05; color: var(--ink); letter-spacing: -0.01em; }
.tz-display em { font-style: italic; color: var(--ember); }

/* Card */
.tz-card {
  background: var(--bg-card);
  border: 1.5px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--tz-shadow);
}
.tz-card-sm { border-radius: var(--r-lg); }
.tz-hover-rise { transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease; }
.tz-hover-rise:hover { transform: translateY(-3px); box-shadow: var(--tz-shadow-lg); }

/* Pills / badges */
.tz-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px; border-radius: 999px;
  font-weight: 800; font-size: 12.5px; line-height: 1;
  white-space: nowrap;
}
.tz-pill-teal   { background: var(--canopy-soft); color: var(--tz-teal); }
.tz-pill-coral  { background: var(--ember-soft);  color: var(--tz-coral-d); }
.tz-pill-gold   { background: #fff3e0;            color: #c2641a; }
.tz-pill-green  { background: #e7f7ee;            color: #0b6b39; }
.tz-pill-purple { background: #f3f0fb;            color: #5b3aa0; }

/* Buttons */
.tz-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none; cursor: pointer;
  font-family: var(--font-sans); font-weight: 800; font-size: 15px;
  padding: 14px 22px; border-radius: var(--r);
  transition: transform .15s ease, filter .15s ease, background .15s ease;
  white-space: nowrap; user-select: none;
}
.tz-btn:disabled { opacity: .5; cursor: not-allowed; }
.tz-btn-sm  { padding: 9px 15px; font-size: 13.5px; border-radius: 12px; }
.tz-btn-lg  { padding: 15px 26px; font-size: 15.5px; border-radius: var(--r-lg); }
.tz-btn-coral { background: var(--tz-coral); color: #fff; box-shadow: 0 12px 24px -12px rgba(242,84,61,.55); }
.tz-btn-coral:hover:not(:disabled) { transform: translateY(-2px); }
.tz-btn-teal  { background: var(--tz-teal); color: #fff; }
.tz-btn-teal:hover:not(:disabled)  { filter: brightness(1.06); }
.tz-btn-green { background: linear-gradient(135deg,#16a06a,#0c7a72); color: #fff; box-shadow: 0 8px 18px -8px rgba(22,160,106,.6); }
.tz-btn-green:hover:not(:disabled) { transform: translateY(-2px); }
.tz-btn-ghost { background: var(--bg-card); color: var(--tz-teal-d); border: 1.5px solid var(--line-strong); }
.tz-btn-ghost:hover:not(:disabled) { transform: translateY(-2px); }

/* Form controls */
.tz-label {
  display: block; font-weight: 800; font-size: 12.5px;
  color: var(--ink-soft); margin-bottom: 7px; letter-spacing: .2px;
}
.tz-input, .tz-select, .tz-textarea {
  width: 100%; box-sizing: border-box;
  border: 1.5px solid var(--line-strong); border-radius: var(--r);
  padding: 12px 14px; background: #fbfdfc;
  font-family: var(--font-sans); font-weight: 700; font-size: 14px; color: var(--ink);
}
.tz-textarea { min-height: 74px; resize: vertical; line-height: 1.45; }

/* Faux map canvas (grid texture) used by player + editor */
.tz-map {
  position: relative; overflow: hidden;
  background: linear-gradient(135deg,#bfe6df,#dcefcf);
  border: 1.5px solid var(--line-strong);
}
.tz-map::before {
  content: ""; position: absolute; inset: 0;
  background-image:
    repeating-linear-gradient(90deg, rgba(255,255,255,.45) 0 1px, transparent 1px 28px),
    repeating-linear-gradient(0deg,  rgba(255,255,255,.45) 0 1px, transparent 1px 28px);
}

/* Ghost button for use on the brand gradient (hero band, etc.) */
.tz-hero-ghost {
  display: inline-flex; align-items: center; gap: 8px;
  height: 50px; padding: 0 24px;
  background: rgba(255,255,255,.15); border: 1.5px solid rgba(255,255,255,.45);
  border-radius: var(--r-lg); color: #fff;
  font-family: var(--font-sans); font-weight: 800; font-size: 15.5px;
  cursor: pointer; transition: background .15s ease, border-color .15s ease;
}
.tz-hero-ghost:hover { background: rgba(255,255,255,.24); border-color: rgba(255,255,255,.65); }

/* Brand logo tile */
.tz-logo {
  display: flex; align-items: center; justify-content: center;
  border-radius: 14px;
/*  background: linear-gradient(135deg,#ffc53d,#f2543d);
  box-shadow: 0 4px 12px rgba(242,84,61,.4);
*/
  font-family: var(--font-serif); font-weight: 700; color: #fff;
  flex: none;
}

/* ═══════════════════════════════════════════════════════════════════════
   Kolaz Vakans — surfaces & interactions
   Présent uniquement dans le thème Kolaz Vakans (n'affecte pas un thème
   réactivé au retour arrière). Donne aux primitives Razor partagées
   (Button → .th-btn, Chip → .th-chip) et aux cartes (.tz-card) le rendu
   « scrapbook » : coins nets, ombres douces, léger soulèvement/inclinaison
   au survol. Les !important ne priment que sur les états :hover/:active.
   ═══════════════════════════════════════════════════════════════════════ */

/* Boutons — appui doux (petit décalage, ombre nette réduite). */
.th-btn { transition: transform .14s ease, box-shadow .14s ease, filter .14s ease, background .14s ease; }
.th-btn:hover:not([disabled]):not(:active) { transform: translateY(-1px); filter: brightness(1.02); }
.th-btn:active:not([disabled]) {
  transform: translate(1px,1px) !important;
  box-shadow: var(--ui-btn-shadow-active, 1px 1px 0 rgba(51,48,42,.2)) !important;
}

/* Chips / filtres — micro-appui. */
.th-chip { transition: transform .14s ease, box-shadow .14s ease, background .14s ease; }
.th-chip:hover:not(:active) { transform: translateY(-1px); }
.th-chip:active { transform: translate(1px,1px) !important; }

/* Cartes — blanc net, ombre douce ; au survol, la « photo » se soulève et se
   redresse légèrement (comme collée de travers dans un album). */
.tz-card { border: none; box-shadow: var(--kv-shadow-soft); }
.tz-card-sm { border-radius: var(--r-lg); }
.tz-hover-rise { transition: transform .18s ease, box-shadow .18s ease; }
.tz-hover-rise:hover { transform: translateY(-4px) rotate(-.5deg); box-shadow: var(--kv-shadow-photo); }

/* Emphase surlignée jaune (comme .display em). */
.tz-display em { font-style: normal; background: var(--kv-jaune); padding: 0 .16em;
                 box-decoration-break: clone; -webkit-box-decoration-break: clone; }

/* ── Helpers scrapbook réutilisables ── */
.kv-hand { font-family: var(--font-hand); font-weight: 700; }              /* note manuscrite (Caveat) */
.kv-photo { background: var(--bg-card); box-shadow: var(--kv-shadow-photo); } /* carte-photo « polaroid » */
.kv-dashed { border: 2px dashed var(--kv-ink); }
.kv-dashed-soft { border: 3px dashed var(--kv-dash); }
/* Ruban adhésif (washi tape) posé en haut d'un conteneur en position:relative */
.kv-tape { position: relative; }
.kv-tape::before { content:""; position:absolute; top:-11px; left:50%; width:98px; height:25px;
                   transform:translateX(-50%) rotate(-4deg); background:var(--kv-tape-jaune);
                   box-shadow:0 2px 6px rgba(0,0,0,.12); }

@keyframes kv-wiggle { 0%,100% { transform:rotate(-2deg); } 50% { transform:rotate(1deg); } }
.kv-wiggle { animation: kv-wiggle 5s ease-in-out infinite; }

@media (prefers-reduced-motion: reduce) {
  .th-btn, .th-chip, .tz-hover-rise, .kv-wiggle { transition: none; animation: none; }
  .th-btn:hover, .th-chip:hover, .tz-hover-rise:hover { transform: none; }
}

/* ══════════════════════════════════════════════════════════════════════
   Écran de jeu — design « 2a affiné » (import Claude Design, Ecran Jeu 2a.dc.html).
   Évolution du template 2a « Polaroid + boussole minimaliste » :
   - panneau « Objectif » REPLIABLE (chevron) : replié, la carte respire plein écran ;
   - chips chrono / étape / tendance chaud-froid en haut de la carte (bandeau de titre épuré) ;
   - pilule de proximité HORIZONTALE consolidée (flèche + distance) en haut à gauche.
   Classes ADDITIVES : les anciennes (.th-play2-sheet, .th-play2-clue-card, .th-play2-mini-*,
   .th-play2-dist-*, .th-play2-zone-chip) restent définies (coque MAUI + thèmes archivés).
   Toutes les couleurs passent par les variables de thème → un seul bloc convient aux 3 thèmes.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Rangée de chips en haut de la carte (zone, chrono, étape, tendance, hors-ligne) ── */
.th-play2-topchips {
  position: absolute;
  top: 10px;
  left: 12px;
  right: 172px;
  z-index: 24;
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  pointer-events: none; /* laisse la carte draggable entre les chips */
}
.th-play2-chip {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 800 10.5px var(--font-sans);
  letter-spacing: 0.03em;
  color: var(--ink);
  background: color-mix(in oklch, var(--bg-card) 97%, transparent);
  padding: 5px 9px;
  border-radius: 7px;
  border: 1.5px solid var(--line);
  white-space: nowrap;
  box-shadow: 0 6px 14px -10px rgba(11, 79, 90, 0.5);
}
/* Preuves REFUSÉES par le serveur (401/403) : le réseau marche, c'est le droit qui manque.
   Distinct de la pastille « hors ligne » — ici réessayer n'y changera rien, et la
   progression n'est pas enregistrée. Sans marque visuelle, une partie définitivement
   perdue ressemble trait pour trait à une partie qui avance. */
.th-play2-chip--refus {
  color: var(--danger, #b3261e);
  border-color: currentColor;
  font-weight: 700;
}
.th-play2-chip--step {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted);
}
/* Bascule Plan ⇄ Satellite du fond de carte : pastille « calque » posée sur la ligne
   suivante (flex-basis:100% force le retour à la ligne dans .th-play2-topchips), donc
   juste SOUS le décompte des étapes. */
.th-play2-maplayer {
  flex-basis: 100%;
  display: flex;
  gap: 6px;
}
.th-play2-chip--layer {
  padding: 5px 6px;
  cursor: pointer;
  color: var(--ink-soft);
  line-height: 0;
}
.th-play2-chip--layer.is-active {
  color: #fff;
  background: var(--tz-teal-d);
  border-color: var(--tz-teal-d);
}
.th-play2-chip--layer:disabled {
  opacity: 0.4;
  cursor: default;
}
/* Chip de tendance « Tu brûles / Tu chauffes » : fond corail plein quand ça chauffe. */
.th-play2-trend-chip--hot {
  color: #fff;
  background: var(--ember);
  border-color: var(--ember);
  box-shadow: 0 8px 18px -8px rgba(242, 84, 61, 0.6);
}

/* ── Pilule de proximité horizontale (bas-gauche de la carte) — consolide pastille flèche + distance ── */
.th-play2-compass-float--pill {
  position: absolute;
  top: auto;
  bottom: 10px;
  left: 12px;
  right: auto;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  max-width: 62%;
}
.th-play2-prox-pill {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  min-height: 37px;
  background: color-mix(in oklch, var(--bg-card) 97%, transparent);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  padding: 4px 10px;
  box-shadow: 0 10px 20px -10px rgba(11, 79, 90, 0.45);
  cursor: pointer;
  transition: background 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease;
}
.th-play2-prox-pill:disabled { cursor: default; }
/* Approche « Tu brûles » : dégradé corail/ambre, textes en blanc. */
.th-play2-prox-pill--hot {
  background: linear-gradient(120deg, var(--tz-coral), var(--tz-amber));
  border-color: transparent;
  box-shadow: 0 14px 28px -14px rgba(242, 84, 61, 0.7);
  animation: tz-pop 0.3s ease;
}
.th-play2-prox-dial {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: 2px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
}
.th-play2-prox-pill--hot .th-play2-prox-dial { border-color: rgba(255, 255, 255, 0.6); }
.th-play2-prox-needle {
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.4s ease;
}
.th-play2-prox-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  line-height: 1.05;
}
.th-play2-prox-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 800 8.5px var(--font-sans);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}
.th-play2-prox-row {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
}
.th-play2-prox-value { font: 800 15px var(--font-sans); line-height: 1; }
.th-play2-prox-unit { font-size: 10px; }
.th-play2-prox-acc { font: 700 10px var(--font-sans); color: var(--ink-soft); }
.th-play2-prox-pill--hot .th-play2-prox-eyebrow,
.th-play2-prox-pill--hot .th-play2-prox-acc { color: rgba(255, 255, 255, 0.9); }

/* ── Cluster horizontal proximité + carnet (bas-gauche de la carte) ── */
.th-play2-prox-cluster {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 8px;
}
.th-play2-carnet-pill {
  gap: 6px;
  padding: 4px 10px 4px 8px;
}
.th-play2-carnet-label {
  font: 800 12px var(--font-sans);
  color: var(--ink);
}
.th-play2-carnet-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 17px;
  height: 17px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--ember);
  color: #fff;
  font: 800 10px var(--font-sans);
  line-height: 1;
}

/* ── Zoom +/- de la carte, pastille horizontale — même ligne que proximité/carnet ── */
.th-play2-zoom-pill {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  min-height: 38px;
  background: color-mix(in oklch, var(--bg-card) 97%, transparent);
  border: 1.5px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 10px 20px -10px rgba(11, 79, 90, 0.45);
  overflow: hidden;
}
.th-play2-zoom-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background: none;
  border: none;
  color: var(--ink-soft);
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}
.th-play2-zoom-btn:hover { background: var(--bg-soft); color: var(--ink); }
.th-play2-zoom-btn:active { background: var(--line); }
.th-play2-zoom-divider {
  width: 1px;
  align-self: stretch;
  background: var(--line);
}

/* ── Panneau « Objectif » repliable (remplace .th-play2-sheet dans le design 2a affiné) ── */
.th-play2-panel {
  position: relative;
  z-index: 30;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border-top: 1.5px solid var(--line);
  box-shadow: 0 -14px 34px -22px rgba(11, 79, 90, 0.5);
  overflow: hidden;
}
/* Replié : le panneau se réduit à son en-tête, la carte prend tout l'espace. */
.th-play2-shell--panel-collapsed .th-play2-panel { flex: 0 0 auto; }
.th-play2-shell--panel-collapsed .th-play2-map { flex: 1 1 auto; }
.th-play2-shell--panel-collapsed .th-play2-panel-head { border-bottom: none; }

.th-play2-panel-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px 9px 18px;
  border-bottom: 1.5px solid var(--line);
}
.th-play2-panel-eyebrow {
  flex-shrink: 0;
  font: 800 10px var(--font-sans);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--tz-teal-d);
}
.th-play2-panel-objective {
  flex: 1;
  min-width: 0;
  font: 700 13.5px var(--font-sans);
  line-height: 1.3;
  color: var(--ink);
}
.th-play2-panel-toggle { flex-shrink: 0; margin-left: auto; }
.th-play2-panel-chev {
  display: flex;
  align-items: center;
  justify-content: center;
  transition: transform 0.2s ease;
}

.th-play2-panel-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 10px 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  scrollbar-width: thin;
  scrollbar-color: var(--tz-coral) transparent;
}
.th-play2-panel-body::-webkit-scrollbar { width: 11px; }
.th-play2-panel-body::-webkit-scrollbar-track { background: transparent; }
.th-play2-panel-body::-webkit-scrollbar-thumb {
  background: var(--tz-coral);
  border-radius: 7px;
  border: 3px solid var(--bg-card);
}
/* Indice — préfixe « L'indice · » coloré + corps serif (esprit carnet). */
.th-play2-clue-text {
  font-family: var(--font-serif);
  font-size: 16px;
  font-weight: 400;
  line-height: 1.55;
  color: var(--ink);
  margin: 0;
}
/* Fondu de débordement en bas du panneau (signal « défile pour lire »). */
.th-play2-panel-fade {
  position: absolute;
  left: 0;
  right: 11px; /* laisse voir la barre de défilement */
  bottom: 0;
  height: 22px;
  background: linear-gradient(to bottom, transparent, var(--bg-card));
  pointer-events: none;
}

/* ══════════════════════════════════════════════════════════════════════
   Écran de jeu — mode DESKTOP / tablette large (design « Ecran Jeu Desktop.dc.html »).
   Le même écran « 2a affiné » bascule, à partir de 1024 px, d'une colonne empilée vers une
   carte PLEIN ÉCRAN surmontée d'un VOLET FLOTTANT (bas-droite) réunissant objectif, indice
   et actions. Piloté uniquement par media-query : markup partagé, le « dock » (.th-play2-dock)
   passe de display:contents (mobile) à carte flottante (desktop). Additif — aucune classe
   mobile n'est modifiée hors media-query.
   PORTÉE : toutes les règles desktop sont limitées à l'écran de jeu (.th-play2-jeu), car
   .th-play2-shell / .th-play2-objective / .th-play2-icon-btn sont AUSSI réutilisés par
   ChasseDetail.razor (mode chasse) — il ne faut surtout pas les toucher hors du jeu.
   ══════════════════════════════════════════════════════════════════════ */

/* Mobile (base) : le dock s'efface (panneau + barre d'actions empilés comme avant), le
   sous-titre d'en-tête est masqué. Ces deux classes sont propres à l'écran de jeu (absentes
   de ChasseDetail) : sûres même hors portée .th-play2-jeu. */
.th-play2-dock { display: contents; }
.th-play2-objective-sub { display: none; }

@media (min-width: 1024px) {
  /* La coque de jeu cesse d'être une colonne « téléphone » centrée (règle ≥ 640 px) : plein écran.
     Compound .th-play2-jeu.th-play2-shell = le même élément (le shell du jeu), pas la chasse. */
  .th-play2-jeu.th-play2-shell {
    max-width: none;
    margin: 0;
    box-shadow: none;
  }

  /* En-tête plus ample + sous-titre « jeu en cours » visible à côté du titre. */
  .th-play2-jeu .th-play2-objective { padding: 11px 24px 12px; }
  .th-play2-jeu .th-play2-objective-title { flex: 0 0 auto; white-space: nowrap; font-size: 22px; }
  .th-play2-jeu .th-play2-objective-sub {
    display: inline-flex;
    align-items: center;
    font: 800 11px var(--font-sans);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    white-space: nowrap;
  }
  .th-play2-jeu .th-play2-icon-btn { width: 34px; height: 34px; }

  /* La carte occupe tout le corps sous l'en-tête (le dock flottant est hors-flux). */
  .th-play2-jeu .th-play2-map { flex: 1 1 auto; }
  /* Overlays légèrement agrandis / repositionnés comme le mock desktop. */
  .th-play2-jeu .th-play2-topchips { top: 16px; left: 16px; right: auto; }
  .th-play2-jeu .th-play2-chip { font-size: 12px; padding: 7px 13px; }
  .th-play2-jeu .th-play2-compass-float--pill { bottom: 16px; left: 24px; top: auto; max-width: 420px; }
  .th-play2-jeu .th-play2-polaroid { top: 16px; right: 24px; --th-polaroid-photo-w: 180px; }
  .th-play2-jeu .th-play2-polaroid-photo { height: 150px; }
  .th-play2-jeu .th-play2-polaroid--nocaption { padding-bottom: 32px; }
  .th-play2-jeu .th-play2-polaroid-tape { width: 74px; height: 20px; }

  /* Volet flottant : panneau « Objectif » + barre d'actions réunis en une carte ancrée en
     bas à droite, superposée à la carte. Repli du panneau → la carte se réduit à son en-tête
     « Objectif » + la barre d'actions (le CTA « J'y suis » reste accessible). */
  .th-play2-jeu .th-play2-dock {
    display: flex;
    flex-direction: column;
    position: absolute;
    right: 24px;
    bottom: 24px;
    width: min(430px, calc(100% - 48px));
    max-height: calc(100% - 32px);
    background: var(--bg-card);
    border: 2px solid var(--line-strong);
    border-radius: 16px;
    box-shadow: 0 30px 60px -25px rgba(11, 79, 90, 0.5);
    overflow: hidden;
    z-index: 40;
  }
  /* Panneau et barre vivent DANS la carte flottante : pas de bordure/ombre propre, fond
     transparent (la carte porte le fond et l'ombre). */
  .th-play2-jeu .th-play2-dock .th-play2-panel {
    flex: 0 1 auto;
    min-height: 0;
    border-top: none;
    box-shadow: none;
    background: transparent;
  }
  .th-play2-jeu .th-play2-dock .th-play2-panel-body { max-height: 34vh; }
  .th-play2-jeu .th-play2-dock .th-play2-panel-fade {
    background: linear-gradient(to bottom, transparent, var(--bg-card));
  }
  .th-play2-jeu .th-play2-dock .th-play2-actionbar {
    background: transparent;
    border-top: 1.5px solid var(--line);
    box-shadow: none;
    padding: 10px 16px 12px;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   Correctifs 2a affiné (revue rendu mobile).
   ══════════════════════════════════════════════════════════════════════ */

/* Barre d'actions : « Indice supp. » et le CTA de validation CÔTE À CÔTE (mockup 2a),
   au lieu d'être empilés pleine largeur. Le média audio/vidéo garde sa propre rangée au-dessus.
   Sans indice supplémentaire, seul le CTA occupe la rangée (donc pleine largeur). */
.th-play2-actionrow {
  display: flex;
  align-items: stretch;
  gap: 8px;
}
.th-play2-actionrow > .th-play2-hint-btn {
  flex: 1 1 0;
  min-width: 0;
  flex-direction: column;
  gap: 1px;
  line-height: 1.15;
}
.th-play2-hint-main { display: inline-flex; align-items: center; gap: 5px; }
.th-play2-hint-pts { font: 700 10px var(--font-sans); color: var(--muted); }
/* Le CTA prend 2 parts (le double de l'indice supp.) et abandonne son width:100% en rangée. */
.th-play2-actionrow > .th-play2-cta { flex: 2 1 0; width: auto; }

/* ── Overlay carte EN COLONNE : chips au-dessus de la pilule de proximité ──
   Robuste anti-chevauchement : quel que soit le nombre de chips (zone/chrono/étape/tendance/
   hors-ligne) ou la largeur d'écran, la pilule suit toujours SOUS les chips (flux vertical),
   au lieu d'être positionnée en absolu et de passer derrière une 2ᵉ ligne de chips.
   Le polaroid reste indépendant (absolu, haut-droite). */
.th-play2-map-overlay {
  position: absolute;
  top: 10px;
  left: 12px;
  right: 12px;
  z-index: 26;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  pointer-events: none; /* laisse la carte draggable ; seuls les chips/pilule captent le pointeur */
}
.th-play2-map-overlay > * { pointer-events: auto; max-width: 100%; }
/* Chips + pilule repassent EN FLUX dans la colonne (annule leur ancienne position absolue). */
.th-play2-map-overlay .th-play2-topchips {
  position: static;
  top: auto;
  left: auto;
  right: auto;
  z-index: auto;
  width: 100%;
}
.th-play2-map-overlay .th-play2-compass-float--pill {
  position: static;
  top: auto;
  left: auto;
  right: auto;
  bottom: auto;
}

@media (min-width: 1024px) {
  /* Desktop : l'overlay se cale en haut à gauche, un peu plus grand (comme le mock desktop). */
  .th-play2-jeu .th-play2-map-overlay {
    top: 16px;
    left: 16px;
    right: auto;
    max-width: 60%;
    gap: 10px;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   Mode joueur « collection » (/collection/{id}, shell immersif de ChasseDetail.razor).
   Le volet « Objectif » du mode traversée est réutilisé tel quel (.th-play2-panel*) mais posé
   AU-DESSUS de la carte ; la carte reçoit ses propres overlays (flèches de boussole, précision
   GPS, photomaton de collection) et la barre du bas se réduit au déclencheur rond précédé du
   mini-aperçu caméra pré-chargé.
   ══════════════════════════════════════════════════════════════════════ */

/* Volet « Objectif » posé AU-DESSUS de la carte : le panneau du mode traversée retourné
   (bordure et ombre côté bas), et qui ne prend jamais la hauteur restante — c'est la carte
   qui respire. */
.th-play2-panel--top {
  flex: 0 0 auto;
  border-top: none;
  border-bottom: 1.5px solid var(--line);
  box-shadow: 0 14px 34px -22px rgba(11, 79, 90, 0.5);
}
.th-play2-panel--top .th-play2-panel-body { max-height: 30vh; }
/* Replié, l'objectif tient sur une ligne (le texte complet s'ouvre d'un toucher sur le chevron). */
.th-play2-panel-objective--clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 1;
  line-clamp: 1;
  overflow: hidden;
}

/* Carte plein écran du mode collection : hauteur résolue par le flex du shell, z-index Leaflet
   confinés (même raison que .th-play2-map / .th-play-map-panel). */
.th-chasse-map {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  isolation: isolate;
  /* Gabarit de la rangée de commandes, SOURCE UNIQUE : le déclencheur en dérive sa taille, et
     tout ce qui est posé au-dessus de la rangée (.th-chasse-compass, contrôles Leaflet du coin
     bas droit) en dérive sa marge basse. Un seul chiffre à changer pour
     redimensionner le déclencheur sans que rien ne vienne se poser dessus.
     --th-chasse-row-h = déclencheur + libellé sous le bouton + marge basse + dégagement. */
  --th-chasse-shutter-size: 144px;
  --th-chasse-row-h: calc(var(--th-chasse-shutter-size) + 42px);
}

/* Interfaces à caméra plein écran (v1b/v1c) : le déclencheur redescend à 76 px — à peine plus
   gros que les deux commandes qui l'encadrent (58 px), assez pour rester le geste principal sans
   manger le décor réel qui est ici tout l'intérêt de l'écran. Comme --th-chasse-row-h en dérive,
   TOUT ce qui se cale sur la rangée (médaillon de carte, flèches de boussole, contrôles Leaflet)
   descend d'autant : la carte se retrouve automatiquement plus basse, sans second réglage. */
.th-chasse-map--cam {
  --th-chasse-shutter-size: 76px;
  /* Les commandes ne sont plus une rangée mais une COLONNE (collection au-dessus du
     déclencheur) : la hauteur à dégager n'est plus « un bouton + son libellé » mais les deux
     empilés — 58 + libellé, gouttière, 76 + libellé, marge basse. Ce qui se cale dessus (les
     flèches de boussole) monte d'autant. Valeur littérale plutôt que dérivée : elle additionne
     deux boutons de tailles différentes et leurs libellés, une formule ne la rendrait pas plus
     lisible. */
  --th-chasse-row-h: 214px;
}

/* ── Flèches de boussole en bordure de carte ──
   Posées sur l'ellipse inscrite dans la carte, à l'azimut du spot (la carte est orientée nord en
   haut). Le conteneur laisse passer le pointeur pour ne pas geler le pan/zoom ; seules les
   flèches le captent. */
.th-chasse-compass {
  position: absolute;
  /* Marge basse = hauteur de la rangée de capture (--th-chasse-row-h) : une flèche plein sud ne
     peut pas venir se poser sur le déclencheur. L'ellipse se décale de quelques dizaines de
     pixels vers le haut, sans conséquence sur la lecture des directions. */
  inset: 8px 8px var(--th-chasse-row-h) 8px;
  z-index: 28;
  pointer-events: none;
}
.th-chasse-compass-arrow {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 7px 3px 5px;
  border: 1.5px solid var(--th-arrow-color, var(--tz-teal-d));
  border-radius: 999px;
  background: color-mix(in oklch, var(--bg-card) 92%, transparent);
  color: var(--th-arrow-color, var(--tz-teal-d));
  box-shadow: 0 6px 16px -10px rgba(11, 79, 90, 0.6);
  cursor: pointer;
  white-space: nowrap;
}
.th-chasse-compass-needle {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.th-chasse-compass-dist {
  font: 800 10.5px var(--font-sans);
  color: var(--ink);
}

/* ── Précision GPS courante, DANS la rangée de légende en haut de la carte ──
   Rendue par ChasseSpotsMap (paramètre LegendExtra), à la suite de la dernière pastille
   (« Trouvé ») : elle se lit avec les autres compteurs de la partie et ne dispute plus le bas de la
   carte aux commandes ni aux flèches de boussole. Le bandeau de légende ne capte pas le pointeur —
   la pastille le rétablit pour elle seule (infobulle), la carte reste manipulable tout autour. */
.th-chasse-gps-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(8, 20, 24, 0.62);
  color: #fff;
  font: 800 11px var(--font-sans);
  white-space: nowrap;
  pointer-events: auto;
}
/* Précision suffisante pour collecter sans se poser de question (GpsPrecision.AcceptableAccuracyM). */
.th-chasse-gps-chip--ok { background: rgba(16, 185, 129, 0.9); }

/* ── Alerte de proximité ──
   REPREND LE PHOTOMATON (.th-play2-polaroid, le polaroid scotché du mode traversée) à
   l'emplacement qu'il occupait, en haut à droite de la carte : l'alerte n'en redéfinit que la
   position, l'anneau ambre et le clignotement — cadre, ruban adhésif et légende manuscrite
   viennent du polaroid. Elle n'apparaît QUE lorsqu'un spot repéré par un autre joueur, encore
   collectable, est à portée, et montre alors LE CLICHÉ DU SPOT (couverture, sinon premier cliché
   de sa galerie — voir AlertPreviewUrl côté page) : le joueur voit ce qu'il cherche.
   Clignotement LÉGER (l'opacité ne descend jamais bas, et l'anneau respire) : la carte reste
   lisible, l'œil est attiré sans être agressé.
   Le décalage vertical la pose sous la LÉGENDE du bandeau de la carte (ChasseSpotsMap), à la
   hauteur de la pastille de score : celle-ci étant ancrée à GAUCHE et l'alerte à DROITE, elles
   partagent la même ligne sans se recouvrir, et l'alerte gagne les ~24 px de la pastille — elle
   remonte donc d'autant, plus près du haut de la carte. */
.th-chasse-alert {
  top: 48px;
  right: 14px;
  z-index: 27;
  box-shadow: 0 0 0 2px var(--ember), 0 18px 38px -16px rgba(51, 48, 42, 0.55);
  animation: th-chasse-alert-pulse 1.8s ease-in-out infinite;
}
/* Légende du polaroid : intitulé de l'alerte + distance, empilés dans la largeur du cliché. */
.th-chasse-alert-caption {
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.th-chasse-alert-title {
  font: 800 11px var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ember);
}
.th-chasse-alert-dist { font: 800 14px var(--font-sans); color: var(--ink); }

/* Aperçu d'un spot de RÉALITÉ AUGMENTÉE : le sprite du fantasti à attraper, faute de photo (un
   spot RA n'en a jamais — cf. FantastiSpriteUrl). Contenu ENTIER dans le cadre (`contain`, marge
   autour) et non rogné comme une photo : la créature doit se reconnaître d'un coup d'œil. Le halo
   teal la détache du papier du polaroid. */
.th-chasse-alert-fantasti {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--tz-teal-d) 20%, transparent), var(--bg-soft) 72%);
}
.th-chasse-alert-fantasti img {
  width: 80%;
  height: 80%;
  object-fit: contain;
  display: block;
}
/* Même sprite en tête de la fiche du spot (celle qu'ouvre l'alerte), quand il n'y a aucun cliché. */
.th-chasse-fiche-fantasti {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 190px;
  background: radial-gradient(circle at 50% 45%, color-mix(in oklch, var(--tz-teal-d) 20%, transparent), var(--bg-soft) 72%);
}
.th-chasse-fiche-fantasti img {
  width: 168px;
  height: 168px;
  object-fit: contain;
  display: block;
}

@keyframes th-chasse-alert-pulse {
  0%, 100% { box-shadow: 0 0 0 2px var(--ember), 0 18px 38px -16px rgba(51, 48, 42, 0.55); opacity: 1; }
  50%      { box-shadow: 0 0 0 4px var(--ember), 0 18px 38px -16px rgba(51, 48, 42, 0.55); opacity: 0.88; }
}
/* Respect de « animations réduites » : l'alerte reste visible, elle cesse simplement de clignoter. */
@media (prefers-reduced-motion: reduce) {
  .th-chasse-alert { animation: none; }
}

/* ── Capture du mode collection : mini-aperçu + déclencheur rond, EN OVERLAY sur la carte ──
   Aucun bandeau sous la carte : celle-ci occupe tout le corps du shell, donc le déclencheur est
   toujours visible sans défilement. Le conteneur ne capte pas le pointeur (la carte reste
   manipulable entre les deux boutons) ; le déclencheur est centré indépendamment de l'aperçu,
   qui est ancré à gauche. */
.th-chasse-capture-row {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 29;
  display: flex;
  /* Alignement PAR LE BAS : les trois libellés se posent alors sur la même ligne, quelle que
     soit la taille du bouton qui les surmonte (le déclencheur est 2,5× plus haut que ses
     voisins). */
  align-items: flex-end;
  justify-content: center;
  gap: 20px;
  pointer-events: none;
}
.th-chasse-capture-row > * { pointer-events: auto; }

/* Interfaces v1b/v1c — les commandes deviennent une COLONNE plaquée dans un coin bas, du côté de
   la main dominante (droitier par défaut). « Ma collection » passe AU-DESSUS du déclencheur :
   l'ordre du DOM est déjà celui-là, seul l'axe change. Empilées d'un seul côté, les deux
   commandes se prennent sans traverser l'écran d'un pouce, et libèrent le coin opposé pour le
   médaillon de carte. align-items est ramené à `center` : dans une colonne, c'est l'axe
   HORIZONTAL, et le `flex-end` de la rangée décalerait les deux boutons contre le bord. */
.th-chasse-capture-row--stack {
  flex-direction: column;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
  left: auto;
  right: 14px;
}
.th-chasse-capture-row--stack.th-chasse-capture-row--left {
  right: auto;
  left: 14px;
}

/* Une commande = un bouton + son libellé, empilés. Le libellé nomme l'action explicitement
   (« Ma collection », « Capture », « Affichage ») : trois pictogrammes seuls ne disaient pas au
   joueur ce qu'il allait déclencher. Il ne capte pas le pointeur — la carte reste manipulable
   entre les boutons — et il est aria-hidden côté balisage, le bouton portant déjà le même nom
   accessible. */
.th-chasse-cmd {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
.th-chasse-cmd-label {
  font: 800 11.5px var(--font-sans);
  letter-spacing: 0.02em;
  color: #fff;
  white-space: nowrap;
  /* Ombre portée plutôt qu'un fond : le libellé reste lisible sur le plan clair comme sur
     l'imagerie satellite, sans ajouter une pastille de plus par-dessus la carte. */
  text-shadow: 0 1px 4px rgba(8, 20, 24, 0.9), 0 0 3px rgba(8, 20, 24, 0.7);
  pointer-events: none;
}

/* Mini-fenêtre d'aperçu de l'objectif : le flux caméra est déjà ouvert (pré-chargement), le
   joueur voit donc ce que verra la capture avant même de la déclencher. */
/* Accès à la collection — CÔTE À CÔTE avec le déclencheur : ICÔNE (jamais la vignette du dernier
   spot, qui se confondait avec le déclencheur et l'alerte de proximité, tous deux porteurs d'une
   image) plus une pastille de comptage. Le bouton lui-même ne rogne pas (overflow visible) pour
   que la pastille déborde du cercle. */
.th-chasse-collection-btn {
  position: relative;
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  flex-shrink: 0;
}
.th-chasse-collection-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 58px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  background: var(--bg-card);
  color: var(--tz-teal-d);
  box-shadow: 0 8px 22px -8px rgba(8, 20, 24, 0.65);
}
.th-chasse-collection-count {
  position: absolute;
  right: -5px;
  top: -5px;
  min-width: 23px;
  height: 23px;
  padding: 0 6px;
  border-radius: 999px;
  background: var(--ember);
  color: #fff;
  font: 800 12px var(--font-sans);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 3px 8px rgba(51, 48, 42, 0.35);
}

/* Options de fond de carte — troisième bouton de la rangée, symétrique de la collection. Le
   sélecteur natif de Leaflet est désactivé sur la carte du jeu (ShowLayerControl="false") pour que
   les trois commandes vivent au même endroit. */
.th-chasse-layers {
  position: relative;
  flex-shrink: 0;
}
.th-chasse-layers-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 58px;
  height: 58px;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  background: var(--bg-card);
  color: var(--tz-teal-d);
  box-shadow: 0 8px 22px -8px rgba(8, 20, 24, 0.65);
  cursor: pointer;
}
.th-chasse-layers-btn.is-open {
  background: var(--tz-teal-d);
  color: #fff;
}
/* Panneau d'options, ouvert AU-DESSUS du bouton et aligné sur son bord droit (il ne peut donc
   jamais déborder de la carte du côté opposé au déclencheur). */
.th-chasse-layers-panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 10px);
  z-index: 2;
  min-width: 178px;
  padding: 10px;
  border: 1.5px solid var(--line-strong);
  border-radius: 14px;
  background: var(--bg-card);
  box-shadow: 0 18px 40px -18px rgba(8, 20, 24, 0.7);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.th-chasse-layers-seg {
  display: flex;
  gap: 4px;
  padding: 3px;
  border-radius: 999px;
  background: var(--bg-soft);
}
.th-chasse-layers-seg button {
  flex: 1;
  border: none;
  border-radius: 999px;
  padding: 7px 10px;
  background: transparent;
  color: var(--ink-soft);
  font: 800 12px var(--font-sans);
  cursor: pointer;
}
.th-chasse-layers-seg button.is-active {
  background: var(--tz-teal-d);
  color: #fff;
}
.th-chasse-layers-check {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 700 12.5px var(--font-sans);
  color: var(--ink);
  cursor: pointer;
}
.th-chasse-layers-check input { accent-color: var(--tz-teal-d); cursor: pointer; }

/* Déclencheur rond façon appareil photo (remplace l'ancien bouton « Prendre une photo »). Bien
   plus gros que les deux commandes qui l'encadrent : c'est le geste du jeu, et il doit rester
   atteignable au pouce sans viser. Taille pilotée par --th-chasse-shutter-size (posée sur
   .th-chasse-map) : ce qui est posé au-dessus de la rangée (.th-chasse-compass, contrôles Leaflet
   du coin bas droit) dérive sa marge basse de cette même variable, si bien qu'un redimensionnement
   du déclencheur ne peut pas le laisser passer dessous. */
.th-chasse-shutter {
  width: var(--th-chasse-shutter-size);
  height: var(--th-chasse-shutter-size);
  border-radius: 50%;
  /* Anneau blanc translucide plutôt qu'ambre : le déclencheur flotte désormais sur la carte, et
     doit rester lisible aussi bien sur le plan clair que sur l'imagerie satellite sombre. */
  border: 6px solid rgba(255, 255, 255, 0.82);
  background: rgba(8, 20, 24, 0.28);
  padding: 6px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
}
.th-chasse-shutter-core {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  overflow: hidden;
  /* Fond ambre visible tant que le flux caméra n'est pas arrivé (permission en attente, refusée,
     ou desktop) : le bouton ne devient jamais un disque vide. */
  background: var(--ember);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 14px 26px -12px rgba(242, 84, 61, 0.55);
  transition: transform 0.12s ease;
}
/* Aperçu caméra EN FOND du déclencheur : le joueur voit ce que verra la photo à l'endroit même où
   il appuie. */
.th-chasse-shutter-core video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* Pictogramme photo par-dessus le flux, sur un voile sombre qui lui garantit son contraste quelle
   que soit la scène cadrée. */
.th-chasse-shutter-glyph {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 20, 24, 0.3);
  color: #fff;
  filter: drop-shadow(0 2px 6px rgba(0, 0, 0, 0.55));
}
.th-chasse-shutter:active .th-chasse-shutter-core { transform: scale(0.92); }

/* Interfaces v1b/v1c — déclencheur SANS aperçu caméra en fond (aucun <video> n'y est monté : le
   flux plein écran est déjà derrière le bouton). Le voile sombre du pictogramme n'a alors plus
   rien à contraster : il assombrissait le disque ambre pour rien. Anneau et rembourrage sont
   resserrés à l'échelle du bouton réduit. */
.th-chasse-map--cam .th-chasse-shutter {
  border-width: 4px;
  padding: 4px;
}
.th-chasse-map--cam .th-chasse-shutter-glyph { background: transparent; }

/* Déclencheur MIS EN ÉVIDENCE à proximité d'un spot repéré (même condition que le photomaton
   d'alerte, cf. NearbyAlert) : anneau ambre + halo qui se propage en boucle. L'alerte signale
   « il y a quelque chose ici », ce clignotement dit « c'est ici qu'on appuie » — les deux
   battent au même rythme (1,8 s) pour se lire comme un seul signal, pas comme deux alarmes.
   Le halo passe par box-shadow (aucune incidence sur la mise en page, donc pas de saccade sur
   la rangée de commandes) et le fond du bouton reste le flux caméra. */
.th-chasse-shutter--alert {
  border-color: var(--ember);
  animation: th-chasse-shutter-pulse 1.8s ease-out infinite;
}
@keyframes th-chasse-shutter-pulse {
  0%       { box-shadow: 0 0 0 0 color-mix(in oklch, var(--ember) 65%, transparent); }
  70%, 100% { box-shadow: 0 0 0 18px color-mix(in oklch, var(--ember) 0%, transparent); }
}
/* « Animations réduites » : le déclencheur garde son anneau ambre — la mise en évidence subsiste,
   seul le battement disparaît (même traitement que .th-chasse-alert). */
@media (prefers-reduced-motion: reduce) {
  .th-chasse-shutter--alert { animation: none; box-shadow: 0 0 0 5px color-mix(in oklch, var(--ember) 35%, transparent); }
}

/* Prise de vue EN COURS : le toucher du déclencheur prend la photo (visée native du capteur puis
   encodage), le temps que ça dure le bouton reste enfoncé et n'accepte plus de second toucher —
   sans quoi un joueur pressé déclencherait deux fois pour un seul cliché attendu. */
.th-chasse-shutter.is-shooting { cursor: default; }
.th-chasse-shutter.is-shooting .th-chasse-shutter-core { transform: scale(0.92); }

/* Déclencheur GRISÉ — collecte fermée, hors de toute zone de capture. Il n'y a rien à collecter
   là où l'on est : le bouton l'annonce au lieu d'ouvrir une fenêtre qui ne saurait dire que
   « approchez-vous ». L'anneau et le pictogramme s'éteignent, le fond ambre laisse place à un
   gris neutre, et l'aperçu caméra en fond est désaturé — le tout reste lisible sur plan comme sur
   satellite. En collecte ouverte, ce cas n'existe pas : on peut toujours signaler un nouveau spot
   là où l'on se tient. */
.th-chasse-shutter.is-disabled {
  cursor: not-allowed;
  border-color: rgba(255, 255, 255, 0.45);
}
.th-chasse-shutter.is-disabled .th-chasse-shutter-core {
  background: rgba(120, 132, 138, 0.9);
  box-shadow: none;
}
.th-chasse-shutter.is-disabled .th-chasse-shutter-core video {
  filter: grayscale(1) brightness(0.55);
}
.th-chasse-shutter.is-disabled .th-chasse-shutter-glyph { color: rgba(255, 255, 255, 0.6); }

/* Éclair de prise de vue, sur toute la zone de jeu : seul retour visible entre le toucher et
   l'ouverture de la fenêtre de collecte. Posé au-dessus des pastilles flottantes (z-index 500,
   cf. bug nº8) et sous les modales plein écran (1000+). */
.th-chasse-flash {
  position: absolute;
  inset: 0;
  z-index: 700;
  pointer-events: none;
  background: #fff;
  opacity: 0;
  animation: th-chasse-flash 0.34s ease-out both;
}
@keyframes th-chasse-flash {
  0%   { opacity: 0.8; }
  100% { opacity: 0; }
}
/* « Animations réduites » : voile constant et discret pendant la prise plutôt qu'un éclair — le
   retour visuel subsiste, c'est la seule confirmation que le geste a porté. */
@media (prefers-reduced-motion: reduce) {
  .th-chasse-flash { animation: none; opacity: 0.28; }
}

/* ── Collection en overlay (ouverte par le photomaton) ── */
.th-chasse-collection-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: color-mix(in oklch, var(--ink) 45%, transparent);
  animation: th-fade-in 0.18s;
}
.th-chasse-collection-sheet {
  width: 100%;
  max-width: 560px;
  max-height: 82vh;
  display: flex;
  flex-direction: column;
  background: var(--bg-card);
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  box-shadow: 0 -24px 60px -24px rgba(11, 79, 90, 0.5);
}
@media (min-width: 640px) {
  .th-chasse-collection-overlay { align-items: center; }
  .th-chasse-collection-sheet { border-radius: var(--r-lg); }
}
.th-chasse-collection-head {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px 10px;
  border-bottom: 1.5px solid var(--line);
}
.th-chasse-collection-head .th-play2-icon-btn { margin-left: auto; }

/* Contrôles Leaflet du coin bas droit du mode collection : le zoom +/-, et au-dessus de lui le
   sélecteur de fond (Plan / Satellite / Repères) quand il est actif (voir leafletSpotsMap.init) — le
   coin haut droit est pris par le photomaton de l'alerte de proximité. Ils sont REMONTÉS de toute la
   hauteur de la rangée de commandes (--th-chasse-row-h, comme les flèches de boussole) : au ras du
   bord, ils passaient sous le bouton « Affichage » et son libellé. */
.th-chasse-map .leaflet-bottom.leaflet-right {
  margin-bottom: calc(var(--th-chasse-row-h) + env(safe-area-inset-bottom));
}
.th-chasse-collection-body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding: 14px 16px calc(16px + env(safe-area-inset-bottom));
}

/* ══════════════════════════════════════════════════════════════════════
   Écran de jeu collection — mode DESKTOP / tablette large.
   Même shell qu'en mobile (carte plein écran + panneau « Objectif » + overlays) : à partir
   de 1024 px, la coque cesse d'être une colonne « téléphone » (règle ≥ 640 px sur
   .th-play2-shell) et les overlays gagnent la même ampleur que ceux du mode traversée
   (cf. .th-play2-jeu). Porté par .th-play2-chasse, qui ne touche jamais .th-play2-shell /
   .th-play2-objective / .th-play2-icon-btn hors de cette portée : ces classes restent
   partagées avec le mode traversée (voir le commentaire au-dessus de .th-play2-jeu).
   ══════════════════════════════════════════════════════════════════════ */
@media (min-width: 1024px) {
  .th-play2-chasse.th-play2-shell {
    max-width: none;
    margin: 0;
    box-shadow: none;
  }

  .th-play2-chasse .th-play2-objective { padding: 11px 24px 12px; }
  .th-play2-chasse .th-play2-objective-title { font-size: 22px; }
  .th-play2-chasse .th-play2-icon-btn { width: 34px; height: 34px; }
  /* Le volet « Objectif » plafonne toujours plus bas que la carte : pas de dock flottant ici
     (le panneau reste un bandeau plein largeur au-dessus, déjà lisible à cette largeur). */
  .th-play2-chasse .th-play2-panel--top .th-play2-panel-body { max-height: 22vh; }

  /* Photomaton d'alerte agrandi, comme le polaroid d'indice du mode traversée. */
  .th-play2-chasse .th-play2-polaroid { --th-polaroid-photo-w: 170px; }
  .th-play2-chasse .th-play2-polaroid-photo { height: 140px; }
  .th-play2-chasse .th-play2-polaroid-caption { font-size: 17px; }
  .th-play2-chasse .th-play2-polaroid-tape { width: 70px; height: 20px; }

  /* Légende/score et précision GPS, un peu plus lisibles à distance de bureau. */
  .th-play2-chasse .th-chasse-gps-chip { font-size: 12.5px; padding: 4px 11px; }
}

/* ══ Interfaces v1b / v1c du mode joueur « collection » ══
   Le bouton « Affichage » ne règle plus seulement le FOND de carte (Plan / Satellite / Repères)
   mais aussi la PLACE que la carte occupe : masquée, normale, ou agrandie de moitié. L'interface
   historique (v1a) n'expose pas ce réglage et n'est donc touchée par AUCUNE des règles ci-dessous :
   elles ne s'appliquent qu'aux modificateurs posés par les deux nouvelles variantes.
   Le choix de l'interface servie est un réglage d'administration (« Paramètres généraux »). */

/* Séparateur + intitulé de la seconde section du panneau « Affichage » (taille de la carte) : le
   panneau porte désormais deux réglages de nature différente, il faut les distinguer d'un coup d'œil. */
.th-chasse-layers-sep {
  height: 1px;
  background: var(--line);
  margin: 2px 0;
}
.th-chasse-layers-title {
  font: 800 10.5px var(--font-sans);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Carte masquée — le fond de carte, les marqueurs et les contrôles Leaflet disparaissent
   (visibility:hidden posé par ChasseSpotsMap, qui garde la carte MONTÉE pour ne pas la
   reconstruire au retour). Reste ce fond neutre sombre, sur lequel compteurs, flèches de boussole
   et commandes restent parfaitement lisibles. En v1c, c'est la caméra qui occupe déjà ce fond :
   pas de teinte à poser par-dessus. */
.th-chasse-map--nomap { background: #0d1b20; }
.th-chasse-map--cam.th-chasse-map--nomap { background: #000; }

/* ── v1c : caméra plein écran, carte en médaillon ──
   Le flux caméra (le MÊME que celui du déclencheur, simplement rebranché sur un second <video>)
   occupe tout le fond ; la carte se replie en médaillon posé en bas à gauche, au-dessus de la
   rangée de commandes. */
.th-chasse-cam-backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: #000;
}

/* Bandeau de compteurs de la v1c — posé sur la CAMÉRA : dans un médaillon de la taille d'une
   vignette, la légende et le score seraient illisibles. Même contenu et même dégradé que le
   bandeau interne de la carte (voir ChasseSpotsMap), ancré à gauche pour laisser le coin haut
   droit à la roue crantée. La légende occupe TOUTE la largeur sur sa propre rangée et les
   compteurs viennent DESSOUS : mis côte à côte, chacun n'avait plus que la moitié de l'écran et
   la légende retombait sur une pastille par ligne — quatre rangées au lieu de deux, soit tout le
   haut de la vue caméra mangé. Ne capte pas le pointeur, sauf ses pastilles à infobulle. */
.th-chasse-camhud {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 26;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 7px;
  /* Marge droite : la roue crantée « main dominante » occupe ce coin (34 px à 12 px du bord), et
     la rangée de légende ne doit pas venir passer dessous — 48 px la dégagent de 2 px, et pas un
     de plus : chaque pixel rendu à la rangée est un pixel qui la garde sur une seule ligne. */
  padding: 10px 48px 22px 12px;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(8, 20, 24, 0.65), rgba(8, 20, 24, 0));
}
/* Pleine largeur, et gouttières resserrées : chaque pixel gagné sur les blancs est un pixel qui
   garde une pastille sur la PREMIÈRE ligne. Le budget est compté — la légende collective la plus
   large (quatre entrées) mesure ~296 px pour ~300 px disponibles sur un écran de 360 px, marge
   qui n'existe que parce que « En attente » a été raccourci en même temps que cette règle. */
.th-chasse-camhud-row {
  width: 100%;
  display: flex;
  align-items: center;
  column-gap: 8px;
  row-gap: 4px;
  /* Repli conservé en dernier recours : sur un écran plus étroit que 360 px, ou dans une langue
     aux libellés plus longs, mieux vaut une deuxième rangée qu'un texte tronqué. */
  flex-wrap: wrap;
}
.th-chasse-camhud-legend {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font: 700 11px var(--font-sans);
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.55);
  white-space: nowrap;
}
.th-chasse-camhud-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
}
.th-chasse-camhud-score {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.4);
  font: 800 12.5px var(--font-sans);
  color: #fff;
}
.th-chasse-camhud-sep {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  border-left: 1px solid rgba(255, 255, 255, 0.3);
  padding-left: 6px;
}

/* Cadre de la carte — posé sur toute la zone de jeu (v1a/v1b). Il existe dans TOUTES les
   interfaces : la carte se dimensionne en % de ce cadre, un conteneur à hauteur automatique la
   laisserait à zéro. */
.th-chasse-mapframe {
  position: absolute;
  inset: 0;
}

/* ── Roue crantée « main dominante » (v1b/v1c) ──
   Tout en haut de la zone de jeu, dans le coin que le bandeau de compteurs laisse libre. C'est le
   seul réglage qui ne porte pas sur la carte mais sur la PRISE EN MAIN — de quel côté tombent les
   commandes — et il se règle une fois, en début de partie : il doit donc être atteignable sans
   être dans le chemin. Au-dessus du photomaton d'alerte (z-index 27), que son panneau recouvre le
   temps du choix. */
.th-chasse-handed {
  position: absolute;
  top: 8px;
  right: 12px;
  z-index: 28;
}
.th-chasse-handed-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  background: rgba(8, 20, 24, 0.72);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 4px 12px -4px rgba(8, 20, 24, 0.8);
}
.th-chasse-handed-btn.is-open {
  background: var(--tz-teal-d);
  border-color: #fff;
}
/* Panneau ouvert SOUS le bouton et aligné sur son bord droit : il ne peut donc pas déborder de
   l'écran du côté opposé. Même habillage que le panneau de calques de la v1a. */
.th-chasse-handed-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 2;
  min-width: 178px;
  padding: 10px;
  border: 1.5px solid var(--line-strong);
  border-radius: 14px;
  background: var(--bg-card);
  box-shadow: 0 18px 40px -18px rgba(8, 20, 24, 0.7);
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* Médaillon de carte (v1b/v1c) — ancré dans le coin bas OPPOSÉ aux commandes : la main qui tient
   l'appareil ne doit pas masquer la carte qu'on vient consulter. Ce coin étant libre de tout
   bouton, le médaillon descend jusqu'au bas de l'écran — inutile de le remonter de la hauteur de
   la colonne de commandes, qui vit de l'autre côté. « ×1,5 » agrandit LE MÉDAILLON : grossir le
   rendu d'une carte déjà réduite à une vignette ne montrerait plus rien de son entourage. */
.th-chasse-minimap {
  inset: auto;
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 27;
  border: 2px solid rgba(255, 255, 255, 0.85);
  /* PAS de overflow:hidden ici : les pastilles de bordure chevauchent le trait, moitié dedans
     moitié dehors, et un rognage au cadre les couperait en deux. Le rognage vit donc sur les
     enfants qui portent du CONTENU — la carte et la miniature, qui se rognent déjà elles-mêmes
     (voir la règle `> div` plus bas) — et pas sur le cadre, qui ne sert plus qu'à porter le
     trait blanc et à situer les pastilles. */
  overflow: visible;
  box-shadow: 0 10px 28px -12px rgba(8, 20, 24, 0.8);
  transition: width 0.16s ease, height 0.16s ease;
}
.th-chasse-minimap--anchor-left { left: 12px; }
.th-chasse-minimap--anchor-right { right: 12px; }

/* Les deux formes de médaillon — SEULE différence entre v1b (rond) et v1c (carré). Même côté,
   même ancrage, mêmes icônes : un diamètre unique (--th-minimap-d) sert de côté au carré comme
   au disque, pour que les deux interfaces occupent exactement la même empreinte à l'écran. */
.th-chasse-minimap--round,
.th-chasse-minimap--square {
  --th-minimap-d: min(52vw, 210px);
  width: var(--th-minimap-d);
  height: var(--th-minimap-d);
}
.th-chasse-minimap--round.th-chasse-minimap--zoom,
.th-chasse-minimap--square.th-chasse-minimap--zoom {
  --th-minimap-d: min(74vw, 300px);
}
/* Un disque se lit comme un instrument de bord (boussole, radar) plutôt que comme une fenêtre :
   il dit « repère-toi », pas « lis la carte ». Le carré, lui, garde le rayon des cartes du jeu. */
.th-chasse-minimap--round { border-radius: 50%; }
.th-chasse-minimap--square { border-radius: var(--r-lg, 12px); }
/* Le conteneur interne de ChasseSpotsMap porte son propre border-radius (Rounded) : il faut le
   rendre circulaire lui aussi, sinon les tuiles Leaflet débordent aux quatre coins du disque.
   Le carré n'a rien à corriger : le rayon posé par le composant est déjà le bon.
   `overflow:hidden` est rappelé ici parce que c'est désormais À CE NIVEAU que le médaillon
   rogne : le cadre, lui, laisse dépasser les pastilles de bordure. Il ne vise que les `div`
   — donc la carte et la miniature, jamais les `button`. */
.th-chasse-minimap--round > div,
.th-chasse-minimap--square > div { overflow: hidden; }
.th-chasse-minimap--round > div { border-radius: 50% !important; }

/* Carte masquée : le médaillon disparaît entièrement — sans cette règle, son cadre blanc
   flotterait, vide, sur la caméra. */
.th-chasse-map--nomap .th-chasse-minimap { display: none; }

/* Contrôles natifs de Leaflet dans le médaillon : supprimés. Ils sont ancrés dans les COINS, où
   le rognage circulaire de la v1b n'en laisserait qu'un moignon et où, en v1c, ils tomberaient
   sous les icônes de bordure. Le médaillon est un instrument de bord — on s'y repère, on n'y
   navigue pas : le pincement zoome toujours, et l'icône « ×1,5 » agrandit la vignette. */
.th-chasse-map--cam .leaflet-control-container { display: none; }

/* Les TROIS icônes posées sur la LIMITE du médaillon : basculer plan ⇄ satellite (coin haut),
   agrandir de moitié (à mi-hauteur) et masquer la carte (coin bas). Elles remplacent entièrement le
   bouton « Affichage » de la rangée basse. Posées À CHEVAL sur le trait du médaillon, centrées
   dessus : elles y gagnent leur surface de touche entière — rentrées, elles mordaient sur la
   carte qu'elles recouvraient — et le médaillon récupère son disque complet. C'est ce qui a
   fait tomber le `overflow:hidden` du cadre, qui les aurait coupées en deux (voir
   .th-chasse-minimap). Les RÉGLAGES sont du côté qui fait face au centre de l'écran (voir
   --anchor-*), à portée du pouce ; seul l'escamotage passe de l'autre côté, à l'opposé
   diamétral du fond de carte — il ne se règle pas, il ferme. --th-rim-diag place les deux pastilles de coin, --th-rim-side celle du
   milieu ; le disque les rentre davantage que le carré, dont les coins sont pleins. */
/* Les retraits ne sont plus des marges : ce sont les COORDONNÉES du point du bord sur lequel
   chaque pastille se centre. Sur le DISQUE, ce point est sur le cercle — d'où des valeurs
   trigonométriques : 14,6 % = (1 − cos 45°)/2, la hauteur à laquelle le cercle croise la
   diagonale ; 6,2 % = la même corde relevée à 26 % de la hauteur. Sur le CARRÉ, le bord
   intérieur est un segment vertical : l'abscisse vaut donc 0 pour toutes, et seules les
   hauteurs les distinguent. */
.th-chasse-minimap--round { --th-rim-diag-y: 14.6%; --th-rim-diag-x: 14.6%; --th-rim-quart-y: 26%; --th-rim-quart-x: 6.2%; }
.th-chasse-minimap--square { --th-rim-diag-y: 8%; --th-rim-diag-x: 0%; --th-rim-quart-y: 26%; --th-rim-quart-x: 0%; }

.th-chasse-minimap-btn {
  position: absolute;
  z-index: 2;
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  background: rgba(8, 20, 24, 0.72);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 4px 12px -4px rgba(8, 20, 24, 0.8);
}
/* Chaque pastille est CENTRÉE sur son point d'ancrage (translate de la moitié de sa taille) :
   c'est ce qui la fait chevaucher le trait du médaillon, moitié dedans moitié dehors, au lieu
   de se ranger en deçà. Elle y gagne sa surface de touche entière — rentrée, une pastille de
   30 px mordait sur la carte qu'elle recouvrait — et le médaillon récupère le disque complet.
   L'escamotage est ancré par `top: calc(100% − …)` plutôt que par `bottom`, pour que la MÊME
   translation les centre toutes.
   Coin HAUT (côté centre de l'écran) : le fond de carte (et, en miniature, le retour à la
   carte). L'escamotage, lui, part à l'OPPOSÉ DIAMÉTRAL de ce coin — donc en bas de l'autre
   flanc, contre le bord de l'écran. C'est la seule des quatre à quitter l'arc du pouce, et
   c'est voulu : elle fait DISPARAÎTRE la carte, alors que les trois autres se prennent et se
   reprennent sans conséquence. La mettre au plus loin du geste courant, sur le point du
   médaillon qu'on ne rencontre jamais en réglant le reste, est ce qui la met hors de portée
   d'un toucher par mégarde ; son liseré rouge (voir plus bas) dit le reste.
   Elle chevauche donc le trait dans l'AUTRE sens que ses voisines : sa translation est
   l'opposée, sans quoi elle se rangerait en deçà du bord au lieu d'être centrée dessus. */
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--layer,
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--thumb,
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--zoom { transform: translate(50%, -50%); }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--layer,
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--thumb,
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--zoom { transform: translate(-50%, -50%); }
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--hide { transform: translate(-50%, -50%); }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--hide { transform: translate(50%, -50%); }

.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--layer { top: var(--th-rim-diag-y); right: var(--th-rim-diag-x); }
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--zoom { top: 50%; right: 0; }
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--hide { top: calc(100% - var(--th-rim-diag-y)); left: var(--th-rim-diag-x); }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--layer { top: var(--th-rim-diag-y); left: var(--th-rim-diag-x); }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--zoom { top: 50%; left: 0; }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--hide { top: calc(100% - var(--th-rim-diag-y)); right: var(--th-rim-diag-x); }

/* Liseré ROUGE, et lui seul parmi les cinq : c'est la commande qui retire la carte de l'écran.
   Le rouge est ici la seule chose qui la distingue avant le toucher — les quatre autres
   pastilles sont identiques au blanc près, et l'icône (une croix de 14 px sur un flux caméra)
   ne se lit pas de loin. `--tz-coral` plutôt que `--ember` : l'accent signature vire au bleu
   dans la palette « cirques », un rouge d'avertissement ne peut pas dépendre de la palette. */
.th-chasse-minimap-btn--hide { border-color: var(--tz-coral); }

/* QUATRIÈME icône — bascule « miniature du spot à trouver », posée entre le fond de carte (coin
   haut) et le zoom (milieu). Elle n'existe qu'en mode carte ET à proximité d'un spot, donc la rangée
   n'est à quatre que par intermittence : les trois autres ne bougent jamais de place, pour rester
   trouvables au pouce sans regarder. Sur le médaillon ROND, le retrait latéral est plus important
   qu'au milieu (--th-rim-quart-x > --th-rim-side) : à 26 % de la hauteur, la corde du disque est
   plus courte qu'au diamètre, et une icône posée au même retrait dépasserait du bord. */
.th-chasse-minimap--anchor-left .th-chasse-minimap-btn--thumb { top: var(--th-rim-quart-y); right: var(--th-rim-quart-x); }
.th-chasse-minimap--anchor-right .th-chasse-minimap-btn--thumb { top: var(--th-rim-quart-y); left: var(--th-rim-quart-x); }

/* Médaillon en MINIATURE : l'image de ce qu'on cherche, posée par-dessus la carte (qui reste
   montée dessous — cf. ChasseDetail.razor). `contain` et non `cover` : une créature de réalité
   augmentée ou un cliché en portrait doivent se voir ENTIERS, quitte à laisser du fond — c'est
   l'objet à reconnaître sur le terrain, le rogner reviendrait à en cacher la moitié. */
.th-chasse-minimap-thumb {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(8, 20, 24, 0.92);
  overflow: hidden;
}
.th-chasse-minimap-thumb img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
}
/* Distance conservée par-dessus la miniature : c'est la seule chose que la carte disait encore
   d'utile une fois sur zone. Centrée, hors des quatre coins où vivent les icônes, et
   EMPILÉE au-dessus de la précision GPS — qui occupe désormais le bas du médaillon. */
.th-chasse-minimap-thumb-dist {
  position: absolute;
  bottom: 24%;
  left: 50%;
  transform: translateX(-50%);
  padding: 4px 10px;
  border-radius: 999px;
  background: rgba(8, 20, 24, 0.75);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  white-space: nowrap;
}
/* Réglage actif (fond satellite, médaillon agrandi) : l'icône s'allume. C'est le seul rappel de
   l'état courant — il n'y a plus de panneau où lire les trois options côte à côte. */
.th-chasse-minimap-btn.is-active {
  background: var(--ember);
  border-color: #fff;
}

/* Médaillon escamoté : une pastille prend sa place, dans le même coin, pour le rappeler. Sans
   elle, les trois icônes disparaîtraient avec la carte qu'elles portent — et « Affichage »
   n'existe plus pour la faire revenir. */
.th-chasse-minimap-show {
  position: absolute;
  left: 12px;
  bottom: calc(14px + env(safe-area-inset-bottom));
  z-index: 27;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.85);
  background: rgba(8, 20, 24, 0.72);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 10px 28px -12px rgba(8, 20, 24, 0.8);
}
.th-chasse-map--lefty .th-chasse-minimap-show {
  left: auto;
  right: 12px;
}

/* ── Précision GPS courante, EN OVERLAY sur le médaillon (v1b/v1c) ──
   Elle qualifie ce que montre la carte — à quelle distance près la position bleue dit vrai — et
   conditionne l'acceptation d'une collecte par le serveur : elle se lit donc AVEC le médaillon,
   et non avec le score. Posée par-dessus lui plutôt qu'en dessous : le médaillon garde ainsi
   exactement sa place et sa taille, la pastille ne coûte pas un pixel de vue caméra.
   Centrée en bas — le seul bord qu'aucune des icônes de rim n'occupe sur toute sa longueur —
   et suffisamment étroite pour rester dans le disque de la v1b, que `overflow: hidden` rogne.
   Transparente au pointeur : le médaillon reste manipulable de bout en bout, l'infobulle ne
   valant de toute façon rien sur un écran tactile. */
.th-chasse-gps-chip--medallion {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 5%;
  transform: translateX(-50%);
  max-width: 78%;
  pointer-events: none;
}

/* Carte escamotée : le médaillon a disparu, il n'y a plus rien à surcharger. La pastille se
   replie alors dans le coin bas qu'il occupait — donc à DROITE en configuration gaucher, comme
   lui — à côté de la pastille de rappel qui a pris sa place. */
.th-chasse-gps-floor {
  position: absolute;
  left: 64px;
  bottom: calc(21px + env(safe-area-inset-bottom));
  z-index: 27;
  display: flex;
  pointer-events: none;
}
.th-chasse-map--lefty .th-chasse-gps-floor {
  left: auto;
  right: 64px;
}

/* ── Écran de choix d'étape (mode challenge uniquement) ────────────────────────
   Même coquille que l'écran de jeu (.th-play2-shell / .th-play2-map) : seule la zone
   basse change — une liste d'étapes acceptables, triée par distance, surmontée par le
   bouton « Accepter l'étape ». En mode parcours cet écran n'existe pas : l'étape
   suivante est sélectionnée automatiquement.
   ─────────────────────────────────────────────────────────────────────────── */
.th-play2-choicesheet {
  flex: none;
  max-height: 46vh;
  overflow-y: auto;
  padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px));
  display: flex;
  flex-direction: column;
  gap: 12px;
  background: var(--bg-card);
  border-top: 1.5px solid var(--line);
  box-shadow: 0 -8px 24px rgba(0, 0, 0, .08);
}
.th-play2-choice-empty {
  margin: 0;
  font: 700 13px var(--font-sans);
  color: var(--muted);
  line-height: 1.5;
  text-align: center;
}
.th-play2-choicelist { display: flex; flex-direction: column; gap: 8px; }

.th-play2-choice {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  padding: 11px 13px;
  border: 1.5px solid var(--line-strong);
  border-radius: var(--r);
  background: var(--bg);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.th-play2-choice:hover { border-color: var(--canopy); }
.th-play2-choice.is-selected {
  border-color: var(--canopy);
  border-width: 2px;
  background: var(--canopy-soft);
}
.th-play2-choice-badge {
  flex: none;
  width: 30px; height: 30px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--bg-soft);
  border: 2px solid var(--canopy);
  color: var(--canopy);
  font: 800 13px var(--font-sans);
}
.th-play2-choice.is-selected .th-play2-choice-badge { background: var(--canopy); color: #fff; }
.th-play2-choice-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.th-play2-choice-name {
  font: 800 13.5px var(--font-sans);
  color: var(--ink);
  line-height: 1.3;
}
.th-play2-choice-meta {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  font: 700 11.5px var(--font-sans);
  color: var(--muted);
}
.th-play2-choice-locked {
  display: flex; align-items: center; gap: 10px;
  padding: 10px 13px;
  border: 1.5px dashed var(--line-strong);
  border-radius: var(--r);
  background: var(--bg-soft);
  font: 700 12.5px var(--font-sans);
  color: var(--muted);
  line-height: 1.45;
}

@media (prefers-reduced-motion: reduce) { .th-play2-choice { transition: none; } }

/* Sur l'écran de choix, la carte prend toute la hauteur restante au-dessus du volet des
   étapes (qui, lui, se dimensionne sur son contenu) — au lieu des 52 % fixes de l'écran de
   jeu, où elle partage la place avec le panneau « Objectif ». */
.th-play2-shell--choix .th-play2-map { flex: 1 1 auto; }



/* ══ Guide de prise en main du mode joueur (PlayerGuide.razor) ══════════════════
   Ouvert par-dessus l'écran de jeu (traversée ou collecte) : au-dessus des fenêtres de
   celui-ci (z-index 1000 pour l'aide/les alertes, 1200 pour le média en grand), mais
   SOUS la fenêtre découverte du filigrane (1600), qui vit elle-même par-dessus la
   capture plein écran. D'où 1400.

   Tout est en CSS + SVG — aucune image, aucune vidéo : le guide s'ouvre donc aussi en
   pleine nature, sans réseau, là où la partie se joue. */
.th-pg-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1400;
  background: rgba(6, 12, 18, 0.82);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(14px + env(safe-area-inset-top)) 14px calc(14px + env(safe-area-inset-bottom));
  animation: th-fade-in 0.2s ease both;
}
.th-pg-sheet {
  width: 100%;
  max-width: 400px;
  max-height: 100%;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  background: #10161d;
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: var(--r);
  padding: 14px 16px 16px;
  color: #fff;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
  animation: th-slide-up 0.24s ease both;
}

.th-pg-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 10px;
}
.th-pg-eyebrow {
  font-size: 10.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--tz-teal);
}
.th-pg-count {
  margin-left: auto;
  font-size: 11px;
  font-weight: 800;
  color: rgba(255, 255, 255, 0.55);
  font-variant-numeric: tabular-nums;
}
.th-pg-close {
  width: 28px;
  height: 28px;
  flex: none;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
}

/* ── Le schéma ──
   Dessin FIXE de l'écran de jeu ; seul le halo (.th-pg-spot) se déplace d'une étape à
   l'autre, aux coordonnées portées par PlayerGuideSteps. Les couleurs restent sourdes :
   c'est le halo qui doit attirer l'œil, pas le décor. */
.th-pg-stage {
  aspect-ratio: 120 / 200;
  max-height: 46vh;
  margin: 0 auto;
  border-radius: 12px;
  overflow: hidden;
}
.th-pg-svg {
  display: block;
  width: 100%;
  height: 100%;
}
.th-pg-phone { fill: #0a1016; stroke: rgba(255, 255, 255, 0.16); stroke-width: 1; }
.th-pg-bar { fill: rgba(255, 255, 255, 0.09); }
.th-pg-map { fill: rgba(46, 196, 182, 0.13); stroke: rgba(255, 255, 255, 0.1); stroke-width: 0.8; }
.th-pg-line { fill: rgba(255, 255, 255, 0.5); }
.th-pg-line--soft { fill: rgba(255, 255, 255, 0.22); }
.th-pg-btn-dot { fill: rgba(255, 255, 255, 0.22); }
.th-pg-chip { fill: rgba(255, 255, 255, 0.28); }
.th-pg-pill { fill: rgba(255, 255, 255, 0.34); }
.th-pg-zone { fill: rgba(255, 209, 102, 0.14); stroke: #ffd166; stroke-width: 1; stroke-dasharray: 3 3; }
.th-pg-me { fill: #4da3ff; stroke: #fff; stroke-width: 1.2; }
.th-pg-spotdot { fill: #ffd166; }
.th-pg-spotdot--found { fill: #2ec4b6; }
.th-pg-arrow { fill: #ffd166; }
.th-pg-polaroid { fill: #f4f1e8; }
.th-pg-polaroid-img { fill: #9fb3ab; }
.th-pg-cta { fill: var(--tz-teal); }
.th-pg-shutter { fill: rgba(255, 209, 102, 0.35); }
.th-pg-shutter-core { fill: #ffd166; }

/* Schéma « collecte, caméra plein écran » (v1b/v1c, l'interface servie par défaut) : le décor
   réel filmé occupe toute la zone de jeu, et tout le reste est POSÉ dessus. Le voile assombrit
   la scène juste assez pour que les pastilles blanches s'y détachent — à l'écran, c'est le
   dégradé du bandeau de compteurs qui joue ce rôle. */
.th-pg-cam { fill: #16323a; }
.th-pg-cam-sun { fill: #ffd166; opacity: 0.85; }
.th-pg-cam-far { fill: #2ec4b6; opacity: 0.55; }
.th-pg-cam-near { fill: #12786f; opacity: 0.8; }
.th-pg-cam-veil { fill: rgba(8, 20, 24, 0.34); }
/* Roue crantée « main dominante » : coin haut droit de la vue caméra, quel que soit le côté
   choisi — c'est le seul réglage qui ne change jamais de bord. */
.th-pg-gear { fill: rgba(8, 20, 24, 0.72); stroke: rgba(255, 255, 255, 0.9); stroke-width: 1.2; }
/* Médaillon de carte : rond en v1b, carré en v1c. Le schéma retient le disque — c'est la
   variante par défaut, et c'est la forme qui dit « instrument de bord » plutôt que « fenêtre ». */
.th-pg-medal {
  fill: rgba(46, 196, 182, 0.22);
  stroke: rgba(255, 255, 255, 0.85);
  stroke-width: 2;
}
.th-pg-medal-btn { fill: rgba(8, 20, 24, 0.8); stroke: rgba(255, 255, 255, 0.75); stroke-width: 1; }

/* Le halo : ce que l'étape montre du doigt. Il PULSE — sur un dessin figé, un simple
   cadre se confond avec les traits du décor, alors que le battement dit « regarde ici »
   sans une ligne de texte. */
.th-pg-spot {
  fill: rgba(255, 255, 255, 0.1);
  stroke: #ffd166;
  stroke-width: 2;
  animation: th-pg-pulse 1.6s ease-in-out infinite;
}
@keyframes th-pg-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.th-pg-title {
  margin: 12px 0 0;
  font-family: var(--font-serif);
  font-size: 20px;
  font-weight: 600;
  line-height: 1.2;
  color: #fff;
}
.th-pg-text {
  margin: 6px 0 0;
  font-size: 13.5px;
  font-weight: 600;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.8);
}
/* Bloc contextuel (ce que montrait l'ancienne fenêtre « ? ») : détaché du texte du guide,
   parce qu'il parle de LA partie en cours et non de l'interface en général. */
.th-pg-context {
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(46, 196, 182, 0.14);
  border: 1px solid rgba(46, 196, 182, 0.45);
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.92);
}
.th-pg-context :is(p, div) { margin: 0; color: inherit; font-size: inherit; }
.th-pg-context strong { color: #fff; }

.th-pg-dots {
  display: flex;
  justify-content: center;
  gap: 6px;
  margin: 14px 0 12px;
}
.th-pg-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.25);
  transition: background 0.15s, transform 0.15s;
}
.th-pg-dot.is-on {
  background: var(--tz-teal);
  transform: scale(1.35);
}

.th-pg-nav {
  display: flex;
  gap: 10px;
}
.th-pg-btn {
  flex: 1 1 0;
  height: 46px;
  border-radius: var(--r);
  font-family: var(--font-sans);
  font-size: 14.5px;
  font-weight: 800;
  cursor: pointer;
}
.th-pg-btn--ghost {
  flex: 0 0 34%;
  border: 1px solid rgba(255, 255, 255, 0.24);
  background: transparent;
  color: rgba(255, 255, 255, 0.78);
}
.th-pg-btn--primary {
  border: none;
  background: var(--tz-teal);
  color: #fff;
}
.th-pg-btn--primary:hover { filter: brightness(1.06); }

.th-pg-foot {
  margin: 10px 0 0;
  text-align: center;
  font-size: 11.5px;
  font-weight: 700;
  line-height: 1.4;
  color: rgba(255, 255, 255, 0.45);
}

/* Écran bas (téléphone tenu à l'horizontale, ou petit portrait) : le schéma passe À CÔTÉ
   du texte, sinon les boutons de navigation — la seule sortie du guide — tombent sous la
   ligne de flottaison. Chaque enfant est placé explicitement : le placement automatique
   autour d'un élément qui couvre plusieurs rangées dépend trop de l'ordre et du nombre
   d'enfants (le bloc contextuel n'est pas toujours là). */
@media (min-width: 560px) and (max-height: 560px) {
  .th-pg-sheet { max-width: 660px; }
  /* La grille est portée par l'ENVELOPPE du contenu, pas par la feuille : le même balisage sert
     aussi de contenu au cartouche de l'overlay, qui lui n'a pas de schéma à placer à côté. */
  .th-pg-sheet .th-pg-body {
    display: grid;
    grid-template-columns: 168px minmax(0, 1fr);
    column-gap: 18px;
    align-content: start;
  }
  .th-pg-head { grid-column: 1 / -1; }
  .th-pg-stage {
    grid-column: 1;
    grid-row: 2 / span 6;
    align-self: start;
    max-height: none;
  }
  .th-pg-title,
  .th-pg-text,
  .th-pg-context,
  .th-pg-dots,
  .th-pg-nav,
  .th-pg-foot {
    grid-column: 2;
  }
  .th-pg-title { margin-top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .th-pg-backdrop,
  .th-pg-sheet {
    animation: none;
  }
  /* Le halo ne bat plus, mais il doit rester repérable : c'est LUI qui porte l'explication.
     Il garde donc son contour, en pleine opacité. */
  .th-pg-spot {
    animation: none;
    opacity: 1;
  }
}

/* ══ Guide de prise en main — rendu « overlay » (PlayerGuide.razor) ═════════════
   Le guide se pose SUR l'écran de jeu et assombrit tout sauf l'élément dont il parle.

   Le trou est découpé par QUATRE BANDES posées autour de l'élément — et surtout pas en
   remontant celui-ci au-dessus d'un voile plein. Élever un bouton de l'écran de jeu
   (z-index, transplantation) casse un empilement déjà délicat : panes Leaflet, règle
   `isolation: isolate` du conteneur de carte, pastilles flottantes (cf. bugs nº5 et nº8).
   Avec les bandes, RIEN n'est déplacé ni réempilé : l'élément reste exactement où il est.

   Le conteneur ne capte pas le pointeur ; ses enfants décident. Bandes, obturateur du trou
   et cartouche le captent (rien ne doit atteindre le jeu pendant le guide) ; les anneaux le
   laissent passer, ils ne sont que du dessin. */
.th-pg-ov {
  position: fixed;
  inset: 0;
  z-index: 1400;
  pointer-events: none;
  animation: th-fade-in 0.2s ease both;
}
.th-pg-ov-band {
  position: fixed;
  background: rgba(6, 12, 18, 0.8);
  pointer-events: auto;
}
/* Aucune cible mesurable (étape d'accueil, ou élément absent de cet écran) : voile plein. */
.th-pg-ov-band--full {
  inset: 0;
}
/* Le trou laisse VOIR l'élément, jamais l'ATTEINDRE : appuyer sur le vrai déclencheur pendant
   le guide prendrait une photo. Un toucher y fait avancer d'une étape — le geste naturel
   « j'ai compris, la suite » tombe donc là où l'œil est déjà. */
.th-pg-ov-hole {
  position: fixed;
  padding: 0;
  border: none;
  background: transparent;
  pointer-events: auto;
  cursor: pointer;
}
.th-pg-ov-ring {
  position: fixed;
  border: 2.5px solid #ffd166;
  border-radius: 14px;
  pointer-events: none;
  box-shadow: 0 0 0 3px rgba(255, 209, 102, 0.22);
  animation: th-pg-pulse 1.6s ease-in-out infinite;
}
/* Cibles secondaires : l'étape en désigne plusieurs (les réglages vivent à trois endroits),
   mais quatre bandes ne découpent qu'un trou. Les suivantes sont donc cerclées sans être
   éclaircies — un second voile superposé assombrirait deux fois. */
.th-pg-ov-ring--sec {
  border-style: dashed;
  border-width: 2px;
  opacity: 0.8;
}
/* Le cartouche prend toute la largeur moins les marges, borné et centré : sur un téléphone
   c'est la seule mise en page qui ne demande pas de mesurer le cartouche lui-même — mesure
   qu'il faudrait reprendre à chaque changement de langue, de taille de police système et
   d'orientation. Sa hauteur est bornée et il défile : une étape contextuelle longue ne doit
   pas pousser les boutons de navigation hors de l'écran. */
.th-pg-ov-callout {
  position: fixed;
  left: 12px;
  right: 12px;
  margin: 0 auto;
  max-width: 420px;
  max-height: 62vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  pointer-events: auto;
  background: #10161d;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--r);
  padding: 12px 14px 14px;
  color: #fff;
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.6);
  animation: th-slide-up 0.2s ease both;
}
/* Dans l'overlay, le trou tient lieu d'illustration : le cartouche n'a que du texte, ses
   marges se resserrent d'autant. */
.th-pg-ov-callout .th-pg-title { margin-top: 0; font-size: 18px; }
.th-pg-ov-callout .th-pg-text { font-size: 13px; }
.th-pg-ov-callout .th-pg-dots { margin: 10px 0 10px; }
.th-pg-ov-callout .th-pg-btn { height: 42px; }

@media (prefers-reduced-motion: reduce) {
  .th-pg-ov,
  .th-pg-ov-callout {
    animation: none;
  }
  /* L'anneau ne bat plus, mais il doit rester repérable : c'est LUI qui désigne l'élément. */
  .th-pg-ov-ring {
    animation: none;
    opacity: 1;
  }
}

/* ══════════════════════════════════════════════════════════════════════
   Carte de jeu tournée — rotation manuelle et « cap en haut »
   ══════════════════════════════════════════════════════════════════════
   Leaflet 1.x ne tourne pas : c'est le CONTENEUR qui pivote en CSS
   (leafletPlayMap, wwwroot/js/leaflet-route-map.js). Le fond, le tracé et les
   zones suivent — ce sont des objets géographiques — mais PAS les étiquettes :
   un numéro d'étape à l'envers ne se lit plus. Chaque pastille est donc
   emballée dans .th-mapicon, contre-tournée d'autant.

   Passer par une variable héritée du conteneur (et non par une passe sur les
   marqueurs) est ce qui fait que les pastilles créées PLUS TARD — la carte se
   redessine à chaque relevé GPS — se redressent sans nouvelle intervention. */
.th-mapicon {
  display: block;
  transform: rotate(var(--th-map-unrotate, 0deg));
  transform-origin: 50% 50%;
}
/* Épingle en goutte : son point d'ancrage est la POINTE (iconAnchor bas-centre),
   pas son centre. La redresser autour de son centre décalerait la pointe du lieu
   qu'elle désigne — d'autant plus loin que la carte est tournée. */
.th-target-pin .th-mapicon { transform-origin: 50% 100%; }

/* Aiguille du bouton « remettre au nord » : elle indique où est le nord, donc
   l'angle même dont la carte a tourné (--th-map-bearing, posée par le JS sur
   l'hôte désigné à l'installation — voir _thMapRotate). Piloter l'aiguille en CSS
   plutôt que depuis .NET est ce qui permet à un geste de rotation de ne RIEN faire
   transiter par le circuit Blazor (cf. CLAUDE.md, bug nº14).
   AUCUNE transition ici : la variable est réécrite à chaque image, aussi bien
   pendant un geste que pendant la remise au nord animée (_thMapRotate.animateTo).
   Une transition ne lisserait donc rien — elle ferait traîner l'aiguille derrière
   la carte, et l'aiguille cesserait d'indiquer le nord pendant qu'elle traîne. */
.th-play2-north-needle {
  display: inline-flex;
  transform: rotate(var(--th-map-bearing, 0deg));
}
/* Le bouton reste toujours en place — sa position ne doit pas sauter d'un geste
   à l'autre — et reste discret : c'est l'aiguille, pas le bouton, qui porte
   l'information « la carte est tournée de tant ». */
.th-play2-chip--north { color: var(--muted); }

/* ══════════════════════════════════════════════════════════════════════
   Mode collection — commandes d'orientation de la carte
   ══════════════════════════════════════════════════════════════════════
   Mêmes fonctions qu'en mode parcours (rotation, retour au nord, suivi de la
   position), même socle JS (_thMapRotate, leaflet-route-map.js). Seule change
   la place des boutons : ici un rail vertical dans le coin bas-gauche, le seul
   libre des contrôles Leaflet natifs (zoom et calques, en bas-droite), de la
   légende (ancrée en haut) et du déclencheur de capture (centré en bas). */
/* Épingle de spot : comme celle de la cible en mode parcours, son point d'ancrage est
   la POINTE (iconAnchor bas-centre). La redresser autour de son centre l'écarterait du
   lieu qu'elle désigne, d'autant plus loin que la carte est tournée. */
.th-spot-pin .th-mapicon { transform-origin: 50% 100%; }

.th-chasse-maprail {
  position: absolute;
  left: 10px;
  bottom: 10px;
  z-index: 550;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.th-chasse-map-btn {
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(255, 255, 255, 0.9);
  background: rgba(8, 20, 24, 0.72);
  color: #fff;
  cursor: pointer;
  box-shadow: 0 4px 12px -4px rgba(8, 20, 24, 0.8);
}
.th-chasse-map-btn:hover { background: rgba(8, 20, 24, 0.88); }
/* Suivi engagé : la pastille s'allume, comme la bascule satellite du même écran. */
.th-chasse-map-btn.is-active {
  background: var(--tz-teal-d, #0c7a72);
  border-color: #fff;
}
/* La boussole reste discrète : c'est l'aiguille, pas le bouton, qui porte
   l'information « la carte est tournée de tant ». */
.th-chasse-map-btn--north { color: rgba(255, 255, 255, 0.82); }

/* Bouton d'orientation du MÉDAILLON (v1b/v1c) : au SOMMET du disque, et non sur l'arc
   des quatre pastilles de réglage. C'est la place qui dit ce qu'il fait — posé en haut,
   il désigne la direction vers laquelle la carte est orientée — et c'est la seule qui ne
   décale aucune des quatre autres, dont les positions sont apprises au pouce.
   Le retrait est celui du zoom (--th-rim-side, au milieu du flanc) : sur un disque, le
   sommet et le flanc sont la même géométrie tournée d'un quart de tour. Une seule règle
   pour les deux ancrages, contrairement aux pastilles de l'arc : le sommet, lui, ne
   change pas de côté selon la main dominante. */
.th-chasse-minimap-btn--follow {
  top: 0;
  left: 50%;
  transform: translate(-50%, -50%);
}
.th-chasse-minimap-btn.is-active {
  background: var(--tz-teal-d, #0c7a72);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Mode escape game — chrono d'échéance, carnet d'équipe, écran « rejoindre »

   Le carnet est un ONGLET ancré au bord GAUCHE de la carte, à mi-hauteur : c'est
   le seul bord libre de l'écran de jeu (chips en haut à gauche, polaroid en haut
   à droite, pilule de proximité en bas à gauche, volet « Objectif » en bas à
   droite). Ancré dans un coin, il aurait recouvert un contrôle Leaflet — voir le
   bug nᵒ8 de CLAUDE.md.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Le compte à rebours change de teinte, jamais de place : un chiffre qui se
   déplace à la dernière minute est plus difficile à lire, pas plus urgent. */
.th-escape-chip--urgent {
  border-color: var(--tz-coral);
  color: var(--tz-coral-d);
}

.th-escape-chip--horsdelai {
  background: var(--ember-soft);
  border-color: var(--tz-coral);
  color: var(--tz-coral-d);
}

.th-escape-carnet {
  position: absolute;
  top: 50%;
  left: 0;
  transform: translateY(-50%);
  z-index: 520;
  display: flex;
  align-items: flex-start;
  gap: 0;
  pointer-events: none;
  max-width: min(92%, 380px);
}

.th-escape-carnet-tab {
  pointer-events: auto;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font: 800 10.5px var(--font-sans);
  letter-spacing: 0.03em;
  color: var(--ink);
  background: color-mix(in oklch, var(--bg-card) 97%, transparent);
  border: 1.5px solid var(--line);
  border-left: none;
  border-radius: 0 8px 8px 0;
  padding: 8px 10px;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 6px 14px -10px rgba(11, 79, 90, 0.5);
}

.th-escape-carnet-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--tz-coral);
  color: #fff;
  font-size: 9.5px;
}

.th-escape-carnet-body {
  pointer-events: auto;
  margin-left: 6px;
  max-height: min(70vh, 460px);
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--bg-card);
  border: 1.5px solid var(--line);
  border-radius: var(--r);
  box-shadow: 0 18px 40px -22px rgba(11, 79, 90, 0.7);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  font-family: var(--font-sans);
}

.th-escape-sec { display: flex; flex-direction: column; gap: 6px; }

.th-escape-sec-title {
  margin: 0;
  font: 800 10px var(--font-sans);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--tz-teal-d);
}

.th-escape-acte-titre { margin: 0; font-size: 14.5px; font-weight: 800; color: var(--ink); }
.th-escape-acte-desc  { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--ink-soft, var(--ink)); }

.th-escape-equipe { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; }
.th-escape-equipe li { display: flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 700; }
.th-escape-ouvreur {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--tz-teal-d); background: var(--canopy-soft); border-radius: 5px; padding: 1px 5px;
}

.th-escape-code {
  display: flex; flex-direction: column; gap: 3px;
  background: var(--canopy-soft); border-radius: var(--r); padding: 8px 10px; margin-top: 4px;
}
.th-escape-code-valeur {
  font-size: 21px; font-weight: 800; letter-spacing: 0.22em; color: var(--tz-teal-d);
  font-variant-numeric: tabular-nums;
}
.th-escape-code-aide { font-size: 11px; line-height: 1.4; color: var(--tz-teal-d); }

.th-escape-vide { margin: 0; font-size: 12px; font-style: italic; color: var(--ink-soft, var(--ink)); }

.th-escape-fragments { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.th-escape-fragments li { display: flex; flex-direction: column; gap: 2px; }
.th-escape-fragment-etape {
  font-size: 9.5px; font-weight: 800; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--tz-teal-d);
}
.th-escape-fragment-texte { font-size: 13px; line-height: 1.45; font-weight: 700; color: var(--ink); }
.th-escape-fragment-masque { font-size: 12px; line-height: 1.4; font-style: italic; color: var(--ink-soft, var(--ink)); }
.th-escape-fragments li.is-masque { opacity: 0.75; }

.th-escape-echelle { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
.th-escape-indice-texte { font-size: 13px; line-height: 1.5; color: var(--ink); }
.th-escape-indice-media { display: block; width: 100%; border-radius: var(--r); margin-top: 6px; }
.th-escape-indice-note { margin: 4px 0 0; font-size: 11px; font-weight: 800; color: var(--tz-coral-d); }
.th-escape-indice-propose { margin: 4px 0 0; font-size: 11px; font-weight: 700; color: var(--tz-teal-d); }

.th-escape-indice-btn {
  display: flex; align-items: center; gap: 6px; width: 100%;
  background: var(--bg-card); border: 1.5px solid var(--line-strong); border-radius: var(--r);
  padding: 9px 11px; cursor: pointer;
  font: 800 12.5px var(--font-sans); color: var(--tz-teal-d); text-align: left;
}
.th-escape-indice-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.th-escape-indice-cout { margin-left: auto; color: var(--tz-coral-d); font-variant-numeric: tabular-nums; }

.th-escape-indice-verrou {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; font-weight: 700; color: var(--ink-soft, var(--ink)); opacity: 0.6;
}

/* ── Écran « rejoindre une équipe » ──────────────────────────────────────── */

.th-escape-join { display: flex; justify-content: center; padding: 28px 18px 48px; }

.th-escape-join-card {
  width: 100%; max-width: 420px;
  display: flex; flex-direction: column; gap: 10px;
  background: var(--bg-card); border: 1.5px solid var(--line); border-radius: var(--r-lg);
  padding: 22px 20px;
  font-family: var(--font-sans);
}

.th-escape-join-title { margin: 0; font-size: 22px; font-weight: 800; color: var(--ink); }
.th-escape-join-sub { margin: 0 0 6px; font-size: 13px; line-height: 1.5; color: var(--ink-soft, var(--ink)); }

.th-escape-join-label {
  font-size: 10px; font-weight: 800; letter-spacing: 0.09em; text-transform: uppercase;
  color: var(--tz-teal-d); margin-top: 6px;
}

.th-escape-join-input,
.th-escape-join-code {
  height: 50px; box-sizing: border-box; width: 100%;
  border: 1.5px solid var(--line-strong); border-radius: var(--r);
  padding: 0 16px; background: #fbfdfc; color: var(--ink);
  font-family: var(--font-sans); font-weight: 800; font-size: 16px; outline: none;
}

/* Le code se saisit en gros, très espacé : il se lit à voix haute, d'un téléphone
   à l'autre, souvent en plein soleil. */
.th-escape-join-code {
  font-size: 24px; letter-spacing: 0.3em; text-align: center; text-transform: uppercase;
}

.th-escape-join-erreur {
  margin: 4px 0 0; padding: 10px 12px; border-radius: var(--r);
  background: var(--ember-soft); color: var(--tz-coral-d);
  font-size: 12.5px; font-weight: 800; line-height: 1.45;
}

.th-escape-join-note { margin: 8px 0 0; font-size: 11.5px; line-height: 1.5; color: var(--ink-soft, var(--ink)); }

/* ══ Écran de jeu du mode ESCAPE GAME (ADR-0033) ══════════════════════════════════════════
   Écran dédié, et non une variante de l'écran de découverte : un acte ouvre plusieurs
   énigmes EN PARALLÈLE, et le plateau ci-dessous est la mise en scène de ce fait. */
.th-esc { display:flex; flex-direction:column; gap:16px; padding:14px; min-height:100dvh; }

.th-esc-vide { padding:40px; text-align:center; color:var(--muted); }
.th-esc-vide-acte { color:var(--muted); font-weight:600; text-align:center; padding:24px; }

.th-esc-tete { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.th-esc-acte-rang { font-size:10px; letter-spacing:.08em; text-transform:uppercase; color:var(--muted); font-weight:800; }
.th-esc-acte-titre { font-size:20px; margin:2px 0 0; line-height:1.15; }
.th-esc-tete-droite { display:flex; flex-direction:column; align-items:flex-end; gap:6px; }

/* Le compte à rebours est l'information la plus lue de l'écran : il est traité comme telle. */
.th-esc-chrono { font-variant-numeric:tabular-nums; font-size:26px; font-weight:800; line-height:1; }
.th-esc-chrono.is-urgent { color:var(--danger, #b3261e); }
.th-esc-chrono.is-horsdelai { color:var(--muted); text-decoration:line-through; }

.th-esc-alerte { font-size:11px; font-weight:800; color:var(--danger, #b3261e); }

/* Le plateau : toutes les énigmes de l'acte, côte à côte. Une grille et non une liste —
   une liste suggère un ordre, et l'ordre est précisément ce que ce mode ne dicte pas. */
.th-esc-plateau { display:grid; grid-template-columns:repeat(auto-fill, minmax(150px, 1fr)); gap:12px; }

.th-esc-enigme {
  display:flex; flex-direction:column; gap:8px; min-height:104px; padding:12px;
  border:1px solid var(--line); border-radius:var(--r); background:var(--bg-card);
  text-align:left; font:inherit; cursor:pointer;
}
.th-esc-enigme.is-resolue { opacity:.55; cursor:default; }
.th-esc-enigme.is-prise { border-color:currentColor; }
.th-esc-enigme-etat { display:flex; align-items:center; min-height:18px; }
.th-esc-enigme-titre { font-weight:700; font-size:13.5px; line-height:1.3; }
.th-esc-enigme-indices { margin-top:auto; font-size:11px; color:var(--muted); font-weight:700; }

/* La patrouille qui a « pris » l'énigme. Un panneau d'affichage, jamais un verrou :
   l'énigme reste ouverte à tous, sans quoi une patrouille hors réseau bloquerait l'équipe. */
.th-esc-pastille-groupe {
  font-size:10px; font-weight:800; padding:2px 7px; border-radius:999px;
  color:var(--teinte, currentColor); border:1px solid currentColor;
}

.th-esc-modale {
  position:fixed; inset:0; z-index:1000; display:flex; align-items:flex-end; justify-content:center;
  background:rgba(0,0,0,.45);
}
.th-esc-modale-corps {
  position:relative; width:100%; max-width:560px; max-height:88dvh; overflow-y:auto;
  padding:20px; border-radius:var(--r) var(--r) 0 0; background:var(--bg-card);
}
.th-esc-modale-fermer {
  position:absolute; top:10px; right:10px; border:0; background:transparent; cursor:pointer; color:var(--muted);
}

/* ══ Éditeur des ACTES d'un escape game (ADR-0033) ════════════════════════════════════════
   Un acte est un verrou, pas un dossier : la liste doit rendre lisible ce qui verrouille
   quoi, et signaler l'acte trop maigre pour faire jouer en parallèle. */
.th-actes { grid-column:1/-1; display:flex; flex-direction:column; gap:10px;
            padding:14px; border:1px solid var(--line); border-radius:var(--r); background:var(--bg-card); }
.th-actes-tete { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.th-actes-tete h3 { margin:0; font-size:15px; }
.th-actes-aide { margin:3px 0 0; font-size:11.5px; color:var(--muted); font-weight:700; max-width:56ch; }
.th-actes-vide { color:var(--muted); font-weight:700; font-size:12.5px; margin:0; }
.th-actes-erreur { color:var(--danger, #b3261e); font-weight:800; font-size:12px; margin:0; }
.th-actes-liste { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px; }

.th-acte { display:flex; align-items:center; gap:10px; padding:8px 10px;
           border:1px solid var(--line); border-radius:var(--r); }
.th-acte.is-maigre { border-style:dashed; }
.th-acte-rang { flex:0 0 26px; height:26px; display:grid; place-items:center; border-radius:999px;
                background:var(--line); font-weight:800; font-size:12px; }
.th-acte-corps { flex:1 1 auto; min-width:0; }
.th-acte-titre { width:100%; border:0; background:transparent; font:inherit; font-weight:700; padding:2px 0; }
.th-acte-meta { display:flex; gap:8px; font-size:11px; color:var(--muted); font-weight:700; margin-top:2px; }
.th-acte-alerte { color:var(--danger, #b3261e); }
.th-acte-actions { display:flex; gap:4px; }
.th-acte-btn { border:1px solid var(--line); background:transparent; border-radius:8px;
               width:28px; height:28px; display:grid; place-items:center; cursor:pointer; font:inherit; }
.th-acte-btn[disabled] { opacity:.4; cursor:default; }
.th-acte-btn--danger { color:var(--danger, #b3261e); }

/* ══ Éditeur de l'ÉCHELLE D'INDICES d'une énigme (escape game) ═══════════════════════════
   Un indice s'achète en TEMPS : le coût est le champ central, pas une option. Le dernier
   échelon porte toujours la solution — d'où sa marque visuelle. */
.th-ech { display:flex; flex-direction:column; gap:9px; border-top:1.5px dashed var(--line); padding-top:12px; }
.th-ech-tete { display:flex; align-items:flex-start; justify-content:space-between; gap:12px; }
.th-ech-aide { font-size:11px; color:var(--muted); font-weight:700; margin-top:2px; max-width:56ch; }
.th-ech-vide { font-size:12px; color:var(--muted); font-weight:700; margin:0; }

.th-ech-item { display:flex; flex-direction:column; gap:7px; padding:9px;
               border:1px solid var(--line); border-radius:var(--r); }
.th-ech-item.is-solution { border-color:currentColor; }
.th-ech-item-tete { display:flex; align-items:center; gap:8px; }
.th-ech-niveau { flex:0 0 22px; height:22px; display:grid; place-items:center; border-radius:999px;
                 background:var(--line); font-weight:800; font-size:11px; }
.th-ech-solution { font-size:10.5px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--muted); }
.th-ech-suppr { margin-left:auto; border:0; background:transparent; cursor:pointer; color:var(--danger, #b3261e); }
.th-ech-suppr[disabled] { opacity:.4; cursor:default; }

.th-ech-reglages { display:grid; grid-template-columns:repeat(auto-fit, minmax(120px, 1fr)); gap:8px; }
.th-ech-reglages label { display:flex; flex-direction:column; gap:3px; font-size:11px; font-weight:700; color:var(--muted); }

.th-ech-pied { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
.th-ech-nonenreg { font-size:11px; font-weight:800; color:var(--danger, #b3261e); }
.th-ech-message { font-size:11px; font-weight:700; color:var(--muted); }

/* ══ Réglages du mode escape game ════════════════════════════════════════════════════════ */
.th-regl { display:flex; flex-direction:column; gap:10px; margin-top:12px;
           padding:12px; border:1px solid var(--line); border-radius:var(--r); }
.th-regl-aide { font-size:11px; color:var(--muted); font-weight:700; margin-top:2px; max-width:60ch; }
.th-regl-grille { display:grid; grid-template-columns:repeat(auto-fit, minmax(150px, 1fr)); gap:9px; }
.th-regl-grille label { display:flex; flex-direction:column; gap:3px; font-size:11.5px; font-weight:700; color:var(--muted); }
.th-regl-grille em { font-style:normal; font-size:10.5px; font-weight:700; color:var(--muted); opacity:.85; }
.th-regl-case { display:flex; align-items:flex-start; gap:9px; font-size:13px; cursor:pointer; }
.th-regl-case strong { display:block; font-size:13px; }
.th-regl-case em { display:block; font-style:normal; font-size:11px; font-weight:700; color:var(--muted); margin-top:2px; }
.th-regl-alerte { margin:0; font-size:11.5px; font-weight:800; color:var(--danger, #b3261e); }
