/* Reusable UI primitives. */

/* Card */
.card {
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-md);
}

/* Button */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  padding: 10px 16px;
  border-radius: var(--r-md);
  border: 1px solid transparent;
  background: var(--c-bg-2);
  color: var(--c-text);
  cursor: pointer;
  font-weight: 500;
  transition: background var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.btn:hover { background: var(--c-bg-3); border-color: var(--c-border-strong); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.55; cursor: not-allowed; }

.btn-primary {
  background: var(--c-accent);
  color: #0a1223;
  font-weight: 600;
}
.btn-primary:hover { background: var(--c-accent-soft); }

.btn-ghost {
  background: transparent;
  border-color: var(--c-border);
}
.btn-ghost:hover { background: var(--c-bg-2); }

.btn-danger {
  background: transparent;
  border-color: var(--c-border);
  color: var(--c-danger);
}
.btn-danger:hover { background: rgba(242,109,109,0.08); }

/* Input */
.input {
  display: block;
  width: 100%;
  padding: 12px 14px;
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  color: var(--c-text);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
  user-select: text;
}
.input::placeholder { color: var(--c-text-dim); }
.input:focus {
  outline: none;
  border-color: var(--c-accent);
  box-shadow: 0 0 0 3px var(--c-accent-ring);
}

/* Labels */
.label {
  display: block;
  font-size: var(--fs-sm);
  color: var(--c-text-soft);
  margin-bottom: var(--sp-2);
  font-weight: 500;
}

/* Code input (pairing) */
.code-input {
  font-family: var(--font-mono);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  text-align: center;
  font-size: var(--fs-xl);
  padding: 16px;
}

/* Status dot */
.dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--c-text-dim);
  box-shadow: 0 0 0 3px rgba(255,255,255,0.03);
}
.dot.dot-ok { background: var(--c-success); box-shadow: 0 0 0 3px rgba(60,207,143,0.15); }
.dot.dot-warn { background: var(--c-warning); box-shadow: 0 0 0 3px rgba(245,180,84,0.15); }
.dot.dot-err { background: var(--c-danger); box-shadow: 0 0 0 3px rgba(242,109,109,0.15); }

/* Status pill */
.pill {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  font-size: var(--fs-sm);
  color: var(--c-text-soft);
}

/* Alert */
.alert {
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  border: 1px solid var(--c-border);
  background: var(--c-bg-2);
  color: var(--c-text-soft);
  font-size: var(--fs-sm);
}
.alert-danger { border-color: rgba(242,109,109,0.35); background: rgba(242,109,109,0.06); color: #ffd3d3; }
.alert-success { border-color: rgba(60,207,143,0.35); background: rgba(60,207,143,0.06); color: #c7f3dd; }

/* Toast — bottom-centered (mobile-first, also fine on desktop). The vertical
   offset clears the composer so toasts never overlap the input. Centered
   horizontally to avoid the jump-to-bottom pill at right: 16px, bottom: 80px. */
.toast-root {
  position: fixed;
  bottom: 80px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  z-index: 1000;
  pointer-events: none;
}
/* On mobile the composer is taller (safe-area + bigger touch targets), so
   bump the offset to keep the toast clear of it. */
@media (max-width: 720px) {
  .toast-root { bottom: 110px; }
}
.toast {
  pointer-events: auto;
  padding: var(--sp-3) var(--sp-4);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  font-size: var(--fs-sm);
  max-width: 360px;
  animation: toast-in 200ms var(--ease-out);
}
.toast-err { border-color: rgba(242,109,109,0.4); }
.toast-ok { border-color: rgba(60,207,143,0.4); }
.toast-msg { margin-right: var(--sp-3); }
.toast-action {
  border: 0;
  background: transparent;
  color: var(--c-accent);
  font: inherit;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--r-sm);
}
.toast-action:hover { background: var(--c-bg-3); }
@keyframes toast-in {
  from { transform: translateX(20px); opacity: 0; }
  to { transform: translateX(0); opacity: 1; }
}

/* Spinner */
.spinner {
  width: 16px;
  height: 16px;
  border: 2px solid var(--c-border-strong);
  border-top-color: var(--c-accent);
  border-radius: 50%;
  animation: spin 800ms linear infinite;
  display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Avatar — initials fallback by default. Border-radius is governed by the
   `[data-avatar-shape]` attribute on <html>, so the same DOM markup adapts
   to the user's "circle" / "square" preference. */
.avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--c-accent), #8b5bff);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #0a1223;
  font-weight: 700;
  font-size: var(--fs-md);
  user-select: none;
  flex-shrink: 0;
  overflow: hidden;
  position: relative;
}
:root[data-avatar-shape="square"] .avatar { border-radius: var(--r-md); }

/* When the avatar carries an image we drop the gradient background — the
   image fills the box. The img sits absolutely so initials below (if any)
   are hidden behind it (and we never render text at the same time anyway,
   per renderAvatar(...)). */
.avatar.avatar-has-image { background: var(--c-bg-2); color: transparent; }
.avatar-img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}

/* STORIES (0.c) — anneau autour de l'avatar d'un contact qui a une story active.
   `overflow:hidden` sur .avatar interdit une bordure (elle serait rognée) → on utilise
   un DOUBLE box-shadow : un liseré de la couleur du fond (le "gap") puis l'anneau
   coloré. `avatar-story-unseen` = story(s) non vue(s) → anneau vif (dégradé accent) ;
   `avatar-story-seen` = tout vu → anneau gris discret. Cliquable (role=button). */
.avatar.avatar-story {
  cursor: pointer;
  box-shadow: 0 0 0 2px var(--c-bg-1), 0 0 0 4px var(--c-border);
}
.avatar.avatar-story-unseen {
  /* Anneau vif : on ne peut pas mettre un dégradé en box-shadow → couleur accent. */
  box-shadow: 0 0 0 2px var(--c-bg-1), 0 0 0 4px var(--c-accent);
}
.avatar.avatar-story-seen {
  box-shadow: 0 0 0 2px var(--c-bg-1), 0 0 0 4px var(--c-border);
}
.avatar.avatar-story:hover { box-shadow: 0 0 0 2px var(--c-bg-1), 0 0 0 4px var(--c-accent), 0 0 8px var(--c-accent-ring); }
.avatar.avatar-story:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }

/* STORIES Phase 1 — ÉDITEUR plein écran (1.0 = squelette). Fond noir volontaire
   (comme le viewer). Colonne : topbar (fermer) / zone média 9:16 centrée / toolbar
   (calques 1.1+) / bottombar (Continuer). Le cadre 9:16 est dimensionné en JS (fit9x16). */
.story-editor {
  position: fixed;
  inset: 0;
  z-index: 1001;
  background: #000;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.story-editor-topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  padding: 12px;
  z-index: 2;
}
.story-editor-close {
  background: rgba(0, 0, 0, 0.4);
  color: #fff;
  border: 0;
  width: 36px; height: 36px;
  border-radius: 50%;
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.story-editor-close:hover { background: rgba(0, 0, 0, 0.6); }
/* Zone média : prend l'espace restant, centre le cadre 9:16 (les 2 axes). */
.story-editor-zone {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* Le cadre 9:16 : taille posée en JS (fit9x16), ne grandit pas. */
.story-editor-frame {
  position: relative;
  border-radius: 12px;
  overflow: hidden;
  background: #111;
  flex: 0 0 auto;
}
.story-editor-media {
  width: 100%;
  height: 100%;
  object-fit: contain;   /* le cadre épouse le ratio du média → contain = média ENTIER, sans rognage */
  display: block;
}
/* STORIES 1.1/1.2 — canvas OVERLAY des calques, épouse exactement le cadre 9:16, au-dessus
   du média DOM. Sa résolution device (width/height) est posée en JS (drawLayers) pour un
   rendu net ; le CSS l'étire à 100%×100%. Reçoit les événements pointeur (1.2 : sélection ;
   1.3+ : déplacer/redimensionner). touch-action:none = on gère nous-mêmes le geste (pas de
   scroll/zoom natif du navigateur qui volerait le drag). */
.story-editor-layers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  touch-action: none;
}
.story-editor-toolbar {
  flex: 0 0 auto;
  display: flex;
  gap: 10px;
  /* Beaucoup d'outils (texte/GIF/emoji/dessin/musique/filtre/z-order/animer/recentrer/lire) →
     défiler HORIZONTALEMENT plutôt que compresser les boutons (retour terrain S23 : icônes
     écrasées). Centré tant que ça tient ; scroll dès que ça déborde. Les boutons gardent leur taille. */
  justify-content: safe center;
  align-items: center;
  padding: 8px 12px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;   /* Firefox : pas de barre visible (le scroll tactile reste) */
}
.story-editor-toolbar::-webkit-scrollbar { display: none; }   /* WebKit : idem */
/* Outils de calque (z-order 1.5, puis texte/sticker en Phase 2). Boutons carrés lisibles
   sur le fond noir de l'éditeur. Ne rétrécissent JAMAIS (flex-shrink:0) → défilent au lieu d'être écrasés. */
.story-editor-tool {
  flex: 0 0 auto;
  width: 40px;
  height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.10);
  color: #fff;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.story-editor-tool:hover:not(:disabled) { background: rgba(255, 255, 255, 0.20); }
.story-editor-tool:disabled { opacity: 0.35; cursor: default; }
.story-editor-tool-text { font-weight: 800; font-size: 18px; }

/* ⭐ FILTRES (Phase 3) : bottom-sheet de vignettes. .story-filter-panel = fond (la classe
   .gif-panel-story ne donne QUE la position bottom-sheet). Rangée scrollable horizontale. */
.story-filter-panel {
  background: rgba(12, 12, 14, 0.96);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px));
}
/* En-tête du panneau filtres : titre à gauche, bouton OK (sortie) à droite. */
.story-filter-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.story-filter-title { color: #fff; font-size: 14px; font-weight: 700; }
.story-filter-ok {
  border: none;
  border-radius: 999px;
  padding: 6px 20px;
  font-size: 14px;
  font-weight: 700;
  background: #5adcff;
  color: #06202a;
  cursor: pointer;
}
.story-filter-row {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.story-filter-tile {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
}
.story-filter-thumb {
  width: 60px;
  height: 84px;           /* 9:16 comme la story */
  border-radius: 10px;
  overflow: hidden;
  border: 2px solid transparent;
  background: #000;
}
.story-filter-thumb img, .story-filter-thumb video {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.story-filter-tile.sel .story-filter-thumb { border-color: #5adcff; }
.story-filter-name { color: #fff; font-size: 12px; line-height: 1; opacity: 0.9; }
.story-filter-tile.sel .story-filter-name { opacity: 1; font-weight: 700; }

/* ⭐ DESSIN (Phase 3) : bottom-sheet du mode crayon (couleur + épaisseur + effacer/OK). Même socle
   que le panneau filtres. */
.story-draw-panel {
  background: rgba(12, 12, 14, 0.96);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px));
}

/* ⭐ MUSIQUE (Phase 5) : bottom-sheet liste de morceaux + trim/écoute. Même socle que les filtres. */
.story-music-panel {
  background: rgba(12, 12, 14, 0.96);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  padding: 12px 12px calc(12px + env(safe-area-inset-bottom, 0px));
}
.story-music-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.story-music-title { color: #fff; font-size: 14px; font-weight: 700; }
.story-music-ok {
  border: none;
  border-radius: 999px;
  padding: 6px 20px;
  font-size: 14px;
  font-weight: 700;
  background: #5adcff;
  color: #06202a;
  cursor: pointer;
}
.story-music-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  max-height: 40vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
.story-music-item {
  display: flex;
  align-items: center;
  text-align: left;
  border: 1px solid transparent;
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  border-radius: 10px;
  padding: 10px 12px;
  font-size: 14px;
  cursor: pointer;
}
.story-music-item.sel {
  border-color: #5adcff;
  background: rgba(90, 220, 255, 0.14);
  font-weight: 700;
}
.story-music-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.story-music-empty { color: rgba(255, 255, 255, 0.6); font-size: 13px; padding: 8px 4px; }
.story-music-trim {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 12px;
}
.story-music-trim-row { display: flex; align-items: center; gap: 12px; }
.story-music-play {
  flex: 0 0 auto;
  width: 44px;
  height: 40px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 10px;
  font-size: 16px;
  cursor: pointer;
}
/* Bande = tout le morceau ; zone surlignée = la fenêtre de story ; trait = position de lecture. */
.story-music-track {
  position: relative;
  flex: 1 1 auto;
  height: 32px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.10);
  overflow: hidden;
  cursor: pointer;
  touch-action: none;   /* le drag horizontal navigue dans le morceau, ne scrolle pas le panneau */
}
.story-music-window {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 12%;
  background: rgba(90, 220, 255, 0.35);
  border: 1px solid #5adcff;
  border-radius: 6px;
  box-sizing: border-box;
  pointer-events: none;
}
.story-music-playhead {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 2px;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}
.story-music-start { flex: 1 1 auto; accent-color: #5adcff; }
.story-music-time {
  flex: 0 0 auto;
  color: #fff;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  min-width: 74px;
  text-align: right;
}
.story-draw-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
}
.story-draw-title { color: #fff; font-size: 14px; font-weight: 700; }
/* Bascule crayon / gomme (en-tête du panneau dessin). */
.story-draw-toolset { display: flex; gap: 8px; }
.story-draw-toolbtn {
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 10px;
  width: 40px;
  height: 34px;
  font-size: 17px;
  cursor: pointer;
}
.story-draw-toolbtn.sel {
  background: #5adcff;
  border-color: #5adcff;
}
.story-draw-ok {
  border: none;
  border-radius: 999px;
  padding: 6px 20px;
  font-size: 14px;
  font-weight: 700;
  background: #5adcff;
  color: #06202a;
  cursor: pointer;
}
.story-draw-swatches {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 2px;
}
.story-draw-swatch {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.35);
  padding: 0;
  cursor: pointer;
}
.story-draw-swatch.sel {
  border-color: #5adcff;
  box-shadow: 0 0 0 2px rgba(90, 220, 255, 0.4);
}
.story-draw-tools {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
}
.story-draw-width { flex: 1 1 auto; accent-color: #5adcff; }
.story-draw-act {
  flex: 0 0 auto;
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 10px;
  width: 40px;
  height: 34px;
  font-size: 16px;
  cursor: pointer;
}
/* Curseur crayon sur la toile pendant le mode dessin + bouton actif dans la toolbar. */
.story-editor-layers.is-drawing { cursor: crosshair; }
.story-editor-layers.is-erasing { cursor: cell; }   /* gomme : curseur distinct */
.story-editor-tool.is-active {
  background: #5adcff;
  color: #06202a;
  border-color: #5adcff;
}

/* Badge DURÉE D'AFFICHAGE (topbar éditeur) : « ⏱ 5 s », cliquable (image) → popup curseur ; pour
 * une vidéo, informatif (durée réelle), non cliquable. */
.story-editor-duration {
  margin-left: auto;   /* pousse le badge à droite (le ✕ reste à gauche) */
  border: 1px solid rgba(255, 255, 255, 0.28);
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  font: inherit;
  font-size: 14px;
  font-weight: 600;
  padding: 6px 14px;
  border-radius: 100px;
  cursor: pointer;
}
.story-editor-duration:hover:not(:disabled) { background: rgba(255, 255, 255, 0.18); }
.story-editor-duration:disabled { cursor: default; opacity: 0.85; }
.story-editor-duration.is-video { border-style: dashed; }

/* STORIES A3 — PANNEAU D'ANIMATION (bottom-sheet, au-dessus de l'éditeur). Le fond translucide
 * ferme au tap ; la feuille monte du bas. Sections empilées avec interrupteur + mini-aperçus. */
.story-anim-backdrop {
  position: fixed; inset: 0; z-index: 1002;
  background: rgba(0, 0, 0, 0.45);
  opacity: 0; transition: opacity 0.18s ease;
}
.story-anim-backdrop.open { opacity: 1; }
.story-anim-panel {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 1003;
  max-height: 72vh; display: flex; flex-direction: column;
  background: var(--c-bg-1); color: var(--c-text);
  border-top-left-radius: 18px; border-top-right-radius: 18px;
  border-top: 1px solid var(--c-border);
  box-shadow: 0 -12px 40px rgba(0, 0, 0, 0.5);
  transform: translateY(100%); transition: transform 0.22s cubic-bezier(0.2, 0.9, 0.3, 1);
  padding-bottom: env(safe-area-inset-bottom, 0);
}
.story-anim-panel.open { transform: translateY(0); }
.story-anim-head {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  padding: 14px 18px 8px; position: relative;
}
.story-anim-head::before {   /* poignée de bottom-sheet */
  content: ""; position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
  width: 38px; height: 4px; border-radius: 3px; background: var(--c-border-strong);
}
.story-anim-title { font-weight: 700; font-size: 16px; }
.story-anim-done {
  border: 0; background: var(--c-accent); color: #fff; font-weight: 600; font-size: 14px;
  padding: 7px 18px; border-radius: 100px; cursor: pointer;
}
.story-anim-body { flex: 1 1 auto; overflow-y: auto; padding: 4px 16px 18px; }

.story-anim-section { border-top: 1px solid var(--c-border); padding: 14px 0 4px; }
.story-anim-section:first-child { border-top: 0; }
.story-anim-sec-head { display: flex; align-items: center; justify-content: space-between; }
.story-anim-sec-name { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 14px; }
.story-anim-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.story-anim-dot-show { background: #b06cf5; }
.story-anim-dot-move { background: #3ad6a0; }
.story-anim-dot-hide { background: #ec4899; }
.story-anim-sec-hint { color: var(--c-text-dim); font-weight: 400; font-size: 12px; margin-left: 2px; }
.story-anim-sec-body { margin-top: 12px; }
.story-anim-section.off .story-anim-sec-body { display: none; }

/* Interrupteur on/off */
.story-anim-sw {
  width: 42px; height: 24px; border-radius: 100px; background: var(--c-bg-3);
  border: 1px solid var(--c-border); position: relative; cursor: pointer; flex: 0 0 auto;
  transition: background 0.15s;
}
.story-anim-sw i { position: absolute; top: 2px; left: 2px; width: 18px; height: 18px; border-radius: 50%; background: var(--c-text-dim); transition: transform 0.15s, background 0.15s; }
.story-anim-sw.on { background: var(--c-accent-weak); border-color: var(--c-accent-ring); }
.story-anim-sw.on i { transform: translateX(18px); background: var(--c-accent); }

/* Grille de vignettes d'effets (mini-aperçus animés) */
.story-anim-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 10px; }
.story-anim-tile {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: var(--c-bg-2); border: 1px solid var(--c-border); border-radius: var(--r-md);
  padding: 10px 6px 7px; cursor: pointer; font: inherit; color: var(--c-text);
}
.story-anim-tile:hover { border-color: var(--c-border-strong); }
.story-anim-tile.sel { border-color: var(--c-accent); background: var(--c-accent-weak); }
.story-anim-demo {
  width: 34px; height: 34px; display: grid; place-items: center; font-size: 22px; color: var(--c-text);
  will-change: transform, opacity;
}
.story-anim-demo-text { font-weight: 800; font-size: 15px; }
.story-anim-neon { color: #fff; text-shadow: 0 0 3px #fff, 0 0 9px #ec4899, 0 0 17px #b06cf5; }
.story-anim-label { font-size: 10.5px; color: var(--c-text-soft); text-align: center; line-height: 1.15; }

/* Lignes de réglage (délai, mode, moment) */
.story-anim-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
.story-anim-row label { font-size: 13px; color: var(--c-text-soft); min-width: 82px; }
.story-anim-slider { flex: 1; accent-color: var(--c-accent); min-width: 0; }
.story-anim-val { font-variant-numeric: tabular-nums; font-weight: 600; font-size: 13px; min-width: 44px; text-align: right; }
.story-anim-seg { display: inline-flex; background: var(--c-bg-2); border: 1px solid var(--c-border); border-radius: var(--r-md); padding: 2px; gap: 2px; }
.story-anim-seg-btn { border: 0; background: transparent; color: var(--c-text-soft); font: inherit; font-size: 12.5px; font-weight: 600; padding: 5px 14px; border-radius: 8px; cursor: pointer; }
.story-anim-seg-btn.on { background: var(--c-accent-weak); color: var(--c-accent); }

/* FRISE temporelle (en haut du panneau) : montre où se placent apparition/mouvement/disparition
 * sur la durée d'affichage réelle. */
.story-anim-timeline { padding: 0 16px 6px; flex: 0 0 auto; }
.story-anim-tl-track {
  position: relative; height: 34px; border-radius: 8px;
  background: var(--c-bg-2); border: 1px solid var(--c-border); overflow: hidden;
}
.story-anim-tl-seg { position: absolute; top: 0; bottom: 0; }
.story-anim-tl-show { left: 0; background: color-mix(in srgb, #b06cf5 22%, transparent); border-right: 1px solid #b06cf5; }
.story-anim-tl-hide { right: 0; background: color-mix(in srgb, #ec4899 20%, transparent); border-left: 1px solid #ec4899; }
.story-anim-tl-lbl { position: absolute; top: 50%; transform: translateY(-50%); font-size: 10px; font-weight: 600; white-space: nowrap; pointer-events: none; }
.story-anim-tl-a { left: 8px; color: #c79cff; }
.story-anim-tl-m { left: 50%; transform: translate(-50%, -50%); color: var(--c-text-soft); }
.story-anim-tl-h { right: 8px; color: #f48ec0; }
.story-anim-tl-scale { display: flex; justify-content: space-between; margin-top: 5px; font-size: 10px; color: var(--c-text-dim); }

/* Aperçu ▶ dans l'éditeur (overlay DOM temporaire par-dessus le cadre) */
.story-editor-anim-preview { z-index: 5; }

@media (prefers-reduced-motion: reduce) {
  .story-anim-panel, .story-anim-backdrop { transition: none; }
  .story-anim-demo { animation: none !important; }
}

/* STORIES Phase 2 — SAISIE de texte (overlay plein écran au-dessus de l'éditeur). Fond
   sombre translucide ; le champ est centré, WYSIWYG (police/couleur/surlignage aperçu). */
.story-text-input {
  position: fixed;
  inset: 0;
  z-index: 1002;   /* AU-DESSUS de l'éditeur (.story-editor = 1001), sinon la saisie passe
                      derrière le média : clavier déplié mais champ invisible (bug terrain). */
  background: rgba(0, 0, 0, 0.72);
  display: flex;
  flex-direction: column;
}
/* PIPETTE : overlay plein écran AU-DESSUS de la saisie texte (1002) pour choisir une couleur dans
   la photo. Le canvas remplit l'écran (cover) ; hint + pastille en superposition ; bouton Fermer. */
.story-pipette {
  position: fixed;
  inset: 0;
  z-index: 1004;
  background: #000;
  overflow: hidden;
  touch-action: none;   /* le doigt sert à prélever, pas à scroller */
}
.story-pipette-canvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.story-pipette-hint {
  position: absolute; left: 50%; top: calc(14px + env(safe-area-inset-top, 0px)); transform: translateX(-50%);
  background: rgba(0, 0, 0, 0.6); color: #fff; padding: 6px 14px; border-radius: 999px;
  font-size: 15px; font-variant-numeric: tabular-nums; pointer-events: none;
}
.story-pipette-swatch {
  position: absolute; right: 16px; top: calc(14px + env(safe-area-inset-top, 0px));
  width: 40px; height: 40px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px #000;
  pointer-events: none;
}
.story-pipette > .story-text-btn {
  position: absolute; left: 50%; bottom: calc(20px + env(safe-area-inset-bottom, 0px)); transform: translateX(-50%);
}
.story-text-topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 14px calc(10px + env(safe-area-inset-top, 0px));
}
.story-text-btn {
  border: none;
  border-radius: 999px;
  padding: 8px 18px;
  font-size: 15px;
  font-weight: 700;
  cursor: pointer;
}
.story-text-cancel { background: rgba(255, 255, 255, 0.16); color: #fff; }
.story-text-ok { background: #fff; color: #111; }
.story-text-hl {
  border: 1px solid rgba(255, 255, 255, 0.4);
  background: transparent;
  color: #fff;
  border-radius: 999px;
  padding: 7px 14px;
  font-size: 13px;
  cursor: pointer;
}
.story-text-hl.on { background: #fff; color: #111; }
.story-text-centre {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
}
/* ⭐ APERÇU WYSIWYG FIDÈLE : un <textarea> ne sait PAS peindre un dégradé de texte
   (background-clip:text est ignoré sur un contrôle de formulaire) ni un néon propre. On
   superpose donc le champ (saisie, texte rendu TRANSPARENT, curseur visible) sur un
   div-MIROIR qui, lui, peint le vrai style (uni/néon/dégradé/surlignage). Les deux partagent
   la même boîte, typo et centrage → ce qu'on voit = ce qu'on obtient une fois posé. */
.story-text-stage {
  position: relative;
  width: 100%;
  max-width: 90vw;
  display: flex;
  align-items: center;
  justify-content: center;
}
.story-text-mirror {
  /* même boîte que le champ, DERRIÈRE lui. pointer-events:none → les taps vont au textarea. */
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  white-space: pre-wrap;
  word-break: break-word;
  pointer-events: none;
  overflow: visible;
}
.story-text-mirror-line { display: inline-block; }
.story-text-area {
  position: relative;
  z-index: 1;   /* le champ (curseur + saisie) AU-DESSUS du miroir */
  width: 100%;
  max-width: 90vw;
  background: transparent;
  border: none;
  outline: none;
  resize: none;
  text-align: center;
  font-size: 32px;
  line-height: 1.25;
  /* Texte rendu TRANSPARENT : c'est le miroir qui peint. Le curseur reste visible (caret-color). */
  color: transparent;
  caret-color: #fff;
  overflow: hidden;
  /* la hauteur s'ajuste au contenu via rows + le champ grandit visuellement */
  field-sizing: content;
}
.story-text-area::placeholder { color: rgba(255, 255, 255, 0.5); -webkit-text-fill-color: rgba(255, 255, 255, 0.5); }
.story-text-controls {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 10px 14px calc(12px + env(safe-area-inset-bottom, 0px));
}
/* Onglets « Texte » / « Fond » : la palette pilote la couleur de l'un ou l'autre. */
.story-text-tabs { display: flex; gap: 8px; justify-content: center; }
.story-text-tab {
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 999px;
  padding: 5px 18px;
  font-size: 14px;
  font-weight: 700;
  cursor: pointer;
}
.story-text-tab.selected { background: #fff; color: #111; }
.story-text-colors, .story-text-fonts { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
/* Pastille « Aucun » (pas de surlignage) : croix barrée sur fond neutre. */
.story-text-none {
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 15px;
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.story-text-color {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.5);
  cursor: pointer;
  padding: 0;
}
.story-text-color.selected { border-color: #fff; box-shadow: 0 0 0 2px #111, 0 0 0 4px #fff; }
.story-text-font {
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 8px;
  padding: 6px 14px;
  font-size: 15px;
  cursor: pointer;
}
.story-text-font.selected { background: #fff; color: #111; }
.story-text-extras { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }
/* ⭐ COURBE : slider -100..100. Libellé + curseur extensible + valeur + reset. */
.story-text-curve-row { display: flex; align-items: center; gap: 10px; justify-content: center; }
.story-text-curve-label { color: #fff; font-size: 13px; font-weight: 700; opacity: 0.85; white-space: nowrap; }
.story-text-curve { flex: 1 1 auto; max-width: 260px; accent-color: #5adcff; }
.story-text-curve-val { color: #fff; font-size: 13px; min-width: 2.5em; text-align: right; font-variant-numeric: tabular-nums; }
.story-text-curve-reset {
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 8px;
  padding: 4px 10px;
  font-size: 15px;
  cursor: pointer;
}
/* ⭐ DÉGRADÉ « Perso » : libellé + 2 pastilles Début/Fin. Cliquer une pastille la sélectionne ;
   la palette/pipette pilotent alors CETTE borne. Un ruban du dégradé courant sert de témoin. */
.story-text-grad-custom {
  display: flex;
  align-items: center;
  gap: 10px;
  justify-content: center;
  flex-wrap: wrap;
}
.story-text-grad-custom[hidden] { display: none !important; }
.story-text-grad-label { color: #fff; font-size: 13px; font-weight: 700; opacity: 0.85; }
.story-text-grad-stops { display: inline-flex; gap: 8px; }
.story-text-grad-stop {
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.5);
  cursor: pointer;
  padding: 0;
  position: relative;
}
.story-text-grad-stop.selected { border-color: #fff; box-shadow: 0 0 0 2px #111, 0 0 0 4px #fff; }
/* Mini-étiquette « D » / « F » pour distinguer Début et Fin sans ambiguïté. */
.story-text-grad-stop::after {
  content: attr(data-tag);
  position: absolute;
  inset: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 800;
  color: #fff;
  text-shadow: 0 0 2px #000, 0 0 3px #000;
  pointer-events: none;
}
.story-text-grad-ribbon {
  width: 64px; height: 16px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.4);
}
.story-text-neon {
  border: 1px solid rgba(255, 255, 255, 0.3);
  background: rgba(255, 255, 255, 0.08);
  color: #fff;
  border-radius: 8px;
  padding: 6px 16px;
  font-size: 15px;
  cursor: pointer;
}
/* Actif : on ÉVOQUE l'effet (halo lumineux cyan) pour que le bouton « ressemble » à ce qu'il fait. */
.story-text-neon.selected {
  background: rgba(90, 220, 255, 0.15);
  border-color: #5adcff;
  color: #eafaff;
  text-shadow: 0 0 4px #5adcff, 0 0 10px #5adcff;
}
.story-editor-bottombar {
  flex: 0 0 auto;
  display: flex;
  justify-content: flex-end;
  padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 2;
}

/* STORIES 0.d — VIEWER plein écran. Fond noir volontaire (comme tout viewer de
   story, indépendant du thème clair/sombre) ; le média est centré, les barres de
   progression segmentées en haut, deux zones de tap invisibles gauche/droite. */
.story-viewer {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: #000;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
/* Voile dégradé en haut : garde barres/avatar/nom/✕ lisibles sur un média clair. */
.story-viewer::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 96px;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45), transparent);
  z-index: 1;
  pointer-events: none;
}
.story-bars {
  position: absolute;
  top: 0; left: 0; right: 0;
  display: flex;
  gap: 4px;
  padding: 10px 12px 0;
  z-index: 3;
}
.story-bar {
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: rgba(255, 255, 255, 0.3);
  overflow: hidden;
}
.story-bar-fill {
  height: 100%;
  width: 0;
  background: #fff;
  border-radius: 2px;
  transition: width 60ms linear;
}
.story-header {
  position: absolute;
  top: 26px; left: 12px;
  display: flex;
  align-items: center;
  gap: 10px;
  z-index: 3;
  color: #fff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.story-header .avatar { width: 34px; height: 34px; font-size: var(--fs-sm); }
.story-header-main { display: flex; flex-direction: column; line-height: 1.2; }
.story-author { font-weight: 700; font-size: var(--fs-sm); }
.story-when { font-size: 12px; opacity: 0.85; }
.story-stage {
  max-width: 100%;
  max-height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.story-media {
  max-width: 100vw;
  max-height: 100vh;
  object-fit: contain;
  display: block;
}
/* STORIES 1.6 — REJOUER la scène : quand une story porte des calques, le média passe en
   COVER dans un cadre 9:16 (comme l'éditeur) + un <canvas> overlay dessine les calques.
   Le cadre prend la plus grande taille 9:16 tenant dans le stage (aspect-ratio + max). */
.story-frame {
  position: relative;
  /* --story-ar = ratio largeur/hauteur du média, posé en JS (défaut 9/16). Le cadre est le plus
     grand rectangle de ce ratio tenant dans le viewport : largeur bornée par 100vw ET par la
     hauteur (100vh × ratio). aspect-ratio dérive la hauteur. Épouse tout ratio (portrait haut,
     carré, paysage) → le média s'affiche EN ENTIER (contain), jamais rogné. */
  --story-ar: calc(9 / 16);
  aspect-ratio: var(--story-ar);
  width: min(100vw, calc(100vh * var(--story-ar)));
  height: auto;
  max-width: 100vw;
  max-height: 100vh;
  overflow: hidden;
}
.story-media-cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;   /* le cadre épouse le ratio du média → contain = média ENTIER, sans rognage */
  display: block;
}
.story-frame-layers {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}
/* A1 — overlay DOM des calques du VIEWER (remplace le canvas ci-dessus). Plein cadre, sert de
 * repère de positionnement absolu pour les nœuds de calques (renderStoryDOM les pose en absolu).
 * Le viewer ne manipule pas les calques → pointer-events:none. overflow visible : un calque animé
 * peut déborder légèrement (façon sticker). */
.story-frame-layers-dom {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  overflow: visible;
}
/* Structure à deux niveaux (A2) : le POSITIONNEUR porte le placement (inline : left/top/width/
 * height/transform de centrage+rotation) — il ne bouge jamais. */
.story-dom-layer-pos {
  position: absolute;
}
/* Le nœud ANIMÉ (interne, plein cadre du positionneur) reçoit l'animation CSS sans perturber le
 * centrage. will-change préparé pour A2. */
.story-dom-layer {
  will-change: transform, opacity;
}
/* Zones de tap invisibles (1/3 gauche = précédent, 2/3 droite = suivant). */
.story-tap {
  position: absolute;
  top: 0; bottom: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  z-index: 2;
  -webkit-tap-highlight-color: transparent;
}
.story-tap-prev { left: 0; width: 33%; }
.story-tap-next { right: 0; width: 67%; }
.story-close {
  position: absolute;
  top: 16px; right: 14px;
  z-index: 4;
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  border: 0;
  width: 34px; height: 34px;
  border-radius: 50%;
  font-size: 18px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
.story-close:hover { background: rgba(0, 0, 0, 0.55); }
.story-loading, .story-error {
  color: rgba(255, 255, 255, 0.85);
  font-size: var(--fs-sm);
  display: flex;
  align-items: center;
  justify-content: center;
}
.story-spinner {
  width: 34px; height: 34px;
  border: 3px solid rgba(255, 255, 255, 0.25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: story-spin 0.8s linear infinite;
}
@keyframes story-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .story-spinner { animation-duration: 2s; }
  .story-bar-fill { transition: none; }
}

/* STORIES 0.e — sélecteur de durée à la publication (dans la modale). */
.story-duration-hint { margin: 0 0 var(--sp-3); color: var(--c-text-dim); font-size: var(--fs-sm); }
.story-duration-list {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}
.story-duration-opt {
  text-align: left;
  padding: var(--sp-3);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-1);
  color: var(--c-text);
  font-size: var(--fs-md);
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.story-duration-opt:hover { background: var(--c-bg-2); }
.story-duration-opt.selected {
  border-color: var(--c-accent);
  background: var(--c-accent-weak);
  font-weight: 600;
}

/* STORIES — curseur « Rester à l'écran » (durée d'AFFICHAGE image, distinct de l'expiration). */
.story-display-section {
  padding-bottom: var(--sp-3);
  margin-bottom: var(--sp-3);
  border-bottom: 1px solid var(--c-border);
}
.story-display-row { display: flex; align-items: center; gap: var(--sp-3); }
.story-display-slider {
  flex: 1;
  accent-color: var(--c-accent);   /* piste + curseur teintés accent (clair+sombre via le token) */
  min-width: 0;
}
.story-display-val {
  min-width: 3.5em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--c-text);
}

/* STORIES — « Gérer mes stories » : liste miniature + durée restante + supprimer. */
.manage-stories-list { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 260px; }
.manage-stories-empty { color: var(--c-text-dim); font-size: var(--fs-sm); margin: var(--sp-2) 0; }
.manage-story-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-1);
}
/* Vignette VERTICALE 9:16 : une story EST portrait → on la montre entière (pas de crop
   carré). Plus grande qu'avant (54×96) pour vraiment distinguer la scène + ses calques. */
.manage-story-thumb {
  width: 54px; height: 96px;
  border-radius: var(--r-sm);
  object-fit: cover;
  flex-shrink: 0;
  background: var(--c-bg-2);
}
.manage-story-thumb-empty { display: inline-flex; align-items: center; justify-content: center; font-size: 26px; }
.manage-story-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.manage-story-posted { font-size: var(--fs-sm); color: var(--c-text); font-weight: 600; }
.manage-story-when { font-size: 12px; color: var(--c-text-dim); }
.manage-story-del { flex-shrink: 0; }
.btn-sm { padding: var(--sp-1) var(--sp-2); font-size: var(--fs-sm); }

/* Topbar avatar — clickable button wrapper around `.avatar` that opens the
   profile editor. Inherits the avatar shape via `[data-avatar-shape]`. */
.topbar-avatar-btn {
  background: transparent;
  border: 0;
  padding: 0;
  cursor: pointer;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
:root[data-avatar-shape="square"] .topbar-avatar-btn { border-radius: var(--r-md); }
.topbar-avatar-btn:hover .avatar { box-shadow: 0 0 0 3px var(--c-accent-ring); }
.topbar-avatar-btn:focus-visible {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}

/* Small button variant */
.btn-sm {
  padding: 6px 12px;
  font-size: var(--fs-sm);
}

/* Badge */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--c-accent);
  color: #0a1223;
  font-size: var(--fs-xs);
  font-weight: 700;
}

/* Topbar kebab (⋮) menu — mobile primarily but works on desktop too. */
.topbar-kebab-wrap {
  position: relative;
  /* Hidden on desktop; the media query in pwa.css flips it on. */
  display: none;
}
.topbar-kebab {
  width: 36px; height: 36px;
  padding: 0;
  font-size: 20px;
  line-height: 1;
  border-radius: 50%;
}
.topbar-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  min-width: 180px;
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-lg);
  padding: 4px;
  /* Higher than .topbar (z-index: 10) and .thread-root overlay (z-index: 2),
     so the menu always paints above everything else in the chat screen. */
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.topbar-menu[hidden] { display: none; }
.topbar-menu-item {
  background: transparent;
  border: 0;
  color: var(--c-text);
  text-align: left;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  cursor: pointer;
  font: inherit;
}
.topbar-menu-item:hover { background: var(--c-bg-2); }
.topbar-menu-item-danger { color: var(--c-danger); }
.topbar-menu-item-danger:hover { background: rgba(242,109,109,0.08); }

/* Modal */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.55);
  display: grid;
  place-items: center;
  z-index: 500;
  animation: fade-in 160ms var(--ease-out);
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.modal-content {
  width: min(440px, calc(100% - 2 * var(--sp-5)));
  /* Cap the modal height to the viewport (minus a small breathing margin)
     and lay it out as a flex column so the body region can be the only
     scroller. Without this, a tall body (e.g. profile editor with avatar
     picker on a small phone) pushes the action row past the bottom of the
     screen and the user has no way to reach the buttons.
     Height source: `--app-height` (written by viewportFix.js from
     visualViewport, i.e. the height ACTUALLY visible above the keyboard) —
     NOT `dvh`, because several Android WebViews report dvh support but don't
     shrink it when the keyboard opens (the very bug viewportFix works around).
     Falling back to dvh only for the first frame / non-mobile. */
  max-height: calc(var(--app-height, 100dvh) - 2 * var(--sp-4));
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-lg);
  animation: modal-in 180ms var(--ease-out);
}
@keyframes modal-in {
  from { transform: translateY(8px) scale(0.98); opacity: 0; }
  to   { transform: translateY(0) scale(1); opacity: 1; }
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--sp-4) var(--sp-5);
  border-bottom: 1px solid var(--c-border);
}
.modal-title { font-size: var(--fs-lg); }
.modal-close {
  background: transparent;
  border: 0;
  color: var(--c-text-soft);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--r-sm);
}
.modal-close:hover { background: var(--c-bg-2); color: var(--c-text); }

/* The body is the only scroller inside the modal — header stays pinned at
   the top, .modal-actions (rendered inside .modal-form) flows with the
   body content so it stays accessible at the bottom of the scroll. */
.modal-body {
  padding: var(--sp-5);
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  /* Reserve room at the bottom so scrollIntoView() (fired on input focus,
     see modal.js) never parks a field UNDER the sticky .modal-actions bar
     (the opaque Cancel/Save row that would otherwise cover it). ~64px covers
     the action row's height + padding. */
  scroll-padding-bottom: 72px;
}

/* Header doesn't shrink so its title + close button stay reachable. */
.modal-header { flex-shrink: 0; }

.modal-form { display: flex; flex-direction: column; gap: var(--sp-4); }
/* Prominent, honest data-loss warning inside a destructive confirmation modal
   (e.g. "Effacer et quitter"). Amber-tinted panel so it reads as a caution the
   user must consciously accept, not just body text. */
.modal-confirm-warn {
  margin: 0;
  padding: 12px 14px;
  border-radius: 10px;
  border: 1px solid color-mix(in srgb, #f0a020 55%, transparent);
  background: color-mix(in srgb, #f0a020 14%, transparent);
  color: var(--c-text, #eee);
  font-size: 13.5px;
  line-height: 1.45;
}
/* Action row at the bottom of a form: pin it visually at the bottom of the
   modal body when the form is tall (typical case with the avatar picker).
   We use sticky so it stays visible while the user scrolls long content
   above it. The negative bottom margin cancels the modal-body padding so
   the row sits flush with the modal's bottom edge. */
.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-3);
  position: sticky;
  bottom: calc(-1 * var(--sp-5));
  margin: 0 calc(-1 * var(--sp-5)) calc(-1 * var(--sp-5));
  padding: var(--sp-3) var(--sp-5);
  background: var(--c-bg-1);
  border-top: 1px solid var(--c-border);
}
/* Stacked variant: full-width buttons, one per row. Used by the disconnect
   choice modal (soft / hard / cancel) where each option needs its own line. */
.modal-actions.modal-actions-stack {
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--sp-2);
}
.modal-actions.modal-actions-stack .btn { width: 100%; }

.mycode-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  align-items: center;
  text-align: center;
}
.mycode-body p { color: var(--c-text-soft); }
.mycode {
  font-family: var(--font-mono);
  font-size: 28px;
  letter-spacing: 0.2em;
  background: var(--c-bg-2);
  border: 1px dashed var(--c-border-strong);
  border-radius: var(--r-md);
  padding: var(--sp-5) var(--sp-7);
  color: var(--c-accent-soft);
  user-select: text;
}

/* Devices modal */
.devices-body { display: flex; flex-direction: column; gap: var(--sp-4); }
.devices-body > p { color: var(--c-text-soft); font-size: var(--fs-sm); margin: 0; }
.devices-list { display: flex; flex-direction: column; gap: var(--sp-2); max-height: 360px; overflow: auto; }
.device-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
}
/* L'appareil courant n'a pas de bouton d'action → une seule colonne (pas de vide
   à droite ni de compression du nom par une 2e colonne fantôme). */
.device-row.current { border-color: var(--c-accent); background: var(--c-accent-weak); grid-template-columns: 1fr; }
.device-main { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.device-label {
  display: flex; align-items: center; gap: var(--sp-2);
  font-weight: 600; min-width: 0; flex-wrap: wrap; /* les badges passent à la ligne */
}
/* Seul le NOM tronque (pas les badges, qui étaient coupés sur mobile). Le premier
   <span> de .device-label porte le nom ; on lui applique l'ellipsis. */
.device-label > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.device-pill { font-size: var(--fs-xs); white-space: nowrap; flex-shrink: 0; }
.device-pill-warn { background: var(--c-danger); color: #fff; border-color: var(--c-danger); }
.device-row.recent { border-color: var(--c-danger); background: color-mix(in srgb, var(--c-danger) 8%, transparent); }
.device-sub { display: flex; gap: var(--sp-2); flex-wrap: wrap; color: var(--c-text-dim); font-size: var(--fs-xs); }
/* Note sur l'appareil courant (à la place du bouton « Révoquer » retiré). Vit DANS
   la colonne infos, pleine largeur, alignée à gauche comme le reste — pas de vide. */
.device-current-note { margin: 2px 0 0; color: var(--c-text-dim); font-size: var(--fs-xs); line-height: 1.35; }
.devices-footer { display: flex; justify-content: flex-end; }
.devices-passphrase {
  display: flex; flex-direction: column; gap: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--c-accent);
  border-radius: var(--r-md);
  background: var(--c-accent-weak);
}
.devices-passphrase-hint { margin: 0; font-size: var(--fs-sm); }
.devices-passphrase[hidden] { display: none !important; }

/* Alerte de sécurité « nouvel appareil » (deviceAlertModal.js). */
.device-alert-body { display: flex; flex-direction: column; gap: var(--sp-3); }
.device-alert-lead { margin: 0; }
.device-alert-card {
  display: flex; flex-direction: column; gap: 4px;
  padding: var(--sp-3);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
}
.device-alert-label { font-weight: 600; }
.device-alert-sub { display: flex; gap: var(--sp-2); flex-wrap: wrap; color: var(--c-text-dim); font-size: var(--fs-xs); }
.device-alert-q { margin: 0; font-weight: 600; }
.device-alert-warn {
  padding: var(--sp-3);
  border: 1px solid var(--c-danger);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--c-danger) 8%, transparent);
}
.device-alert-warn p { margin: 0 0 var(--sp-2); }
.device-alert-warn p:last-child { margin-bottom: 0; }
.device-alert-warn-conseq { color: var(--c-text-dim); font-size: var(--fs-sm); }

/* Liste des appareils (plusieurs alertes groupées) : DÉFILE sur petit écran. */
.device-alert-list {
  display: flex; flex-direction: column; gap: var(--sp-2);
  max-height: 46vh; overflow-y: auto;
  /* léger padding pour que la scrollbar ne colle pas au contenu */
  padding-right: 2px;
}
.device-alert-card-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; margin-top: 4px; }
.device-alert-followup { display: flex; flex-direction: column; gap: var(--sp-2); }
.device-alert-global { justify-content: flex-start; gap: var(--sp-2); flex-wrap: wrap; }

/* Point rouge sur les boutons/items « Appareils » quand une alerte est en attente
   (GUIDE, non cliquable en soi — le bouton porte déjà son action). */
.btn.has-alert, .topbar-menu-item.has-alert { position: relative; }
.btn.has-alert::after {
  content: '';
  position: absolute;
  top: 2px; right: 2px;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--c-danger);
  box-shadow: 0 0 0 2px var(--c-bg-1);
}
.topbar-menu-item.has-alert::after {
  content: '';
  position: absolute;
  top: 50%; right: 10px; transform: translateY(-50%);
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--c-danger);
}

.fingerprint-box {
  margin-top: var(--sp-3);
  padding: var(--sp-3);
  border-top: 1px dashed var(--c-border-strong);
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.fingerprint-label {
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}
.fingerprint-code {
  font-family: var(--font-mono);
  font-size: var(--fs-md);
  letter-spacing: 0.1em;
  color: var(--c-accent-soft);
  user-select: text;
}
.fingerprint-hint { font-size: var(--fs-xs); color: var(--c-text-dim); margin: 0; }
/* Actions avec explication : passage en COLONNE (bouton + hint empilés) pour que
   chaque fonction porte son « à quoi ça sert ». Chaque bloc prend toute la largeur. */
.fingerprint-actions { display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-2); }
.device-action { display: flex; flex-direction: column; gap: var(--sp-1); align-items: flex-start; }
.device-action .btn { flex: 0 0 auto; }
.device-action-hint { font-size: var(--fs-xs); color: var(--c-text-dim); margin: 0; line-height: 1.35; }

/* Lightbox — full-screen in-app media viewer (image / video / pdf).
   Sits above modals (z-index higher than .modal-overlay). */
.lightbox-overlay {
  position: fixed;
  inset: 0;
  /* 0.92 → 0.97 : avec un média centré (album), le chat restait perceptible
     DERRIÈRE la photo et parasitait la lecture. La visionneuse doit isoler le
     média, pas le poser sur le fil. */
  background: rgba(0, 0, 0, 0.97);
  z-index: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
  animation: fade-in 140ms var(--ease-out);
}
.lightbox-toolbar {
  position: absolute;
  top: calc(env(safe-area-inset-top) + 8px);
  right: calc(env(safe-area-inset-right) + 8px);
  display: flex;
  gap: 8px;
  z-index: 1;
}
.lightbox-btn {
  width: 44px;
  height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  border: 0;
  border-radius: 999px;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.lightbox-btn:hover { background: rgba(255, 255, 255, 0.22); }

/* ── ALBUM étape 5 : navigation entre les médias ────────────────────────────
   La « scène » porte le média courant ; c'est le SEUL élément reconstruit à la
   navigation (la barre, le retour Android, le clavier et le focus ne bougent
   pas). Elle occupe toute la place et centre son contenu. */
.lightbox-stage {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  min-width: 0;
  min-height: 0;
}
/* Flèches sur les bords, discrètes : elles ne doivent jamais manger la photo.
   Sur téléphone, le geste principal reste le BALAYAGE — les flèches sont un
   secours (et le seul moyen à la souris / au clavier). */
.lightbox-nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
  /* Fond DENSE + ombre : les chevrons doivent rester lisibles PAR-DESSUS
     n'importe quelle photo (mesuré : sur une image claire, un fond à 45 % les
     faisait disparaître). Pas de flou — le user n'en veut pas. */
  background: rgba(8, 11, 16, 0.72);
  color: #fff;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.45);
  font-size: 30px;
  padding-bottom: 3px;              /* centrage optique des chevrons ‹ › */
}
.lightbox-nav:hover { background: rgba(8, 11, 16, 0.88); }
.lightbox-prev { left: calc(env(safe-area-inset-left) + 8px); }
.lightbox-next { right: calc(env(safe-area-inset-right) + 8px); }
.lightbox-nav:disabled {
  opacity: 0.25;
  cursor: default;
  pointer-events: none;             /* aux extrémités : pas de bouclage */
}
/* Compteur « 2 / 5 » dans la barre, pour savoir où l'on est dans l'album. */
.lightbox-counter {
  display: inline-flex;
  align-items: center;
  padding: 0 12px;
  height: 44px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
.tv .lightbox-nav { width: 64px; height: 64px; font-size: 40px; }
/* Focus ring is critical on Android TV (the user navigates with a remote and
   must always see where focus lives). Stronger than the hover state. */
.lightbox-btn:focus-visible {
  background: rgba(255, 255, 255, 0.28);
  outline: 3px solid var(--c-accent, #5b8cff);
  outline-offset: 2px;
}
/* TV: bigger buttons, bigger focus ring, viewable from across the room. */
.tv .lightbox-btn { width: 64px; height: 64px; font-size: 30px; }
.tv .lightbox-btn:focus-visible { outline-width: 4px; outline-offset: 3px; }
.tv .lightbox-toolbar { gap: 14px; top: calc(env(safe-area-inset-top) + 16px); right: calc(env(safe-area-inset-right) + 16px); }
.tv .lightbox-vid:focus,
.tv .lightbox-vid:focus-visible {
  outline: 4px solid var(--c-accent, #5b8cff);
  outline-offset: 4px;
}
.lightbox-img,
.lightbox-vid {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  /* Pinch-zoom on mobile, scroll-pan with mouse wheel. */
  touch-action: pinch-zoom;
}
.lightbox-pdf {
  width: min(960px, 100%);
  height: 100%;
  border: 0;
  background: #fff;
}

/* Touch fallback for PDF (mobile has no inline iframe PDF viewer): a clear
   panel with an explicit "Ouvrir le PDF" action that hands the blob to the OS.
   The lightbox overlay is ALWAYS a dark scrim (independent of the app theme),
   so this panel uses FIXED high-contrast colours — NOT theme variables, which
   would resolve to dark-on-dark under a light theme. */
.lightbox-pdf-fallback {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  width: min(460px, 86vw);
  max-width: 86vw;
  height: auto;
  box-sizing: border-box;
  padding: 32px 24px;
  background: #23262d;
  color: #f2f4f8;
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 16px;
  text-align: center;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.lightbox-pdf-icon { font-size: 60px; line-height: 1; }
.lightbox-pdf-name {
  font-size: 15px;
  line-height: 1.35;
  color: #f2f4f8;
  word-break: break-word;
  max-width: 100%;
}
.lightbox-pdf-open {
  display: inline-block;
  width: auto;
  max-width: 100%;
  margin-top: 4px;
  padding: 13px 28px;
  font-size: 16px;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;      /* keep the label on ONE line */
  border: 0;
  border-radius: 12px;
  background: #4f7cff;
  color: #fff;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.lightbox-pdf-open:active { background: #3f68e0; }

/* Context menu — single shared instance attached to <body>.
   Positioned absolutely; viewport-clamped by JS. */
.ctx-menu {
  position: fixed;
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: 8px;
  padding: 6px 0;
  min-width: 180px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.35);
  z-index: 1000;
}
.ctx-menu[hidden] { display: none !important; }
.ctx-menu-item {
  display: block;
  width: 100%;
  padding: 10px 16px;
  border: 0;
  background: transparent;
  color: var(--c-text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}
.ctx-menu-item:hover,
.ctx-menu-item:focus { background: var(--c-bg-2); outline: none; }
.ctx-menu-item:active { background: var(--c-bg-3); }
/* Entrée destructive (supprimer, etc.) : rouge, cohérent avec les autres
   actions danger de l'app (--c-danger). */
.ctx-menu-item-danger { color: var(--c-danger); }

/* WhatsApp-style message selection. The .bubble.selected highlight lives in
   layout.css (where .bubble.mine/.theirs backgrounds are defined, so
   specificity stacks predictably). The selection topbar reuses
   .thread-header styling — only the buttons are themed here.

   The compound selector `.thread-header.sel-topbar` is required to beat
   `.thread-header { z-index: 5 }` from layout.css, which is loaded AFTER
   components.css (source-order rules — same specificity loses). With the
   compound selector we get specificity 0,2,0 (vs the bare `.thread-header`
   at 0,1,0) so the z-index: 11 actually wins. The kebab dropdown sits
   inside this topbar at z-index 100 (its own `.topbar-menu` rule), which
   then bubbles up through the topbar's z-index 11 — comfortably above the
   react-picker's z-index 10. */
.thread-header.sel-topbar { z-index: 11; }
.sel-topbar { gap: var(--sp-2); }
.sel-topbar[hidden] { display: none !important; }
.sel-topbar-back {
  width: 40px; height: 40px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--c-text);
  font-size: 22px;
  line-height: 1;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
}
.sel-topbar-back:hover { background: var(--c-bg-2); }
.sel-topbar-spacer { flex: 1; }
.sel-topbar-action {
  height: 36px;
  padding: 0 14px;
  border: 0;
  background: transparent;
  color: var(--c-text);
  font: inherit;
  border-radius: 8px;
  cursor: pointer;
  /* Le libellé ne doit ni se couper sur 2 lignes ni laisser le bouton rétrécir
     sous sa largeur de texte : sinon le fond du hover (rouge pour « Supprimer »)
     ne couvre qu'une partie du libellé (bug « fond à moitié »). nowrap + pas de
     shrink → le bouton fait TOUJOURS la largeur de son texte, fond compris. */
  white-space: nowrap;
  flex-shrink: 0;
}
.sel-topbar-action:hover { background: var(--c-bg-2); }
.sel-topbar-action[hidden] { display: none !important; }
.sel-topbar-action-danger { color: var(--c-danger); }
.sel-topbar-action-danger:hover { background: rgba(242,109,109,0.08); }

/* Inline action row (desktop). Hidden under the responsive breakpoint
   below in favour of the kebab. */
.sel-topbar-inline {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* Kebab fallback (mobile / narrow desktop). Reuses .topbar-menu styling. */
.sel-topbar-kebab-wrap {
  position: relative;
  /* Hidden on desktop by default; the media query flips it on. */
  display: none;
}
.sel-topbar-kebab-wrap[hidden] { display: none !important; }
.sel-topbar-kebab {
  width: 36px; height: 36px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--c-text);
  font-size: 20px;
  line-height: 1;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
}
.sel-topbar-kebab:hover { background: var(--c-bg-2); }

/* Responsive : basculer la rangée de boutons inline vers le kebab quand il n'y a
   pas la place. ⚠️ Le seuil doit dépendre de la LARGEUR DU FIL, pas du viewport :
   sur Electron, la fenêtre est large mais la colonne contacts (redimensionnable)
   rétrécit le fil → l'ancien @media (viewport) laissait la rangée inline déborder
   et se faire COUPER hors écran. On passe donc en CONTAINER QUERY sur l'en-tête.
   Fallback @media conservé pour un navigateur sans support (rangée cachée sous
   720px de viewport — comportement d'origine). */
.thread-header.sel-topbar { container-type: inline-size; container-name: seltopbar; }

/* Sous ~840px de LARGEUR D'EN-TÊTE : inline caché, kebab affiché. Seuil MESURÉ :
   la rangée inline la plus large (Copier/Modifier/Transférer/Épingler/Supprimer×2)
   fait 666px ; avec dos (40) + compteur (~100) + espaces, l'en-tête a besoin de
   ~846px pour tout afficher sans couper → on bascule au kebab dès 840px. */
@container seltopbar (max-width: 840px) {
  .sel-topbar-inline { display: none; }
  .sel-topbar-kebab-wrap { display: block; }
}
/* Fallback navigateurs sans @container (aucun de nos cibles, mais ceinture) :
   garde l'ancien seuil viewport. Neutralisé quand @container matche au-dessus. */
@supports not (container-type: inline-size) {
  @media (max-width: 720px) {
    .sel-topbar-inline { display: none; }
    .sel-topbar-kebab-wrap { display: block; }
  }
}

/* --- Settings modal --- */
.settings-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  min-height: 0;
  /* The body lives inside .modal-body which already padding-controls; here we
     just give the panel + tab-bar enough room and let the overflow happen on
     the panel only so the tabs stay visible while scrolling. Cap on
     --app-height (visible-above-keyboard), not vh, for the same reason as
     .modal-content above. */
  max-height: min(var(--app-height, 72vh), 720px);
}
.settings-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--c-border);
}
.settings-tab {
  border: 0;
  background: transparent;
  color: var(--c-text-soft);
  font: inherit;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  cursor: pointer;
}
.settings-tab:hover { background: var(--c-bg-2); color: var(--c-text); }
.settings-tab.active {
  background: var(--c-bg-2);
  color: var(--c-text);
  box-shadow: inset 0 -2px 0 var(--c-accent);
}
.settings-panel {
  overflow-y: auto;
  padding-right: var(--sp-2);
  -webkit-overflow-scrolling: touch;
  /* Same reservation as .modal-body: when the "Surnom local" field is focused
     (chatSettingsModal) scrollIntoView shouldn't leave it under the
     Effacer/Enregistrer row or the keyboard. */
  scroll-padding-bottom: 72px;
}
.settings-section {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.settings-label {
  font-size: var(--fs-sm);
  color: var(--c-text-soft);
  font-weight: 500;
  margin-top: var(--sp-2);
}
.settings-hint {
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
  margin-top: var(--sp-1);
}
.settings-about-version {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
}
.settings-profile-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  padding: var(--sp-2) var(--sp-3);
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
}
.settings-profile-name {
  font-weight: 600;
  color: var(--c-text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Écran « vous avez été déconnecté » (révocation confirmée). */
.revoked-card { text-align: center; }
.revoked-icon { font-size: 40px; margin-bottom: var(--sp-2); }
.revoked-title { margin: 0 0 var(--sp-2); }
.revoked-lead { margin: 0 0 var(--sp-3); color: var(--c-text-dim); }
.revoked-warn {
  text-align: left;
  padding: var(--sp-3);
  border: 1px solid var(--c-danger);
  border-radius: var(--r-md);
  background: color-mix(in srgb, var(--c-danger) 8%, transparent);
  margin-bottom: var(--sp-3);
}
.revoked-warn p { margin: 0 0 var(--sp-2); }
.revoked-warn p:last-child { margin-bottom: 0; }
.revoked-advice { color: var(--c-text-dim); font-size: var(--fs-sm); }

/* Recovery-passphrase modal */
.recovery-intro { font-size: var(--fs-sm); color: var(--c-text-soft); margin: 0 0 var(--sp-2); }
.recovery-suggest-row {
  display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap;
  margin: 0 0 var(--sp-2);
}
.recovery-suggest-hint { font-size: var(--fs-xs); color: var(--c-text-dim); flex: 1; min-width: 140px; }
.recovery-warning {
  font-size: var(--fs-sm);
  color: var(--c-text);
  background: var(--c-warning-bg, rgba(242,180,80,0.12));
  border: 1px solid var(--c-warning, rgba(242,180,80,0.4));
  border-radius: var(--r-md);
  padding: var(--sp-2) var(--sp-3);
  margin: 0 0 var(--sp-3);
}
.recovery-strength { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-1) 0 var(--sp-2); }
.recovery-meter {
  flex: 1 1 auto;
  height: 6px;
  background: var(--c-bg-3);
  border-radius: var(--r-pill);
  overflow: hidden;
}
.recovery-meter-fill {
  height: 100%;
  width: 0;
  background: var(--c-accent);
  transition: width var(--dur-fast) var(--ease-out);
}
.recovery-meter-label { font-size: var(--fs-xs); color: var(--c-text-soft); min-width: 3.5em; }
.settings-about-version-label {
  font-size: var(--fs-sm);
  color: var(--c-text-soft);
  font-weight: 500;
}
.settings-about-version-value {
  font-family: var(--font-mono, ui-monospace, Menlo, monospace);
  font-size: var(--fs-xs);
  color: var(--c-text);
  user-select: text;
}
.settings-actions {
  display: flex;
  gap: var(--sp-2);
  flex-wrap: wrap;
  margin-top: var(--sp-3);
}

/* Theme picker tiles. Each tile shows a small swatch styled as the theme it
   represents — :not(:hover) keeps the swatch crisp; .selected adds the ring. */
/* App-background picker tiles — like the theme tiles but the swatch is an
   image preview (or a flat color for "Aucun" / a color fill). */
.settings-appbg-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
  gap: var(--sp-2);
}
.settings-appbg-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 8px;
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  cursor: pointer;
  color: var(--c-text);
}
.settings-appbg-tile:hover { border-color: var(--c-border-strong); background: var(--c-bg-3); }
.settings-appbg-tile.selected {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 2px var(--c-accent-ring);
}
.settings-appbg-swatch {
  width: 100%;
  height: 46px;
  border-radius: var(--r-sm);
  border: 1px solid var(--c-border);
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}
.settings-appbg-label {
  font-size: var(--fs-xs);
  color: var(--c-text-soft);
  text-align: center;
  line-height: 1.2;
}

.settings-themes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  gap: var(--sp-2);
}
.settings-theme {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  padding: 10px;
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  cursor: pointer;
  color: var(--c-text);
  font: inherit;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}
.settings-theme:hover { border-color: var(--c-border-strong); background: var(--c-bg-3); }
.settings-theme.selected {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 2px var(--c-accent-ring);
}
.settings-theme-swatch {
  width: 100%;
  height: 38px;
  border-radius: var(--r-sm);
  border: 1px solid var(--c-border);
}
.settings-theme.theme-preview-dark .settings-theme-swatch {
  background: linear-gradient(135deg, #11141b 0%, #1f2431 60%, #5b8cff 100%);
}
.settings-theme.theme-preview-light .settings-theme-swatch {
  background: linear-gradient(135deg, #ffffff 0%, #eef1f7 60%, #3a6ff0 100%);
  border-color: #d4dae6;
}
.settings-theme.theme-preview-paper .settings-theme-swatch {
  background: linear-gradient(135deg, #faf3e2 0%, #f1e7cd 60%, #b86b3e 100%);
  border-color: #d6c79f;
}
.settings-theme.theme-preview-midnight .settings-theme-swatch {
  background: linear-gradient(135deg, #110f25 0%, #1a1736 60%, #9a6cff 100%);
}
.settings-theme.theme-preview-forest .settings-theme-swatch {
  background: linear-gradient(135deg, #121e19 0%, #182824 60%, #3ccf8f 100%);
}
.settings-theme.theme-preview-sand .settings-theme-swatch {
  background: linear-gradient(135deg, #fdf7ec 0%, #f1e7d3 60%, #c25c3f 100%);
  border-color: #d6c39a;
}
.settings-theme.theme-preview-ocean .settings-theme-swatch {
  background: linear-gradient(135deg, #f6fbfc 0%, #dcecf0 60%, #0c8896 100%);
  border-color: #aacfd6;
}
.settings-theme.theme-preview-rose .settings-theme-swatch {
  background: linear-gradient(135deg, #fff7fa 0%, #f7e0e6 60%, #c9347a 100%);
  border-color: #e0aebb;
}
.settings-theme.theme-preview-msn .settings-theme-swatch {
  /* Three-stop gradient evoking the MSN background fade plus the
     vivid blue accent — readable at 32px and on either bg-1 or bg-2. */
  background: linear-gradient(135deg, #f0f5ff 0%, #3399ff 60%, #0066cc 100%);
  border-color: #a8c4ec;
}
.settings-theme.theme-preview-sketch .settings-theme-swatch {
  /* Cream paper background + a hand-drawn "Aa" in Caveat to cue the
     hand-written nature of the theme. The Caveat font is loaded by
     tokens.css's @font-face; the swatch falls back to a generic
     cursive if Caveat isn't ready yet (no FOUT issue, the swatch is
     decorative). */
  background-color: #fffaf0;
  background-image:
    linear-gradient(135deg, transparent 0%, transparent 60%, rgba(255, 119, 51, 0.15) 100%);
  border-color: #2a2a2a;
  position: relative;
}
.settings-theme.theme-preview-sketch .settings-theme-swatch::after {
  content: 'Aa';
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-family: 'Caveat', cursive;
  font-size: 22px;
  color: #1a1a1a;
  letter-spacing: 1px;
}
.settings-theme.theme-preview-inherit .settings-theme-swatch {
  background: repeating-linear-gradient(45deg, var(--c-bg-2) 0 8px, var(--c-bg-3) 8px 16px);
}
.settings-theme-label { font-size: var(--fs-sm); color: var(--c-text-soft); }
.settings-theme.selected .settings-theme-label { color: var(--c-text); font-weight: 600; }

/* Scale picker (text size, emoji size). */
.settings-scale {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.settings-scale-btn {
  border: 1px solid var(--c-border);
  background: var(--c-bg-2);
  color: var(--c-text);
  padding: 8px 14px;
  border-radius: var(--r-md);
  cursor: pointer;
  font: inherit;
}
.settings-scale-btn:hover { background: var(--c-bg-3); }
.settings-scale-btn.selected {
  border-color: var(--c-accent);
  background: var(--c-accent-weak);
  color: var(--c-text);
  box-shadow: 0 0 0 2px var(--c-accent-ring);
}

/* Wallpaper picker grid. Tiles are 1:1 with a label band underneath. */
.wp-picker { display: flex; flex-direction: column; gap: var(--sp-2); }
.wp-status { font-size: var(--fs-sm); color: var(--c-text-dim); padding: var(--sp-2); }
.wp-status[hidden] { display: none !important; }
.wp-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
  gap: var(--sp-2);
}
.wp-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
  padding: 0;
  cursor: pointer;
  overflow: hidden;
  font: inherit;
  color: var(--c-text);
  text-align: left;
}
.wp-tile:hover { border-color: var(--c-border-strong); }
.wp-tile.selected {
  border-color: var(--c-accent);
  box-shadow: 0 0 0 2px var(--c-accent-ring);
}
.wp-tile-img {
  display: block;
  width: 100%;
  height: 88px;
  object-fit: cover;
  background: var(--c-bg-3);
}
.wp-tile-empty {
  display: grid;
  place-items: center;
  height: 88px;
  background:
    repeating-linear-gradient(45deg, var(--c-bg-3) 0 6px, var(--c-bg-2) 6px 12px);
  font-size: 22px;
  color: var(--c-text-dim);
}
.wp-tile-label {
  padding: 6px 8px;
  font-size: var(--fs-xs);
  color: var(--c-text-soft);
  background: var(--c-bg-1);
  border-top: 1px solid var(--c-border);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wp-tile-remove {
  position: absolute;
  top: 4px; right: 4px;
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 0;
  background: rgba(0,0,0,0.55);
  color: #fff;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  display: grid; place-items: center;
}
.wp-tile-remove:hover { background: rgba(0,0,0,0.75); }
.wp-tile-add { background: var(--c-bg-2); }
.wp-tile-add-icon {
  display: grid;
  place-items: center;
  height: 88px;
  font-size: 32px;
  color: var(--c-accent);
  border-bottom: 1px dashed var(--c-border-strong);
  background:
    radial-gradient(circle at center, var(--c-accent-weak) 0%, transparent 70%);
}

/* --- Profile modal (rename + avatar) --- */
.profile-form {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-height: 0;
}
.profile-grid {
  display: grid;
  /* 1 column on narrow screens, 2 columns on wider ones — the right column
     hosts the avatar picker which benefits from extra space. */
  grid-template-columns: 1fr;
  gap: var(--sp-4);
  min-height: 0;
}
@media (min-width: 600px) {
  .profile-grid { grid-template-columns: 200px 1fr; }
}
.profile-left, .profile-right {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  min-width: 0;
}
.profile-name-label { margin-top: var(--sp-2); }
.profile-preview {
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--c-accent), #8b5bff);
  display: grid;
  place-items: center;
  align-self: center;
  overflow: hidden;
  position: relative;
  box-shadow: 0 4px 16px rgba(0,0,0,0.18);
}
:root[data-avatar-shape="square"] .profile-preview { border-radius: var(--r-md); }
.profile-preview-img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.profile-preview-initials {
  font-size: 38px;
  font-weight: 700;
  color: #0a1223;
}

/* --- Avatar picker grid (catalog + actions) --- */
.avatar-picker { display: flex; flex-direction: column; gap: var(--sp-2); min-height: 0; }
.avatar-status { font-size: var(--fs-sm); color: var(--c-text-dim); padding: var(--sp-2); }
.avatar-status[hidden] { display: none !important; }
.avatar-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
  gap: var(--sp-2);
  max-height: 320px;
  overflow-y: auto;
  padding-right: var(--sp-1);
}
.avatar-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 6px;
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  cursor: pointer;
  font: inherit;
  color: var(--c-text);
}
.avatar-tile:hover { border-color: var(--c-border-strong); }
.avatar-tile-img {
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--c-bg-3);
}
:root[data-avatar-shape="square"] .avatar-tile-img { border-radius: var(--r-sm); }
.avatar-tile-empty {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: repeating-linear-gradient(45deg, var(--c-bg-3) 0 6px, var(--c-bg-2) 6px 12px);
  color: var(--c-text-dim);
  font-size: 18px;
}
:root[data-avatar-shape="square"] .avatar-tile-empty { border-radius: var(--r-sm); }
.avatar-tile-icon {
  width: 56px; height: 56px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--c-bg-3);
  font-size: 26px;
}
:root[data-avatar-shape="square"] .avatar-tile-icon { border-radius: var(--r-sm); }
.avatar-tile-label {
  font-size: var(--fs-xs);
  color: var(--c-text-soft);
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

/* --- Crop modal --- */
.crop-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  align-items: center;
}
.crop-stage {
  position: relative;
  width: 280px;
  height: 280px;
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  touch-action: none;
}
.crop-canvas {
  width: 280px;
  height: 280px;
  display: block;
  cursor: grab;
}
.crop-canvas:active { cursor: grabbing; }
/* Circular guide overlay — drawn with two stacked elements: a square
   "outside" tint (so the user sees what they'll lose if their contacts use
   circle), and a circular dashed border for the visible-in-circle area. */
.crop-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(circle at center,
      transparent 0,
      transparent calc(50% - 1px),
      rgba(255,255,255,0.5) calc(50% - 1px),
      rgba(255,255,255,0.5) calc(50% + 1px),
      transparent calc(50% + 1px));
  /* The outer square mask — slightly tint the corners so the user sees the
     difference between the circle and the square crop. */
}
.crop-overlay::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(circle at center,
      transparent 50%,
      rgba(0, 0, 0, 0.30) 50.5%);
}
.crop-controls {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 280px;
}
.crop-zoom {
  flex: 1 1 auto;
  cursor: pointer;
}
.crop-zoom-label {
  font-size: var(--fs-sm);
  color: var(--c-text-soft);
}
.crop-hint {
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
  text-align: center;
  max-width: 360px;
  line-height: 1.4;
}
.crop-error {
  width: 280px; height: 280px;
  display: grid; place-items: center;
  background: var(--c-bg-2);
  color: var(--c-danger);
  border-radius: var(--r-md);
}

/* Section labels — sit between groups in the wallpaper picker. */
.wp-section-label {
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--c-text-dim);
  margin-top: var(--sp-3);
  margin-bottom: var(--sp-1);
}

/* Color tile — same dimensions as image tiles, but the preview is just a
   solid swatch driven by the inline `--wp-color` custom property. */
.wp-tile-color { background: var(--c-bg-2); }
.wp-tile-color-swatch {
  height: 88px;
  background: var(--wp-color, var(--c-bg-3));
}
/* Custom-color tile — wraps a real <input type=color> so a single click
   opens the native picker. The input is laid out invisibly across the
   tile so the picker stays anchored on it. */
.wp-tile-custom-color {
  background: var(--c-bg-2);
  cursor: pointer;
  position: relative;
}
.wp-tile-custom-swatch {
  height: 88px;
  display: grid;
  place-items: center;
  font-size: 30px;
  background:
    conic-gradient(from 0deg,
      #ff6b6b 0deg, #ffd166 60deg, #06d6a0 120deg,
      #118ab2 180deg, #6f5bff 240deg, #ef476f 300deg, #ff6b6b 360deg);
  border-bottom: 1px solid var(--c-border);
}
.wp-tile-color-input {
  /* Native color input takes the full tile area but stays invisible — clicks
     on the tile cascade to it via <label> association. */
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  border: 0;
  padding: 0;
  cursor: pointer;
}

/* Image grid: cap height a bit more than the others so a long catalog
   doesn't dominate the modal — the user scrolls within the panel. */
.wp-grid-images {
  /* Same grid template as the parent .wp-grid; declared here so future
     overrides can target the image section specifically without disturbing
     the color row above it. */
}
/* `.wp-grid-none` only ever has one tile; constrain its width so the
   "Aucun" tile doesn't stretch to fill 4 columns on a wide modal. */
.wp-grid-none {
  grid-template-columns: minmax(120px, 200px);
}

/* Color swatch picker (bubble colors). */
.color-swatches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(36px, 1fr));
  gap: 6px;
}
.color-swatch {
  width: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  border: 1px solid var(--c-border);
  cursor: pointer;
  padding: 0;
  font: inherit;
  color: inherit;
  position: relative;
  transition: transform var(--dur-fast) var(--ease-out);
}
.color-swatch:hover { transform: scale(1.06); }
.color-swatch.selected {
  outline: 2px solid var(--c-accent);
  outline-offset: 2px;
}
.color-swatch-none {
  background: var(--c-bg-2);
  color: var(--c-text-dim);
  font-size: 14px;
  display: grid;
  place-items: center;
}
.color-swatch-input {
  /* Native color picker: keep the visual circle but let the underlying input
     drive the color. We size it the same way as a swatch. */
  background: linear-gradient(45deg, #ff6b6b, #ffd166, #06d6a0, #118ab2, #9b5de5);
  -webkit-appearance: none;
  appearance: none;
  border: 1px solid var(--c-border);
  cursor: pointer;
}
.color-swatch-input::-webkit-color-swatch-wrapper { padding: 0; }
.color-swatch-input::-webkit-color-swatch { border: 0; border-radius: 50%; }
.color-swatch-input::-moz-color-swatch { border: 0; border-radius: 50%; }

/* Bubble preview strip in default-chat tab. */
.settings-preview {
  margin-top: var(--sp-3);
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.settings-preview-label {
  font-size: var(--fs-xs);
  color: var(--c-text-dim);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.settings-preview-pane {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
  padding: var(--sp-4);
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  overflow: hidden;
  min-height: 130px;
}
.settings-preview-pane.has-wallpaper::before {
  content: "";
  position: absolute;
  inset: 0;
  background-image: var(--chat-wallpaper-url);
  background-size: cover;
  background-position: center;
  pointer-events: none;
  z-index: 0;
}
/* For solid-color previews, the inline `background-color` set in JS is
   what paints the layer — kill the scrim and the ::before image rule so
   nothing covers the swatch. */
.settings-preview-pane.wallpaper-color::before { display: none; }
.settings-preview-pane.wallpaper-color::after  { display: none; }
.settings-preview-pane.has-wallpaper::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Scrim color is theme-aware; opacity comes from the inline `--scrim-op`
     custom property set by the preview builder so it tracks the user's
     wallpaperScrim choice live. */
  background: #0b0d12;
  opacity: var(--scrim-op, 0.25);
  pointer-events: none;
  z-index: 0;
}
[data-theme="light"] .settings-preview-pane.has-wallpaper::after {
  background: #ffffff;
}
[data-theme="paper"] .settings-preview-pane.has-wallpaper::after {
  background: #faf3e2;
}
.settings-preview-pane > * {
  position: relative;
  z-index: 1;
}
.settings-preview-bubble {
  max-width: 80% !important;
  animation: none !important;
}
.settings-preview-bubble.theirs { align-self: flex-start; }
.settings-preview-bubble.mine { align-self: flex-end; }

/* Quick-reaction picker: small floating row of emoji buttons positioned by JS
   above the selected bubble. Anchored on .thread-pane (position: relative). */
.react-picker {
  position: absolute;
  display: flex;
  gap: 4px;
  padding: 6px;
  background: var(--c-bg-1);
  border: 1px solid var(--c-border);
  border-radius: 999px;
  box-shadow: 0 6px 18px rgba(0,0,0,0.35);
  z-index: 10;
}
.react-picker[hidden] { display: none !important; }
.react-picker-btn {
  width: calc(36px * var(--app-emoji-scale));
  height: calc(36px * var(--app-emoji-scale));
  border: 0;
  background: transparent;
  font-size: calc(22px * var(--app-emoji-scale));
  line-height: 1;
  border-radius: 50%;
  cursor: pointer;
  padding: 0;
}
.react-picker-btn:hover { background: var(--c-bg-2); }
.react-picker-btn:active { background: var(--c-bg-3); }

/* Notifications tab — muted-thread list. Shown in the global settings
   "Notifications" tab; one row per actively-muted conversation with a
   reactivate button. */
.muted-thread-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 6px;
}
.muted-thread-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: 8px 10px;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background: var(--c-bg-2);
}
.muted-thread-name {
  flex: 1 1 auto;
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.muted-thread-until {
  flex: 0 0 auto;
  color: var(--c-text-soft);
  font-size: var(--fs-sm);
  font-style: italic;
}

/* ══ MISE EN FORME DU TEXTE (marqueurs WhatsApp) ═════════════════════════════
   Posée par util/richText.js sous forme de VRAIS NŒUDS (<strong>/<em>/<s>/
   <code>), jamais via innerHTML — le texte d'un message vient du réseau, il ne
   doit jamais pouvoir être interprété.
   Les marqueurs (*_~```) n'apparaissent PAS à l'écran : ils restent dans le
   message stocké/envoyé (c'est le texte réel, et un client qui ignore la mise
   en forme l'affiche tel quel, lisible), mais le rendu les masque. */
.bubble-text .rt-bold   { font-weight: 700; }
.bubble-text .rt-italic { font-style: italic; }
.bubble-text .rt-strike { text-decoration: line-through; opacity: 0.85; }
.bubble-text .rt-code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
  /* Fond très discret, dérivé de la couleur du texte : lisible sur une bulle
     claire comme sombre, sans coder une couleur en dur par thème. */
  background: color-mix(in srgb, currentColor 12%, transparent);
  padding: 0.05em 0.35em;
  border-radius: 4px;
  /* Un bloc de code ne doit pas déborder de la bulle sur un long jeton. */
  overflow-wrap: anywhere;
}

/* ── Annonce admin « Système » : bulle officielle distincte ─────────────── */
.bubble-announcement {
  /* Pleine largeur (une annonce n'est pas un échange 1:1), fond neutre teinté,
     liseré gauche coloré par niveau. */
  align-self: stretch;
  max-width: 100%;
  border: 1px solid var(--c-border);
  border-left: 3px solid var(--c-accent);
  background: var(--c-bg-2);
}
.bubble-announcement.level-warn { border-left-color: var(--c-danger); }
.announcement-head { margin-bottom: 4px; }
.announcement-badge {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.02em;
  color: var(--c-accent);
}
.bubble-announcement.level-warn .announcement-badge { color: var(--c-danger); }
.announcement-title { font-weight: 700; margin-bottom: 2px; }
/* Annonce AVEC média : c'est une .file-bubble décorée en officielle. On rétablit le
   fond/liseré officiels (fileBubble met sa propre présentation) et on aère le badge,
   le média et la légende. Le média (image/vidéo) garde sa présentation fileBubble. */
.bubble-announcement.file-bubble {
  background: var(--c-bg-2);
  border: 1px solid var(--c-border);
  border-left: 3px solid var(--c-accent);
  padding: 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.bubble-announcement.file-bubble.level-warn { border-left-color: var(--c-danger); }
.announcement-caption { margin-top: 2px; }

/* Bandeau lecture seule (fil « Système ») affiché à la place du composeur. */
.thread-readonly {
  padding: var(--sp-3); text-align: center; color: var(--c-text-dim);
  font-size: var(--fs-sm); font-style: italic;
  border-top: 1px solid var(--c-border); background: var(--c-bg-1);
}
.thread-readonly[hidden] { display: none; }

/* Badge « officiel » du pseudo-contact « Système » dans la liste des contacts. */
.contact-official {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: 0.02em;
  background: var(--c-accent-weak); border-color: var(--c-accent);
  color: var(--c-accent-soft); flex-shrink: 0;
}

/* Pastille « groupe » : neutre et discrète (distincte de l'accent « officiel »).
   Raffinée à l'étape UI groupe ; ici elle rend juste la ligne lisible. */
.contact-group {
  font-size: var(--fs-xs); font-weight: 600; letter-spacing: 0.02em;
  background: var(--c-border); border-color: var(--c-border-strong);
  color: var(--c-text-dim); flex-shrink: 0;
}

/* Modal « Nouveau groupe » / « Gérer le groupe » — réutilise .pick-contact ;
   ces règles n'ajoutent que l'espacement du champ nom + des actions. */
.new-group-field { margin-bottom: 10px; }
.new-group-field .input { width: 100%; }
.new-group-hint,
.manage-group-hint {
  font-size: var(--fs-sm); color: var(--c-text-dim); margin: 4px 0 10px;
}
.pick-contact-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--c-border);
}
.manage-group-actions {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--c-border);
}
.manage-group-row { align-items: center; }
.manage-group-remove { margin-left: auto; }
