/* ===================================================================
   DESIGN SYSTEM — CV ATS Studio (v3 : tokens partagés avec l'accueil,
   cartes, responsive mobile-first)
   Tokens identiques à cv-generator/public/index.html — toute modification
   de palette/typo doit être répercutée des deux côtés.
=================================================================== */

:root {
  /* Charte du site (re-chartage 2026-08, tâche 2) : crème/encre/bleu marine,
     alignée sur resultats.html et index.html. --bg/--accent etc. sont
     conservés comme NOMS (référencés partout dans ce fichier) mais prennent
     les valeurs de la charte ; --paper/--surface/--brand/--brand-hover sont
     les nouveaux tokens canoniques consommés par les tâches suivantes. */
  --paper: #F5F4EF;
  --surface: #FFFFFF;
  --bg: #F5F4EF;          /* était #ffffff — le fond passe en crème */
  --bg-page: #F5F4EF;
  --bg-input: #f5f6f8;
  --bg-input-focus: #eef0f4;
  --ink: #12172B;         /* était #1b2230 */
  --ink-soft: #4B5170;
  --ink-faint: #8B90A8;
  --title-gray: #4B5170;
  --muted: #4B5170;
  --faint: #8B90A8;
  --border-tag: #c7cbd4;
  --border-light: #E3E1D8;
  --line: #E3E1D8;        /* était #e6e8ee */
  --brand: #2B3A8F;
  --brand-hover: #212E73;
  --accent: #2B3A8F;      /* alias : était #3652f6 */
  --accent-ink: #212E73;
  --accent-soft: #eaefff;

  --success: #17a34a;
  --success-soft: #e9f8ef;
  --danger: #dc2626;
  --danger-soft: #fdecec;

  --font-display: 'Space Grotesk', sans-serif; /* était Fraunces — alignement charte, les titres du site utilisent Space Grotesk */
  --font-mono: 'IBM Plex Mono', 'SFMono-Regular', monospace;
  --font-body: 'Inter', -apple-system, 'Segoe UI', sans-serif;
  --font-section: 'Space Grotesk', sans-serif;

  --radius-sm: 6px;
  --radius-md: 9px;
  --radius-lg: 12px;
  --radius-pill: 999px;

  --shadow-card: 0 10px 28px -20px rgba(27,34,48,.28);
  --shadow-card-open: 0 16px 36px -22px rgba(27,34,48,.22);
  --shadow-accent: 0 14px 30px -18px rgba(54,82,246,.45);

  /* Popovers de la toolbar flottante (dock + mini-panneaux) — tokens
     partagés pour que tous les popovers soient perçus comme un même
     système de composants (addendum polish, 20 juillet 2026). Valeurs
     glass alignées sur la maquette liquid glass (tâche 4, re-chartage) :
     fond translucide + rayon/ombre/padding maquette. backdrop-filter n'est
     pas tokenisé ici (valeurs différentes dock 28px / panneaux 32px),
     déclaré directement sur .design-dock/.design-mini-panel. */
  --popover-radius: 16px;
  --popover-shadow: 0 14px 40px rgba(18,23,43,.16), inset 0 1px 0 rgba(255,255,255,.75);
  --popover-shadow-up: 0 -14px 40px rgba(18,23,43,.12), inset 0 1px 0 rgba(255,255,255,.75);
  --popover-padding: 10px;
  --popover-bg: rgba(255,255,255,.65);
}

* { box-sizing: border-box; }

/* Tokens du design-system des modals (§5.1). Repris des maquettes fournies —
   échelle d'espacement 4px, rayons, motion, couleurs de statut. Le vert
   #34c759 est le vert UNIQUE du produit (Q4) : le #16A34A de l'ancienne
   maquette post-paiement est banni. Seul le vert propre WhatsApp (#25D366)
   fait exception, parce qu'il appartient à la marque WhatsApp. Second bloc
   :root volontaire : ces noms ne recoupent aucun token du bloc ci-dessus
   (vérifié par grep) — les modals consomment les leurs, le reste de l'app
   garde les siens.
   Re-chartage (tâche 6) : les tokens de fond/bordure/texte/rayon/ombre
   ci-dessous sont ré-aliasés sur les tokens canoniques de la charte
   (--surface/--paper/--line/--ink/--radius-*) pour que les modals (offre,
   post-paiement, rescan) et le bandeau rendent comme le reste de l'éditeur —
   un seul point de vérité, plus de doublon de valeurs à faire évoluer en
   parallèle. Les couleurs de statut/marque (vert produit, vert WhatsApp)
   NE sont PAS touchées : elles sont explicitement protégées par le
   commentaire ci-dessus et par le brief de cette tâche. */
:root {
  --color-surface: var(--surface);
  --color-surface-raised: var(--paper);
  --color-surface-muted: var(--bg-input);
  --color-border-modal: var(--line);
  --color-border-strong: var(--line);
  --color-text-primary: var(--ink);
  --color-text-secondary: var(--ink-soft);
  --color-text-tertiary: var(--faint);
  --color-positive: #34c759;
  --color-positive-muted: rgba(52, 199, 89, 0.08);
  --color-btn-primary: #34c759;
  --color-whatsapp: #25d366;

  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px;

  /* md/lg alignés sur l'échelle de rayons de la charte (--radius-md: 9px,
     --radius-lg: 12px — valeurs quasi identiques aux 8px/10px d'origine,
     différence imperceptible) ; 2xl repris de --popover-radius (16px, déjà
     utilisé par le dock/mini-panneaux flottants de la tâche 4) pour que les
     grandes surfaces (modal plein écran, carte de scan) partagent le même
     rayon que les autres grands panneaux de l'éditeur. xl n'est consommé
     par aucune règle actuelle : laissé en valeur fixe. */
  --radius-modal-md: var(--radius-md);
  --radius-modal-lg: var(--radius-lg);
  --radius-modal-xl: 12px;
  --radius-modal-2xl: var(--popover-radius);

  /* Ombres re-teintées en encre (rgb 27,34,48, déjà la teinte de
     --shadow-card/--shadow-card-open et du bandeau plus bas) au lieu du noir
     pur d'origine — même intensité/portée, mais cohérent avec la charte. */
  --shadow-modal: 0 8px 32px rgba(27, 34, 48, 0.16), 0 2px 8px rgba(27, 34, 48, 0.08);
  --shadow-backdrop: rgba(27, 34, 48, 0.5);

  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --duration-slow: 350ms;

  --modal-max-width: 420px;
  --touch-target: 44px;
}

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  line-height: 1.5;
  /* Même trame technique très discrète que la page d'accueil. */
  background-image: radial-gradient(var(--line) 1px, transparent 1px);
  background-size: 22px 22px;
  /* Layout verrouillé (Q1) : la fenêtre ne défile jamais sur desktop. Le
     formulaire et l'aperçu défilent indépendamment dans leur colonne. La
     media query mobile (max-width: 760px) rétablit `overflow: auto` : en
     dessous, l'aperçu est un overlay et le formulaire redevient la page. */
  overflow: hidden;
}

:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- Barre de marque ---------- */

.app-topbar {
  height: 56px;
  display: grid;
  /* minmax(0,1fr), pas 1fr seul (point 10) : avec 1fr, une colonne ne peut
     jamais descendre sous son min-content — si topbar-left/.topbar-right
     ont des largeurs de contenu intrinsèques différentes (cas fréquent sur
     mobile), la grille élargit la colonne la plus large au-delà de son 1fr
     nominal, cassant la symétrie et décentrant .autosave-wrap. minmax(0,1fr)
     autorise chaque colonne à rétrécir jusqu'à 0, l'overflow étant géré par
     les enfants (text-overflow ellipsis sur .status-message, flex-shrink
     naturel des boutons). */
  grid-template-columns: minmax(0,1fr) auto minmax(0,1fr);
  align-items: center;
  gap: 14px;
  padding: 0 20px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 50;
}

/* --- Logo : pastille carrée --brand, glyphe blanc (charte du site, tâche 2) --- */
/* Logo identique aux autres pages (accueil/résultats) : pictogramme de
   lignes de texte + pastille de validation, pas l'icône pleine. */
.logo-mark {
  position: relative;
  width: 22px;
  height: 24px;
  flex-shrink: 0;
  border: 2px solid var(--ink);
  border-radius: 5px;
  background:
    linear-gradient(var(--ink-faint), var(--ink-faint)) 4px 5px/7px 2px no-repeat,
    linear-gradient(var(--ink-faint), var(--ink-faint)) 4px 10px/10px 2px no-repeat,
    linear-gradient(var(--ink-faint), var(--ink-faint)) 4px 15px/5px 2px no-repeat;
}
.logo-mark::after {
  content: "✓";
  position: absolute;
  right: -5px;
  bottom: -5px;
  width: 12px;
  height: 12px;
  background: var(--brand);
  color: #fff;
  font-size: 8px;
  font-weight: 700;
  line-height: 12px;
  text-align: center;
  border-radius: 50%;
  border: 2px solid #fff;
}

/* --- Indicateur de sauvegarde automatique (nuage, centre du header) --- */
.autosave-wrap {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  justify-self: center;
  min-width: 0;
}
#autosave-cloud {
  display: inline-flex;
  color: var(--faint);
  transition: color .2s ease;
}
#autosave-cloud svg { display: block; }
#autosave-cloud.saving { color: var(--accent); animation: cloud-pulse 1s ease-in-out infinite; }
#autosave-cloud.saved { color: var(--success); }
#autosave-cloud.error { color: var(--danger); }
#autosave-cloud.dirty { color: var(--danger); }
@keyframes cloud-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: .45; transform: scale(.92); }
}
.status-message {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.status-message.error { color: var(--danger); }

/* --- Bouton Télécharger : fixé en haut à droite (dans la barre sticky) --- */
.topbar-right {
  justify-self: end;
  display: flex;
  align-items: center;
  gap: 8px;
}
#btn-download-pdf {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--brand);
  color: #fff;
  border: none;
  border-radius: var(--radius-md);
  padding: 9px 16px;
  font-family: var(--font-body);
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  z-index: 60;
  transition: background .15s ease, box-shadow .15s ease, transform .1s ease;
}
#btn-download-pdf:hover { background: var(--brand-hover); box-shadow: var(--shadow-accent); }
#btn-download-pdf:active { transform: translateY(1px); }
#btn-download-pdf:disabled { opacity: .6; cursor: wait; }
#cv-nom-input {
  width: auto;
  flex: 1;
  min-width: 60px;
  padding: 6px 9px;
  margin: 0;
  border: 1.5px solid transparent;
  border-radius: var(--radius-sm);
  background: transparent;
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--ink);
}
#cv-nom-input:hover { background: var(--bg-input); border-color: var(--line); }
#cv-nom-input:focus {
  outline: none;
  background: #ffffff;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
.brand-link {
  display: flex;
  align-items: center;
  gap: 9px;
  border-radius: var(--radius-sm);
  padding: 4px 6px;
  margin: 0 0 0 -6px;
  text-decoration: none;
  transition: background .15s ease;
}
.brand-link:hover { background: var(--bg-input); }
.topbar-left {
  display: flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
}
#btn-undo {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease;
}
#btn-undo:hover:not([aria-disabled="true"]) { border-color: var(--ink-faint); color: var(--ink); }
#btn-undo[aria-disabled="true"] { opacity: .35; cursor: default; }
.brand-logo { flex-shrink: 0; }
.brand-text {
  font-family: var(--font-section);
  font-weight: 600;
  font-size: 18px;
  color: var(--ink);
  letter-spacing: 0.01em;
}
.brand-text em {
  font-family: var(--font-display);
  font-style: italic;
  font-weight: 500;
  color: var(--brand);
}
.app-topbar .back-hint {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
}

.app {
  display: flex;
  height: calc(100vh - 56px);
  min-height: 0; /* autorise les enfants overflow-y:auto à réellement défiler */
}

/* ---------- Panneau formulaire ---------- */

.form-panel {
  width: 44%;
  overflow-y: auto;
  padding: 28px 32px 40px;
  background: transparent;
}

/* Police système sur tout le chrome du formulaire (§4.1). Ne concerne QUE
   l'interface : le CV rendu vit dans une iframe isolée (#preview-frame) et
   garde sa propre police, pilotée par designSettings.police. */
.form-panel,
.form-panel input,
.form-panel textarea,
.form-panel select,
.form-panel button {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}

/* Fiabilise le glisser-déposer (§4.2) : sans ça, un glissement démarré sur le
   titre sélectionne le texte au lieu de déplacer la section. */
.section-header,
.section-title,
.section-drag-handle {
  -webkit-user-select: none;
  user-select: none;
}

/* Conserve la gouttière de la poignée pour que le titre reste aligné avec les
   autres sections, sans afficher d'affordance de déplacement (§4.3). */
.section-drag-handle-locked {
  visibility: hidden;
  cursor: default;
}

/* Un label visible par champ (§4.5) : plus aucun champ nu dont le sens ne
   tient qu'au placeholder (invisible dès que le champ est rempli). Le label
   enveloppe le contrôle : association implicite, pas besoin d'id/for sur les
   champs générés dynamiquement par form.js. */
.field-label {
  display: block;
  margin-bottom: 10px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  -webkit-user-select: none;
  user-select: none;
}
.field-label > input,
.field-label > textarea,
.field-label > select {
  display: block;
  width: 100%;
  margin-top: 4px;
  font-weight: 400;
  color: var(--ink);
}
/* Bouton de suppression des champs optionnels de l'identité (même motif que
   .chip button : ✕ nu, discret, à côté du libellé). */
.field-remove {
  background: none;
  border: none;
  color: var(--ink-faint);
  cursor: pointer;
  font-weight: bold;
  font-size: 12px;
  line-height: 1;
  margin-left: 6px;
  padding: 2px;
  vertical-align: middle;
}
.field-remove:hover { color: var(--danger); }
/* Dans une .field-row (2 colonnes), le label porte la largeur, pas le champ. */
.field-row > .field-label { flex: 1; margin-bottom: 0; }
/* Libellé de groupe : titre seul au-dessus d'une .field-row, sans contrôle
   enveloppé (d'où <span> et non <label>). */
.field-label-groupe { display: block; margin-bottom: 4px; }

/* ---------- Bloc import de CV ---------- */

/* Carte pointillée façon maquette (Task 3, step 2) : plus de fond plein ni
   d'ombre — juste un contour en tirets qui invite au dépôt de fichier. */
.import-block {
  background: transparent;
  border: 1.5px dashed var(--line);
  border-radius: 14px;
  padding: 16px;
  margin-bottom: 18px;
  text-align: center;
  box-shadow: none;
}
.import-label {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: var(--brand);
  color: white;
  padding: 10px 16px;
  border-radius: var(--radius-md);
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
  transition: background 0.15s ease, transform 0.1s ease, box-shadow .15s ease;
}
.import-label:hover { background: var(--accent-ink); box-shadow: var(--shadow-accent); }
.import-label:active { transform: translateY(1px); }
#import-cv-input { display: none; }
.import-hint {
  display: block;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 10px;
}
.import-status { font-size: 12.5px; margin-top: 8px; min-height: 16px; font-weight: 500; }
.import-status.loading { color: var(--accent-ink); }
.import-status.success { color: var(--success); }
.import-status.error { color: var(--danger); }

.progress-track {
  display: none;
  height: 6px;
  background: var(--bg-input);
  border-radius: var(--radius-pill);
  overflow: hidden;
  margin-top: 12px;
}
.progress-track.visible { display: block; }
.progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: var(--radius-pill);
  transition: width 0.2s ease;
}
.progress-track.indeterminate .progress-fill {
  width: 40% !important;
  animation: progress-indeterminate 1.1s ease-in-out infinite;
}
@keyframes progress-indeterminate {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(250%); }
}

/* ---------- Sections (cartes, ouverture unique) ---------- */

.sections-container { margin-top: 4px; display: flex; flex-direction: column; gap: 8px; }

/* Carte façon maquette (Task 3, step 1) : le padding horizontal migre du
   conteneur (.form-section) vers .section-header/.section-body — comme
   .card/.card-head/.card-body dans la maquette — pour que le fond blanc du
   header colle aux bords arrondis (overflow:hidden). */
.form-section {
  /* position:relative : sans ça, z-index n'a aucun effet (élément statique)
     — nécessaire pour que la carte en cours de glissement (flipReorder,
     form.js) passe visuellement au-dessus de ses voisines pendant
     l'animation plutôt que de rester sous les cartes suivantes du DOM. */
  position: relative;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  overflow: hidden;
  transition: box-shadow .18s ease, border-color .18s ease, opacity 0.15s ease;
  box-shadow: var(--shadow-card);
}
.form-section:not(.collapsed) {
  border-color: #d7deff;
  box-shadow: var(--shadow-card-open);
}
.form-section.dragging { opacity: 0.4; }

.section-header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  cursor: pointer;
}

/* Poignée de glisser-déposer : picto nu sans bordure, façon .drag-handle de
   la maquette (survol = simple fond clair, pas de contour). */
.section-drag-handle {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  border: none;
  background: none;
  color: var(--ink-faint);
  font-size: 14px;
  letter-spacing: -2px;
  cursor: grab;
  user-select: none;
  transition: background 0.15s ease, color 0.15s ease;
}
.section-drag-handle:hover { background: var(--paper); color: var(--ink-soft); }
.section-drag-handle:active { cursor: grabbing; }

.section-title {
  flex: 1;
  font-family: var(--font-section);
  font-weight: 600;
  font-size: 15.5px;
  color: var(--title-gray);
  letter-spacing: -0.01em;
  transition: color 0.15s ease;
}
.form-section:not(.collapsed) .section-title { color: var(--ink); }

.form-section.collapsed .section-body { display: none; }

/* Bouton +/× d'ouverture-fermeture : carré 28px bordé, façon .chevron-btn de
   la maquette (contour --line, survol en bleu marine). */
.section-add {
  flex-shrink: 0;
  width: 28px;
  height: 28px;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, border-color 0.15s ease, transform 0.15s ease, color .15s ease;
}
.section-add svg { display: block; }
.section-add:hover { border-color: var(--brand); color: var(--brand); }
.form-section:not(.collapsed) .section-add {
  transform: rotate(45deg);
  color: var(--accent-ink);
  border-color: var(--accent);
  background: var(--accent-soft);
}

.section-body { padding: 0 16px 18px; }

.subsection-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  margin: 14px 0 8px;
}

/* ---------- Champs ---------- */

/* Look ".text-input" de la maquette (Task 3, step 2) : fond crème --paper au
   repos, bascule vers --surface (blanc) + bordure --brand au focus — le champ
   se "détache" visuellement de la carte quand on y écrit. */
input[type="text"], input[type="email"], input[type="tel"], input[type="date"], select, textarea {
  width: 100%;
  padding: 10px 12px;
  margin-bottom: 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--paper);
  font-family: var(--font-body);
  font-size: 13.5px;
  color: var(--ink);
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease;
}
input::placeholder, textarea::placeholder { color: var(--faint); }
input:hover, select:hover, textarea:hover {
  border-color: var(--ink-faint);
}
input:focus, select:focus, textarea:focus {
  outline: none;
  background: var(--surface);
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
textarea { min-height: 78px; resize: vertical; line-height: 1.5; }

/* align-items:flex-end (au lieu du stretch par défaut) : rapproche le
   bouton "+ Ajouter" du bas de l'input voisin plutôt que de l'étirer sur
   toute la hauteur de .field-label (légende + input). Un écart résiduel de
   quelques px subsiste (poursuite prévue) mais c'est une nette amélioration
   par rapport à l'étirement complet (retour utilisateur, 06/08). */
.field-row { display: flex; align-items: flex-end; gap: 12px; }
.field-row input, .field-row select { flex: 1; min-width: 0; }
/* Bouton "+ Ajouter" (compétences/intérêts) : garde sa largeur de contenu
   dans la ligne plutôt que le width:100% de base (.btn-tag), pensé pour un
   bouton seul sous une liste, pas voisin d'un input. */
.field-row > .btn-tag {
  width: auto;
  flex-shrink: 0;
  white-space: nowrap;
}
/* Alignement parfait input ↔ bouton (poursuite du décalage résiduel noté
   ci-dessus) : l'input hérite d'un margin-bottom global de 10px qui, dans un
   .field-label, laissait le bas du label (donc du bouton en flex-end) 10px
   sous le bas de l'input. On l'annule dans une .field-row à bouton pour que
   input, label et bouton partagent exactement la même ligne de base. */
[data-section-id="competences"] .field-row > .field-label > input,
[data-section-id="interets"] .field-row > .field-label > input {
  margin-bottom: 0;
}

.char-counter {
  font-family: var(--font-mono);
  font-size: 10.5px;
  color: var(--faint);
  text-align: right;
  margin-top: -6px;
}

/* ---------- Photo (cercle 120x120 avec icône SVG) ---------- */

.photo-upload { margin-bottom: 18px; }

/* Grille à 4 lignes (label, input, label, input) façon maquette : la photo
   est ancrée sur les lignes 2 à 4 (grid-row:2/5), donc son haut coïncide
   avec le haut de l'input Prénom et son bas avec le bas de l'input Nom,
   quelle que soit la hauteur réelle des labels. .identity-name-fields garde
   son nœud DOM (accessibilité/JS inchangés) mais devient display:contents
   pour laisser ses 4 enfants participer directement à cette grille. */
.identity-photo-row {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 16px;
  row-gap: 6px;
  margin-bottom: 4px;
}
.identity-photo-row .photo-upload {
  grid-column: 1;
  grid-row: 2 / 5;
  margin-bottom: 0;
  /* Empile le cercle et le bouton « Retirer la photo », centrés dans la
     cellule qui couvre les lignes Prénom→Nom. */
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
}
/* Le cercle ne dérive plus sa hauteur de la cellule (il partage désormais la
   place avec le bouton Retirer) : taille fixe carrée, quelle que soit l'image
   chargée (recadrée via object-fit: cover ci-dessous). */
.identity-photo-row .photo-circle {
  width: 92px;
  height: 92px;
  flex: 0 0 auto;
  aspect-ratio: auto;
}
.identity-name-fields { display: contents; }
.identity-name-fields .field-label:nth-of-type(1) { grid-column: 2; grid-row: 1; }
.identity-name-fields input:nth-of-type(1) { grid-column: 2; grid-row: 2; }
.identity-name-fields .field-label:nth-of-type(2) { grid-column: 2; grid-row: 3; }
.identity-name-fields input:nth-of-type(2) { grid-column: 2; grid-row: 4; }
.identity-name-fields .field-label { margin-bottom: 0; align-self: end; }
.identity-name-fields input { margin-bottom: 0; }

#identite-photo {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0,0,0,0);
  white-space: nowrap;
  border: 0;
}

.photo-circle {
  /* Hauteur = celle de sa cellule de grille (haut input Prénom → bas input
     Nom), largeur dérivée par aspect-ratio pour rester un cercle parfait. */
  width: auto;
  height: 100%;
  aspect-ratio: 1 / 1;
  border-radius: 50%;
  background: var(--paper);
  border: 1.5px dashed var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  color: var(--ink-faint);
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.photo-circle:hover {
  background: var(--surface);
  border-color: var(--brand);
  color: var(--brand);
}
#identite-photo:focus-visible + .photo-circle {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.photo-preview-img {
  display: none;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-preview-img.has-photo { display: block; }
.photo-circle.has-photo { border-style: solid; }
.photo-circle.has-photo .photo-icon { display: none; }

.photo-hover-label {
  display: none;
  position: absolute;
  inset: 0;
  align-items: center;
  justify-content: center;
  background: rgba(16, 22, 36, 0.55);
  color: #ffffff;
  font-size: 12px;
  font-weight: 600;
}
.photo-circle.has-photo:hover .photo-hover-label { display: flex; }

.btn-remove-photo {
  background: none;
  border: none;
  color: var(--danger);
  cursor: pointer;
  font-size: 12px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
}
.btn-remove-photo:hover { background: var(--danger-soft); }
.btn-remove-photo[hidden] { display: none; }

/* ---------- Champs personnalisés ---------- */

.custom-field-row { align-items: center; }

/* ---------- Cartes de listes dynamiques (Terminé + glisser-déposer) ------- */

.entry-card {
  border: 1px solid var(--line);
  border-radius: var(--radius-md);
  margin-bottom: 12px;
  background: var(--paper);
  transition: box-shadow 0.15s ease, opacity 0.15s ease, border-color .15s ease;
}
.entry-card:not(.is-collapsed) { border-color: #d7deff; }

.entry-card-header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 11px 13px;
}

.entry-summary {
  flex: 1;
  font-size: 14px;
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.entry-card-actions { display: flex; gap: 6px; flex-shrink: 0; }

.icon-btn {
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  background: #ffffff;
  border: 1px solid var(--border-tag);
  color: var(--muted);
  font-size: 14.5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.icon-btn:hover { background: var(--bg-input); border-color: var(--faint); }
.btn-toggle-entry { color: var(--success); border-color: #a9dcbb; }
.btn-toggle-entry:hover { background: var(--success-soft); }
.btn-delete-entry:hover { background: var(--danger-soft); border-color: #f3b9b9; color: var(--danger); }

.entry-card-body {
  padding: 4px 17px 18px;
  border-top: 1px solid var(--line);
}
.entry-card.is-collapsed .entry-card-body { display: none; }

.entry-card .field-row { margin-bottom: 8px; }

.date-label {
  font-size: 12px;
  color: var(--muted);
  margin: 10px 0 5px;
}
.date-label-row {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  margin: 10px 0 5px;
  flex-wrap: wrap;
  gap: 8px;
}
.date-label-row .date-label { margin: 0; }

/* ---------- Interrupteur (toggle switch) ---------- */

.toggle-switch { display: inline-flex; align-items: center; gap: 7px; cursor: pointer; }
.toggle-switch input { display: none; }
.toggle-slider {
  width: 32px; height: 18px;
  background: var(--border-tag);
  border-radius: var(--radius-pill);
  position: relative;
  transition: background 0.15s ease;
  flex-shrink: 0;
}
.toggle-slider::before {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  background: white;
  border-radius: 50%;
  top: 2px; left: 2px;
  transition: transform 0.15s ease;
  box-shadow: 0 1px 2px rgba(0,0,0,0.2);
}
.toggle-switch input:checked + .toggle-slider { background: var(--accent); }
.toggle-switch input:checked + .toggle-slider::before { transform: translateX(14px); }
.toggle-text { font-size: 11.5px; color: var(--muted); }

.mission-item { display: flex; align-items: flex-start; gap: 6px; margin-bottom: 6px; }
.mission-item input { flex: 1; margin-bottom: 0; }
.mission-item > .field-label { flex: 1; margin-bottom: 0; }
.mission-textarea {
  width: 100%;
  resize: vertical;
  min-height: 40px;
  line-height: 1.4;
}
.mission-item button, .btn-remove-inline {
  background: none; border: none; color: var(--danger); cursor: pointer;
  font-size: 15px; padding: 0 6px; border-radius: var(--radius-sm);
}
.mission-item button:hover, .btn-remove-inline:hover { background: var(--danger-soft); }

.checkbox-row { display: flex; align-items: center; gap: 7px; font-size: 12.5px; color: var(--ink); margin-bottom: 10px; }
.checkbox-row input { width: auto; margin: 0; }

/* ---------- Boutons "tags" (style étiquette cliquable) ---------- */

/* Look ".add-entry-btn" de la maquette (Task 3, step 2) : pointillé pleine
   largeur, survol en bleu marine. Réutilisé aussi bien pour les boutons
   d'ajout autonomes (expérience, formation...) que pour le "+ Ajouter"
   compétence/intérêt niché dans un .field-row à côté d'un champ — d'où
   l'exception ci-dessous qui lui rend une largeur naturelle dans ce contexte. */
.btn-tag {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  width: 100%;
  background: var(--paper);
  color: var(--ink-soft);
  border: 1.5px dashed var(--line);
  border-radius: 10px;
  padding: 9px 16px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color .15s ease;
}
.btn-tag:hover { border-color: var(--brand); color: var(--brand); }
/* Compétences / centres d'intérêt : le bouton vit à côté d'un input dans un
   .field-row, pas seul sous une liste — la pleine largeur écraserait le
   champ, donc on revient à une largeur de contenu ici. */
.field-row > .btn-tag { width: auto; flex: 0 0 auto; }

.btn-remove {
  display: block;
  margin-top: 6px;
  background: none;
  border: none;
  color: var(--danger);
  font-size: 11.5px;
  font-weight: 500;
  cursor: pointer;
}
.btn-remove:hover { text-decoration: underline; }

/* ---------- Compétences (chips) ---------- */

.chips-list { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 10px; }
/* Tags "+ Libellé" des champs optionnels de l'identité : sans ce correctif,
   le width:100% de base de .btn-tag (pensé pour un bouton seul sous une
   liste) fait que chaque tag occupe toute la largeur du conteneur et se
   retrouve seul sur sa ligne au lieu de s'aligner à la suite des autres
   (retour utilisateur, 06/08 : "un seul ne va pas prendre toute la largeur
   mais ils se suivent"). */
.chips-list > .btn-tag { width: auto; flex: 0 0 auto; }
.chip {
  background: var(--paper);
  color: var(--ink-soft);
  border: 1px solid var(--line);
  padding: 7px 9px 7px 14px;
  border-radius: var(--radius-pill);
  font-size: 13.5px;
  font-weight: 500;
  display: flex;
  align-items: center;
  gap: 7px;
}
.chip button {
  background: none; border: none; color: var(--muted);
  cursor: pointer; font-weight: bold; font-size: 13px; line-height: 1;
}


/* ---------- Langues : zone de saisie plus grande ---------- */

#langues-list .field-row input[type="text"] {
  flex: 2.2;
  padding: 13px 14px;
  font-size: 15.5px;
}
#langues-list .field-row select {
  flex: 1;
  padding: 13px 12px;
  border: 1.5px solid var(--line);
  background: var(--bg-input);
  border-radius: var(--radius-sm);
  font-size: 14.5px;
  color: var(--ink);
}

/* ---------- Panneau de prévisualisation ---------- */

/* Desktop : colonne droite, échelle recalculée en continu par JS pour tenir
   dans la largeur réelle du panneau (§2.2a, voir --apercu-echelle ci-dessous).
   Le CV interne (rendu dans l'iframe) n'est jamais touché : seul son
   affichage est réduit via transform: scale() sur l'iframe entière. */
.preview-panel {
  width: 56%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: 20px 24px;
  /* Zone défilable propre (§2.3) : les pages suivantes du CV apparaissent au
     défilement du panneau, jamais de celui de la fenêtre. */
  overflow-y: auto;
  overflow-x: hidden;
  background: #f4f5f7;
  border-left: 1px solid var(--line);
  position: relative;
  /* §2.1 : l'aperçu est une image de rendu, pas un document à sélectionner.
     N'affecte pas l'extraction ATS (le PDF est produit côté serveur). */
  -webkit-user-select: none;
  user-select: none;
  /* Échelle pilotée par JS (§2.2a, preview.js/ajusterEchelleApercu) : la page
     A4 tient TOUJOURS dans la largeur disponible du panneau, quelle que soit
     la taille de la fenêtre — plus de paliers figés qui laissaient soit des
     marges perdues, soit un CV rogné. 0.72 n'est qu'une valeur de secours
     avant le premier calcul JS (qui s'exécute de façon synchrone au chargement). */
  --apercu-echelle: 0.72;
}
/* La mise à l'échelle porte sur le wrapper d'affichage uniquement : le
   document rendu dans l'iframe (.cv-page) n'est jamais touché. */
.preview-scale-wrap {
  width: calc(210mm * var(--apercu-echelle));
  /* Hauteur = nombre de pages × A4, à l'échelle courante (§2.2b, pagination
     visuelle) — --apercu-pages posé par paginerApercu() (preview.js). */
  height: calc(297mm * var(--apercu-echelle) * var(--apercu-pages, 1));
  flex-shrink: 0;
  margin: 0 auto; /* centrage horizontal (§2.2a) */
  cursor: zoom-in;
  position: relative; /* ancre les gouttières ::after ci-dessous */
  transition: width .25s ease, height .25s ease;
}

/* Gouttière grise entre pages A4 : bandes posées PAR-DESSUS l'iframe, en
   pointer-events:none. Purement décoratives — elles n'entrent pas dans le
   document rendu, donc ni dans le PDF ni dans l'extraction ATS. Le pas du
   dégradé répété est exactement la hauteur d'une page à l'échelle courante :
   chaque séparation tombe donc sur une vraie coupure de page. */
.preview-scale-wrap::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image: repeating-linear-gradient(
    to bottom,
    transparent 0,
    transparent calc(297mm * var(--apercu-echelle) - 12px),
    #f4f5f7 calc(297mm * var(--apercu-echelle) - 12px),
    #f4f5f7 calc(297mm * var(--apercu-echelle))
  );
}
/* Une seule page : aucune gouttière à dessiner. */
.preview-scale-wrap[data-pages="1"]::after { display: none; }
#preview-frame {
  width: 210mm;
  height: 297mm;
  border: none;
  background: white;
  box-shadow: var(--shadow-card-open);
  border-radius: 3px;
  display: block;
  transform: scale(var(--apercu-echelle));
  transform-origin: top left;
  transition: transform .25s ease;
  pointer-events: none; /* le survol/curseur appartient au conteneur */
}
/* Zoom au clic (desktop uniquement, > 980px — géré en JS dans preview.js) :
   le CV passe à sa taille quasi réelle et le panneau devient défilable ; le
   dock reste ancré (position: absolute sur .preview-panel, position:
   relative) donc toujours visible/cliquable même si le CV défile dessous. */
.preview-panel.preview-zoomed {
  overflow: auto;
  align-items: flex-start;
  justify-content: flex-start;
}
.preview-panel.preview-zoomed .preview-scale-wrap {
  /* --apercu-echelle remise à 1 ici : la gouttière (::after ci-dessus, ligne
     889-891) calcule son pas sur CETTE MÊME variable — sans cette remise à
     1, le pas restait à l'échelle fit-to-width (ex. 0.5) pendant que le
     wrapper passait à la taille réelle, dessinant deux fois trop de
     gouttières, une sur deux hors de la vraie coupure de page. */
  --apercu-echelle: 1;
  width: 210mm;
  /* Échelle figée à 1 en zoom : reprendre le nombre de pages réel, sinon un
     CV multi-pages resterait tronqué à la hauteur d'une seule page A4 une
     fois zoomé (§2.2b). */
  height: calc(297mm * var(--apercu-pages, 1));
  cursor: zoom-out;
}
.preview-panel.preview-zoomed #preview-frame {
  transform: scale(1);
}

/* ---------- Bouton flottant "aperçu" (mobile uniquement) ---------- */

.preview-fab {
  display: none;
  position: fixed;
  right: 18px;
  bottom: 18px;
  width: 56px;
  height: 56px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: #fff;
  cursor: pointer;
  z-index: 70;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-accent);
  transition: background .15s ease, box-shadow .15s ease, transform .1s ease, opacity var(--duration-normal) var(--ease-out);
}
.preview-fab:hover { background: var(--accent-ink); }
.preview-fab:active { transform: translateY(1px); }

/* Barre de l'écran d'aperçu (§3.3) : retour / titre / téléchargement. Masquée
   sur desktop (display: none), où l'aperçu n'est pas un écran à part entière
   — activée en display: grid par le media query mobile ci-dessous, même
   convention que .preview-fab juste au-dessus. */
.preview-topbar { display: none; }
.preview-topbar-title { text-align: center; font-weight: 600; font-size: 15px; }
.preview-topbar-btn {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border: none; background: none; color: var(--ink);
  border-radius: var(--radius-modal-md);
  cursor: pointer;
}
.preview-topbar-btn-primary { background: var(--accent); color: #fff; }

/* ---------- Responsive (mobile-first) ---------- */

/* Les anciens paliers de réduction d'échelle en dur (0.72/0.60/0.42 selon
   media query, dont un bloc @media (max-width: 1110px) désormais supprimé)
   laissaient des marges perdues ou un CV rogné entre deux seuils — en
   particulier dans la bande 761–1110px. L'échelle est maintenant recalculée
   en continu par ajusterEchelleApercu() (preview.js, §2.2a) à partir de la
   largeur RÉELLE du panneau : ces media queries ne gèrent donc plus que la
   mise en page (largeurs de colonnes, curseur), jamais plus les dimensions
   de .preview-scale-wrap/#preview-frame. */
@media (max-width: 980px) {
  .form-panel { width: 50%; padding: 24px 22px 36px; }
  .preview-panel { width: 50%; padding: 20px 12px; }
  .preview-scale-wrap {
    cursor: default;
  }
}

@media (max-width: 760px) {
  /* Sous 760px, l'aperçu est un overlay plein écran : la page redevient un
     document normal, qui défile (le verrou 100vh ne s'applique qu'au desktop). */
  body { overflow: auto; }
  .app { flex-direction: column; height: auto; min-height: calc(100vh - 56px); }
  .form-panel { width: 100%; overflow-y: visible; padding: 20px 18px 96px; }
  .app-topbar { padding: 0 14px; gap: 14px; }
  .topbar-left { gap: 8px; }
  #btn-download-pdf { padding: 9px 14px; font-size: 12.5px; }

  /* Écran d'aperçu : glisse de la gauche vers la droite (§3.3). Toujours
     présent dans le flux (translaté hors écran) pour que la transition joue
     dans les deux sens — `display: none` l'annulerait. */
  .preview-panel {
    display: flex;
    position: fixed;
    inset: 0;
    z-index: 80;
    width: 100%;
    border: none;
    background: #f4f5f7;
    padding: 60px 12px 24px;
    overflow-y: auto;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--duration-slow) var(--ease-out), visibility 0s linear var(--duration-slow);
  }
  body.preview-open .preview-panel {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--duration-slow) var(--ease-out), visibility 0s;
  }
  body.preview-open { overflow: hidden; }

  /* Barre de l'écran d'aperçu (§3.3) : retour / titre / téléchargement.
     display:none par défaut (§ci-dessous) : sortie de .preview-panel (bug
     scroll corrigé), elle n'est plus masquée "gratuitement" par le transform
     hors-écran de son ex-parent en mode édition — sans ce garde explicite,
     elle recouvrait en permanence l'app-topbar (nom du CV, annuler...), même
     hors aperçu. */
  .preview-topbar {
    display: none;
    position: fixed;
    top: 0; left: 0; right: 0;
    height: 52px;
    z-index: 90;
    grid-template-columns: 44px 1fr 44px;
    align-items: center;
    padding: 0 8px;
    background: var(--color-surface);
    border-bottom: 1px solid var(--line);
  }
  body.preview-open .preview-topbar { display: grid; }

  /* Le bandeau persistant, quand il est déplacé dans .preview-panel par
     preview.js (placerBandeauSelonContexte), rejoint le flux normal juste
     sous la topbar au lieu d'y être ancré en position:fixed en permanence :
     sinon .preview-panel réservait un espace fixe (112px) même bandeau
     absent (zone vide) et le CV touchait le bandeau sans respiration. Il
     défile désormais avec le CV — le FAB prend le relais une fois hors champ. */
  .preview-panel > .offer-banner {
    position: static;
    width: 100%;
    margin: 0 0 10px;
    box-shadow: none;
    border-bottom: 1px solid var(--line);
  }
  body.preview-open .preview-panel { padding-top: 52px; }

  /* Bouton flottant remonté (§3.1) ; overflow: hidden contient les faces
     superposées ci-dessous. */
  .preview-fab { display: flex; bottom: 84px; overflow: hidden; }

  /* .preview-panel passe en z-index:80 dès que preview-open est actif — sans
     ce rehaussement, le FAB (z-index:70 de base) se retrouvait PEINT SOUS le
     panneau plein écran. 88 dépasse le panneau (80). */
  body.preview-open .preview-fab { z-index: 88; }

  /* Alternance libellé ⇄ icône œil (§3.2, Q5). Les faces sont superposées ;
     seule l'active est visible. */
  .preview-fab-face {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-size: 13px; font-weight: 600;
    opacity: 0; transform: scale(0.7);
    transition: opacity var(--duration-normal) var(--ease-out), transform var(--duration-normal) var(--ease-out);
  }
  .preview-fab-face-oeil { animation: fabAlternance 5s steps(1, end) infinite; }
  .preview-fab-face-mot  { animation: fabAlternance 5s steps(1, end) infinite -2.5s; }
  @keyframes fabAlternance {
    0%, 49%   { opacity: 1; transform: scale(1); }
    50%, 100% { opacity: 0; transform: scale(0.7); }
  }
  /* Dans l'aperçu : plus d'alternance, une seule face (rappel WhatsApp). */
  body.preview-open .preview-fab-face-oeil,
  body.preview-open .preview-fab-face-mot { animation: none; opacity: 0; }
  body.preview-open .preview-fab-face-rappel { opacity: 1; transform: scale(1); }

  /* Masqué par défaut en écran d'aperçu (point mobile) : il ne déclenche plus
     le téléchargement (topbar seule s'en charge) et ne réapparaît que pour
     rappeler le CTA WhatsApp une fois le bandeau défilé hors champ, sous sa
     couleur/rôle (preview.js, verifierFabRappelBandeau). */
  body.preview-open .preview-fab {
    opacity: 0;
    pointer-events: none;
  }
  body.preview-open .preview-fab.preview-fab-rappel {
    opacity: 1;
    pointer-events: auto;
    background: #25D366;
  }
  body.preview-open .preview-fab.preview-fab-rappel:hover { background: #1ebe5b; }

  .preview-scale-wrap {
    cursor: default;
  }
}

/* prefers-reduced-motion : DOIT rester hors du bloc @media (max-width: 760px)
   ci-dessus, même piège d'ordre de cascade que documenté plus bas dans ce
   fichier — un bloc nommé après gagne la cascade à spécificité égale. */
@media (prefers-reduced-motion: reduce) {
  .preview-panel { transition: none; }
  .preview-fab-face-oeil { animation: none; opacity: 1; transform: scale(1); }
  .preview-fab-face-mot  { animation: none; }
}

@media (max-width: 480px) {
  .app-topbar { height: 52px; }
  .brand-text { display: none; } /* le logo suffit, libère la place au centre */
  .form-panel { padding: 16px 14px 96px; }

  /* En colonne (mobile), on repasse en align-items: stretch : sinon le
     align-items: flex-end du desktop (aligne input↔bouton sur une ligne)
     rétrécit et cale à droite les enfants empilés (input ~205px flottant,
     bouton étroit). En stretch, input et bouton "+ Ajouter" reprennent la
     pleine largeur, empilés proprement. */
  .field-row { flex-direction: column; gap: 0; align-items: stretch; }
  .field-row input, .field-row select { width: 100%; }
  .field-row > .btn-tag { width: 100%; }

  #langues-list .field-row { flex-direction: row; }

  /* Identité sur mobile (§4.6, réf. IMG 4) : photo centrée en haut, puis
     Prénom / Nom sur une même ligne à 2 colonnes, puis les champs pleine
     largeur empilés (titre, email, téléphone, ville).
     IMPORTANT : le desktop passe .identity-photo-row en `display: grid`
     (grille 4 lignes photo↔noms). Sans repasser explicitement en `flex` ici,
     `flex-direction: column` n'a AUCUN effet sur un conteneur grid — la vue
     mobile restait alors coincée dans la grille 2 colonnes du desktop (photo
     et noms côte à côte au lieu d'empilés). */
  .identity-photo-row {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 18px;
  }
  .identity-name-fields {
    display: flex;
    flex-direction: row;
    gap: 10px;
    width: 100%;
  }
  .identity-name-fields > .field-label { flex: 1; margin-bottom: 0; }

  /* La règle générique `.field-row { flex-direction: column }` ci-dessus
     empile les paires sur mobile — c'est le comportement voulu pour
     email/téléphone (§4.6 : pleine largeur, empilés). Prénom/Nom échappe à
     cette règle car il vit dans .identity-name-fields, pas dans .field-row. */

  .section-title { font-size: 17px; }

  #btn-download-pdf { padding: 8px 12px; font-size: 12px; }
  #btn-download-pdf .btn-label { display: none; } /* icône seule sur très petit écran */
}

@media (prefers-reduced-motion: reduce) {
  /* .design-theme-swatch et .design-carousel-sheet ne sont PAS ici : leur
     transition de base est déclarée plus bas dans ce fichier (après ce
     bloc), donc à spécificité égale un transition:none placé ICI perdrait
     la cascade sur l'ordre — même piège que .design-dock et le hover des
     miniatures (cf. leurs commentaires respectifs). Chacun a son propre
     bloc reduced-motion placé juste après sa règle de base. */
  .import-label, .section-add, .btn-tag, .icon-btn, #btn-download-pdf, .preview-fab,
  input, select, textarea, .preview-scale-wrap, #preview-frame {
    transition: none;
  }
  #autosave-cloud.saving { animation: none; }
}

/* ---------- Panneau de design flottant (Phase 4, restylé point 1) ---------- */

.design-dock {
  /* Fixe au viewport (point 2) : un enfant position:absolute d'un ancêtre
     devenu scrollable (.preview-panel.preview-zoomed) défile avec son
     contenu — position:fixed en sort. Le décalage gauche suit la largeur
     réelle de .preview-panel à chaque palier (44% desktop, ajusté dans les
     media queries ci-dessous), la droite reste 10px car .preview-panel va
     toujours jusqu'au bord droit du viewport, à tous les paliers.
     Habillage « liquid glass » (tâche 4, maquette .app-toolbar) : fond
     translucide + flou, bord clair, ombre portée + liseré interne clair.
     Hauteur et position reprennent les valeurs d'origine (bottom:10px,
     padding compact) sur demande utilisateur après premier retour visuel —
     seul l'habillage visuel (fond/flou/ombre) reste de la tâche 4. */
  position: fixed;
  left: calc(44% + 10px);
  right: 10px;
  bottom: 10px;
  display: flex;
  justify-content: space-around;
  align-items: center;
  gap: 4px;
  background: rgba(255, 255, 255, .55);
  backdrop-filter: blur(28px) saturate(180%);
  -webkit-backdrop-filter: blur(28px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, .6);
  border-radius: 20px;
  padding: 8px 16px;
  box-shadow: 0 10px 34px rgba(18, 23, 43, .14), 0 2px 8px rgba(18, 23, 43, .06), inset 0 1px 0 rgba(255, 255, 255, .7);
  z-index: 60;
}
.design-dock-icon {
  /* Onglet maquette (.tab-btn) : icône + libellé empilés, plus de soulignement
     actif — l'état actif se signale désormais par un fond clair + une ombre
     interne (règle [data-active="true"] ci-dessous), comme la maquette.
     Padding/gap/taille repris de la géométrie d'origine (retour utilisateur). */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  background: none;
  border: none;
  border-radius: 12px;
  color: var(--ink-soft);
  font-family: var(--font-body);
  font-size: 10px;
  font-weight: 600;
  cursor: pointer;
  padding: 6px 8px 4px;
  transition: color .15s ease, background .15s ease, box-shadow .15s ease;
}
.design-dock-icon svg { display: block; width: 19px; height: 19px; }
.design-dock-icon .glyph { font-family: var(--font-section); font-size: 16px; font-weight: 700; line-height: 1; }
.design-dock-icon:hover { background: rgba(255, 255, 255, .5); }
.design-dock-icon[data-active="true"] {
  color: var(--brand);
  background: rgba(255, 255, 255, .75);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .8), 0 2px 6px rgba(18, 23, 43, .08);
}
.design-mini-panel {
  /* Remonte au-dessus du dock (tâche 4, maquette .tab-panel) : le DOM réel
     est PLAT (dock et panneaux sont des frères, tous deux enfants directs de
     document.body — cf. commentaire de design-panel.js). Chaque panneau
     (hors Design) est repositionné dynamiquement par design-panel.js pour
     s'ouvrir centré au-dessus de son propre bouton (`left` inline posé en
     JS) plutôt que de rester ancré à une position fixe de l'écran — la
     déclaration `left` ci-dessous n'est qu'un repli avant mesure JS.
     `bottom` dégage la hauteur réelle du dock revenue à sa géométrie
     d'origine (bottom dock 10px + hauteur ~54px + interstice 10px ≈ 74px). */
  position: fixed;
  left: calc(44% + 10px);
  right: 10px;
  bottom: 74px;
  background: var(--popover-bg);
  backdrop-filter: blur(32px) saturate(180%);
  -webkit-backdrop-filter: blur(32px) saturate(180%);
  border: 1px solid rgba(255, 255, 255, .6);
  border-radius: var(--popover-radius);
  box-shadow: var(--popover-shadow);
  padding: var(--popover-padding);
  z-index: 61;
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  max-height: 320px;
  overflow-y: auto;
}

/* Décalage gauche du dock/mini-panel par palier — doit rester APRÈS les
   règles de base .design-dock/.design-mini-panel ci-dessus : à spécificité
   égale, c'est l'ordre dans le fichier qui tranche la cascade, pas la
   position dans/hors d'un bloc @media. Voir .design-dock plus haut. */
@media (max-width: 980px) {
  .design-dock, .design-mini-panel { left: calc(50% + 10px); }
}

@media (max-width: 760px) {
  /* Barre glass pleine largeur (tâche 7) : sous 760px .preview-panel occupe
     déjà 100% du viewport (cf. media query plus haut), donc le décalage
     gauche calé sur 44%/50% de sa largeur (règles desktop ci-dessus) n'a
     plus de sens — left/right symétriques à 12px, comme la topbar/le FAB
     de l'aperçu mobile juste au-dessus. bottom resserré à 12px (au lieu de
     22px desktop) : plus de colonne formulaire à ménager à côté sur mobile,
     la barre peut coller un peu plus bas du bord. */
  .design-dock { left: 12px; right: 12px; bottom: 12px; }

  /* Panneaux pleine largeur eux aussi, mêmes marges que le dock. width:auto
     redondant avec left+right en position:fixed (le calcul de largeur est
     déjà implicite) mais explicite ici pour éviter qu'une largeur fixe
     desktop (aucune actuellement) ne survive à un futur changement. Les
     sous-panneaux compacts (.design-mini-panel-row, .design-mini-panel-couleur)
     gardent leur propre largeur fixe (230px/220px, cascade après ce bloc) :
     rail segmenté et color picker restent des widgets compacts, pas de
     raison de les étirer sur 351px de large.
     max-height : 50vh tient sur le plus petit viewport ciblé (iPhone SE,
     667px de haut → 50vh ≈ 333px) sans jamais chevaucher le dock (bottom
     12px + hauteur ~74px ≈ 86px réservés) ni remonter sous la topbar
     d'aperçu (52px, cf. .preview-topbar) : marge confortable des deux
     côtés sur tous les viewports mobiles courants. */
  .design-mini-panel { left: 12px; right: 12px; width: auto; max-height: 50vh; }

  /* Masqués par défaut en mode édition (point mobile) : le dock/les
     mini-panneaux vivent maintenant au niveau de document.body (bug scroll
     corrigé, cf. design-panel.js) et ne sont donc plus masqués "gratuitement"
     par le transform hors-écran de .preview-panel en édition — sans ce
     garde explicite ils s'affichaient en permanence PAR-DESSUS le
     formulaire au lieu de rester réservés à l'aperçu. visibility (pas
     display) : .design-mini-panel bascule déjà son display via JS
     (openPanel/closeAllPanels dans design-panel.js), qu'un display:none ici
     écraserait pour les mauvaises raisons ; visibility se cumule proprement
     par-dessus sans interférer avec ce style inline. z-index rehaussé
     au-dessus de .preview-panel (80) — au niveau de body, le dock n'hérite
     plus du contexte d'empilement de .preview-panel qui le faisait
     auparavant apparaître par-dessus son contenu quel que soit son z-index. */
  .design-dock, .design-mini-panel {
    visibility: hidden;
    pointer-events: none;
  }
  body.preview-open .design-dock,
  body.preview-open .design-mini-panel {
    visibility: visible;
    pointer-events: auto;
  }
  body.preview-open .design-dock { z-index: 84; }
  body.preview-open .design-mini-panel { z-index: 86; }

  /* Fix revue finale (branche entière) : les 5 onglets du dock (padding/
     font-size hérités de la règle desktop .design-dock-icon, calibrée pour
     un dock demi-largeur) débordent de la barre glass pleine largeur sous
     ~402px (iPhone SE/mini/12-15, Galaxy S, Pixel 7). flex:1 1 0 + min-width:0
     fait partager la largeur du dock à parts égales entre les 5 onglets et
     les autorise à rétrécir sous leur largeur naturelle ; padding/font-size
     réduits pour que le libellé + l'icône restent lisibles à cette taille. */
  .design-dock-icon { padding: 8px 6px; font-size: 10px; flex: 1 1 0; min-width: 0; }
}

/* Panneau "Design" (thème) : bottom sheet ancré à la toolbar (addendum
   polish, 20 juillet 2026) — pleine largeur de .preview-panel, flush avec
   le dock, coins arrondis hauts uniquement, ombre biaisée vers le haut. */
.design-carousel-sheet {
  /* Pas de position:relative ici — .design-carousel-sheet hérite déjà
     position:fixed de .design-mini-panel (ancrage au viewport, indispensable
     — voir la règle .design-mini-panel plus haut dans ce fichier). Ce
     position:fixed hérité établit tout autant un bloc conteneur pour les
     flèches/fondus en position:absolute ci-dessous ; le redéclarer en
     position:relative écraserait le position:fixed et casserait l'ancrage
     au dock. */
  border-radius: var(--popover-radius) var(--popover-radius) 0 0;
  box-shadow: var(--popover-shadow-up);
  /* hauteur explicite du dock ci-dessous, zéro interstice : dock revenu à
     bottom:10px + hauteur ~54px ≈ 64px (géométrie d'origine). */
  bottom: 64px;
  padding: var(--popover-padding) var(--popover-padding) 12px;
  overflow: hidden; /* clippe les fondus proprement sous les coins arrondis */
  transform: translateY(100%);
  transition: transform .18s ease;
}
/* prefers-reduced-motion : DOIT rester après la règle ci-dessus (même piège
   d'ordre de cascade que .design-dock et le hover des miniatures — à
   spécificité égale, l'ordre dans le fichier tranche, un transition:none
   placé dans le bloc partagé plus haut perdrait contre cette déclaration).
   transform:translateY(...) reste inchangé ici, volontairement : l'état
   ouvert/fermé doit rester visuellement différent, seule l'animation entre
   les deux disparaît (bascule instantanée). */
@media (prefers-reduced-motion: reduce) {
  .design-carousel-sheet { transition: none; }
}
.design-carousel-sheet.design-carousel-open {
  transform: translateY(0);
}
.design-carousel-track {
  /* width:100% + min-width:0 : .design-carousel-sheet est display:flex
     (hérité de .design-mini-panel) et .design-carousel-track en est
     l'unique enfant qui participe au flex layout (fades/flèches sont
     position:absolute, retirés du flux flex). Sans ces deux propriétés,
     un flex item se dimensionne par défaut sur son contenu intrinsèque —
     ici les 13 cartes mises bout à bout, plus large que le panneau — et
     déborderait au lieu de rester contraint avec un scroll interne. */
  width: 100%;
  min-width: 0;
  display: flex;
  gap: 12px;
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none; /* Firefox */
}
.design-carousel-track::-webkit-scrollbar { display: none; } /* Chrome/Safari/Edge */
.design-carousel-fade {
  position: absolute;
  top: var(--popover-padding);
  bottom: 12px;
  width: 32px;
  pointer-events: none;
  transition: opacity .15s ease;
}
.design-carousel-fade-left { left: 0; background: linear-gradient(to right, var(--popover-bg), transparent); }
.design-carousel-fade-right { right: 0; background: linear-gradient(to left, var(--popover-bg), transparent); }
.design-carousel-arrow {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: none;
  background: #fff;
  box-shadow: 0 2px 8px rgba(0,0,0,.2);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  z-index: 2;
}
.design-carousel-arrow:disabled { opacity: 0; pointer-events: none; }
.design-carousel-arrow-left { left: 4px; }
.design-carousel-arrow-right { right: 4px; }
.design-mini-panel-list {
  /* Liste de polices : défilement vertical UNIQUEMENT. flex-wrap:wrap
     hérité de .design-mini-panel de base ferait passer les boutons en
     colonne suivante une fois max-height dépassé (9 polices, ça arrive) —
     nowrap force un empilement vertical continu qui scrolle au lieu de
     déborder en largeur. Largeur fixe pour ne pas dépendre de la longueur
     du nom de police le plus long. */
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
  width: 220px;
  overflow-x: hidden;
}
.design-mini-panel-row {
  /* Rail segmenté maquette (.segmented) : ce conteneur EST l'élément
     `.design-mini-panel` (mêmes classes sur un seul <div>, cf.
     buildDensityPanel dans design-panel.js) — gap/padding ci-dessous
     l'emportent donc sur les 10px de la règle .design-mini-panel de base
     (même ordre de cascade), pour resserrer les pilules dans le rail.
     width:230px (même logique que .design-mini-panel-couleur plus bas, déjà
     à 220px) : sans ça le panneau hérite du left/right pleine largeur de
     .design-mini-panel et les 3 pilules s'étirent sur toute la largeur de
     l'aperçu au lieu de former un rail compact façon maquette. */
  justify-content: center;
  width: 230px;
  background: rgba(255, 255, 255, .45);
  border-radius: 11px;
  padding: 3px;
  gap: 3px;
}
.design-option-row {
  /* Rangée de police maquette (.font-option) : bouton pleine largeur, pas de
     bordure visible au repos (border transparente pour garder la même boîte
     qu'à l'état actif, évite un micro-saut de layout au clic). */
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid transparent;
  background: none;
  border-radius: 9px;
  padding: 9px 12px;
  font-size: 14px;
  color: var(--ink);
  cursor: pointer;
}
.design-option-row:hover {
  background: rgba(255, 255, 255, .6);
}
.design-option-row[data-active="true"] {
  border-color: var(--brand);
  background: rgba(255, 255, 255, .85);
  color: var(--brand);
  font-weight: 600;
}
.design-option-pill {
  /* Pilule segmentée maquette (.seg-btn) : plus de bordure/fond individuels,
     le fond « rail » vit sur le conteneur .design-mini-panel-row ci-dessus. */
  flex: 1;
  border: none;
  background: transparent;
  padding: 9px 6px;
  border-radius: 9px;
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  white-space: nowrap;
}
.design-option-pill:hover {
  background: rgba(255, 255, 255, .6);
}
.design-option-pill[data-active="true"] {
  background: var(--brand);
  color: #fff;
  box-shadow: 0 2px 8px rgba(43, 58, 143, .32);
}
.design-color-swatch {
  /* Pastille ronde maquette (.color-swatch), agrandie à 36px. */
  position: relative;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 2px solid transparent;
  box-shadow: 0 0 0 1px var(--line);
  cursor: pointer;
  padding: 0;
}
.design-color-swatch[data-active="true"] {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, .8), 0 0 0 3px var(--ink);
}
.design-color-swatch[data-active="true"]::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 13l4 4L19 7' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px;
  filter: drop-shadow(0 0 1px rgba(0,0,0,.5));
}
.design-theme-card {
  /* Pas de cadre sur le bouton englobant (retour à l'état d'avant la tâche 5,
     sur demande utilisateur) : le seul indicateur de sélection reste la
     bordure de .design-theme-swatch ci-dessous, comme précédemment. */
  text-align: center;
  cursor: pointer;
  padding: 6px;
  border: none;
  background: none;
  font-family: inherit; /* <button> n'hérite pas de la police par défaut du navigateur */
  font-size: inherit;
  color: inherit;
  flex-shrink: 0;
  scroll-snap-align: start;
}
/* .design-carousel-track a overflow-y: hidden et sa hauteur colle exactement
   à celle des cartes (aucune marge verticale) : l'anneau de focus par défaut
   (outline-offset positif, donc dessiné à l'extérieur de la carte) se
   retrouvait coupé en haut et en bas par ce clip, sur 100% des cartes.
   Décalage négatif : l'anneau se dessine dans le padding de 6px de la carte,
   donc reste toujours à l'intérieur de la zone visible. Vérifié : revue
   manuelle du focus clavier, Task 5 du plan polish. */
.design-theme-card:focus-visible {
  outline-offset: -3px;
}
.design-theme-card-locked {
  opacity: 0.4;
  cursor: not-allowed;
}
.design-theme-swatch {
  width: 144px;
  height: 204px; /* ratio A4 strict : 144/204 ≈ 210/297, agrandi pour lisibilité */
  background-color: #eee;
  background-size: cover;
  background-position: top center;
  border: 1px solid var(--line);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--faint); /* couleur du cadenas SVG sur les cartes verrouillées */
  transition: transform .15s ease, box-shadow .15s ease;
}
/* prefers-reduced-motion : même piège d'ordre de cascade que ci-dessus —
   doit rester après la règle de base. Sans effet visible aujourd'hui (le
   correctif hover plus bas neutralise déjà transform/box-shadow), mais
   évite que cette transition redevienne active si un futur ajout donne à
   .design-theme-swatch un autre état animé que le survol. */
@media (prefers-reduced-motion: reduce) {
  .design-theme-swatch { transition: none; }
}
.design-theme-card:not(.design-theme-card-locked):hover .design-theme-swatch {
  transform: translateY(-2px);
  box-shadow: 0 6px 14px rgba(0,0,0,.18);
}
/* prefers-reduced-motion : DOIT rester après la règle ci-dessus, pas dans le
   bloc @media (prefers-reduced-motion: reduce) partagé plus haut dans ce
   fichier. À spécificité égale (même sélecteur), c'est l'ordre dans le
   fichier qui tranche la cascade, indépendamment de la présence d'un @media
   — même piège déjà rencontré pour .design-dock (cf. commentaire plus haut).
   transition:none seul (dans le bloc partagé) ne suffit pas : la translation
   elle-même doit être neutralisée ici, pas seulement son animation. */
@media (prefers-reduced-motion: reduce) {
  .design-theme-card:not(.design-theme-card-locked):hover .design-theme-swatch {
    transform: none;
    box-shadow: none;
  }
}
.design-theme-card-active .design-theme-swatch {
  border-color: var(--brand);
  border-width: 2px;
}
.design-theme-card-active span {
  font-weight: 600;
}

/* --- Écran d'offre (Phase 1) : design-system des modals (§5.1/§5.2/§5.3) --
   Tokens et structure repris de la maquette fournie. #offer-overlay et
   #unlock-overlay (modal post-paiement, tâche 7) partagent les classes
   .offer-overlay/.offer-card historiques : les états d'ouverture (fondu,
   translation, bottom-sheet mobile) restent scopés sur l'id #offer-overlay
   plutôt que sur la classe. #unlock-overlay bascule bien la classe
   is-visible (tâche 7, piegerFocus compris) mais aucune règle CSS n'y
   réagit pour l'instant : son ouverture/fermeture reste un simple
   affichage instantané piloté par l'attribut hidden, comme avant la tâche
   7 — cohérent avec l'absence de bottom-sheet mobile dédiée (voir plus bas).
   Les propriétés purement statiques (fond, rayon, ombre, disposition) restent
   partagées : elles ne dépendent d'aucun état JS et profitent aux deux. */
.offer-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--shadow-backdrop);
  padding: var(--space-5);
}
.offer-overlay[hidden] { display: none; }

#offer-overlay {
  opacity: 0;
  transition: opacity var(--duration-normal) var(--ease-standard);
}
#offer-overlay.is-visible { opacity: 1; }

.offer-card {
  position: relative;
  width: 100%;
  max-width: var(--modal-max-width);
  max-height: calc(100vh - var(--space-8));
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--color-surface);
  border-radius: var(--radius-modal-2xl);
  box-shadow: var(--shadow-modal);
  text-align: left;
}
#offer-overlay .offer-card {
  transform: translateY(16px) scale(0.98);
  transition: transform var(--duration-normal) var(--ease-out);
}
#offer-overlay.is-visible .offer-card {
  transform: translateY(0) scale(1);
}

.offer-header {
  padding: var(--space-6) var(--space-6) var(--space-4);
  border-bottom: 1px solid var(--color-border-modal);
}
.offer-header-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}
.offer-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 700;
  color: var(--color-text-primary);
  line-height: 1.3;
}
.offer-close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--space-8);
  height: var(--space-8);
  border: 0;
  border-radius: var(--radius-modal-md);
  background: var(--color-surface-muted);
  color: var(--color-text-secondary);
  cursor: pointer;
}
.offer-close:hover { background: var(--color-border-modal); }
.offer-close svg { width: 18px; height: 18px; }

.offer-score-row {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  margin-top: var(--space-4);
}
.offer-score-value {
  font-size: 2rem;
  font-weight: 800;
  color: var(--color-positive);
  line-height: 1;
}
.offer-score-label {
  font-size: 0.85rem;
  color: var(--color-text-tertiary);
}
.offer-score-bar {
  margin-top: var(--space-3);
  height: 8px;
  border-radius: var(--radius-modal-md);
  background: var(--color-surface-muted);
  overflow: hidden;
}
/* Largeur posée en JS (afficherOffre, preview.js) — jamais de valeur en dur
   ici : le score réel pilote à la fois le chiffre et la barre, impossible
   qu'ils divergent. */
.offer-score-bar-fill {
  height: 100%;
  width: 0%;
  border-radius: var(--radius-modal-md);
  background: var(--color-positive);
  transition: width 1s var(--ease-out) 0.3s;
}

.offer-body {
  padding: var(--space-5) var(--space-6) var(--space-6);
  overflow-y: auto;
  flex: 1;
  min-height: 0;
}
.offer-body-text {
  margin: 0 0 var(--space-4);
  font-size: 0.92rem;
  line-height: 1.5;
  color: var(--color-text-secondary);
}

.offer-price-card {
  border: 1px solid var(--color-border-modal);
  border-radius: var(--radius-modal-lg);
  background: var(--color-surface-raised);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.offer-price-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}
.offer-price-title { font-size: 0.92rem; font-weight: 600; color: var(--color-text-primary); }
.offer-price {
  display: flex;
  align-items: baseline;
  gap: var(--space-1);
}
.offer-price-value { font-size: 1.4rem; font-weight: 800; color: var(--color-text-primary); }
.offer-price-currency { font-size: 0.8rem; color: var(--color-text-tertiary); }

.offer-benefits { list-style: none; margin: 0; padding: 0; }
.offer-benefits li {
  position: relative;
  padding-left: var(--space-5);
  margin: var(--space-2) 0;
  font-size: 0.88rem;
  line-height: 1.4;
  color: var(--color-text-secondary);
}
/* Coche verte en CSS (::before/::after), pas en SVG répété par puce — même
   rendu, markup plus court. */
.offer-benefits li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.35em;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-positive-muted);
  box-shadow: inset 0 0 0 1px var(--color-positive);
}
.offer-benefits li::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.55em;
  width: 6px;
  height: 3px;
  border-left: 2px solid var(--color-positive);
  border-bottom: 2px solid var(--color-positive);
  transform: rotate(-45deg);
}

.offer-guarantee {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-bottom: var(--space-5);
  font-size: 0.8rem;
  color: var(--color-text-tertiary);
}
.offer-guarantee-icon { flex: none; width: 18px; height: 18px; margin-top: 1px; }

.offer-actions { display: flex; }
.offer-cta {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 100%;
  min-height: var(--touch-target);
  padding: 0 var(--space-5);
  background: var(--color-btn-primary);
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
  text-decoration: none;
  border-radius: var(--radius-modal-lg);
  overflow: hidden;
  animation: cta-pulse 2.2s ease-in-out infinite;
}
.offer-cta-icon { width: 20px; height: 20px; }
.offer-cta::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.35) 50%, transparent 100%);
  transform: translateX(-100%);
  animation: cta-shine 2.6s ease-in-out infinite;
}

@keyframes cta-pulse {
  0%, 100% { box-shadow: 0 0 0 0 var(--color-positive-muted); }
  50% { box-shadow: 0 0 0 8px rgba(52, 199, 89, 0); }
}
@keyframes cta-shine {
  0% { transform: translateX(-100%); }
  50%, 100% { transform: translateX(100%); }
}

@media (max-width: 480px) {
  /* Scopé sur #offer-overlay (pas .offer-overlay/.offer-card) : ces classes
     sont partagées avec #unlock-overlay (cf. commentaire ligne ~1330), qui ne
     doit pas hériter du rendu bottom-sheet mobile propre à l'écran d'offre. */
  #offer-overlay { align-items: flex-end; padding: 0; }
  #offer-overlay .offer-card {
    max-width: 100%;
    width: 100%;
    max-height: 90vh;
    border-radius: var(--radius-modal-2xl) var(--radius-modal-2xl) 0 0;
    transform: translateY(100%);
  }
  #offer-overlay.is-visible .offer-card { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
  #offer-overlay,
  #offer-overlay .offer-card {
    transition: none !important;
  }
  #offer-overlay .offer-card,
  #offer-overlay.is-visible .offer-card {
    transform: none !important;
  }
  .offer-score-bar-fill,
  .offer-cta,
  .offer-cta::before {
    transition: none !important;
    animation: none !important;
  }
  .offer-cta::before { display: none; }
}

/* --- Spinner du bouton Télécharger (Lot A, item 2) ----------------------- */
@keyframes spin-360 { to { transform: rotate(360deg); } }

#btn-download-pdf .btn-icon-spinner,
#btn-download-pdf-mobile .btn-icon-spinner { display: none; }
#btn-download-pdf.generating .btn-icon-download,
#btn-download-pdf-mobile.generating .btn-icon-download { display: none; }
#btn-download-pdf.generating .btn-icon-spinner,
#btn-download-pdf-mobile.generating .btn-icon-spinner {
  display: block;
  animation: spin-360 0.8s linear infinite;
}

/* --- Bandeau persistant post-offre (Lot A, item 8) ------------------------ */
.offer-banner {
  /* §2.4 : le bandeau ne couvre QUE la largeur du panneau formulaire (44 %,
     50 % sous 980px, 100 % sous 760px) — il ne recouvre jamais l'aperçu. Les
     largeurs suivent exactement celles de .form-panel, dont il est la barre
     d'action ancrée en bas. */
  position: fixed;
  left: 0;
  bottom: 0;
  width: 44%;
  z-index: 60;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 16px; background: var(--surface); border-top: 1px solid var(--line);
  box-shadow: 0 -6px 16px -12px rgba(27,34,48,.25);
}
@media (max-width: 980px) { .offer-banner { width: 50%; } }
@media (max-width: 760px) { .offer-banner { width: 100%; } }
/* Réserve la hauteur du bandeau en bas du formulaire quand il est visible. */
body:has(.offer-banner:not([hidden])) .form-panel { padding-bottom: 88px; }
.offer-banner[hidden] { display: none; }
.offer-banner-score { font-size: 0.9rem; color: var(--muted); }
/* Vert WhatsApp (#25D366) conservé tel quel : couleur de marque, pas de la
   palette encre/accent — seul le rayon (aligné --radius-lg) change. */
.offer-banner-cta {
  background: #25D366; color: #fff; text-decoration: none; font-weight: 700;
  padding: 8px 16px; border-radius: var(--radius-lg); font-size: 0.9rem; white-space: nowrap;
}
.offer-banner-close {
  border: none; background: none; color: var(--faint);
  font-size: 1rem; cursor: pointer; padding: 4px 6px;
  line-height: 1; flex-shrink: 0;
}
.offer-banner-close:hover { color: var(--ink); }

/* --- Modal post-paiement (§5.3) ------------------------------------------ */
.unlock-card { padding: var(--space-6) var(--space-6) var(--space-6); text-align: center; }
.unlock-top { display: flex; justify-content: flex-end; }

.unlock-success-icon {
  width: 48px; height: 48px;
  margin: 0 auto var(--space-4);
  border: 1.5px solid var(--color-positive);
  border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  color: var(--color-positive);
  font-size: 20px;
  opacity: 0; transform: scale(0.6);
  animation: unlockSuccessPop 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) 0.15s forwards;
}
@keyframes unlockSuccessPop {
  0%   { opacity: 0; transform: scale(0.6); }
  70%  { opacity: 1; transform: scale(1.08); }
  100% { opacity: 1; transform: scale(1); }
}

.unlock-title { font-family: var(--font-display); font-size: 18px; font-weight: 700; color: var(--color-text-primary); line-height: 1.35; margin: 0 0 6px; }
.unlock-subtitle { font-size: 14px; color: var(--color-text-tertiary); line-height: 1.5; margin: 0 0 var(--space-6); }

.unlock-actions { display: flex; flex-direction: column; gap: 10px; }

.unlock-btn {
  display: flex; align-items: center; justify-content: center; gap: var(--space-2);
  width: 100%; min-height: var(--touch-target);
  border: none; border-radius: 999px;
  font-family: inherit; font-size: 14px; font-weight: 600;
  text-decoration: none; cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out), transform 0.1s;
}
.unlock-btn svg { width: 18px; height: 18px; flex-shrink: 0; }
.unlock-btn:active { transform: scale(0.98); }
.unlock-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Vert UNIQUE du produit (Q4) — l'ancien #16A34A de la maquette est banni. */
.unlock-btn-primary { background: var(--color-btn-primary); color: #fff; }
.unlock-btn-primary:hover { filter: brightness(0.94); }

.unlock-btn-secondary { background: var(--color-surface-muted); color: var(--color-text-secondary); }
.unlock-btn-secondary:hover { background: var(--color-border-modal); }
.unlock-btn-secondary:disabled { opacity: 0.55; cursor: default; }

/* Vert de marque WhatsApp : seule exception au vert unique. */
.unlock-btn-whatsapp { background: var(--color-whatsapp); color: #fff; }
.unlock-btn-whatsapp:hover { filter: brightness(0.94); }
.unlock-btn-whatsapp svg { fill: currentColor; stroke: none; }

.unlock-divider { height: 1px; background: var(--color-border-modal); margin: var(--space-5) 0 var(--space-4); }
.unlock-help { font-size: 12.5px; color: var(--color-text-tertiary); margin: 0 0 var(--space-3); }

@media (prefers-reduced-motion: reduce) {
  .unlock-success-icon { animation: none; opacity: 1; transform: none; }
}

/* --- Color picker enrichi (point 1, 19 juillet 2026) ---------------------- */
.design-mini-panel-couleur {
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
  /* Fix revue finale (branche entière) : à 220px, les 5 swatches (36px +
     10px de gap chacune) débordaient de 37px la boîte de contenu disponible
     (220px - padding - chrome de scrollbar), et l'overflow-y:auto hérité de
     .design-mini-panel (cf. règle plus haut dans ce fichier) forçait aussi
     un overflow-x:auto par la spec CSS dès qu'un axe n'est plus visible —
     d'où une double scrollbar ET un 5e swatch clippé. Ce panneau n'a pas
     besoin de scroller lui-même (ses zones internes qui en ont besoin sont
     des éléments séparés) : on l'élargit pour que tout tienne et on repasse
     en overflow visible. */
  width: 250px;
  overflow: visible;
}
.couleur-sl-zone {
  position: relative;
  width: 100%;
  height: 140px;
  border-radius: 8px;
  touch-action: none;
  background:
    linear-gradient(to top, #000, transparent),
    linear-gradient(to right, #fff, var(--couleur-teinte-actuelle, #3652f6));
  /* Liseré charte en box-shadow (inset), pas en border : une bordure réelle
     agrandirait la boîte lue par getBoundingClientRect() dans glisser()
     (design-panel.js) et décalerait le calcul x/y du curseur — le
     box-shadow, lui, ne participe pas au rect. */
  box-shadow: inset 0 0 0 1px var(--line);
}
.couleur-sl-curseur {
  position: absolute;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.35), 0 1px 4px rgba(0,0,0,.4);
  transform: translate(-50%, -50%);
  pointer-events: none;
}
.couleur-teinte-slider {
  position: relative;
  width: 100%;
  height: 14px;
  border-radius: 999px;
  touch-action: none;
  background: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);
  /* Même raison que .couleur-sl-zone ci-dessus : box-shadow inset plutôt
     qu'une bordure réelle, pour ne pas fausser le rect lu par glisser(). */
  box-shadow: inset 0 0 0 1px var(--line);
}
.couleur-teinte-curseur {
  position: absolute;
  top: 50%;
  width: 16px; height: 16px;
  border-radius: 50%;
  border: 2px solid #fff;
  box-shadow: 0 0 0 1px rgba(0,0,0,.35), 0 1px 4px rgba(0,0,0,.4);
  transform: translate(-50%, -50%);
  pointer-events: none;
  background: #fff;
}
.couleur-swatches-row { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
.couleur-hex-row {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--bg-input);
  border: 1.5px solid var(--line);
  border-radius: 8px;
  padding: 0 10px;
}
.couleur-hex-row:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.couleur-hex-prefix {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
}
.couleur-hex-input {
  flex: 1;
  min-width: 0;
  padding: 8px 4px 8px 0;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: 13px;
}
.couleur-hex-input:focus { outline: none; }
.couleur-contraste-avertissement {
  margin: 0;
  font-size: 11px;
  color: var(--danger);
  text-align: center;
}
.design-mini-panel-couleur::after {
  /* Pas de position:relative ici — .design-mini-panel-couleur hérite déjà
     position:fixed de .design-mini-panel, qui établit tout autant un bloc
     conteneur pour ce ::after en position:absolute. Ajouter position:relative
     écraserait ce position:fixed hérité et casserait l'ancrage au viewport. */
  content: "";
  position: absolute;
  bottom: -8px;
  left: 50%;
  transform: translateX(-50%);
  width: 16px;
  height: 8px;
  background: var(--popover-bg);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}

/* --- Modal de scan dupliqué depuis index.html (point 6) -------------------
   Re-chartage (tâche 6) : fond/bordure/rayon/ombre alignés sur --surface,
   --line et le token de backdrop partagé --shadow-backdrop (déjà utilisé par
   #offer-overlay) — un seul point de vérité pour l'assombrissement derrière
   toute modale de l'éditeur. L'animation laser et son timing ne changent
   pas. */
.scan-overlay {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: var(--shadow-backdrop);
  backdrop-filter: blur(3px);
}
.scan-overlay[hidden] { display: none; }
.scan-modal {
  width: 100%; max-width: 460px;
  background: var(--surface); border-radius: var(--radius-modal-2xl);
  box-shadow: 0 40px 90px -24px rgba(27,34,48,.5);
  padding: 36px 32px 30px;
  text-align: center;
}
.scan-doc {
  position: relative;
  width: 118px; height: 150px;
  margin: 0 auto 26px;
  background: var(--surface);
  border: 1.5px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 14px 34px -18px rgba(27,34,48,.35);
  overflow: hidden;
}
.scan-doc::before {
  content: ""; position: absolute; top: 0; right: 0;
  border-width: 0 16px 16px 0; border-style: solid;
  border-color: transparent var(--bg-input) transparent transparent;
  filter: drop-shadow(-1px 1px 1px rgba(0,0,0,.06));
}
.scan-doc-lines { padding: 16px 14px; display: flex; flex-direction: column; gap: 8px; }
.scan-doc-lines span { display: block; height: 6px; border-radius: 3px; background: var(--line); }
.scan-doc-lines span:nth-child(1) { width: 60%; height: 9px; background: var(--faint); margin-bottom: 2px; }
.scan-doc-lines span:nth-child(4) { width: 40%; }
.scan-doc-lines span:nth-child(6) { width: 75%; }
.scan-laser {
  position: absolute; left: 0; right: 0; height: 3px;
  background: linear-gradient(90deg, transparent, var(--accent) 20%, #4bd6a0 50%, var(--accent) 80%, transparent);
  /* Lueur re-teintée sur le nouveau --accent (bleu marine #2B3A8F, tâche 2) —
     l'ancienne valeur rgba(54,82,246,.7) référençait l'ex-bleu #3652f6 d'avant
     re-chartage et ne correspondait plus au dégradé ci-dessus. */
  box-shadow: 0 0 12px 2px rgba(43,58,143,.7);
  animation: scan-sweep 2.1s ease-in-out infinite;
}
@keyframes scan-sweep {
  0% { top: 6%; opacity: 0; }
  8% { opacity: 1; }
  50% { top: 92%; opacity: 1; }
  58% { opacity: 0; }
  59% { top: 6%; }
  100% { top: 6%; opacity: 0; }
}
.scan-title { font-family: var(--font-display); font-weight: 600; font-size: 1.2rem; margin: 0 0 8px; }
.scan-status { min-height: 40px; font-size: .92rem; color: var(--muted); display: flex; align-items: center; justify-content: center; padding: 0 8px; }
.scan-progress { height: 4px; border-radius: 999px; background: var(--line); margin: 18px 0 4px; overflow: hidden; }
.scan-progress-fill {
  height: 100%; width: 8%; border-radius: 999px;
  background: linear-gradient(90deg, var(--accent), #4bd6a0);
  transition: width 1.1s cubic-bezier(.4,0,.2,1);
}

/* --- Transition flip entre les 2 modals (point 6) -------------------------- */
.flip-transition { transition: transform .3s ease, opacity .3s ease; }
.flip-out { transform: rotateY(90deg); opacity: 0; }

@media (prefers-reduced-motion: reduce) {
  .scan-laser { animation: none; top: 50%; }
  .flip-transition { transition: opacity .15s ease; }
  .flip-out { transform: none; }
}

/* --- Télécharger depuis l'aperçu mobile (point 11) ------------------------ */
/* Ancien bouton de téléchargement mobile distinct (#btn-download-pdf-mobile
   habillé en .preview-download-mobile) : remplacé par la tâche 11 — l'id est
   réutilisé sur le bouton droit de .preview-topbar (.preview-topbar-btn),
   qui porte maintenant tout le style visuel. */
