/* SOCLE — polices, variables, fond, primitives (typo, surfaces, boutons,
   champs, chips) et coque du jeu.

   Cette feuille est la premiere de six, decoupees dans l'ancien theme.css.
   L'ordre des <link> reproduit l'ordre du fichier d'origine et fait partie
   du style : le changer change la cascade. Ordre a tenir partout —
     base, ecrans, jeu, composants, aventure, organisateur.

   Quatre endroits les declarent et doivent rester alignes :
   frontend/index.html, admin/index.html, veille/index.html, et SHELL_FILES
   dans frontend/sw.js (sans quoi la feuille manque hors ligne). */

/* Police embarquee (SIL OFL, voir assets/fonts/OFL.txt) : fichier variable,
   une seule requete pour toutes les graisses, aucun appel reseau externe. */
@font-face {
  font-family: "Nunito Sans";
  src: url("/app/assets/fonts/nunito-sans.woff2") format("woff2-variations");
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}

/* Manuscrite des notes et de la baseline (Caveat, SIL OFL). Sous-ensemble
   latin uniquement : 75 ko, aucun autre alphabet a charger. */
@font-face {
  font-family: "Caveat";
  src: url("/app/assets/fonts/caveat.woff2") format("woff2");
  font-weight: 400 700;
  font-style: normal;
  font-display: swap;
}

/* =========================================================================
   TROLLTRAIL VOUILLANTS — systeme visuel
   Verre vert sombre sur photo de sous-bois, or grave, bois et parchemin.
   ========================================================================= */

:root {
  /* Fonds verre des cadres : ~70 % de voile, le decor transparait sans que le
     texte y perde. Le flou de fond (backdrop-filter) fait le reste. */
  --glass-1: rgba(30, 66, 48, 0.72);
  --glass-2: rgba(13, 33, 24, 0.70);
  --glass-3: rgba(9, 24, 18, 0.74);
  --ink-900: #071510;
  --ink-800: #0b1f16;

  /* accents */
  --moss:      #7fd07f;
  --moss-soft: #a8e0a0;
  --moss-deep: #2f7a42;
  --gold:      #e2c98d;
  --gold-deep: #b3934f;
  --ember:     #d98b46;
  --rot:       #d9695c;
  --sky:       #7fb6c9;
  --gem:       #64c8f0;

  /* texte */
  --parchment: #eef1e6;
  --parch-dim: #b9c9b6;
  --parch-faint: #8ba28c;

  /* cartes claires (liste des missions) */
  --card-light: #f4f2e7;
  --card-light-2: #e9e6d6;
  --ink-card: #1e3227;

  /* bois / parchemin */
  --wood-1: #c9a468;
  --wood-2: #a3763f;
  --wood-ink: #3a2412;

  --radius: 18px;
  --radius-sm: 12px;
  --radius-lg: 24px;

  --line: 1px solid rgba(168, 224, 160, 0.15);
  --line-soft: 1px solid rgba(238, 241, 230, 0.09);
  --bevel: inset 0 1px 0 rgba(255, 255, 255, 0.09);
  --shadow-1: 0 2px 8px rgba(0, 0, 0, 0.35);
  --shadow-2: 0 8px 24px rgba(0, 0, 0, 0.45);
  --shadow-3: 0 18px 46px rgba(0, 0, 0, 0.60);
  --glow-moss: 0 0 22px rgba(110, 200, 120, 0.30);
  --glow-gold: 0 0 22px rgba(226, 201, 141, 0.28);

  --header-h: 62px;
  --title-h: 50px;        /* hauteur du logo du bandeau */
  --nav-h: 70px;
  /* Recouvrement de la barre d'onglets par <main> : la carte passe SOUS la
     courbe, sinon les deux angles laissent voir le fond noir de la page. */
  --nav-lap: 22px;
  --rail-w: 80px;
  --chat-w: 330px;

  /* Retraits de bord d'ecran. Le decor, lui, reste plein cadre (#bg-photo est
     en position: fixed, inset: 0) : seuls les elements d'interface reculent.

     En haut : la valeur systeme et RIEN de plus. Une fois l'app installee,
     apple-mobile-web-app-status-bar-style vaut "black" (voir index.html) et
     iOS place deja la fenetre sous la barre d'etat — env() renvoie alors 0 a
     juste titre. Un plancher ajouterait la un retrait par-dessus un retrait,
     et sur ces ecrans qui tiennent en une hauteur c'est autant de perdu.

     En bas : un petit plancher se justifie, lui. Le systeme ne reserve rien
     pour les angles arrondis du chassis, et les onglets s'y poseraient. */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-bot: max(env(safe-area-inset-bottom, 0px), 10px);

  /* La meme valeur, mais A NU et JAMAIS ecrite par le JS. C'est la sonde de
     guardEdges() : quand env() ment, il pose un plancher sur --safe-top, et
     s'il relisait --safe-top il relirait sa propre correction — il en
     conclurait que le systeme repond maintenant 44 px et retirerait tout. La
     mise en page sautait donc une rotation sur deux. */
  --safe-top-env: env(safe-area-inset-top, 0px);

  /* Trois millimetres de plus au-dessus de la barre de geste de l'iPhone.

     Les 34 pt qu'iOS reserve suffisent a ne pas POSER un bouton sur la barre,
     pas a empecher le doigt de la toucher : la pulpe est large, elle deborde
     sous l'icone visee, et le balayage vers le haut fait remonter l'app comme
     si on la fermait. Au milieu d'une partie, sur un sentier, c'est la pire
     chose qui puisse arriver.

     18 px et non 11 : un pixel CSS vaut environ 0,166 mm sur ces dalles
     (390 px de large pour 64,6 mm sur un 13 Pro), donc trois millimetres en
     font dix-huit — la conversion de bureau (1 mm = 3,78 px) en donnerait la
     moitie.

     min() plutot que max() : c'est une condition deguisee. Sur un telephone
     sans barre de geste l'encart systeme vaut 0, le supplement vaut donc 0
     aussi et on ne perd pas une ligne d'ecran pour rien ; des qu'un appareil
     reserve quelque chose en bas, il a une barre, et les 18 px s'ajoutent. */
  --gesture-gap: min(18px, env(safe-area-inset-bottom, 0px));

  /* Une seule famille pour toute l'app : Nunito Sans, embarquee en local
     (pas de CDN). --display sert aux titres, avec un poids plus fort. */
  --sans: "Nunito Sans", "Avenir Next", "Segoe UI", system-ui, -apple-system, sans-serif;
  --display: var(--sans);
  /* Manuscrite : notes de bois, pancarte, baseline. Caveat monte tres haut sur
     la ligne de base, d'ou les tailles plus grosses partout ou elle sert. */
  --hand: "Caveat", "Bradley Hand", "Segoe Script", cursive;
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
[hidden] { display: none !important; }

/* Hauteur de l'application, definie ICI et nulle part ailleurs : #root et la
   suite descendent en height: 100%. Deux 100dvh independants sur un parent et
   son enfant finissent toujours par diverger, et comme body coupe (overflow:
   hidden) c'est le bas de l'interface qui disparait.

   100dvh et non 100vh : sur telephone, 100vh vaut la hauteur SANS les barres
   du navigateur, celles-ci se retirant au defilement — sur une page qui ne
   defile jamais elles ne partent pas et le bas du jeu reste dessous. Le vh
   reste en secours pour les moteurs qui ignorent dvh. */
html, body {
  margin: 0;
  height: calc(100vh + var(--fit-adj, 0px)); height: calc(100dvh + var(--fit-adj, 0px));
  overflow: hidden; overscroll-behavior: none;
}

/* Une fois l'app installee il n'y a plus de barre de navigateur a esquiver, et
   c'est la que dvh trahit sur iPhone : il renvoie une hauteur amputee et laisse
   une bande morte en bas. lvh vaut alors la hauteur reelle de l'ecran. Mesure
   faite sur l'appareil avec /test-plein-ecran. */
@media (display-mode: standalone), (display-mode: fullscreen) {
  html, body { height: calc(100vh + var(--fit-adj, 0px)); height: calc(100lvh + var(--fit-adj, 0px)); }
}

/* Calage choisi a la main (Reglages de l'appareil, `cdp.fit`). Aucune unite ne
   dit la verite sur tous les telephones : dvh ampute la fenetre d'une app
   installee sur iPhone, lvh passe sous les barres du navigateur, svh laisse
   une bande morte. On ne peut pas le deviner depuis le code — le joueur
   choisit celui qui tombe juste sur SON ecran, et le selecteur d'attribut sur
   <html> bat les deux regles ci-dessus, media query comprise. */
html[data-fit="ecran"], html[data-fit="ecran"] body { height: calc(100vh + var(--fit-adj, 0px)); height: calc(100lvh + var(--fit-adj, 0px)); }
html[data-fit="reduit"], html[data-fit="reduit"] body { height: calc(100vh + var(--fit-adj, 0px)); height: calc(100svh + var(--fit-adj, 0px)); }
/* Dernier recours : ni dvh ni lvh ni svh, mais les pixels que le JS releve a
   chaque redimensionnement (--vh-px, pose par measureFit dans app.js). */
html[data-fit="mesure"], html[data-fit="mesure"] body { height: calc(var(--vh-px, 100vh) + var(--fit-adj, 0px)); }

/* Le rattrapage en pixels, et c'est LUI qui donne sa prise au reglage.

   Une fois l'application installee sur l'ecran d'accueil, il n'y a plus de
   barre de navigateur a esquiver : dvh, lvh et svh renvoient alors exactement
   la meme hauteur, et les quatre calages ne changeaient plus rien du tout —
   on touchait le reglage, l'ecran restait identique, et rien ne disait
   pourquoi. Quelques pixels retires ou ajoutes, eux, se voient partout. */

body {
  font-family: var(--sans);
  color: var(--parchment);
  background: var(--ink-900);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}

/* --------------------------- fond photo de foret --------------------------- */

#bg-photo {
  position: fixed; inset: 0; z-index: 0;
  background: url("../assets/bg/foret.jpg") center/cover no-repeat;
  transform: scale(1.06);
  filter: saturate(0.86);
}
#bg-veil {
  position: fixed; inset: 0; z-index: 0;
  background:
    radial-gradient(120% 80% at 50% -10%, rgba(8, 26, 18, 0.22), transparent 55%),
    radial-gradient(100% 60% at 50% 112%, rgba(4, 14, 10, 0.80), transparent 62%),
    linear-gradient(180deg, rgba(6, 20, 14, 0.42), rgba(5, 16, 11, 0.66));
}
/* Les ecrans de jeu ont besoin d'un fond plus dense que les ecrans d'intro.

   Le flou vient d'un FICHIER deja flou, plus d'un filter: blur(3px). Ce filtre
   portait sur un calque plein ecran affiche pendant toute la partie : le GPU
   refloutait 1080x1919 pixels a chaque recomposition, des heures durant. Le
   fichier cuit donne la meme image pour rien, et ne pese que 24 ko en 360 px
   de large — un flou n'a plus de detail a rendre, l'agrandissement ne se voit
   pas. saturate() reste : un filtre de couleur est une simple table, sans
   commune mesure avec le cout d'un flou. */
body.in-game #bg-photo {
  background-image: url("../assets/bg/foret-flou.jpg");
  filter: saturate(0.7);
}
body.in-game #bg-veil { background: linear-gradient(180deg, rgba(6, 22, 15, 0.90), rgba(4, 14, 10, 0.95)); }

#particles { position: fixed; inset: 0; z-index: 1; pointer-events: none; }

/* ---------------------------- canevas ---------------------------- */
/* L'UI se dessine directement a la taille reelle du viewport : pas de mise a
   l'echelle globale, chaque composant est responsive independamment.

   Surtout, #root reste dans le FLUX NORMAL. En position: fixed, Safari iOS le
   calait sur son viewport de mise en page et non sur la zone visible : top: 0
   s'accrochait au-dessus du bord visible et tout le bloc, barre d'onglets
   comprise, etait remonte de la hauteur de la barre d'adresse. position:
   relative sert uniquement de reference aux .screen.full (position: absolute).

   La hauteur se herite de body (voir plus haut), jamais redeclaree en dvh. */
#root {
  position: relative; z-index: 2;
  width: 100%; height: 100%;
}

/* ============================ typographie ============================ */

/* Sans empattements, un titre a besoin de plus de graisse pour tenir son rang. */
h1, h2, h3 { margin: 0; font-weight: 800; }
h1 { font-family: var(--display); font-size: 1.62rem; letter-spacing: -0.01em; }
h2 { font-family: var(--display); font-size: 1.22rem; letter-spacing: -0.005em; }
h3 { font-size: 0.92rem; }

.serif { font-family: var(--display); font-weight: 800; }
.eyebrow {
  font-size: 0.66rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--parch-faint); font-weight: 800;
}
.muted { color: var(--parch-dim); }
.faint { color: var(--parch-faint); }
.sm { font-size: 0.86rem; }
.xs { font-size: 0.72rem; }
.mono { font-variant-numeric: tabular-nums; font-feature-settings: "tnum"; }
.center { text-align: center; }
.grow { flex: 1; min-width: 0; }
.leaf { filter: drop-shadow(0 1px 2px rgba(0,0,0,0.4)); }

/* Titre grave dore, comme le logo. */
.gold-title {
  font-family: var(--display);
  font-weight: 800;
  color: var(--gold);
  text-shadow: 0 1px 0 rgba(255,255,255,0.16), 0 3px 10px rgba(0,0,0,0.6);
  letter-spacing: 0.01em;
}
.moss-title { font-family: var(--display); font-weight: 800; color: var(--moss); font-size: 1.16rem; }

/* Sous-titre "VOUILLANTS" du logo. L'interlettre laisse un blanc apres la
   derniere lettre : centre tel quel, le mot penche a gauche du titre. Le
   text-indent rend ce blanc au debut, partout d'un coup. */
.wordmark-sub {
  font-family: var(--display);
  font-size: 1.05rem;
  letter-spacing: 0.34em;
  text-indent: 0.34em;
  text-transform: uppercase;
  color: var(--moss-soft);
  text-shadow: 0 2px 8px rgba(0,0,0,0.6);
  margin-top: -2px;
}
.wordmark-sub.big { font-size: 1.25rem; }
.wordmark-sub.xs { font-size: 0.62rem; letter-spacing: 0.34em; opacity: 0.92; }
/* Le logo vertical laisse du blanc sous le titre : on remonte le nom du
   terrain pour qu'il se lise comme la seconde ligne du logo. */
.wordmark-sub.tight { margin-top: -16px; }

.baseline {
  font-family: var(--hand); font-weight: 600;
  color: #dfe9d6; font-size: 1.02rem;
  text-shadow: 0 2px 8px rgba(0,0,0,0.7);
  margin-top: 8px;
}
.baseline.sm { font-size: 1.15rem; margin-top: 5px; }
/* Manuscrite : la hauteur d'x de Caveat est basse, il faut monter la taille. */
.baseline.script { font-size: 1.62rem; line-height: 1.15; color: #cfe4bd; margin-top: 10px; }

/* ============================ surfaces ============================ */

.panel {
  background: linear-gradient(165deg, var(--glass-1), var(--glass-2) 60%, var(--glass-3));
  border: var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-2), var(--bevel);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  padding: 16px;
  position: relative;
  overflow: hidden;
}
.panel.tight { padding: 12px; }
.panel.glow { border-color: rgba(127, 208, 127, 0.42); box-shadow: var(--shadow-2), var(--glow-moss); }
.panel.plain { background: rgba(9, 26, 19, 0.70); }

.divider { height: 1px; background: linear-gradient(90deg, transparent, rgba(238,241,230,0.16), transparent); margin: 14px 0; }

/* Entetes de section : vraie pancarte de bois, feuilles comprises. Le fond est
   etire (100% 100%) et non contenu, pour que le texte se cale en pourcentage
   sur la planche quelle que soit la largeur ; aspect-ratio garde les
   proportions de l'image, donc l'etirement ne deforme rien. Le retrait lateral
   de 13 % correspond aux feuilles, qui debordent de la planche. */
.wood-sign {
  display: flex; align-items: center; justify-content: center;
  /* Deborde un peu du retrait du panneau : c'est un bandeau, et le texte le
     plus long ("Rejoindre une equipe") a besoin de ces quelques pixels. */
  width: calc(100% + 12px); max-width: 412px;
  aspect-ratio: 1190 / 236;
  margin: -4px -6px 14px;
  /* Le bois s'arrete a 13 % de chaque bord (les feuilles debordent) et son
     centre est a 51,4 % de la hauteur, d'ou le decalage vers le bas. */
  padding: 2.8% 13% 0;
  text-align: center;
  font-family: var(--display);
  font-size: 1.36rem;
  font-weight: 800;
  line-height: 1.1;
  color: #3d2a11;
  background: url("../assets/ui/pancarte.webp") center/100% 100% no-repeat;
  text-shadow: 0 1px 0 rgba(255, 246, 226, 0.42);
  filter: drop-shadow(0 6px 14px rgba(0, 0, 0, 0.45));
}

/* Petite note manuscrite sur bois clair. */
.wood-note {
  display: inline-block;
  padding: 12px 18px;
  font-family: var(--hand);
  font-weight: 600;
  font-size: 1.3rem;
  line-height: 1.12;
  color: var(--wood-ink);
  background: linear-gradient(172deg, #e5cfa4, #c9a874 60%, #b08d5c);
  border-radius: 6px / 14px;
  border: 1px solid rgba(90, 60, 25, 0.45);
  box-shadow: var(--shadow-2), inset 0 2px 0 rgba(255,255,255,0.42);
  transform: rotate(-1.6deg);
}
.wood-note.small { font-size: 1.06rem; padding: 9px 12px; }
.wood-note.wide { display: block; text-align: center; margin: 18px 6px 4px; transform: rotate(-0.6deg); }

/* Parchemin : descriptions de mission. */
.parchment {
  padding: 16px 18px;
  border-radius: 10px;
  color: #3a2a13;
  background:
    radial-gradient(80% 60% at 20% 0%, rgba(255,255,255,0.28), transparent 60%),
    linear-gradient(168deg, #ddc79b, #c3a976 55%, #ab8f60);
  border: 1px solid rgba(90, 62, 28, 0.42);
  box-shadow: var(--shadow-2), inset 0 2px 0 rgba(255,255,255,0.35);
  position: relative;
  overflow: hidden;
}
.parchment h3 { color: #4b6b34; font-size: 0.94rem; margin-bottom: 6px; }
.parchment p { margin: 0; font-size: 0.9rem; line-height: 1.62; }
.parchment .quote { font-family: var(--display); font-style: italic; font-size: 0.84rem; color: #5a4522; margin-top: 12px; }

/* ============================ boutons ============================ */

.btn {
  appearance: none;
  border: 1px solid rgba(168, 224, 160, 0.22);
  background: linear-gradient(180deg, rgba(52, 92, 62, 0.92), rgba(20, 46, 32, 0.94));
  color: var(--parchment);
  font: inherit;
  font-weight: 700;
  padding: 12px 20px;
  border-radius: 999px;
  box-shadow: var(--shadow-1), var(--bevel);
  cursor: pointer;
  transition: transform 0.12s var(--ease), box-shadow 0.18s var(--ease), filter 0.18s;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 48px;
}
.btn:active { transform: translateY(2px) scale(0.985); filter: brightness(0.94); }
.btn:disabled { opacity: 0.42; pointer-events: none; }
.btn svg { width: 20px; height: 20px; }

.btn.primary {
  background: linear-gradient(180deg, #4f9b5b, #2c6b3f 55%, #1c4a2c);
  border-color: rgba(160, 232, 168, 0.42);
  box-shadow: var(--shadow-2), 0 6px 26px rgba(60, 150, 80, 0.32), inset 0 1px 0 rgba(255,255,255,0.30);
  color: #f2fbef;
}
.btn.gold {
  background: linear-gradient(180deg, var(--gold), var(--gold-deep));
  color: #2a1e08;
  border-color: rgba(226, 201, 141, 0.6);
  box-shadow: var(--shadow-2), var(--glow-gold), inset 0 1px 0 rgba(255,255,255,0.35);
}
.btn.outline { background: rgba(238, 241, 230, 0.04); border-color: rgba(214, 231, 210, 0.28); }
.btn.danger { background: linear-gradient(180deg, #a8483a, #6f2c23); color: #fbe9e4; }
.btn.wide { width: 100%; }
.btn.xl { min-height: 62px; font-size: 1.1rem; font-family: var(--display); letter-spacing: 0.01em; }
.btn.sm { min-height: 38px; padding: 8px 14px; font-size: 0.82rem; }
/* Angles droits adoucis, a la place de la pilule. */
.btn.square { border-radius: var(--radius); }
/* Retrait lateral : le bouton ne vient pas lecher le bord de l'ecran. */
.btn.inset { width: calc(100% - 24px); margin-left: 12px; margin-right: 12px; }
.btn .btn-ic { display: grid; place-items: center; flex: none; }
.btn .btn-ic img { width: 26px; height: 26px; display: block; }
/* Une pastille dessinee remplace le cercle trace de `.ring` : elle porte deja
   son propre disque, un anneau par-dessus en ferait deux. Elle prend donc la
   taille qu'occupait l'anneau, sinon la ligne du bouton s'affaisse. */
.btn .btn-ic.big img { width: 38px; height: 38px; }
.btn .btn-ic.ring {
  width: 38px; height: 38px; border-radius: 50%;
  border: 1.5px solid rgba(214, 240, 210, 0.55);
  background: rgba(255,255,255,0.06);
}
.btn.xl .grow { text-align: left; }

/* bouton rond d'entete */
.round-btn {
  width: 44px; height: 44px; flex: none;
  display: grid; place-items: center;
  border-radius: 50%;
  background: linear-gradient(180deg, rgba(30, 62, 44, 0.88), rgba(12, 30, 22, 0.92));
  border: var(--line);
  box-shadow: var(--shadow-1), var(--bevel);
  color: var(--parchment);
  cursor: pointer;
  position: relative;
  transition: transform 0.12s var(--ease);
}
.round-btn:active { transform: scale(0.92); }
/* Un rond qui commande un CALQUE dit s'il est allume : sans etat visible, on
   le touche deux fois pour savoir dans quel sens on vient de le basculer. */
.round-btn.on {
  color: var(--moss);
  border-color: rgba(127, 208, 127, 0.55);
  background: linear-gradient(180deg, rgba(74, 150, 90, 0.34), rgba(16, 44, 28, 0.92));
  box-shadow: var(--shadow-1), var(--bevel), var(--glow-moss);
}
.round-btn svg { width: 20px; height: 20px; }

/* bouton pilule d'entete */
.pill-btn {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 9px 14px;
  border-radius: 999px;
  font: inherit; font-size: 0.86rem; font-weight: 700;
  color: var(--parchment);
  background: linear-gradient(180deg, rgba(30, 62, 44, 0.88), rgba(12, 30, 22, 0.92));
  border: var(--line);
  box-shadow: var(--shadow-1), var(--bevel);
  cursor: pointer;
}
.pill-btn svg { width: 17px; height: 17px; opacity: 0.9; }
.pill-btn .pill-arrow svg { width: 14px; height: 14px; opacity: 0.6; }

/* ============================ champs ============================ */

.field { display: block; margin-bottom: 14px; }
.field > span {
  display: block; margin-bottom: 6px;
  font-size: 0.7rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--parch-faint); font-weight: 800;
}

/* `input:not([type])` compte : l'espace cartographe ecrit ses champs sans
   attribut `type`, et sans cette clause ils restaient blancs sur blanc au
   milieu d'un panneau sombre. Les cases a cocher portent toujours un type,
   elles ne sont donc jamais attrapees ici. */
input:not([type]), input[type="text"], input[type="password"], input[type="number"], textarea, select {
  width: 100%;
  font: inherit;
  color: var(--parchment);
  background: rgba(0, 0, 0, 0.34);
  border: 1px solid rgba(214, 231, 210, 0.18);
  border-radius: var(--radius-sm);
  padding: 13px 15px;
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.40);
  outline: none;
  transition: border-color 0.18s, box-shadow 0.18s;
  -webkit-user-select: text;
  user-select: text;
}
input:focus, textarea:focus, select:focus {
  border-color: rgba(127, 208, 127, 0.55);
  box-shadow: inset 0 2px 6px rgba(0,0,0,0.4), 0 0 0 3px rgba(110, 200, 120, 0.16);
}
textarea { resize: vertical; min-height: 78px; }

/* Champ de nom coiffe d'un de : le tirage se relance d'un pouce, sans vider
   le champ a la main. Le bouton est POSE SUR le champ plutot qu'a cote : une
   colonne de plus reduirait la saisie a rien sur une dalle de 320 pt. */
.input-dice { position: relative; }
.input-dice input { padding-right: 46px; }
.dice-btn {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: 10px;
  background: rgba(238, 241, 230, 0.07);
  color: var(--moss-soft);
  cursor: pointer; -webkit-tap-highlight-color: transparent;
  transition: background 0.18s, color 0.18s, transform 0.4s var(--ease);
}
.dice-btn svg { width: 21px; height: 21px; }
.dice-btn:active { background: rgba(127, 208, 127, 0.22); color: var(--parchment); }
/* Le de roule quand on le lance : un quart de tour suffit a dire qu'il s'est
   passe quelque chose, meme si le nom tire ressemble au precedent. */
.dice-btn.rolling { transform: translateY(-50%) rotate(360deg); }
::placeholder { color: var(--parch-faint); }

.code-input {
  text-align: center; letter-spacing: 0.5em; text-indent: 0.5em;
  font-size: 1.9rem; font-weight: 800; padding: 16px 10px; text-transform: uppercase;
}

/* ============================ chips ============================ */

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 12px; border-radius: 999px;
  font-size: 0.74rem; font-weight: 700;
  background: rgba(238, 241, 230, 0.07);
  border: 1px solid rgba(238, 241, 230, 0.10);
  color: var(--parch-dim);
  white-space: nowrap;
}
.chip svg { width: 14px; height: 14px; }
.chip.moss { background: rgba(110,200,120,0.16); border-color: rgba(110,200,120,0.35); color: var(--moss); }
.chip.gold { background: rgba(226,201,141,0.15); border-color: rgba(226,201,141,0.35); color: var(--gold); }
.chip.rot  { background: rgba(217,105,92,0.16); border-color: rgba(217,105,92,0.35); color: #eda093; }

/* badges d'etat des cartes claires */
.badge {
  padding: 4px 10px; border-radius: 999px;
  font-size: 0.66rem; font-weight: 800; white-space: nowrap;
}
.badge.current  { background: #d7e6ea; color: #2c4a54; }
.badge.todo     { background: #e3e2d8; color: #55584c; }
.badge.done     { background: #cfeccd; color: #2c6234; }
.badge.near     { background: #d5e7f2; color: #2b5068; }

/* ============================ layout jeu ============================ */

.screen.full { position: absolute; inset: 0; display: none; }
.screen.full.active { display: block; animation: fadeIn 0.4s var(--ease); }
.scroller {
  position: absolute; inset: 0;
  overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: calc(var(--safe-top) + 18px) 16px calc(28px + var(--safe-bot));
  max-width: 560px; margin: 0 auto;
}
.scroller.narrow { max-width: 460px; }

.shell {
  display: grid;
  height: 100%;
  grid-template-rows: auto auto auto 1fr auto;
  grid-template-areas: "head" "role" "rally" "main" "nav";
}

.topbar {
  grid-area: head;
  display: flex; align-items: center; gap: 10px;
  padding: calc(var(--safe-top) + 8px) 12px 28px;
  background: linear-gradient(180deg, rgba(7, 22, 15, 0.95), rgba(7, 22, 15, 0.72));
  /* 10 px et non 14 : le bandeau et la barre d'onglets sont les deux seuls
     backdrop-filter poses en permanence AU-DESSUS de contenu qui bouge (la
     carte se deplace, les vues defilent). Le flou de fond est alors recalcule
     a chaque image, et son cout croit avec le rayon. A ces valeurs l'oeil ne
     fait pas la difference sur un verre deja teinte a 90 %. */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  border-bottom: 0;
  min-height: var(--header-h);
  /* Pas de border-radius ici : c'est <main> qui porte l'arrondi et passe
     par-dessus l'entete (voir plus bas). Un arrondi des deux cotes creuserait
     deux encoches sombres dans les angles. */
}
/* EN PARTIE, LA TETE SEULE. Le bandeau porte trois choses, et deux d'entre
   elles se touchent ou se lisent en marchant : le chrono et les boutons. Le
   titre, lui, ne dit rien qu'on ne sache deja — on est dans le jeu depuis une
   heure. Il volait pourtant la moitie de la largeur, et sur une dalle de
   360 pt le chrono finissait colle au menu.
   La tete reste : elle signe l'ecran d'un coup d'oeil, et elle est carree.
   `flex: 0 1 auto` avec `min-width: 0` dit la priorite en une ligne — elle
   retrecit, puis disparait, avant que le temps ou un bouton ne cede un pixel
   (eux restent en `flex: none`). */
.topbar .tb-brand {
  flex: 0 1 auto; min-width: 0; overflow: hidden;
  display: flex; flex-direction: column; align-items: flex-start;
}
/* Hors partie, le nom du terrain se centre sous le mot "TrollTrail" et non sous le logo
   entier : la tete de troll occupe le premier tiers de l'image. Le retrait a
   gauche exclut cette part, le centrage joue sur ce qui reste. Le text-indent
   compense l'interlettre, qui laisse sinon un blanc a droite du dernier
   caractere et decale le mot vers la gauche.

   Mesures relevees sur titre-large.webp (1200x355) : le mot "TrollTrail" va de
   x 419 a 1200 (35 % a 100 %, centre 67,5 %) et de y 85 a 258. La tete, elle,
   occupe toute la hauteur — d'ou pres d'un quart d'image vide sous le mot, que
   la marge negative rattrape pour coller le nom du terrain sous le titre. */
#topbar-main .tb-brand { align-self: center; }
#topbar-main .tb-brand .wordmark-sub {
  width: 100%; padding-left: 35%;
  margin-top: calc(-0.27 * var(--title-h));
  text-align: center; text-indent: 0.34em;
}
.topbar .tb-brand.center { flex: 0 1 auto; margin: 0 auto; align-items: center; }
.topbar .tb-title { height: var(--title-h); width: auto; max-width: 100%;
  object-fit: contain; filter: drop-shadow(0 2px 5px rgba(0,0,0,0.6)); }
/* La tete est carree, la planche du titre ne l'etait pas : a hauteur egale
   elle prend trois fois moins de large, ce qui est tout l'objet du changement. */
#topbar-main .tb-title, .topbar.sub .tb-title { height: calc(var(--title-h) - 6px); }
/* Sous 340 px de bandeau il n'y a plus la place : le temps et les boutons
   passent devant, la tete s'efface. */
@media (max-width: 340px) {
  .topbar .tb-brand { display: none; }
}
/* Meme rattrapage pour l'entete des sous-vues, ou le logo est centre. */
.topbar.sub .tb-brand .wordmark-sub { margin-top: calc(-0.27 * var(--title-h)); }
.topbar.sub { justify-content: space-between; }

.timer-pill {
  margin-left: auto;
  display: flex; align-items: center; gap: 9px;
  padding: 7px 14px;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(16, 38, 26, 0.92), rgba(8, 22, 15, 0.94));
  border: 1px solid rgba(226, 201, 141, 0.22);
  box-shadow: var(--shadow-1), var(--bevel);
}
.timer-pill svg { width: 20px; height: 20px; color: var(--gold); }
.timer-pill .tp-ic { width: 22px; height: auto; flex: none; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); }
.timer-pill .tp-lab { font-size: 0.68rem; letter-spacing: 0.01em; color: var(--parch-dim); }
.timer-pill .tp-val { font-size: 1.06rem; font-weight: 800; color: var(--gold); letter-spacing: 0.02em; line-height: 1.15; }
.timer-pill.warn .tp-val { color: var(--ember); }
.timer-pill.crit { border-color: rgba(217,105,92,0.5); }
.timer-pill.crit .tp-val { color: #f0907c; animation: pulse 1.1s infinite; }
@keyframes pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.42; } }

main {
  grid-area: main; position: relative;
  /* Remonte sous l'entete pour que l'arrondi se decoupe sur le bandeau. */
  margin-top: -18px;
  border-radius: 22px 22px 0 0;
  border-top: 1px solid rgba(238, 241, 230, 0.16);
  overflow: hidden;
  /* Doit couvrir l'entete, sinon l'arrondi passe dessous et ne se voit pas. */
  z-index: 1;
}
.view {
  position: absolute; top: 0; left: 0; right: 0;
  /* <main> descend desormais sous la courbe des onglets (--nav-lap) : les vues
     s'arretent au-dessus, sinon leur bas passe derriere la barre. Seul le decor
     plein cadre (carte, camera) profite du recouvrement. */
  bottom: var(--nav-lap);
  overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch;
  padding: 14px 14px 16px;
  display: none;
  animation: viewIn 0.3s var(--ease);
}
.view.active { display: block; }
.view.nopad { padding: 0; }
#view-map, #view-capture { bottom: 0; }
@keyframes viewIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

/* -------------------------- barre d'onglets -------------------------- */

.tabbar {
  grid-area: nav;
  display: grid; grid-template-columns: repeat(6, 1fr);
  align-items: stretch; gap: 2px;
  /* Le fond de la barre descend jusqu'au bord bas (c'est voulu, le decor est
     plein cadre) mais les icones remontent au-dessus de la barre de geste ET
     des angles arrondis du chassis : d'ou le supplement au-dessus de
     --safe-bot, qui porte deja son propre plancher. --gesture-gap ajoute les
     trois millimetres qui empechent la pulpe du doigt de mordre dans la zone
     de balayage systeme (voir sa definition en tete de feuille). */
  padding: 7px 8px calc(10px + var(--safe-bot) + var(--gesture-gap));
  background: linear-gradient(0deg, rgba(5, 16, 11, 0.98), rgba(10, 28, 19, 0.92));
  /* Meme raison que pour le bandeau : flou reduit, il est recalcule a chaque
     deplacement de la carte qui passe dessous. */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  /* Liseré plus marqué que --line-soft : c'est lui qui dessine la courbe,
     y compris sur un fond sombre comme l'image de la camera. */
  border-top: 1px solid rgba(238, 241, 230, 0.20);
  border-radius: 22px 22px 0 0;
  box-shadow: 0 -10px 30px rgba(0,0,0,0.5);
  min-height: var(--nav-h);
  /* Remonte sur <main>, comme <main> remonte sur l'entete. z-index : sans lui
     main (z-index 1) repasserait par-dessus la barre. */
  margin-top: calc(-1 * var(--nav-lap));
  position: relative; z-index: 2;
}
.tab {
  border: 0; background: none;
  color: var(--parch-faint);
  font: inherit; font-size: 0.6rem; font-weight: 700;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
  padding: 8px 1px;
  border-radius: 14px;
  cursor: pointer; position: relative;
  transition: color 0.2s, background 0.2s;
}
.tab svg { width: 24px; height: 24px; transition: transform 0.22s var(--ease); }
.tab.on {
  color: var(--moss);
  background: linear-gradient(180deg, rgba(70, 150, 90, 0.30), rgba(30, 80, 48, 0.22));
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.10);
}
.tab.on svg { filter: drop-shadow(0 0 8px rgba(110,200,120,0.55)); }
.tab:active svg { transform: scale(0.9); }
.badge-dot {
  position: absolute; top: 7px; right: 50%; margin-right: -16px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--rot); box-shadow: 0 0 8px var(--rot);
  border: 1.5px solid var(--ink-800);
}

/* La bulle chiffree de l'onglet des preuves. Un point suffit a dire « il y a
   du nouveau » ; ici il faut dire COMBIEN attendent, et surtout que quelqu'un
   attend maintenant — d'ou le battement. Le chat, lui, garde son point : un
   message peut se lire dans dix minutes, pas un verdict. */
.badge-bubble {
  position: absolute; top: 4px; right: 50%; margin-right: -20px;
  min-width: 17px; height: 17px; padding: 0 4px;
  border-radius: 999px;
  background: var(--ember); color: #1a1008;
  border: 1.5px solid var(--ink-800);
  font-size: 0.58rem; font-weight: 900; line-height: 14px; text-align: center;
  box-shadow: 0 0 10px rgba(217, 139, 70, 0.75);
  animation: bubblePulse 1.5s ease-in-out infinite;
}
@keyframes bubblePulse {
  0%, 100% { transform: scale(1); box-shadow: 0 0 8px rgba(217, 139, 70, 0.55); }
  50%      { transform: scale(1.16); box-shadow: 0 0 16px rgba(217, 139, 70, 0.95); }
}
@media (prefers-reduced-motion: reduce) {
  .badge-bubble { animation: none; }
}

