/* Palette pastel : violine douce en accent, fond légèrement teinté plutôt que
   gris neutre. Un jeu de variables clair (:root) + un jeu sombre, appliqué
   automatiquement via prefers-color-scheme et surchargeable manuellement par
   [data-theme] (voir front/components/ThemeToggle.js et le script anti-flash
   dans front/app/layout.js qui pose l'attribut avant le premier paint). */
:root {
  --bg: #fbfdff;
  --surface: #ffffff;
  --border: #e4dff2;
  --text: #332f47;
  --text-muted: #726b8c;
  --accent: #4c7bd9;
  --accent-hover: #3a63b9;
  --accent-soft: #e8eefc;
  --accent-border: #c3d8f7;
  --accent-contrast: #ffffff;
  --focus: #4c7bd9;
  --border-strong: #cbc0ea;
  --danger: #b0323f;
  --danger-bg: #fbebec;
  --danger-border: #f3d4d7;
  --success: #2f7a4f;
  --success-bg: #e9f6ee;
  --success-border: #cbe9d6;
  /* Couleurs dédiées aux graphiques (dashboard admin) : distinctes de
     --accent (réservé au chrome UI), calées sur la bande de luminosité
     catégorielle validée par le skill dataviz (2 séries max ici). */
  --chart-1: #4c7bd9;
  --chart-2: #c97a3f;
  --radius: 12px;
  --max-width: 1480px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #1e1b2b;
    --surface: #272334;
    --border: #3a3450;
    --text: #ece9f5;
    --text-muted: #a99fc4;
    --accent: #82aef2;
    --accent-hover: #a0c2f6;
    --accent-soft: #223250;
    --accent-border: #35507c;
    --accent-contrast: #14213b;
    --focus: #9bc0f6;
    --border-strong: #4f4768;
    --danger: #f2939b;
    --danger-bg: #402029;
    --danger-border: #5c2c37;
    --success: #8fd6ab;
    --success-bg: #1f3a2c;
    --success-border: #2f5940;
    --chart-1: #5f8ee0;
    --chart-2: #c07f45;
    color-scheme: dark;
  }
}

:root[data-theme="dark"] {
  --bg: #1e1b2b;
  --surface: #272334;
  --border: #3a3450;
  --text: #ece9f5;
  --text-muted: #a99fc4;
  --accent: #82aef2;
  --accent-hover: #a0c2f6;
  --accent-soft: #223250;
  --accent-border: #35507c;
  --accent-contrast: #14213b;
  --focus: #9bc0f6;
  --border-strong: #4f4768;
  --danger: #f2939b;
  --danger-bg: #402029;
  --danger-border: #5c2c37;
  --success: #8fd6ab;
  --success-bg: #1f3a2c;
  --success-border: #2f5940;
  --chart-1: #5f8ee0;
  --chart-2: #c07f45;
  color-scheme: dark;
}

* {
  box-sizing: border-box;
}

/* Le header est en position: sticky (voir .site-header plus bas) : sans ce
   décalage, tout lien d'ancre (#cookies, etc.) fait défiler sa cible juste
   sous le bord haut du viewport, où elle se retrouve masquée derrière le
   header fixe. ~66px de header + marge de confort. */
html {
  scroll-padding-top: 80px;
}

body {
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* Améliore les coupures de ligne du texte courant (évite qu'un retour à la
   ligne isole un mot seul ou coupe juste après une parenthèse ouvrante, ex.
   "(ex" / ": 200)" sur deux lignes) — support croissant mais pas universel
   (Safari < 17.5, etc.), donc un pur enrichissement progressif : les
   navigateurs qui l'ignorent gardent le retour à la ligne standard. */
p {
  text-wrap: pretty;
}

a {
  color: var(--text);
}

a:hover {
  color: var(--accent-hover);
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

img {
  max-width: 100%;
}

/* Icônes : toujours des symboles du sprite bootstrap-icons auto-hébergé
   (front/public/assets/bootstrap-icons.svg, généré par
   front/scripts/build-icons.js), jamais de SVG dessiné à la main ni d'emoji.
   <svg class="icon"><use href="/assets/bootstrap-icons.svg#<id>"/></svg> */
.icon {
  width: 20px;
  height: 20px;
  flex: none;
  fill: currentColor;
}

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 1.5rem;
}

/* Fil d'Ariane */

/* --bg (fond de page) plutôt que --surface (utilisé par .site-header juste
   au-dessus) : même couleur que le header, le fil d'ariane se fondait dedans
   au lieu de s'en distinguer. */
.breadcrumb-bar {
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg);
}

.breadcrumb-bar .container {
  padding-top: 0.6rem;
  padding-bottom: 0.6rem;
}

/* Défilement horizontal (façon puces de filtre Amazon) plutôt qu'un
   retour à la ligne, pour les fils d'ariane trop longs pour la largeur de
   l'écran. */
.breadcrumb-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: none;
}

.breadcrumb-scroll::-webkit-scrollbar {
  display: none;
}

.breadcrumb {
  display: flex;
  align-items: center;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.85rem;
  white-space: nowrap;
}

.breadcrumb a {
  color: var(--text-muted);
  text-decoration: none;
}

.breadcrumb li {
  color: var(--text-muted);
}

.breadcrumb a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.breadcrumb li[aria-current="page"] {
  color: var(--text);
  font-weight: 600;
}

.breadcrumb li + li::before {
  content: "/";
  margin: 0 0.5rem;
  color: var(--border);
}

/* Header */

.site-header {
  flex-shrink: 0;
  position: sticky;
  top: 0;
  z-index: 900;
  border-bottom: 1px solid var(--border);
  background: var(--surface);
}

.site-header .container {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 1rem;
  padding-top: 1rem;
  padding-bottom: 1rem;
}

/* `display: contents` en desktop : brand et nav-burger participent
   directement au flex du container ci-dessus, comme s'il n'y avait pas de
   wrapper — ce dernier n'existe que pour redevenir une vraie boîte
   positionnée en mobile (voir @media max-width: 640px), où le burger doit se
   placer en absolu par rapport à cette rangée plutôt que par rapport à tout
   le header (qui grandit quand le tiroir de nav s'ouvre en dessous). */
.header-top-row {
  display: contents;
}

.brand {
  display: flex;
  align-items: center;
}

.brand img {
  height: 32px;
  width: auto;
  display: block;
}

.nav-burger,
.header-back {
  display: none;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--text);
  padding: 0.4rem;
  border-radius: 6px;
}

.nav-burger:hover,
.header-back:hover {
  background: var(--accent-soft);
}

.main-nav {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  flex-wrap: nowrap;
  /* Ancrée par son bord gauche (au lieu d'un `justify-content: space-between`
     sur le conteneur, qui ancrait la nav par son bord droit) : quand un
     élément apparaît tardivement (ex. le menu Admin, monté après la réponse
     de /api/admin/session), la nav s'élargit vers la droite sans décaler les
     éléments déjà affichés avant lui. */
  margin-left: auto;
}

.main-nav > a {
  text-decoration: none;
  font-size: 0.95rem;
  color: var(--text-muted);
  padding: 0.25rem 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}

.main-nav > a:hover,
.main-nav > a[aria-current="page"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

/* Sépare les catégories d'outils du menu Paramètres (voir Header.js). En nav
   horizontale desktop, un trait vertical de la hauteur de la ligne ; devient
   un trait horizontal pleine largeur dans le tiroir mobile (voir @media
   max-width: 640px). */
.nav-divider {
  width: 1px;
  align-self: stretch;
  background: var(--border);
}

/* Menus déroulants (catégories d'outils, réglages, admin) — un seul composant
   graphique pour tous, voir front/components/Menu.js. */
.menu {
  position: relative;
}

.menu-trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  background: none;
  color: var(--text-muted);
  border: none;
  padding: 0.25rem 0;
  font-size: 0.95rem;
  font-weight: 400;
  border-radius: 0;
  border-bottom: 2px solid transparent;
  white-space: nowrap;
}

.menu-trigger:hover,
.menu-trigger[aria-expanded="true"],
.menu-trigger[aria-current="page"] {
  background: none;
  color: var(--text);
  border-bottom-color: var(--accent);
}

.menu-caret {
  width: 12px;
  height: 12px;
  transition: transform 0.15s ease;
}

.menu-trigger[aria-expanded="true"] .menu-caret {
  transform: rotate(180deg);
}

.menu-panel {
  position: absolute;
  top: calc(100% + 0.6rem);
  z-index: 1000;
  width: max-content;
  min-width: 200px;
  max-width: min(320px, calc(100vw - 3rem));
  padding: 0.6rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

/* `display: flex` ci-dessus écraserait sinon le display:none que le
   navigateur applique par défaut à l'attribut hidden (règle auteur > règle
   user-agent, indépendamment de la spécificité) — d'où ce panneau visible en
   permanence sans cette règle. */
.menu-panel[hidden] {
  display: none;
}

.menu-panel-left {
  left: 0;
}

.menu-panel-right {
  right: 0;
}

.menu-panel form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.menu-links {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

/* Élément de menu "plat" (lien catégorie/outil, bouton de langue) — distinct
   des .btn-secondary empilés du menu admin, qui gardent leur look de bouton. */
.menu-item {
  display: block;
  width: 100%;
  text-align: left;
  text-decoration: none;
  background: none;
  color: var(--text);
  border: none;
  padding: 0.45rem 0.6rem;
  border-radius: 6px;
  font-size: 0.9rem;
  font-weight: 400;
  white-space: nowrap;
}

.menu-item:hover,
.menu-item[aria-current="page"],
.menu-item[aria-pressed="true"] {
  background: var(--accent-soft);
  color: var(--accent);
}

.menu-divider {
  height: 1px;
  background: var(--border);
  margin: 0.3rem 0;
}

/* Petit intitulé de sous-section dans un panneau de menu (ex. "Langue" au-
   dessus des boutons de langue dans SettingsMenu.js) — même traitement que
   .category-filter-label. */
.menu-section-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  padding: 0 0.6rem;
}

/* .menu-item pour la forme (rangée pleine largeur dans le panneau de
   SettingsMenu.js) ; .theme-toggle n'ajoute que la mise en ligne icône+
   libellé, qui remplace le display:block par défaut de .menu-item. */
.theme-toggle {
  display: flex;
  align-items: center;
  gap: 0.6rem;
}

/* Menu Admin (visible uniquement si l'IP est whitelistée, voir AdminMenu.js) —
   structure/positionnement via .menu/.menu-panel/.menu-links/.menu-item
   génériques ci-dessus ; ne reste ici que ce qui est spécifique à ce menu. */

.admin-menu-error {
  color: var(--danger);
  font-size: 0.82rem;
  margin: 0;
}

/* Main */

main {
  flex: 1 0 auto;
  padding: 3rem 0 4rem;
}

.hero {
  text-align: center;
  margin-bottom: 3rem;
}

.hero img {
  height: 64px;
  margin-bottom: 1.25rem;
}

.hero .icon {
  width: 56px;
  height: 56px;
  margin-bottom: 1.25rem;
  color: var(--accent);
}

.hero h1 {
  font-size: 1.9rem;
  margin: 0 0 0.5rem;
}

.hero p {
  color: var(--text-muted);
  margin: 0;
  font-size: 1.05rem;
}

.hero p + p {
  margin-top: 0.6rem;
}

.about-stat {
  font-size: 1.05rem;
}

.about-stat strong {
  color: var(--accent);
  font-size: 1.3rem;
}

/* Cartes "valeurs" (page À propos) : même gabarit visuel que .tool-card mais
   non cliquables (pas de lien, pas d'effet de survol qui suggérerait le
   contraire). */
.value-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1.25rem;
  margin-bottom: 2.5rem;
}

.value-card {
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-align: center;
}

.value-card .icon {
  width: 30px;
  height: 30px;
  margin-bottom: 0.9rem;
  color: var(--accent);
}

.value-card h2 {
  font-size: 1.05rem;
  margin: 0 0 0.35rem;
}

.value-card p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.notfound-game-intro {
  text-align: center;
  margin-bottom: 1.5rem;
}

/* Jeu de mémoire (page 404, front/components/NotFoundGame.js) : 30 icônes
   vendues (voir build-icons.js), réutilisées en boucle sur les niveaux les
   plus grands, aucune dépendance externe. --memory-max-width/-cols/-gap sont
   posées en style inline par le composant sur .memory-grid (une valeur par
   niveau, 2×2 à 10×10) — jamais sur .memory-game, dont la largeur doit rester
   celle du panneau parent pour que .memory-status (texte du même gabarit à
   tous les niveaux) ne s'écrase pas contre une grille minuscule (niveau 1). */
.memory-status {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 2rem;
  margin-bottom: 1.25rem;
  color: var(--text-muted);
  font-size: 0.92rem;
}

.memory-status strong {
  color: var(--text);
}

.memory-grid {
  display: grid;
  grid-template-columns: repeat(var(--memory-cols, 4), 1fr);
  gap: var(--memory-gap, 0.85rem);
  max-width: var(--memory-max-width, 360px);
  margin: 0 auto;
}

.memory-card {
  aspect-ratio: 1;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
  perspective: 600px;
}

.memory-card:disabled {
  cursor: default;
}

.memory-card-inner {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  transition: transform 0.5s;
  transform-style: preserve-3d;
}

.memory-card.flipped .memory-card-inner,
.memory-card.matched .memory-card-inner {
  transform: rotateY(180deg);
}

.memory-card-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  backface-visibility: hidden;
}

.memory-card-front {
  background: var(--accent-soft);
}

.memory-card-front .icon {
  width: 40%;
  height: 40%;
  color: var(--accent-border);
}

.memory-card-back {
  background: var(--surface);
  transform: rotateY(180deg);
}

.memory-card-back .icon {
  width: 45%;
  height: 45%;
  color: var(--accent);
}

.memory-card.matched .memory-card-back {
  background: var(--success-bg);
}

.memory-card.matched .memory-card-back .icon {
  color: var(--success);
}

.memory-win {
  text-align: center;
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
}

.memory-win .icon {
  width: 32px;
  height: 32px;
  color: var(--success);
  margin-bottom: 0.5rem;
}

.memory-win p {
  margin: 0 0 1rem;
  font-weight: 600;
}

.page-header {
  margin-bottom: 2rem;
}

.page-header h1 {
  margin: 0 0 0.4rem;
  font-size: 1.6rem;
}

.page-header p {
  color: var(--text-muted);
  margin: 0;
}

/* Tool cards grid */

.tools-search-field {
  position: relative;
  max-width: 360px;
  margin-bottom: 0.5rem;
}

.tools-search-field .icon {
  position: absolute;
  top: 50%;
  left: 0.7rem;
  transform: translateY(-50%);
  color: var(--text-muted);
  pointer-events: none;
}

.tools-search-field .tools-search {
  padding-left: 2.2rem;
}

.category-filter {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.65rem;
  margin-bottom: 1.5rem;
  padding-bottom: 1.25rem;
  border-bottom: 1px solid var(--border);
}

.category-filter-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
}

.category-filter-chips {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
}

.category-filter-btn {
  display: inline-flex;
  align-items: center;
  padding: 0.3rem 0.8rem;
  border-radius: 999px;
  border: 1.5px solid var(--border);
  background: transparent;
  color: var(--text-muted);
  font-size: 0.82rem;
  font-weight: 600;
  font-family: inherit;
  line-height: 1.4;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}

.category-filter-btn:hover {
  background: transparent;
  border-color: var(--text-muted);
  color: var(--text);
  transform: translateY(-1px);
}

.category-filter-btn.is-active {
  border-color: var(--accent-border);
  background: var(--accent-soft);
  color: var(--accent);
}

.category-filter-btn.is-active:hover {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent-hover);
}

/* Alternative mobile aux puces ci-dessus (voir @media max-width: 640px) :
   masqué en desktop, où les puces suffisent. */
.category-filter-select {
  display: none;
  width: 100%;
  max-width: 320px;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  border: 1.5px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 0.9rem;
  font-family: inherit;
}

.tools-grid {
  display: grid;
  /* Le plancher de chaque piste est le plus grand entre 220px et 1/5 de la
     largeur disponible : impossible d'avoir plus de 5 colonnes, tout en
     restant fluide (moins de colonnes) sur les écrans plus étroits — sans
     max-width propre, la grille garde donc la largeur du .container (celle
     du header) au lieu d'un pavé recentré et plus étroit que lui.
     auto-fill (et non auto-fit) : avec moins de tuiles que de colonnes
     possibles (ex. 2 après filtrage), les pistes vides restent réservées au
     lieu d'être supprimées, donc les tuiles gardent leur taille normale au
     lieu de s'étirer pour combler toute la largeur de la ligne. */
  grid-template-columns: repeat(auto-fill, minmax(max(220px, (100% - 4 * 1.25rem) / 5), 1fr));
  gap: 1.25rem;
}

.tool-card {
  display: block;
  padding: 1.5rem;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  text-decoration: none;
  color: var(--text);
  transition: border-color 0.15s ease, transform 0.15s ease;
}

.tool-card:hover {
  border-color: var(--accent);
  transform: translateY(-2px);
}

.tool-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.75rem;
}

.tool-card .icon {
  width: 28px;
  height: 28px;
  color: var(--accent);
}

.tool-card h2 {
  font-size: 1.05rem;
  margin: 0 0 0.35rem;
}

.tool-card p {
  margin: 0;
  color: var(--text-muted);
  font-size: 0.9rem;
}

/* Forms */

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 1.75rem;
}

.panel + .panel {
  margin-top: 1.5rem;
}

.panel h2 {
  margin: 0 0 1rem;
  font-size: 1.2rem;
}

.panel p + p {
  margin-top: 1rem;
}

.field {
  margin-bottom: 1.25rem;
}

.field label {
  display: block;
  font-size: 0.9rem;
  font-weight: 600;
  margin-bottom: 0.4rem;
}

.field .hint {
  color: var(--text-muted);
  font-size: 0.82rem;
  margin-top: 0.35rem;
}

.field .field-error {
  color: var(--danger);
  font-size: 0.82rem;
  margin-top: 0.35rem;
}

.field.has-error input,
.field.has-error select,
.field.has-error textarea {
  border-color: var(--danger);
}

.summary-text {
  font-size: 1.05rem;
  margin: 0;
}

input[type="text"],
input[type="number"],
input[type="email"],
input[type="password"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1.5px solid var(--border-strong);
  border-radius: 8px;
  font-size: 0.95rem;
  font-family: inherit;
  background: var(--bg);
  color: var(--text);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

input[type="text"]:focus,
input[type="number"]:focus,
input[type="email"]:focus,
input[type="password"]:focus,
input[type="search"]:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.field.has-error input:focus,
.field.has-error select:focus,
.field.has-error textarea:focus {
  box-shadow: 0 0 0 3px var(--danger-bg);
}

textarea {
  resize: vertical;
}

.textarea-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.85rem;
  background: var(--surface);
  resize: none;
  overflow: hidden;
}

input[type="range"] {
  width: 100%;
}

.checkbox-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

/* Espace la ligne à cocher du champ suivant (ex. contact.js) quand celui-ci
   est un vrai champ visible et pas le honeypot (invisible, sans besoin de
   respiration visuelle). */
.checkbox-row + .field {
  margin-top: 0.75rem;
}

.checkbox-row input {
  width: 16px;
  height: 16px;
}

.checkbox-row label {
  margin: 0;
  font-weight: 400;
}

.options-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 0.25rem 1rem;
}

.field-group {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1.25rem;
  margin-bottom: 1.25rem;
}

/* `display: grid` ci-dessus écraserait sinon le display:none par défaut de
   l'attribut hidden (même raison que .menu-panel[hidden] plus haut) — utile
   quand un groupe de champs entier est basculé selon un mode (ex : capital à
   refinancer saisi directement vs dérivé du prêt d'origine, module
   renegociation-de-credit). */
.field-group[hidden] {
  display: none;
}

/* Firefox dessine un rectangle pointillé interne natif sur les boutons au
   focus (pseudo-élément ::-moz-focus-inner), entièrement indépendant des
   propriétés outline/box-shadow ci-dessous — rien ne le supprime tant qu'il
   n'est pas ciblé spécifiquement. Invisible dans Chrome/Safari (qui n'ont
   pas ce pseudo-élément), il ne se voit que sur un bouton qui reçoit le
   focus clavier, d'où un artefact resté longtemps invisible partout sauf sur
   le bouton fermer de la modale de jeu (mods/generateur-aleatoire), le seul
   à être focus() par script plutôt que par Tab. */
button::-moz-focus-inner {
  border: 0;
  padding: 0;
}

button,
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  background: var(--accent);
  color: var(--accent-contrast);
  border: none;
  padding: 0.65rem 1.2rem;
  border-radius: 8px;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

button:hover,
.btn:hover {
  background: var(--accent-hover);
}

button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button[aria-busy="true"],
.btn[aria-busy="true"] {
  position: relative;
  color: transparent;
  pointer-events: none;
}

button[aria-busy="true"]::after,
.btn[aria-busy="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  margin: auto;
  width: 16px;
  height: 16px;
  border: 2px solid color-mix(in srgb, var(--accent-contrast) 35%, transparent);
  border-top-color: var(--accent-contrast);
  border-radius: 50%;
  animation: btn-spin 0.6s linear infinite;
}

.btn-secondary[aria-busy="true"]::after {
  border-color: var(--accent-border);
  border-top-color: var(--accent);
}

@keyframes btn-spin {
  to {
    transform: rotate(360deg);
  }
}

@media (prefers-reduced-motion: reduce) {
  button[aria-busy="true"]::after,
  .btn[aria-busy="true"]::after {
    animation-duration: 1.4s;
  }
}

.btn-secondary {
  background: var(--accent-soft);
  color: var(--accent);
  border: 1.5px solid var(--accent-border);
}

.btn-secondary:hover {
  background: var(--accent-border);
}

.icon-btn {
  padding: 0.55rem;
}

.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;
}

form > button[type="submit"] {
  display: flex;
  margin-left: auto;
}

/* Plusieurs actions sur une même ligne, alignées à droite (ex : "Mon IP" +
   "Rechercher" dans mods/analytic-ip) ; remplace la règle ci-dessus pour un
   bouton submit qui n'est alors plus enfant direct du form. */
/* Le bouton d'impression (voir PrintButton.js) suit toujours un
   .data-table-wrap (tableau d'amortissement) dans #results : sans marge
   dédiée il colle directement au tableau du dessus, contrairement aux
   .form-actions qui suivent un .field-group (déjà espacé par sa propre
   margin-bottom). */
.data-table-wrap + .form-actions {
  margin-top: 1.25rem;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
}

#sshResult > button,
#shareBtn {
  display: flex;
  margin-left: auto;
  margin-top: 1rem;
}

/* #shareBtn (contrairement à downloadZipBtn) porte lui-même l'attribut
   [hidden] plutôt qu'un conteneur englobant : sans ceci, le "display: flex"
   ci-dessus (sélecteur par id, plus spécifique que la règle native
   [hidden] { display: none }) l'emporterait et annulerait le masquage. */
#shareBtn[hidden] {
  display: none;
}

/* Résultats */

.result {
  margin-top: 1.5rem;
  padding: 1.1rem;
  border-radius: var(--radius);
  background: var(--bg);
  border: 1px solid var(--border);
}

.result-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.result-value {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.95rem;
  word-break: break-all;
}

/* Modificateurs pour un résultat dont le signe porte du sens (ex : économie
   réalisée en renégociant un crédit, positive ou négative selon les cas). */
.result-value.positive {
  color: var(--success);
}

.result-value.negative {
  color: var(--danger);
}

.result + .result {
  margin-top: 0.75rem;
}

/* Résultat mis en avant (ex : net/brut/coût employeur parmi une liste de
   résultats secondaires) : lecture rapide des valeurs clés. */
.result-emphasis .result-row {
  font-weight: 700;
}

/* Complément explicatif sous un résultat (ex : différence net / net
   imposable) — même traitement visuel que .field .hint, hors contexte de
   formulaire. */
.result-hint {
  color: var(--text-muted);
  font-size: 0.82rem;
  font-style: italic;
  margin-top: 0.5rem;
}

/* Libellé de résultat accompagné d'un bouton d'info (.info-toggle) qui
   affiche/masque un .result-hint associé. */
.result-label-info {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
}

/* Bouton réduit à sa seule icône (contrairement à .btn/.btn-secondary,
   pensés pour un vrai bouton avec fond et libellé) : pas de fond, pas de
   bordure, juste l'icône, un peu plus petite qu'ailleurs sur le site. */
.info-toggle {
  display: inline-flex;
  align-items: center;
  background: none;
  border: none;
  padding: 0;
  color: var(--text-muted);
  cursor: pointer;
}

.info-toggle:hover,
.info-toggle:focus-visible {
  background: none;
  color: var(--accent);
}

.info-toggle .icon {
  width: 14px;
  height: 14px;
}

.result-label {
  display: block;
  margin-bottom: 0.5rem;
}

/* Valeur copiable (span ou textarea readonly + bouton), cf. CopyValue.js.
   Sans modificateur : reproduit l'ancien look .result-row (posé sur le fond
   --bg de .result, bouton --surface). */
.copy-value {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

.copy-value-multiline {
  align-items: flex-start;
}

.copy-value-framed {
  padding: 0.7rem 0.85rem;
  border-radius: 8px;
  border: 1.5px solid var(--border-strong);
  background: var(--bg);
}

/* Cadre blanc (--surface) : le bouton copier s'y fondrait avec son fond
   --surface par défaut, donc on inverse sur --bg pour rester visible. */
.copy-value-white {
  background: var(--surface);
}

.copy-value-framed textarea {
  flex: 1;
  width: auto;
  min-height: 1.4em;
  border: none;
  padding: 0;
  background: none;
}

.copy-value .btn-secondary {
  flex: none;
}

.copy-value-white .btn-secondary {
  background: var(--bg);
}

.copy-value-white .btn-secondary:hover {
  background: var(--border);
}

/* Tableau de données scrollable (ex : tableau d'amortissement de prêt, jusqu'à
   ~400 lignes) — jamais un tableau nu : le conteneur porte le scroll
   horizontal (overflow-x) pour que la page elle-même ne défile jamais
   latéralement, et le scroll vertical + l'en-tête collant pour garder les
   colonnes lisibles sur une longue liste. */
.data-table-wrap {
  margin-top: 1.5rem;
  max-height: 480px;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

/* Variante pour les listes courtes et bornées (ex : détail de cotisations,
   une quinzaine de lignes) où la limite de hauteur n'a pas de sens — il n'y
   a jamais assez de lignes pour justifier un scroll vertical. Le scroll
   horizontal (overflow hérité de .data-table-wrap) reste actif pour les
   libellés longs sur mobile. */
.data-table-wrap--flat {
  max-height: none;
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
  white-space: nowrap;
}

.data-table th,
.data-table td {
  padding: 0.55rem 0.85rem;
  text-align: right;
  border-bottom: 1px solid var(--border);
}

.data-table th:first-child,
.data-table td:first-child {
  text-align: left;
}

.data-table thead th {
  position: sticky;
  top: 0;
  background: var(--surface);
  color: var(--text-muted);
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  /* Contrairement aux cellules de données (nombres/dates, héritent du
     nowrap de .data-table pour rester sur une ligne), un intitulé long
     ("Restant dû avant échéance") peut passer sur deux lignes : sinon la
     colonne s'élargit pour loger tout l'intitulé sur une seule ligne,
     laissant un vide énorme au-dessus de valeurs bien plus courtes. */
  white-space: normal;
}

.data-table tbody tr:nth-child(even) {
  background: var(--bg);
}

/* Ligne correspondant au mois en cours (ex : tableaux d'amortissement des
   modules prêt/crédit) — l'emporte sur le zébrage ci-dessus via l'ordre en
   cascade, pas besoin de spécificité supplémentaire. */
.data-table tbody tr.current-row {
  background: var(--accent-soft);
  font-weight: 600;
}

.data-table tbody tr:hover {
  background: var(--accent-soft);
}

.data-table tfoot th {
  border-top: 1.5px solid var(--border-strong);
  border-bottom: none;
}

/* Deux tableaux distincts (cotisations salariales / patronales) dont
   chacun dimensionnerait sinon ses colonnes indépendamment selon son
   propre contenu — largeur fixe sur Taux/Montant pour que les deux
   tableaux restent alignés une fois empilés, quelle que soit la longueur
   des libellés de la première colonne. */
.salary-detail-table th:nth-child(2),
.salary-detail-table td:nth-child(2),
.salary-detail-table th:nth-child(3),
.salary-detail-table td:nth-child(3) {
  width: 20%;
}

/* Grille label/valeur (ex : détails géoloc IP), cf. mods/analytic-ip. Deux
   colonnes fixes plutôt qu'un auto-fill (qui alignait jusqu'à 4-5 colonnes
   étroites sur grand écran) ; repasse à une seule colonne sur mobile, cf.
   @media (max-width: 640px) plus bas. */
.info-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.9rem 1.5rem;
  margin: 1rem 0 0;
  padding: 0;
}

.info-item dt {
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 0.15rem;
}

.info-item dd {
  margin: 0;
  font-weight: 600;
  word-break: break-word;
}

/* Variante d'.info-item dont la valeur est une ou plusieurs .badge (statut,
   liste de serveurs de noms/enregistrements DNS...) plutôt qu'un texte brut —
   cf. mods/analyse-de-domaine et mods/analytic-ip. */
.info-item dd.badge-group {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  font-weight: 400;
}

.toast-container {
  position: fixed;
  bottom: 1rem;
  right: 1rem;
  z-index: 1100;
  display: flex;
  flex-direction: column;
  /* stretch (valeur par défaut) forcerait chaque toast à la pleine largeur
     du conteneur ; flex-end les laisse se dimensionner à leur propre
     contenu tout en restant alignés sur le bord droit. */
  align-items: flex-end;
  gap: 0.6rem;
  max-width: min(360px, calc(100vw - 2rem));
}

.toast {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  padding: 0.75rem 1rem;
  border-radius: 8px;
  font-size: 0.9rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
  animation: toast-in 0.2s ease-out;
  transition: opacity 0.2s ease-in, transform 0.2s ease-in;
}

.toast-hide {
  opacity: 0;
  transform: translateX(8px);
}

.toast-text {
  flex: 1;
  margin: 0;
}

.toast-close {
  flex: none;
  display: inline-flex;
  padding: 0;
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.toast-close .icon {
  width: 14px;
  height: 14px;
}

.toast-error {
  background: var(--danger-bg);
  color: var(--danger);
  border: 1px solid var(--danger-border);
}

.toast-success {
  background: var(--success-bg);
  color: var(--success);
  border: 1px solid var(--success-border);
}

@keyframes toast-in {
  from {
    opacity: 0;
    /* Positif (pas -8px) : le conteneur est maintenant ancré en bas de
       l'écran, l'entrée doit venir du bas, pas du haut. */
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Bandeau d'information cookies (front/components/CookieConsent.js) : fixe en
   bas d'écran, sous le menu-panel (z-index 1000) mais au-dessus du contenu de
   page. */
.cookie-banner {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 950;
  /* --accent-soft (teinte) plutôt que --surface (blanc/quasi-blanc) : sur les
     pages au fond clair, un bandeau --surface se fondait presque totalement
     dans le contenu au-dessus, seule l'ombre le distinguait à peine. */
  background: var(--accent-soft);
  border-top: 1px solid var(--accent-border);
  box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.12);
}

.cookie-banner-inner {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding-top: 1rem;
  padding-bottom: 1rem;
}

.cookie-banner-inner .icon {
  flex-shrink: 0;
  width: 20px;
  height: 20px;
  color: var(--accent);
}

.cookie-banner-text {
  flex: 1;
  min-width: 240px;
  margin: 0;
  font-size: 0.9rem;
  color: var(--text-muted);
}

.cookie-banner-text a {
  color: var(--accent);
}

.note {
  color: var(--text-muted);
  font-size: 0.85rem;
  margin-top: 1.25rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border);
  /* .note est parfois un <div> (pas un <p>) selon les modules : la règle
     globale sur `p` ne suffit pas toujours à le couvrir. */
  text-wrap: pretty;
}

.entropy-bar {
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
  margin-top: 0.5rem;
}

.entropy-bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
}

/* Modale de jeu plein écran (générateur aléatoire) */

.game-modal {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.5rem;
  padding: 1.5rem;
  background: var(--accent-soft);
}

.game-modal[hidden] {
  display: none;
}

.game-modal-close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  /* .btn-secondary est déjà teinté --accent-soft (voir plus haut) : sur le
     fond --accent-soft de la modale, il s'y fondrait presque entièrement.
     --surface le détache nettement, comme la tuile. */
  background: var(--surface);
  /* Filet de sécurité WebKit/Safari : supprime le halo de surbrillance natif
     au tap, distinct de :focus/:focus-visible ci-dessous et non couvert par
     eux. */
  -webkit-tap-highlight-color: transparent;
}

/* openGameModal() (script.js) donne le focus à ce bouton dès l'ouverture de
   la modale (accessibilité clavier — le focus DOM reste utile aux lecteurs
   d'écran et à la navigation clavier même sans anneau visible). L'anneau
   `outline`/`box-shadow` de :focus-visible s'est avéré se confondre
   partiellement avec la bordure existante du bouton (teintes proches),
   ne laissant apparaître qu'un fragment façon "petit trait" plutôt qu'un
   anneau net — d'où la suppression complète plutôt qu'une variante colorée
   différemment. */
.game-modal-close:focus,
.game-modal-close:focus-visible {
  outline: none;
  box-shadow: none;
}

.game-modal-logo {
  opacity: 0.5;
}

.game-modal-body {
  flex: 1;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 1.5rem;
}

.game-tile {
  /* display: block (pas inline-flex, valeur par défaut de la règle
     "button" plus haut) : transform-style: preserve-3d sur .game-tile-inner
     (plus bas) est silencieusement ignoré par certains moteurs de rendu
     quand l'élément transformé est un enfant flex — la tuile restait alors
     plate, jamais de retournement visible. */
  display: block;
  width: min(80vw, 520px);
  height: min(60vh, 520px);
  perspective: 1400px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.game-tile:hover {
  /* Sans cette ligne, la règle globale "button:hover" (couleur d'accent)
     l'emporte sur le fond des faces plus bas et teinte toute la tuile au
     survol — indésirable ici, la tuile n'est pas un bouton coloré classique. */
  background: none;
}

.game-tile.game-tile-wide {
  width: min(90vw, 720px);
  height: min(32vh, 260px);
}

.game-tile-inner {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition-property: transform;
  transition-duration: 0.5s;
  /* linear (pas ease) : le texte de la face arrière est réécrit exactement
     à la moitié de la durée (voir script.js), ce qui ne tombe pile sur le
     passage de profil (donc invisible) que si le temps avance à la même
     vitesse que l'angle tout du long. */
  transition-timing-function: linear;
}

.game-tile-face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2rem;
  text-align: center;
  border-radius: 1.5rem;
  border: 1px solid var(--accent-border);
  /* Les deux faces partagent le même fond/texte : si un artefact de rendu
     laisse un instant apercevoir la "mauvaise" face pendant la rotation, ça
     ne fait jamais apparaître une couleur ou un texte incohérent.
     --accent/--accent-contrast (pas --surface/--text) : ces deux variables
     s'inversent délibérément entre thèmes (voir le commentaire sur
     --accent-contrast en tête de fichier) — tuile foncée + texte clair en
     thème clair, tuile claire + texte foncé en thème sombre, donc toujours
     à l'opposé du fond --accent-soft de la modale (qui lui suit le thème). */
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 700;
  word-break: break-word;
  overflow-wrap: anywhere;
  font-size: clamp(4rem, 16vw, 10rem);
}

.game-tile.game-tile-wide .game-tile-face {
  font-size: clamp(1.75rem, 5vw, 3.25rem);
}

/* Taille dédiée à la face avant ("Cliquez ici") : la grande taille de
   .game-tile-face (voir plus haut, pensée pour un résultat court comme un
   nombre) faisait déborder ce texte sur plusieurs lignes. white-space:
   nowrap force une seule ligne ; le clamp reste modéré pour ne jamais
   dépasser la largeur de la tuile, y compris en format allongé. Répété avec
   .game-tile-wide en préfixe pour l'emporter sur la règle wide ci-dessus, de
   même spécificité (3 classes) sinon. */
.game-tile-front,
.game-tile.game-tile-wide .game-tile-front {
  font-size: clamp(1.75rem, 6vw, 3.25rem);
  white-space: nowrap;
}

.game-tile-back {
  transform: rotateY(180deg);
}

.game-next-hint {
  margin: 0;
  color: var(--text-muted);
  font-weight: 600;
  text-align: center;
  /* visibility (pas [hidden]/display:none) : l'espace reste réservé même
     avant la première révélation, sinon .game-modal-body (centré
     verticalement) recalcule sa hauteur totale quand ce texte apparaît et
     décale la tuile au-dessus. visibility:hidden retire aussi l'élément de
     l'arbre d'accessibilité tant qu'il n'est pas pertinent. */
  visibility: hidden;
}

@media (prefers-reduced-motion: reduce) {
  .game-tile-inner {
    transition: none;
  }
}

/* Modale de compte à rebours (mods/compte-a-rebours), même coquille
   .game-modal que le générateur aléatoire ci-dessus. */

.countdown-label {
  margin: 0;
  font-size: clamp(1.25rem, 4vw, 1.75rem);
  font-weight: 700;
  color: var(--text);
  text-align: center;
  word-break: break-word;
}

.countdown-ring-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: min(70vw, 380px);
  height: min(70vw, 380px);
  /* .countdown-ring-wrap est un <button> (mode "chacun son tour" : cliquer
     le cercle passe le tour, voir script.js) — on efface entièrement le
     style de bouton par défaut (fond accent, padding, coins arrondis) pour
     revenir à un simple conteneur, comme .game-tile pour la même raison. */
  padding: 0;
  border: none;
  background: none;
  font: inherit;
  color: inherit;
  cursor: default;
}

.countdown-ring-wrap:hover {
  background: none;
}

/* button:disabled (règle globale) assombrit à 0.5 d'opacité — pertinent pour
   un bouton d'action, pas ici : en mode simple (non cliquable) le cercle doit
   rester parfaitement lisible. */
.countdown-ring-wrap:disabled {
  opacity: 1;
  cursor: default;
}

.countdown-ring-wrap:not(:disabled) {
  cursor: pointer;
}

.countdown-ring-wrap:not(:disabled):hover .countdown-ring-progress {
  filter: brightness(1.12);
}

.countdown-ring {
  width: 100%;
  height: 100%;
  /* Fait partir le tracé du dépôt (12h) plutôt que de 3h, position par
     défaut d'un cercle SVG (angle 0 = axe des x). */
  transform: rotate(-90deg);
}

.countdown-ring-track,
.countdown-ring-progress {
  fill: none;
  stroke-width: 10;
}

.countdown-ring-track {
  stroke: var(--border);
}

.countdown-ring-progress {
  stroke: var(--accent);
  stroke-linecap: round;
  /* Circonférence d'un cercle de rayon 90 (2 * π * 90) — doit rester en phase
     avec RING_RADIUS dans script.js, qui recalcule la même valeur pour piloter
     strokeDashoffset à chaque frame. Fixé ici plutôt qu'en JS pour ne jamais
     toucher cet attribut avant la fin de l'hydratation React de la page (voir
     commentaire sur RING_CIRCUMFERENCE dans script.js). */
  stroke-dasharray: 565.487;
  /* strokeDashoffset est recalculé à chaque frame par script.js (via
     requestAnimationFrame, jamais de dérive setInterval) ; cette transition
     très courte ne fait que lisser l'à-coup entre deux frames, jamais
     perceptible comme un délai. */
  transition: stroke-dashoffset 0.12s linear, stroke 0.3s ease;
}

.countdown-ring-wrap.is-critical .countdown-ring-progress {
  stroke: var(--danger);
}

.countdown-ring-wrap.is-critical .countdown-digits {
  color: var(--danger);
}

.countdown-digits {
  position: absolute;
  font-size: clamp(2.75rem, 11vw, 4.5rem);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

@keyframes countdown-tick {
  0% {
    transform: scale(1);
  }
  30% {
    transform: scale(1.08);
  }
  100% {
    transform: scale(1);
  }
}

.countdown-digits.tick {
  animation: countdown-tick 0.35s ease;
}

@keyframes countdown-switch-pop {
  0% {
    transform: scale(0.92);
    opacity: 0.4;
  }
  100% {
    transform: scale(1);
    opacity: 1;
  }
}

.countdown-ring-wrap.is-switching {
  animation: countdown-switch-pop 0.35s ease;
}

@keyframes countdown-flash {
  0%,
  100% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--danger) 55%, transparent);
  }
  50% {
    box-shadow: 0 0 0 18px color-mix(in srgb, var(--danger) 0%, transparent);
  }
}

.countdown-ring-wrap.is-done {
  border-radius: 50%;
  animation: countdown-flash 1s ease-out 2;
}

.countdown-pass-hint {
  margin: 0;
  color: var(--text-muted);
  font-weight: 600;
  text-align: center;
}

.countdown-status {
  margin: 0;
  min-height: 1.75rem;
  font-weight: 700;
  color: var(--danger);
  text-align: center;
}

.countdown-status.is-success {
  color: var(--success);
}

.countdown-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1.25rem;
  flex-wrap: wrap;
}

/* Boutons de contrôle de la modale (pause/reprise, recommencer) : ronds et
   nettement plus grands que la taille .icon-btn par défaut (pensée pour des
   boutons de formulaire), pour rester faciles à viser en plein écran. */
.countdown-controls .icon-btn {
  padding: 1.1rem;
  border-radius: 999px;
}

.countdown-controls .icon-btn .icon {
  width: 28px;
  height: 28px;
}

.countdown-players {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  max-width: min(90vw, 720px);
}

.countdown-player-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  border: 1.5px solid var(--border);
  background: var(--surface);
  color: var(--text-muted);
  font-weight: 600;
  transition: border-color 0.2s ease, background 0.2s ease, color 0.2s ease;
}

.countdown-player-chip .countdown-player-time {
  font-variant-numeric: tabular-nums;
}

.countdown-player-chip.is-active {
  border-color: var(--accent-border);
  background: var(--accent-soft);
  color: var(--accent);
}

.countdown-player-chip.is-winner {
  border-color: var(--success-border);
  background: var(--success-bg);
  color: var(--success);
}

.countdown-player-chip.is-eliminated {
  opacity: 0.55;
}

.countdown-player-chip.is-eliminated .countdown-player-name {
  text-decoration: line-through;
}

@media (prefers-reduced-motion: reduce) {
  .countdown-digits.tick,
  .countdown-ring-wrap.is-switching,
  .countdown-ring-wrap.is-done {
    animation: none;
  }

  .countdown-ring-progress {
    transition: stroke 0.3s ease;
  }
}

/* Onglets (admin) */

.tabs {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid var(--border);
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}

.tabs button {
  background: none;
  color: var(--text-muted);
  border: none;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  padding: 0.6rem 0.9rem;
  font-weight: 600;
}

.tabs button:hover {
  background: none;
  color: var(--text);
}

.tabs button[aria-selected="true"] {
  color: var(--text);
  border-bottom-color: var(--accent);
}

.tab-panel[hidden] {
  display: none;
}

/* Tableau (admin) */

.admin-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.admin-table th,
.admin-table td {
  text-align: left;
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}

.admin-table th {
  color: var(--text-muted);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-weight: 600;
}

.admin-table tbody tr:hover {
  background: var(--bg);
}

/* Badges */

.badge {
  display: inline-flex;
  align-items: center;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  background: var(--border);
  color: var(--text);
}

.badge.success {
  background: var(--success-bg);
  color: var(--success);
}

.badge.danger {
  background: var(--danger-bg);
  color: var(--danger);
}

.badge.accent {
  background: var(--accent-soft);
  color: var(--accent);
}

/* Puce pour une valeur technique (IP, nom d'hôte, ASN...) dans un
   dd.badge-group — même composant .badge, juste la police qui change.
   overflow-wrap : certaines valeurs (ex. enregistrement TXT de vérification de
   domaine) sont une seule chaîne sans espace de 40+ caractères, qui sinon
   déborderait de la pilule plutôt que de passer à la ligne. */
.badge-mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.75rem;
  max-width: 100%;
  overflow-wrap: anywhere;
}

/* Statistiques (admin) */

.stat-tile {
  padding: 1rem 1.1rem;
  border-radius: var(--radius);
  background: var(--bg);
  border: 1px solid var(--border);
}

.stat-tile .stat-number {
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.2;
}

.stat-tile .stat-label {
  color: var(--text-muted);
  font-size: 0.82rem;
}

.stat-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}

.stat-bar-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.stat-bar-row .stat-bar-label {
  flex: 0 0 140px;
  font-size: 0.85rem;
  color: var(--text-muted);
}

.stat-bar {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--border);
  overflow: hidden;
}

.stat-bar > span {
  display: block;
  height: 100%;
  background: var(--accent);
}

.stat-bar-row .stat-bar-count {
  flex: 0 0 auto;
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
}

.stat-chart-wrap {
  position: relative;
  height: 220px;
  margin-bottom: 1.5rem;
}

.stat-last-used {
  list-style: none;
  padding: 0;
  margin: 0 0 1.5rem;
}

.stat-last-used li {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: 0.85rem;
}

.stat-last-used li:last-child {
  border-bottom: none;
}

.stat-last-used .stat-last-used-tool {
  color: var(--text);
}

.stat-last-used .stat-last-used-time {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.section-label {
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-muted);
  margin: 1.5rem 0 0.75rem;
}

/* Modificateur : trait sous le libellé de catégorie, qui traverse toute la
   largeur (les 2 colonnes du .info-grid suivant) pour bien compartimenter les
   sections d'un résultat (ex : Registre/DNS, Localisation/Réseau) — cf.
   mods/analyse-de-domaine et mods/analytic-ip. */
.section-label-category {
  padding-bottom: 0.5rem;
  border-bottom: 1px solid var(--border);
}

.admin-actions {
  display: flex;
  justify-content: flex-end;
  margin-bottom: 1rem;
}

.admin-log {
  max-height: 320px;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 0.85rem 1rem;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8rem;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Honeypot (piège anti-bot, invisible pour un humain) */

/* Honeypot anti-spam : doit rester réellement invisible (taille nulle,
   opacité nulle) pour ne pas être rempli par l'auto-remplissage du
   navigateur/gestionnaire de mots de passe, qui peut cibler un champ
   simplement décalé hors écran (position + left négatif) même minuscule. */
.hp-field {
  position: absolute;
  height: 0;
  width: 0;
  opacity: 0;
  overflow: hidden;
  pointer-events: none;
}

/* Footer */

.site-footer {
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  padding: 2rem 0;
  color: var(--text-muted);
  font-size: 0.85rem;
}

.site-footer .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.site-footer nav {
  display: flex;
  gap: 1.25rem;
}

.site-footer a {
  color: var(--text-muted);
  text-decoration: none;
}

.site-footer a:hover {
  color: var(--text);
}

/* max-width seul ne couvre pas un téléphone tourné en paysage : sa largeur
   dépasse alors largement 640px, mais c'est toujours un mobile (l'espace
   vertical, lui, reste réduit) — la deuxième condition (orientation
   paysage + hauteur courte) fait basculer la même vue mobile (burger,
   chevron retour, tiroir...) dans ce cas plutôt que la nav desktop, qui n'a
   pas la place de s'afficher correctement sur un écran aussi bas. */
@media (max-width: 640px), (max-height: 500px) and (orientation: landscape) {
  .site-header .container {
    flex-direction: column;
    align-items: stretch;
  }

  .header-top-row {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
  }

  .brand {
    width: 100%;
    justify-content: center;
  }

  .nav-burger,
  .header-back {
    display: inline-flex;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
  }

  .nav-burger {
    right: 0;
  }

  .header-back {
    left: 0;
  }

  /* Tiroir replié par défaut (voir mobileOpen dans Header.js) : ouvert via le
     burger, plutôt que la rangée de liens/menus qui débordait auparavant.
     Rendu comme une vraie liste de rangées pleine largeur (accordéon pour les
     catégories/langue) plutôt que les onglets/menus déroulants du desktop,
     qui ne se prêtent pas au tactile. */
  .main-nav {
    display: none;
    flex-direction: column;
    align-items: stretch;
    width: 100%;
    gap: 0.15rem;
    margin-left: 0;
    margin-top: 1rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--border);
  }

  .main-nav.is-open {
    display: flex;
  }

  /* Le trait vertical desktop (séparateur avant Paramètres) devient un trait
     horizontal pleine largeur dans la liste verticale du tiroir. */
  .nav-divider {
    width: 100%;
    height: 1px;
    align-self: auto;
    margin: 0.35rem 0;
  }

  /* Chaque rangée de premier niveau (lien "Accueil", déclencheurs
     catégorie/langue, bouton thème) prend la même forme qu'un .menu-item :
     pleine largeur, cible tactile confortable, fond au survol/actif plutôt
     que le soulignement du desktop (pensé pour une nav horizontale). */
  .main-nav > a,
  .main-nav .menu-trigger,
  .main-nav .theme-toggle {
    display: flex;
    width: 100%;
    justify-content: flex-start;
    gap: 0.6rem;
    padding: 0.7rem 0.6rem;
    border-radius: 8px;
    border-bottom: none;
    font-size: 0.95rem;
    margin-left: 0;
  }

  .main-nav .menu-trigger {
    justify-content: space-between;
  }

  .main-nav > a:hover,
  .main-nav > a[aria-current="page"],
  .main-nav .menu-trigger:hover,
  .main-nav .menu-trigger[aria-expanded="true"],
  .main-nav .menu-trigger[aria-current="page"],
  .main-nav .theme-toggle:hover {
    background: var(--accent-soft);
    color: var(--accent);
    border-bottom: none;
  }

  /* Sous-menu (catégories d'outils, langues) : plus un panneau flottant mais
     une liste indentée directement sous son déclencheur, comme un accordéon
     mobile classique. */
  .main-nav .menu {
    width: 100%;
  }

  .main-nav .menu-panel {
    position: static;
    width: 100%;
    max-width: none;
    margin-top: 0;
    padding: 0.2rem 0 0.4rem 1.1rem;
    background: none;
    border: none;
    box-shadow: none;
  }

  .admin-menu-slot {
    display: none;
  }

  .category-filter-chips {
    display: none;
  }

  .category-filter-select {
    display: block;
  }

  .info-grid {
    grid-template-columns: 1fr;
  }

  /* .field-group passe forcément en une seule colonne à cette largeur (ses
     pistes de 200px n'y tiennent plus à deux). Le row-gap de la grille sert
     alors à l'espacement *entre* les champs empilés, à la place du
     margin-bottom de chaque .field : dans une grille, la marge du dernier
     item d'une piste est incluse dans la hauteur de cette piste (elle ne
     "sort" pas du conteneur comme en flux normal), donc garder ce
     margin-bottom en plus du gap ajoutait un espace fantôme après le
     dernier champ — doublant l'écart avant l'élément suivant (ex. .note)
     par rapport aux écarts entre champs. */
  .field-group {
    gap: 1rem;
    margin-bottom: 1rem;
  }

  .field-group > .field {
    margin-bottom: 0;
  }

  .field {
    margin-bottom: 1rem;
  }

  /* .note suit toujours un champ (ou le résumé) : son margin-top à 1.25rem
     (collapsé avec le margin-bottom du champ précédent, donc le max des
     deux gagne) restait plus grand que le 1rem désormais uniforme partout
     ailleurs dans le formulaire. */
  .note {
    margin-top: 1rem;
    padding-top: 1rem;
  }

  /* Boutons de formulaire en pleine largeur plutôt que des cibles tactiles
     étroites alignées à droite. Un bouton seul (form > button[type=submit],
     hors .form-actions) prend toute la largeur ; dans .form-actions, chaque
     bouton vise 50/50 (flex-basis 45% + le gap), mais le white-space:nowrap
     empêche de le comprimer sous la largeur de son propre texte — si les
     deux boutons ne tiennent pas côte à côte (libellés longs), ils passent
     chacun sur leur propre ligne en pleine largeur au lieu de rétrécir. */
  form > button[type="submit"] {
    width: 100%;
    margin-left: 0;
  }

  .form-actions {
    flex-wrap: wrap;
  }

  .form-actions > .btn,
  .form-actions > button {
    flex: 1 1 45%;
    white-space: nowrap;
  }

  /* Un bouton icône seule (ex. inverser les unités) doit rester compact,
     pas s'étirer en pleine largeur comme les boutons texte. */
  .form-actions > .icon-btn {
    flex: 0 0 auto;
    white-space: normal;
  }

  /* Le fil d'ariane (.breadcrumb-bar) juste au-dessus prend déjà sa propre
     respiration (padding-top/bottom) : les 3rem de .main s'ajoutaient à ça,
     laissant un vide disproportionné avant le titre de page en mobile. */
  main {
    padding-top: 1.5rem;
  }

  /* Libellé légèrement réduit pour tenir sur une ligne aux côtés de la case
     (ex. "Recevoir une copie de ce message par e-mail" en contact) ; si
     malgré tout il déborde sur plusieurs lignes, flex-start aligne la case
     sur la première ligne du texte plutôt que de la centrer sur tout le
     bloc. */
  .checkbox-row {
    align-items: flex-start;
  }

  .checkbox-row input {
    margin-top: 0.2rem;
  }

  .checkbox-row label {
    font-size: 0.85rem;
  }
}

/* Sélecteur de date et heure (front/components/DateTimePicker.js) : remplace
   <input type="datetime-local">, dont la popup native ne peut pas être
   stylée. Le déclencheur reprend le look d'un champ de formulaire standard
   (bordure/radius/fond identiques à input[type="text"]) ; le panneau
   reprend la structure de .menu-panel (positionnement, ombre, radius). */
.dtpicker {
  position: relative;
}

.dtpicker-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.6rem 0.7rem;
  border: 1.5px solid var(--border-strong);
  border-radius: 8px;
  background: var(--bg);
  color: var(--text);
  font-size: 0.95rem;
  font-weight: 400;
  font-family: inherit;
  text-align: left;
  justify-content: flex-start;
}

.dtpicker-trigger:hover {
  background: var(--bg);
  border-color: var(--accent);
}

.dtpicker-trigger[aria-expanded="true"] {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.dtpicker-trigger .icon {
  color: var(--text-muted);
  flex-shrink: 0;
}

.dtpicker-trigger-placeholder {
  color: var(--text-muted);
}

.dtpicker-panel {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 1000;
  width: max-content;
  min-width: 280px;
  padding: 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
}

.dtpicker-panel[hidden] {
  display: none;
}

.dtpicker-cal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Bouton disclosure au centre de l'en-tête : "juillet 2026" → clic → "2026"
   (grille des 12 mois) → clic → "2020–2031" (grille de 12 ans, plus de
   niveau au-dessus donc désactivé) — évite de cliquer sur "mois suivant"
   des dizaines de fois pour atteindre une date lointaine. */
.dtpicker-cal-label {
  background: none;
  border: none;
  color: var(--text);
  font-weight: 600;
  font-size: 0.9rem;
  font-family: inherit;
  text-transform: capitalize;
  padding: 0.2rem 0.5rem;
  border-radius: 6px;
}

.dtpicker-cal-label:not(:disabled):hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.dtpicker-cal-label:disabled {
  cursor: default;
}

.dtpicker-nav-btn {
  padding: 0.3rem;
  background: none;
  color: var(--text-muted);
  border: none;
  border-radius: 6px;
}

.dtpicker-nav-btn:hover {
  background: var(--accent-soft);
  color: var(--accent);
}

.dtpicker-weekdays {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 0.15rem;
  text-align: center;
}

.dtpicker-weekdays span {
  font-size: 0.75rem;
  color: var(--text-muted);
  padding: 0.2rem 0;
  text-transform: capitalize;
}

/* Grille de cellules générique, réutilisée pour les trois niveaux (jours,
   mois, années) — seul le nombre de colonnes change. */
.dtpicker-cells {
  display: grid;
  gap: 0.15rem;
  text-align: center;
}

.dtpicker-cells-7 {
  grid-template-columns: repeat(7, 1fr);
}

.dtpicker-cells-3 {
  grid-template-columns: repeat(3, 1fr);
}

.dtpicker-cell {
  background: none;
  color: var(--text);
  border: none;
  border-radius: 6px;
  padding: 0.5rem 0;
  font-size: 0.85rem;
  font-weight: 400;
  text-transform: capitalize;
}

.dtpicker-cell:hover {
  background: var(--accent-soft);
}

.dtpicker-cell-muted {
  color: var(--text-muted);
  opacity: 0.5;
}

.dtpicker-cell-current {
  font-weight: 700;
  color: var(--accent);
}

.dtpicker-cell-selected,
.dtpicker-cell-selected:hover {
  background: var(--accent);
  color: var(--accent-contrast);
}

.dtpicker-time {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  border-top: 1px solid var(--border);
  padding-top: 0.7rem;
}

.dtpicker-time input {
  width: 2.75rem;
  padding: 0.4rem 0.3rem;
  text-align: center;
}

.dtpicker-time-sep {
  color: var(--text-muted);
  font-weight: 600;
}

.dtpicker-actions {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
  border-top: 1px solid var(--border);
  padding-top: 0.7rem;
}

/* Impression : un vrai document, pas une capture de l'écran. Au clic sur un
   .print-btn (voir front/components/PrintButton.js et wirePrintButtons()
   dans front/public/js/ui.js), le script reconstruit un résumé texte
   (.print-summary : saisies + résultats + tableau) inséré en fin de <body> ;
   lui et .print-banner (front/components/PrintBanner.js, rendu une seule
   fois dans le layout racine) sont les deux seuls éléments visibles à
   l'impression, tout le reste (nav, formulaire, boutons, notes) est masqué.
   Brique générique du site : ne dépend d'aucune classe propre aux modules
   finance, réutilisable par n'importe quel outil qui pose un .print-btn dans
   son résultat. */
.print-banner,
.print-summary {
  display: none;
}

@media print {
  @page {
    margin: 1.5cm;
  }

  /* Le site entier est déjà piloté par ces variables (voir palette pastel en
     tête de fichier) : les neutraliser ici suffit à rendre panneau, résultats
     et tableau clonés en noir et blanc sans règle supplémentaire. */
  :root {
    --bg: #fff;
    --surface: #fff;
    --border: #ccc;
    --border-strong: #999;
    --text: #000;
    --text-muted: #444;
    --accent: #000;
    --accent-hover: #000;
    --accent-soft: #fff;
    --accent-border: #999;
    --accent-contrast: #000;
    --danger: #000;
    --success: #000;
  }

  body {
    background: #fff;
  }

  .site-header,
  .breadcrumb-bar,
  .site-footer,
  .page-header,
  .panel,
  .no-print {
    display: none !important;
  }

  .print-banner {
    /* !important : doit gagner face à l'attribut `hidden` posé en JSX
       (voir PrintBanner.js) qui masque ce bloc à l'écran. */
    display: flex !important;
    align-items: center;
    gap: 0.9rem;
    padding-bottom: 1rem;
    margin-bottom: 1.5rem;
    border-bottom: 1px solid var(--border-strong);
  }

  .print-banner img {
    height: 42px;
    width: auto;
  }

  .print-banner-text {
    display: flex;
    flex-direction: column;
    gap: 0.15rem;
  }

  .print-banner-link {
    font-weight: 600;
  }

  .print-date {
    font-size: 0.85rem;
    color: var(--text-muted);
  }

  .print-summary {
    display: block;
  }

  .print-summary h1 {
    font-size: 1.4rem;
    margin: 0 0 1.25rem;
  }

  .print-summary section {
    margin-bottom: 1.5rem;
    break-inside: avoid;
  }

  .print-summary h2 {
    font-size: 0.95rem;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--text-muted);
    margin: 0 0 0.6rem;
  }

  .print-summary dl {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 0.35rem 1.5rem;
    margin: 0;
  }

  .print-summary dt {
    font-weight: 600;
  }

  .print-summary dd {
    margin: 0;
  }

  /* Le tableau d'amortissement cloné hérite de .data-table-wrap/.data-table
     (voir plus haut), dont il ne faut garder que la mise en forme (bordures,
     alignement) : la hauteur limitée + défilement de l'écran empêcherait
     sinon d'imprimer les lignes au-delà de 480px. */
  .print-summary .data-table-wrap {
    max-height: none;
    overflow: visible;
    border: none;
    margin-top: 0;
  }

  .print-summary .data-table thead th {
    position: static;
  }

  .print-summary .data-table tr {
    break-inside: avoid;
  }
}
