/**
 * console/console.css — système visuel de la Console Selvix, extrait de ~/console-selvix-maquette.html.
 * Scope volontairement limité aux composants utilisés par les pages migrées de cette mission
 * (sidebar 6 sections, topbar, KPI, panels, tableaux, badges, statuts) + le pattern "ligne repliable"
 * (absent de la maquette d'origine, ajouté ici pour Cours/Hébergements — voir CONSOLE-INTEGRATION-PLAN.md §5).
 *
 * [selvix-theming 2026-07-09] Toutes les couleurs/rayons/ombres viennent EXCLUSIVEMENT de
 * assets/selvix-tokens.css (chargé AVANT ce fichier, cf. layout_top.php) — aucune couleur en dur
 * ici : voir rapport de mission pour la preuve par grep. Thème clair (:root) / sombre
 * ([data-theme="dark"]) : ce fichier ne connaît QUE les variables, jamais les valeurs concrètes.
 *
 * [CHARTE UI CONSOLE — navigation/action = bouton, 2026-07-13, décision Denis, Mission 5/7]
 * Règle absolue : toute navigation et toute action de l'interface console (retour, breadcrumb,
 * "voir plus", lien de menu, action sur une ligne de tableau, CTA, bascule de langue, déconnexion...)
 * s'affiche comme un BOUTON — bordure + hover, même famille visuelle que .btn/.btn-primary/
 * .btn-outline-primary/.btn-ghost/.btn-xs déjà définis ci-dessous. Le lien texte souligné (<a> stylé
 * comme du texte cliquable simple, sans bordure ni fond) n'existe QUE dans du contenu RÉDIGÉ — le
 * texte d'une réponse assistant, un lien externe cité dans un paragraphe explicatif — JAMAIS comme
 * élément d'interface de navigation/action. En cas de doute sur la classe à utiliser : imiter le
 * patron déjà en usage dans le même fichier pour ce type d'action plutôt que d'inventer une nouvelle
 * classe (ex. retour de fiche → .btn.btn-outline-primary, action compacte en ligne de tableau →
 * .btn.btn-xs.btn-outline-primary). Exemple d'exception légitime : layout_bottom.php génère les
 * liens Markdown [Libellé](url) du widget d'aide en <a> simple — c'est du CONTENU rédigé par
 * l'assistant (cf. .aide-thread .msg a plus bas), pas un élément d'interface. Historique complet :
 * E125, ~/catalogue-diagnostic/REGISTRE-CHANGEMENTS-SYNERGEE-CHAT.md.
 */

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  /* [E177 2026-07-15] Pile CJK ajoutée pour ja/ko/zh (préparés, non actifs) : polices système par OS
     (Mac/Win/Linux) avant repli générique — évite les tofu boxes sans dépendre d'un chargement web font. */
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Hiragino Sans', 'Hiragino Kaku Gothic Pro',
    'Yu Gothic', 'Malgun Gothic', 'Apple SD Gothic Neo', 'Noto Sans CJK KR', 'PingFang SC', 'Microsoft YaHei',
    'Noto Sans CJK SC', sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

.app { display: grid; grid-template-columns: 260px 1fr; min-height: 100vh; }

/* SIDEBAR — toujours sombre (branding permanent), cf. selvix-tokens.css */
.sidebar {
  background: var(--sidebar);
  color: var(--sidebar-text);
  padding: 24px 0;
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  height: 100vh;
  overflow-y: auto;
}

.brand { padding: 0 24px 28px; border-bottom: 1px solid var(--sidebar-hover); margin-bottom: 20px; }

.brand-logo {
  font-size: 20px; font-weight: 700; letter-spacing: -0.02em; color: var(--sidebar-text-bright);
  display: flex; align-items: center; gap: 10px;
}
.brand-logo::before { content: ''; width: 10px; height: 10px; background: var(--accent-luminous); border-radius: var(--radius-sm); }

/* [fix-layout-selecteur-e769 20/08/2026] Empilé verticalement, jamais côte à côte : la sidebar a une
   largeur FIXE (260px, cf. .app grid-template-columns) quel que soit le viewport -- aucun point de
   rupture mobile n'existe pour elle (les deux seuls @media de ce fichier ne la touchent pas). Un
   layout en ROW (ancien `justify-content: space-between`) empile jusqu'à TROIS `.tenant-switch-form`
   (organization_id + website_id + outside_reach_website_id, cf. layout_top.php ~502-570) à côté de
   `.tenant-info` dans ~212px de largeur utile -- largement insuffisant, d'où le chevauchement du
   libellé et la troncature du second select constatés par Denis. Empiler verticalement rend chaque
   select à la largeur PLEINE du conteneur, quel que soit leur nombre -- la même correction sert
   desktop et mobile puisque la largeur de la sidebar ne varie jamais entre les deux. */
.tenant {
  margin-top: 14px; padding: 10px 12px; background: var(--sidebar-hover); border-radius: var(--radius-sm);
  display: flex; flex-direction: column; align-items: stretch; gap: 8px;
}
.tenant-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.tenant-label { font-size: 11px; color: var(--sidebar-text-dim); text-transform: uppercase; letter-spacing: 0.05em; }
.tenant-name { font-size: 13px; font-weight: 500; color: var(--sidebar-text-bright); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tenant-switch { color: var(--sidebar-text-dim); font-size: 16px; }
.tenant-switch-form { margin: 0; }
/* [fix-layout-selecteur-e769] Label propre par select (organisation vs tenant vs tenants hors
   périmètre, cf. switcher.organization_select_label/tenant_select_label/tenants_outside_reach_label)
   -- avant ce correctif les deux/trois selects ne partageaient qu'un seul libellé visible "GROUPE"
   (celui de .tenant-info, qui décrit en réalité le tenant ACTIF, pas ces sélecteurs de bascule). */
.tenant-select-label { display: block; font-size: 10px; color: var(--sidebar-text-dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 3px; }
.tenant-select {
  background: var(--sidebar); color: var(--sidebar-text-bright); border: 1px solid var(--sidebar-border); border-radius: var(--radius-sm);
  font-size: 11px; padding: 4px 6px; width: 100%; font-family: inherit;
}

.nav-section { padding: 0 12px; }
/* [nav-sidebar-hierarchy 2026-07-10] Contraste renforcé (text-dim -> text, 600 -> 700) : au retour
   Denis, les intitulés de section étaient trop proches visuellement des enfants. Reste discret —
   un seul palier, pas le blanc plein (réservé à l'état actif/hover, cf. --sidebar-text-bright). */
.nav-section-title {
  padding: 12px 12px 8px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em;
  color: var(--sidebar-text); font-weight: 700;
}

/* [sidebar-ux 2026-07-08] Sections repliables : intitulé cliquable + chevron, corps masqué par
   défaut (display:none inline posé côté serveur selon l'état de session — le JS ne fait que
   basculer l'état après le premier clic, jamais de flash de contenu déplié au chargement). */
.nav-section-header {
  display: flex; align-items: center; justify-content: space-between; width: 100%;
  background: none; border: none; padding: 0; margin: 0; cursor: pointer; font-family: inherit;
}
.nav-section-header .nav-section-title { padding: 12px 12px 8px; }
.nav-section-header:hover .nav-section-title { color: var(--sidebar-text-bright); }
/* [nav-sidebar-hierarchy 2026-07-10] Chevron agrandi + contrasté (9px dim -> 12px text, quasi
   invisible au constat de Denis) — la rotation 90° (▶ replié -> ▼ déplié) était déjà correcte,
   seule la taille/couleur manquait de présence. Éclairci au survol ET à l'état ouvert (renforce la
   lecture "c'est déplié" au-delà de la seule rotation). */
.nav-section-chevron {
  font-size: 12px; color: var(--sidebar-text); margin-right: 12px; transition: transform 0.12s, color 0.12s;
  display: inline-block; line-height: 1;
}
.nav-section-header:hover .nav-section-chevron { color: var(--sidebar-text-bright); }
.nav-section.open .nav-section-chevron { transform: rotate(90deg); color: var(--sidebar-text-bright); }
.nav-section-dimmed .nav-section-title,
.nav-section-dimmed .nav-section-chevron { opacity: .55; }

/* [nav-sidebar-hierarchy 2026-07-10] Fil d'appartenance : la section qui contient la page active
   porte un point discret devant son intitulé (même token --accent-luminous que le point du
   brand-logo juste au-dessus dans la sidebar, cf. .brand-logo::before — convention déjà en place,
   pas une couleur inventée), pour lire d'un coup d'œil "je suis dans X > Y" même repliée.
   Dérivé de $ACTIVE_SECTION, déjà calculé côté PHP — n'ajoute aucun état, aucune mécanique. */
.nav-section-current .nav-section-title::before {
  content: ''; display: inline-block; width: 5px; height: 5px; border-radius: 50%;
  background: var(--accent-luminous); margin-right: 7px; vertical-align: middle;
}

/* [nav-sidebar-hierarchy 2026-07-10] Repère vertical discret reliant les enfants à leur parent —
   indentation + filet, jusque-là absents (aucune règle .nav-section-body n'existait). */
.nav-section-body {
  margin-left: 12px; padding-left: 10px; border-left: 1px solid var(--sidebar-border);
}

.nav-item {
  display: flex; align-items: center; justify-content: space-between; padding: 8px 12px;
  border-radius: var(--radius-sm); color: var(--sidebar-text-dim); cursor: pointer; font-size: 13px; margin-bottom: 2px;
  transition: background 0.12s; user-select: none; text-decoration: none;
}
.nav-item:hover { background: var(--sidebar-hover); color: var(--sidebar-text-bright); }
.nav-item.active { background: var(--sidebar-active); color: var(--sidebar-text-bright); }
.nav-item.disabled { opacity: .45; cursor: default; }
.nav-item.disabled:hover { background: none; color: var(--sidebar-text-dim); }

.nav-item-label { display: flex; align-items: center; gap: 10px; flex: 1; min-width: 0; }
.nav-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.badge { font-size: 9px; padding: 2px 6px; border-radius: var(--radius-sm); font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; flex-shrink: 0; }
.badge-beta { background: var(--badge-beta-bg); color: var(--badge-beta-text); }
.badge-soon { background: var(--badge-soon-bg); color: var(--badge-soon-text); }
.badge-selvix { background: var(--badge-selvix-bg); color: var(--badge-selvix-text); }
/* [E170 2026-07-15] Badge compteur numérique sur une entrée de menu (ex: écoles à corriger sur
   "État de mon offre", registre E170) -- même famille .badge (calibrée fond sombre sidebar) que
   badge-soon/badge-selvix, jamais text-transform:uppercase (un chiffre n'a pas de casse). */
.badge-count { background: var(--danger); color: #fff; text-transform: none; letter-spacing: normal; }
/* [ATTENTION-HUMAINE-CONSOLE 2026-07-21, E268] Badge « À traiter » de la barre latérale. Hérite du
   gabarit .badge-count (forme, graisse, absence de majuscules forcées) — jamais un style inventé —
   mais PAS de sa couleur : la mission distingue deux régimes, et les confondre viderait le rouge de
   son sens. Ambre = des dossiers attendent une main humaine. Rouge = au moins un dossier IMPORTANT
   attend. Un badge toujours rouge cesse d'être un signal au bout de trois jours ; un rouge réservé
   reste lisible. Les deux teintes reprennent les tokens existants (--warning / --danger), les mêmes
   que la coloration des lignes qu'elles résument. */
.badge-attention { background: var(--warning); color: #fff; }
.badge-attention.is-important { background: var(--danger); color: #fff; }
/* [E161 2026-07-15] Vacances & jours fériés -- miroir affichage du seuil moteur (≥3 = déduite, ≥5 =
   catégorisée), mêmes tokens que .notice.warning/.notice.danger, jamais une couleur inventée. */
.badge-vac-deduite { background: var(--warning-soft); color: var(--warning); text-transform: none; letter-spacing: normal; }
.badge-vac-categorisee { background: var(--danger-soft); color: var(--danger); text-transform: none; letter-spacing: normal; }
/* [E163 2026-07-15] Promotions -- phrase lisible auto-construite en tête d'éditeur, se recompose en
   direct depuis le même tampon que la sauvegarde (jamais un second état parallèle). */
.promo-sentence-panel { background: var(--accent-soft); border-color: var(--accent); }
.promo-sentence { font-size: 15px; font-weight: 600; color: var(--text); line-height: 1.5; }

.sidebar-footer {
  margin-top: auto; padding: 20px 24px 8px; border-top: 1px solid var(--sidebar-hover);
  font-size: 12px; color: var(--sidebar-text-dim); display: flex; align-items: center; gap: 10px;
}
.sidebar-footer a { color: var(--sidebar-text-dim); }
.sidebar-footer-text { color: var(--sidebar-text-bright); font-weight: 500; font-size: 12px; }
.sidebar-footer-sub { font-size: 11px; color: var(--sidebar-text-dim); }
.sidebar-footer-row { font-size: 11px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sidebar-footer-sep { color: var(--sidebar-text-dim); }
/* [E177 2026-07-15, registre E177] .locale-current/.locale-link (toggle en ligne 2 langues)
   dépréciées -- remplacées par .locale-select (sélecteur déroulant, jusqu'à 8 langues), classes
   laissées en place au cas où un écran hors de ce balayage les référencerait encore. */
.locale-current { color: var(--sidebar-text-bright); font-weight: 600; }
.locale-link { background: none; border: none; padding: 0; color: var(--sidebar-text-dim); cursor: pointer; font-size: 11px; font-family: inherit; text-decoration: underline; }
.locale-select {
  background: var(--sidebar); color: var(--sidebar-text-dim); border: 1px solid var(--sidebar-border);
  border-radius: var(--radius-sm); font-size: 11px; padding: 2px 4px; font-family: inherit; cursor: pointer;
}
.locale-select:hover, .locale-select:focus { color: var(--sidebar-text-bright); }
.theme-toggle {
  background: none; border: 1px solid var(--sidebar-border); border-radius: var(--radius-full); cursor: pointer;
  color: var(--sidebar-text-dim); font-size: 11px; font-family: inherit; padding: 4px 10px;
  display: inline-flex; align-items: center; gap: 6px; transition: all .12s;
}
.theme-toggle:hover { border-color: var(--sidebar-text-dim); color: var(--sidebar-text-bright); }
.theme-toggle .theme-icon { font-size: 12px; line-height: 1; }
.avatar {
  width: 28px; height: 28px; border-radius: var(--radius-full);
  background: linear-gradient(135deg, var(--avatar-grad-start), var(--avatar-grad-end));
  display: flex; align-items: center; justify-content: center; color: var(--sidebar-text-bright); font-weight: 600; font-size: 12px;
  flex-shrink: 0;
}

/* MAIN */
.main { padding: 32px 40px 48px; max-width: 1280px; width: 100%; }
.topbar {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 16px;
  padding-bottom: 24px; margin-bottom: 32px; border-bottom: 1px solid var(--border);
}
.breadcrumb { font-size: 12px; color: var(--text-muted); margin-bottom: 6px; font-family: 'JetBrains Mono', monospace; letter-spacing: -0.01em; }

h1 { font-size: 24px; font-weight: 600; letter-spacing: -0.02em; color: var(--text); }
h2 { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 4px; }
h3 { font-size: 13px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.06em; margin-bottom: 12px; }

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

.btn {
  display: inline-flex; align-items: center; gap: 6px; padding: 8px 14px; font-size: 13px; font-weight: 500;
  border-radius: var(--radius-md); border: 1px solid var(--border-strong); background: var(--bg-white); color: var(--text);
  cursor: pointer; transition: all 0.12s; font-family: inherit; text-decoration: none;
}
.btn:hover { border-color: var(--text-muted); }
.btn-primary { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.btn-primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn-ghost { border-color: transparent; background: transparent; }
.btn-outline-primary { background: var(--bg-white); color: var(--accent); border-color: var(--accent); }
.btn-outline-primary:hover { background: var(--accent-soft); }
.btn-danger { background: var(--danger); color: var(--on-accent); border-color: var(--danger); }
.btn-danger:hover { filter: brightness(0.88); }
/* [M4c 2026-07-08] Bouton "Marquer porté OVH" — action positive de clôture de cycle. */
.btn-success { background: var(--success); color: var(--on-accent); border-color: var(--success); }
.btn-success:hover { filter: brightness(0.88); }
.btn-xs { padding: 3px 8px; font-size: 11px; gap: 4px; }

/* [E208 2026-07-16, registre E208] État d'attente générique pour un bouton d'action (import, sauvegarde,
   publication...) -- composant partagé, cf. window.ConsoleBusyButton (layout_bottom.php). Spinner
   discret réutilisant .btn (déjà display:inline-flex + gap), jamais de librairie externe. Le texte du
   bouton est TOUJOURS conservé à côté du spinner (jamais remplacé par une barre de progression
   fictive) -- seules les vraies étapes serveur, quand elles existent, s'affichent en texte. */
.btn-spinner {
  display: inline-block; width: 12px; height: 12px; flex-shrink: 0;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: btn-spinner-spin .6s linear infinite; opacity: .85;
}
@keyframes btn-spinner-spin { to { transform: rotate(360deg); } }
.btn[disabled] { opacity: .65; cursor: not-allowed; }

/* KPI ROW */
.kpi-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 16px; margin-bottom: 32px; }
.kpi { background: var(--bg-white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 20px; position: relative; box-shadow: var(--shadow-sm); }
.kpi-label { font-size: 12px; color: var(--text-muted); margin-bottom: 8px; font-weight: 500; }
.kpi-value { font-size: 28px; font-weight: 600; letter-spacing: -0.02em; color: var(--text); font-variant-numeric: tabular-nums; }
.kpi.accent { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.kpi.accent .kpi-label { color: var(--on-accent-muted); }
.kpi.accent .kpi-value { color: var(--on-accent); }
.kpi.kpi-success { background: var(--success-soft); border-color: var(--success-border); }
.kpi.kpi-success .kpi-value { color: var(--success); }
.kpi.kpi-danger { background: var(--danger-soft); border-color: var(--danger-border); }
.kpi.kpi-danger .kpi-value { color: var(--danger); }
.kpi-sub { font-size: 11px; color: var(--text-faint); margin-top: 8px; font-family: 'JetBrains Mono', monospace; }
.kpi.accent .kpi-sub, .kpi.kpi-success .kpi-sub, .kpi.kpi-danger .kpi-sub { color: var(--kpi-sub-on-tint); }

/* [MISSION 4/4 -- liste Prospects, 2026-07-12] Compteurs de tête CLIQUABLES (filtrent la liste) --
   <button> plutôt que <div>, reset des styles natifs de bouton pour rester visuellement identique à
   un .kpi classique + état actif visible (le compteur en cours de filtre reste repéré une fois cliqué). */
.kpi-clickable {
  font: inherit; text-align: left; cursor: pointer; width: 100%;
  transition: box-shadow .15s ease, transform .1s ease;
}
.kpi-clickable:hover { box-shadow: var(--shadow-md); }
.kpi-clickable.is-active { outline: 2px solid var(--accent); outline-offset: -2px; }
.kpi.kpi-warning { background: var(--warning-soft); border-color: var(--warning); }
.kpi.kpi-warning .kpi-value { color: var(--warning); }

/* Ligne "à traiter" -- teinte légère, cohérente avec .thread-card-attention (mission 3/4).
   [MISSION 4/7 -- couleurs d'urgence, 2026-07-13] CORRECTIF : malgré le commentaire "ambre" ci-dessus,
   --accent-soft est en réalité BLEU (cf. selvix-tokens.css, --accent = #1E40AF) -- jamais un vrai ambre.
   Remplacé par --warning-soft (véritable ambre) pour être cohérent avec le reste du 3-niveaux (fiche
   ET liste). row-urgent (nouveau) = needsAttention ET leads_is_important() (mission 3/7) -- ROUGE. */
tr.row-attention { background: var(--warning-soft); }
tr.row-urgent { background: var(--danger-soft); }

/* Avatar/initiales par prospect (référence visuelle validée par Denis). */
.prospect-avatar {
  display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px;
  border-radius: var(--radius-full); background: var(--accent-soft); color: var(--accent);
  font-size: 11px; font-weight: 700;
}

.prospects-anonymous-footer { margin-top: 12px; font-size: 12px; color: var(--text-muted); }

/* [MISSION 3/7 -- priorité par valeur, 2026-07-13] Légende sobre de la règle "important" -- même patron
   que .prospects-anonymous-footer ci-dessus. */
.prospects-importance-footer { margin-top: 4px; font-size: 12px; color: var(--text-muted); }

/* Tuile KPI affichant plusieurs montants par devise ("12 400 EUR + 3 200 AUD") -- .kpi-value standard
   (28px) est calibré pour un nombre seul, trop large pour une liste de montants multi-devises. */
.kpi-value-multi { font-size: 16px; font-weight: 600; line-height: 1.3; }

/* Montant de devis "important" (>= seuil de sa devise, cf. leads_is_important()) -- mise en évidence
   PAR POIDS uniquement (pas de nouvelle couleur), la pastille .tag-info à côté porte le signal couleur. */
.quote-important-amount { font-weight: 800; }

/* PANEL */
.panel { background: var(--bg-white); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 24px; margin-bottom: 24px; box-shadow: var(--shadow-sm); }
.panel-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 20px; gap: 12px; }
.panel-title { font-size: 15px; font-weight: 600; color: var(--text); }
.panel-subtitle { font-size: 12px; color: var(--text-muted); margin-top: 2px; }

.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 24px; }
.placeholder-panel { background: var(--bg); border-style: dashed; }
.placeholder-panel .panel-header { margin-bottom: 10px; }
.placeholder-text { font-size: 12px; color: var(--text-muted); margin: 0; }

/* TABLE */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th { text-align: left; padding: 10px 12px; font-size: 11px; font-weight: 600; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 1px solid var(--border); background: var(--bg); }
td { padding: 12px; border-bottom: 1px solid var(--border); color: var(--text); vertical-align: middle; }
tr:last-child td { border-bottom: none; }
.num { font-variant-numeric: tabular-nums; font-family: 'JetBrains Mono', monospace; font-size: 12px; }

/* STATUS / TAGS (badges pour zones claires — le .badge du sidebar est calibré pour fond sombre) */
.status { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; }
.status-dot { width: 6px; height: 6px; border-radius: var(--radius-full); flex-shrink: 0; }
.status-dot.live { background: var(--success); }
.status-dot.pending { background: var(--warning); }
.status-dot.danger { background: var(--danger); }

.tag { display: inline-block; font-size: 11px; padding: 2px 8px; border-radius: var(--radius-sm); font-weight: 600; }
.tag-success { background: var(--success-soft); color: var(--success); }
.tag-warning { background: var(--warning-soft); color: var(--warning); }
.tag-danger { background: var(--danger-soft); color: var(--danger); }
.tag-muted { background: var(--neutral-soft); color: var(--text-muted); }
.tag-info { background: var(--accent-soft); color: var(--accent); }

.notice { display: flex; gap: 12px; padding: 14px 16px; border-radius: var(--radius-lg); margin-bottom: 20px; }
.notice.danger { background: var(--danger-soft); color: var(--danger); }
.notice.warning { background: var(--warning-soft); color: var(--warning); }
.notice.success { background: var(--success-soft); color: var(--success); }
.notice.info { background: var(--accent-soft); color: var(--accent); }

/* FORMULAIRES (pages Conseiller / Nouvelle école) */
.field-group { margin-bottom: 18px; }
.field-label { display: block; font-size: 12px; font-weight: 500; color: var(--text-muted); margin-bottom: 6px; }
.field-hint { font-size: 11px; color: var(--text-faint); margin-top: 4px; }
.field-input, .field-select, .field-textarea {
  width: 100%; padding: 8px 10px; border: 1px solid var(--border-strong); border-radius: var(--radius-md);
  font-family: inherit; font-size: 13px; color: var(--text); background: var(--bg-white);
}
.field-textarea { resize: vertical; }
/* [UX-CHAMPS-PROSE 2026-07-21, E264] Champs de PROSE (Quartier / repères, Accès / transports,
   Complément équipements…) : zones de rédaction auto-dimensionnées. Le script de layout_bottom.php
   ajuste `height` au contenu ; ces règles fixent le plancher (≈3 lignes, l'attribut `rows` est neutralisé
   dès que la hauteur est calculée) et le PLAFOND, au-delà duquel on défile À L'INTÉRIEUR du champ
   plutôt que de pousser le bouton d'enregistrement hors de l'écran.
   `min-height` en `em` : suit la taille de police du champ, jamais un nombre de pixels figé.
   Sans JavaScript, l'attribut `rows` s'applique normalement et le champ reste redimensionnable à la
   main : la dégradation est propre, aucun texte n'est jamais coupé sans moyen de le lire. */
.field-textarea.js-autosize {
  min-height: 5.4em;      /* ≈ 3 lignes + padding */
  max-height: 22em;       /* ≈ 12 lignes : au-delà, défilement interne */
  overflow-y: auto;
  line-height: 1.5;
  resize: vertical;       /* l'auto-dimensionnement n'enlève pas la poignée manuelle */
}
.field-input:focus, .field-select:focus, .field-textarea:focus { outline: none; border-color: var(--accent); }
.field-checkbox-row { display: flex; flex-wrap: wrap; gap: 16px; }
.field-checkbox { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; }

/* [E200 2026-07-16, registre E200] Zone de dépôt de fichier -- même champ <input type="file">
   natif (name/id/accept/required inchangés, zéro changement de la chaîne d'extraction), placé en
   overlay transparent PLEIN CADRE au-dessus de la zone stylée : le navigateur gère nativement le
   clic-pour-parcourir ET le glisser-déposer directement sur l'input (aucune manipulation JS de
   DataTransfer/input.files requise, donc aucun risque de divergence cross-navigateur). Le JS ne
   pilote QUE l'affichage (état survol/dépôt, nom + taille une fois choisi) -- purement cosmétique. */
.file-dropzone {
  position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 108px; padding: 20px; border: 2px dashed var(--border-strong);
  border-radius: var(--radius-lg); background: var(--bg); text-align: center;
  transition: border-color .15s ease, background-color .15s ease;
}
.file-dropzone:hover, .file-dropzone:focus-within, .file-dropzone.is-dragover {
  border-color: var(--accent); background: var(--accent-soft);
}
.file-dropzone-input {
  position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; padding: 0; opacity: 0;
  cursor: pointer; z-index: 1;
}
.file-dropzone-icon { font-size: 22px; line-height: 1; color: var(--accent); margin-bottom: 4px; }
.file-dropzone-title { font-size: 13px; font-weight: 500; color: var(--text); }
.file-dropzone-subtext { font-size: 11px; color: var(--text-faint); }
/* :not([hidden]) plutôt qu'un display:flex nu -- sinon cette règle (spécificité classe, feuille
   auteur) l'emporte sur la règle UA [hidden]{display:none} (même spécificité, ordre de cascade),
   et les deux vues (vide/sélectionnée) restent visibles superposées quand [hidden] est posé. */
.file-dropzone-selected:not([hidden]) { display: flex; align-items: center; gap: 12px; width: 100%; }
.file-dropzone-file-icon { font-size: 22px; line-height: 1; flex-shrink: 0; }
.file-dropzone-file-info { flex: 1; min-width: 0; text-align: left; }
.file-dropzone-file-name {
  font-size: 13px; font-weight: 500; color: var(--text); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap;
}
.file-dropzone-file-size { font-size: 11px; color: var(--text-faint); margin-top: 2px; }
.file-dropzone-remove {
  position: relative; z-index: 2; flex-shrink: 0; width: 24px; height: 24px; border-radius: var(--radius-full);
  border: 1px solid var(--border-strong); background: var(--bg-white); color: var(--text-muted);
  cursor: pointer; font-size: 15px; line-height: 1; display: flex; align-items: center; justify-content: center;
  font-family: inherit;
}
.file-dropzone-remove:hover { border-color: var(--text-muted); color: var(--text); }

.list-row { display: flex; gap: 8px; margin-bottom: 8px; align-items: center; }
.list-row .field-input { flex: 1; }

.fieldset { border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px; margin-bottom: 16px; }
.fieldset-legend { font-size: 13px; font-weight: 600; color: var(--text); margin-bottom: 12px; }

/* LIGNE REPLIABLE (cours / hébergements) — pattern ajouté, absent de la maquette d'origine */
.tier-toggle {
  background: none; border: none; cursor: pointer; color: var(--accent); font-family: inherit;
  font-size: 12px; font-weight: 500; display: inline-flex; align-items: center; gap: 6px; padding: 2px 0;
}
/* [tiers-visibles 2026-07-08] Chevron agrandi (10px -> 13px, couleur accent explicite) + toute la
   ligne cliquable (curseur pointer + surbrillance au survol) : un utilisateur qui teste en réel ne
   voyait aucune invitation à déplier — "une fonction invisible n'existe pas". */
.tier-toggle .chev { display: inline-block; transition: transform .12s; font-size: 13px; color: var(--accent); }
.tier-toggle.open .chev { transform: rotate(90deg); }
.tier-row { cursor: pointer; }
.tier-row:hover { background: var(--row-hover); }
.tier-detail { display: none; background: var(--bg); }
.tier-detail.open { display: table-row; }
.tier-detail td { padding: 14px 12px; }
.row-gap { background: var(--danger-soft); }

/* [preview-detail-e203 2026-07-16, registre E203] Détail dépliable par item dans l'aperçu par école
   mode groupe (import_group_mode.php) — <details> natif imbriqué dans la rubrique parente, aucun JS.
   Marqueur triangle natif du navigateur conservé comme seule affordance (même logique que la rubrique
   parente, spec mission : pas de compteur seul, le détail chiffré doit être accessible en un clic). */
.group-mode-line-detail { margin: 2px 0; }
.group-mode-line-detail > summary { list-style-position: outside; }
.group-mode-line-list { font-size: 12px; margin: 4px 0 4px 18px; color: var(--text-muted); }
.group-mode-line-list li { margin-bottom: 2px; }

/* [exclusion-e207 2026-07-16, registre E207] Action "Ne pas importer"/"Réintégrer" par ligne — icône
   discrète (spec mission point 1 : "pas un bouton criard"), jamais mise en avant visuellement autant
   que les actions primaires (btn-primary/btn-danger) du reste de la console. */
.group-mode-excl-toggle-form { margin-left: 8px; }
.group-mode-excl-toggle-btn {
  background: none; border: none; cursor: pointer; color: var(--text-muted); font-family: inherit;
  font-size: 11px; padding: 0 2px; opacity: .75; transition: opacity .12s, color .12s;
}
.group-mode-excl-toggle-btn:hover { opacity: 1; color: var(--accent); }

/* [exclusion-e207 2026-07-16, registre E207] Ligne exclue à l'écran 1 (tableau d'affectation) —
   grisée/barrée, visible mais non ambiguë (spec mission point 1 : "visible mais grisée/barrée,
   réintégrable d'un clic"), MÊME traitement visuel que l'écran 2 (cf. .js-group-excl-row ci-dessous). */
.js-group-excl-row.is-excluded td { color: var(--text-muted); text-decoration: line-through; }
.js-group-excl-row.is-excluded .field-select,
.js-group-excl-row.is-excluded .group-mode-excl-checkbox-label { text-decoration: none; }
.group-mode-excl-checkbox-label { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; color: var(--text-muted); cursor: pointer; white-space: nowrap; }

/* [M3c 2026-07-08] Cellule Simulateur (import_journal.php) : verdict/progression/boutons/dépli en
   pile verticale espacée, plutôt que collés les uns aux autres. */
.sim-cell { min-width: 220px; }
.sim-cell > * + * { margin-top: 4px; }

/* [refonte-cockpit 2026-07-08] Bloc héro dashboard.php : la capacité du conseiller en GRAND et en
   VERT (ce n'est pas une alerte), pas une tuile parmi d'autres dans une grille de KPI. */
.hero-panel { display: flex; align-items: center; justify-content: space-between; gap: 24px; flex-wrap: wrap; }
.hero-value { font-size: 56px; font-weight: 700; color: var(--success); line-height: 1; }
.hero-label { font-size: 15px; font-weight: 600; margin-top: 6px; }
.hero-sub { font-size: 13px; color: var(--text-muted); margin-top: 2px; }
.hero-advisor { text-align: right; font-size: 13px; color: var(--text-muted); }
.hero-advisor-row { margin-top: 4px; }
.hero-advisor-row strong { color: var(--text); }

/* BARRE D'OUTILS (recherche/filtres/tri) — pattern ajouté pour Mes écoles */
.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.toolbar .field-input { width: 240px; max-width: 100%; }
.toolbar .field-select { width: 190px; max-width: 100%; }
.toolbar-spacer { flex: 1; }
.toolbar-count { font-size: 12px; color: var(--text-muted); white-space: nowrap; }

/* PAGINATION — pattern ajouté pour Mes écoles */
.pagination-bar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);
}
.pagination-info { font-size: 12px; color: var(--text-muted); }
.pagination-controls { display: flex; gap: 6px; }

/* [POLISH-CONSOLE 2026-07-10] Bulles de fil de conversation (détail Conversations, E56) — recette
   MUTUALISÉE depuis le chat de test refondu (E66, chat-frame.html : .messages/.msg),
   portée ici plutôt que dupliquée. Conseiller = assistant = gauche (pastille accent), prospect = user
   = droite (fond accent). Pas d'horodatage par bulle : history[] n'en porte aucun (constat E56) —
   rien d'inventé. */
.msg-thread { display: flex; flex-direction: column; gap: 16px; }

.msg {
  max-width: 70%;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  border-radius: var(--radius-md);
  white-space: pre-wrap;
  line-height: 1.5;
  font-size: 14px;
  position: relative;
}
.msg-text { white-space: pre-wrap; }

.msg.assistant {
  align-self: flex-start;
  margin-left: 30px;
  background: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  border-bottom-left-radius: 4px;
}
.msg.assistant::before {
  content: '';
  position: absolute;
  left: -30px;
  top: 2px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--accent);
}

.msg.user {
  align-self: flex-end;
  background: var(--accent);
  color: var(--on-accent);
  border-bottom-right-radius: 4px;
}

/* Marqueurs d'événement (devis envoyé/échec, E59) — déjà des séparateurs centrés hors bulle ;
   simple portage en classe réutilisable (auparavant inline style), position chronologique inchangée. */
.msg-event { align-self: center; padding: 6px 14px; border-radius: var(--radius-full); font-size: 12px; font-weight: 600; }
.msg-event.success { background: var(--success-soft); color: var(--success); }
.msg-event.failure { background: var(--danger-soft); color: var(--danger); }
.pagination-controls .btn[disabled] { opacity: .4; cursor: default; pointer-events: none; }

/* [MISSION 3/4 -- FIL UNIFIÉ, 2026-07-12] Cartes email (devis/relance/réponse) dans le même fil que
   les bulles chat -- badge de canal discret, dépliable pour le contenu long, teinte "à traiter" pour
   une réponse non encore marquée traitée, pointillés pour une relance en attente/projetée/annulée
   (jamais un événement qui s'est réellement produit -- distinction visuelle intentionnelle). Tokens
   uniquement, cohérent avec .msg-event existant. */
.thread-card {
  align-self: stretch; border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 10px 14px; background: var(--bg-white); font-size: 13px;
}
.thread-card-dashed { border-style: dashed; background: transparent; }
/* [MISSION 4/7 -- couleurs d'urgence, 2026-07-13] thread-card-attention passait par --accent (bleu,
   cf. correctif row-attention ci-dessus) -- corrigé en --warning (véritable ambre, "à traiter"
   standard). thread-card-urgent (nouveau) = même événement mais sur un dossier leads_is_important()
   -- ROUGE, cf. leads_attention_level(). */
.thread-card-attention { border-color: var(--warning); background: var(--warning-soft); }
.thread-card-urgent { border-color: var(--danger); background: var(--danger-soft); }
.thread-card-summary { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.thread-card-subject { color: var(--text-muted); font-style: italic; }
.thread-card-amount { margin-left: auto; }
.thread-card-date { margin-left: auto; color: var(--text-muted); font-size: 12px; }
.channel-badge {
  display: inline-flex; align-items: center; padding: 2px 8px; border-radius: var(--radius-full);
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em;
}
.channel-badge-quote { background: var(--success-soft); color: var(--success); }
.channel-badge-relance { background: var(--accent-soft); color: var(--accent); }
.channel-badge-reply { background: var(--warning-soft); color: var(--warning); }
/* [MISSION 4/7 -- chronologie repliée, 2026-07-13] Badge du canal "Chat" -- neutre (le chat lui-même
   n'est jamais un événement "à traiter"), pour la carte unique "Conversation — N tours". */
.channel-badge-chat { background: var(--neutral-soft); color: var(--text-muted); }
.thread-card-details { margin-top: 8px; }
.thread-card-details summary { cursor: pointer; font-size: 12px; color: var(--accent); }
.thread-card-body { margin-top: 6px; color: var(--text); white-space: pre-wrap; font-size: 13px; }
.thread-card-actions { display: flex; gap: 8px; margin-top: 10px; }
.thread-card-treated-note { margin-top: 8px; font-size: 12px; color: var(--text-muted); font-style: italic; }
.thread-card-cancel-note { flex-basis: 100%; font-size: 12px; color: var(--text-muted); font-style: italic; }
.channel-card-failure { border-color: var(--danger); }
.thread-filter .btn.is-active { background: var(--accent-soft); }

/* [MISSION 4/7 -- chronologie repliée, 2026-07-13] Carte "Conversation — N tours" -- <details> natif
   (chevron navigateur natif, repliée par défaut : ni "open", ni JS). La disposition flex du résumé vit
   sur un <span> ENFANT de <summary> (pas sur <summary> lui-même) -- poser display:flex directement sur
   <summary> désactive le marqueur natif de repli dans Chrome/Firefox (constat), on le préserve donc en
   gardant <summary> en affichage par défaut (list-item). */
.thread-card-chat > summary { cursor: pointer; }
.thread-card-chat .msg-thread { margin-top: 12px; }

/* [MISSION 4/7 -- mini-légende, 2026-07-13] Même patron que .prospects-importance-footer (leads.php). */
.thread-urgency-footer { margin-top: 12px; font-size: 12px; color: var(--text-muted); }

/* [AIDE-WIDGET 2026-07-10] Bulle flottante de l'assistant d'aide -- remplace le panneau latéral
   (AIDE-CONSOLE, E78) : constat Denis, le panneau prenait trop de place et masquait la console pour
   un usage ponctuel. Tokens uniquement (aucune couleur en dur, cf. mission "Grep zéro couleur en
   dur"). Bulles .msg/.msg-thread réutilisées telles quelles (E69/POLISH-CONSOLE), CONTENU du widget
   inchangé -- seul le CONTENANT change de panneau plein-hauteur à fenêtre compacte ancrée bas-droite.
   Plus d'overlay de fond : le widget ne recouvre jamais la page, rien à assombrir. z-index choisis
   sous le modal d'édition de tarif (catalogue_ecole.php, #price-modal, z-index:1050) -- un modal doit
   toujours passer devant un widget d'aide, jamais l'inverse. */
.aide-bubble-toggle {
  position: fixed; right: 24px; bottom: 24px; width: 52px; height: 52px; border-radius: var(--radius-full);
  background: var(--accent); color: var(--on-accent); border: none; cursor: pointer;
  display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 700;
  box-shadow: var(--shadow-md); z-index: 900; transition: transform .15s ease, box-shadow .15s ease;
}
.aide-bubble-toggle:hover { background: var(--accent-hover); transform: scale(1.06); }
.aide-bubble-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* [MISSION 2/4 -- HAUTEUR ADAPTATIVE, 2026-07-12] Ancienne hauteur FIXE (min(60vh, 640px)) quel que
   soit le contenu réel -- un échange court affichait un grand vide. Remplacée par une hauteur
   intrinsèque (aucune valeur "height" posée : le flex column prend la taille de son contenu) bornée
   par min-height (jamais plus petit qu'un input + un message court) et max-height (~70vh, plafond
   demandé) -- au-delà, .aide-thread (flex:1 1 auto + overflow-y:auto, inchangé) absorbe le
   dépassement en scroll interne, patron flexbox standard "grandit jusqu'à un plafond puis scrolle".
   .aide-form garde flex-shrink:0 (inchangé) -- l'input reste toujours visible en bas. */
.aide-widget {
  position: fixed; right: 24px; bottom: 88px; width: 380px; max-width: calc(100vw - 32px);
  min-height: 240px; max-height: min(70vh, calc(100vh - 120px));
  background: var(--bg-white); border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-md); z-index: 899; display: flex; flex-direction: column; overflow: hidden;
  transform-origin: bottom right; animation: aide-widget-in .18s ease-out;
}
/* [BUGFIX 2026-07-10, correctif E81] CAUSE RACINE du widget bloqué ouvert en permanence : la règle
   .aide-widget ci-dessus fixe "display: flex" SANS CONDITION. L'attribut HTML `hidden` (posé/retiré
   par le JS via widget.hidden = true/false) applique "display: none" via la feuille de style
   UTILISATEUR-AGENT du navigateur -- mais cette règle UA a la MÊME spécificité (0,1,0, un sélecteur
   d'attribut) que ".aide-widget" (0,1,0, un sélecteur de classe) ci-dessus. À spécificité égale,
   l'ORIGINE de la feuille tranche : une règle AUTEUR (ce fichier, console.css) l'emporte TOUJOURS sur
   la feuille UA, quel que soit l'ordre des règles -- donc "display: flex" restait actif même quand
   `hidden` était bien présent dans le DOM (le JS d'ouverture/fermeture fonctionnait réellement, mais
   n'avait aucun effet visuel : même défaut déjà présent dans .aide-panel avant ce chantier, jamais
   détecté faute de test navigateur réel). Fix : règle `.aide-widget[hidden]` ci-dessous, spécificité
   (0,2,0) STRICTEMENT supérieure à `.aide-widget` seule -- l'emporte quel que soit l'ordre des
   déclarations, garantit display:none tant que l'attribut hidden est présent. */
.aide-widget[hidden] { display: none; }

@keyframes aide-widget-in {
  from { opacity: 0; transform: scale(.94) translateY(10px); }
  to   { opacity: 1; transform: scale(1) translateY(0); }
}
.aide-widget-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 14px 16px; border-bottom: 1px solid var(--border); flex-shrink: 0;
}
.aide-widget-title { font-weight: 600; font-size: 14px; color: var(--text); }
.aide-close {
  background: none; border: none; cursor: pointer; font-size: 20px; line-height: 1;
  color: var(--text-muted); padding: 2px 6px; border-radius: var(--radius-md);
}
.aide-close:hover { background: var(--accent-soft); color: var(--accent); }
.aide-thread { flex: 1 1 auto; overflow-y: auto; padding: 16px; min-height: 0; }
.aide-thread .msg { max-width: 90%; }
.aide-thread .msg.assistant { margin-left: 26px; }
.aide-thread .msg.assistant::before { left: -26px; width: 16px; height: 16px; }
.aide-thread .msg a { color: inherit; text-decoration: underline; font-weight: 600; }
.aide-thread .msg.user a { color: var(--on-accent); }
/* [MISSION 2/4 -- BOUTONS D'ACTION, 2026-07-12] Rangée de boutons EN TÊTE d'une réponse assistant
   qui référence un/des écran(s) console -- l'action d'abord, le texte ensuite (doctrine ergonomie).
   Réutilise .btn/.btn-outline-primary/.btn-xs existants (sobre, cohérent avec la charte), aucune
   nouvelle classe de bouton inventée. */
.aide-actions { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
.aide-loading {
  padding: 0 16px 8px; font-size: 12px; color: var(--text-muted); font-style: italic; flex-shrink: 0;
}
.aide-form {
  display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--border); flex-shrink: 0;
}
.aide-input {
  flex: 1 1 auto; padding: 9px 12px; border: 1px solid var(--border-strong);
  border-radius: var(--radius-md); font-size: 13px; font-family: inherit; color: var(--text);
  background: var(--bg-white);
}
.aide-input:focus { outline: none; border-color: var(--accent); }
.aide-send { padding: 9px 16px; font-size: 13px; white-space: nowrap; }

@media (max-width: 560px) {
  /* [AIDE-WIDGET] Plein écran mobile (mission) -- la pastille reste visible et flottante, rapprochée
     du coin pour ne jamais empiéter sur une éventuelle barre de navigation système. */
  .aide-widget {
    right: 0; left: 0; bottom: 0; top: 0; width: 100%; max-width: 100%; height: 100%; max-height: 100%;
    border-radius: 0; border: none;
  }
  .aide-bubble-toggle { right: 16px; bottom: 16px; }
}

/* [TON-CONSEILLER 2026-07-11] Curseur "Ton d'écriture" (conseiller_caractere.php) — <input
   type="range"> réel restylé pour ressembler au track/fill/handle de la maquette d'origine
   (~/console-selvix-maquette.html, section Personality > Writing tone, qui n'était que des div
   statiques sans interactivité). Tokens uniquement. --tone-fill posée en JS sur chaque `input`
   (pourcentage courant) et lue par le gradient du track pour dessiner le remplissage sans dupliquer
   la logique de rendu entre navigateurs. */
.tone-slider-row { display: flex; align-items: center; gap: 16px; padding: 12px 0; }
.tone-slider-endpoint { font-size: 13px; font-weight: 500; color: var(--text); white-space: nowrap; }
.tone-slider {
  --tone-fill: 50%;
  flex: 1 1 auto; -webkit-appearance: none; appearance: none;
  width: 100%; height: 4px; background: transparent; cursor: pointer; outline: none;
}
.tone-slider::-webkit-slider-runnable-track {
  height: 4px; border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--accent) var(--tone-fill), var(--border) var(--tone-fill));
}
.tone-slider::-webkit-slider-thumb {
  -webkit-appearance: none; width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-white); box-shadow: 0 0 0 1px var(--accent);
  margin-top: -5px; cursor: pointer;
}
.tone-slider::-moz-range-track {
  height: 4px; border-radius: var(--radius-full);
  background: linear-gradient(to right, var(--accent) var(--tone-fill), var(--border) var(--tone-fill));
}
.tone-slider::-moz-range-thumb {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-white); box-shadow: 0 0 0 1px var(--accent);
  cursor: pointer;
}
.tone-slider:focus-visible::-webkit-slider-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.tone-slider:focus-visible::-moz-range-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.tone-slider:disabled { cursor: default; opacity: .6; }
.tone-slider:disabled::-webkit-slider-thumb { cursor: default; }
.tone-slider:disabled::-moz-range-thumb { cursor: default; }
.tone-slider-readout {
  text-align: center; font-family: 'JetBrains Mono', monospace; font-size: 12px;
  color: var(--text-muted); margin-top: 4px;
}

/* [RELANCES-REVUE 2026-07-11] Aperçu email en iframe sandboxée (leads_relances_revue.php) -- le HTML
   prévisualisé est un document <!DOCTYPE html> complet (LeadsRelanceBridge::renderRelanceHtml()),
   jamais injectable directement dans la page (imbrication html/body invalide) ; l'iframe isole aussi
   proprement tout style email des styles console. Tokens uniquement. */
.relance-preview-frame {
  width: 100%; height: 420px; border: 1px solid var(--border); border-radius: var(--radius-md);
  background: var(--bg-white); margin-top: 12px;
}

/* [BATCH 7-ONGLETS 2026-07-14] Onglets génériques de l'éditeur de cours (parité Bindows) --
   réutilisable partout où une entité a plusieurs blocs d'édition distincts. Tokens uniquement. */
.course-tabs { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 20px; border-bottom: 1px solid var(--border); }
.course-tab-link {
  padding: 10px 16px; font-size: 13px; font-weight: 600; color: var(--text-muted); cursor: pointer;
  border: none; background: transparent; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.course-tab-link:hover { color: var(--text); }
.course-tab-link.active { color: var(--accent); border-bottom-color: var(--accent); }
.course-tab-panel { display: none; }
.course-tab-panel.active { display: block; }
.tier-table { width: 100%; border-collapse: collapse; margin-bottom: 16px; font-size: 13px; }
.tier-table th, .tier-table td { padding: 8px 10px; border-bottom: 1px solid var(--border); text-align: left; }
.tier-table th { color: var(--text-muted); font-weight: 600; font-size: 11px; text-transform: uppercase; }
.tier-table td input.field-input { padding: 4px 6px; font-size: 13px; }

/* [BATCH RECETTE 2026-07-14] Barre "Enregistrer les modifications" sticky en bas de viewport --
   modèle de sauvegarde uniforme sur les 7 onglets (tampon local, un seul clic Enregistrer).
   [E176 2026-07-15, registre E176] REMPLACÉE par .console-save-bar (position FIXED, indépendante du
   scroll, un seul composant partagé pour toute la console) -- classe conservée le temps de la
   migration écran par écran, à retirer une fois tous les écrans migrés (cf. registre E176). */
.sticky-save-bar {
  position: sticky; bottom: 0; background: var(--bg-white); border-top: 1px solid var(--border);
  padding: 12px 0; margin-top: 8px; z-index: 5;
}

/* [E176 2026-07-15, registre E176] Barre de sauvegarde collante GÉNÉRIQUE -- composant unique adopté
   par tous les écrans console à formulaire (remplace .sticky-save-bar ci-dessus + tout bouton
   Enregistrer bas-de-page propre à un écran, jamais deux mécanismes concurrents sur le même écran).
   position:fixed (pas sticky) : reste ancrée au bas du VIEWPORT quel que soit le scroll de la page,
   contrairement à l'ancienne barre qui ne collait qu'au bas de son propre conteneur scrollable.
   Pleine largeur (jamais couplée en dur à la largeur de la sidebar, cf. .app grid-template-columns --
   un futur changement de largeur de sidebar ne casse jamais ce composant). */
:root { --save-bar-height: 56px; }
/* [E182 2026-07-15, root cause registre E182] `display: flex` était posé SANS garde -- une règle
   auteur (même spécificité qu'un sélecteur de classe) l'emporte TOUJOURS sur la règle par défaut du
   navigateur `[hidden] { display: none; }` (priorité d'origine CSS : auteur > agent utilisateur,
   indépendamment de la spécificité). Résultat : l'attribut HTML `hidden` posé par le markup et
   correctement basculé par le JS (barEl.hidden = true/false, cf. layout_bottom.php) n'avait AUCUN
   effet visuel -- la barre était donc TOUJOURS affichée (vide tant qu'aucun setDirty() n'était
   appelé), sur CHAQUE page y compris celles sans formulaire. Bug jamais détecté en E176 car le
   harnais de test ne capturait que le HTML brut (présence de l'attribut `hidden` dans le markup
   serveur), jamais un rendu réel en navigateur. Fix : `display: flex` désormais posé UNIQUEMENT
   via `:not([hidden])`, laissant l'attribut `hidden` reprendre son effet natif quand il est présent. */
.console-save-bar {
  position: fixed; left: 0; right: 0; bottom: 0; height: var(--save-bar-height);
  background: var(--sidebar); color: var(--sidebar-text-bright); box-shadow: var(--shadow-md);
  align-items: center; gap: 12px; padding: 0 24px; z-index: 950;
}
.console-save-bar:not([hidden]) { display: flex; }
.console-save-bar-count { font-size: 13px; font-weight: 500; }
.console-save-bar-spacer { flex: 1; }
.console-save-bar .btn-default { background: transparent; color: var(--sidebar-text-bright); border: 1px solid var(--sidebar-border); }
.console-save-bar .btn-default:hover { background: var(--sidebar-hover); }
/* Quand la barre est visible, la bulle/fenêtre d'aide (bottom:24px/88px) remonte d'autant pour ne
   jamais être recouverte -- classe posée par le JS de la barre (jamais l'inverse : l'aide ne sait
   rien de la barre), cf. console_save_bar_init() dans layout_bottom.php. */
body.has-save-bar .aide-bubble-toggle { bottom: calc(var(--save-bar-height) + 24px); }
body.has-save-bar .aide-widget { bottom: calc(var(--save-bar-height) + 88px); }
/* Contenu principal : marge basse ajoutée quand la barre est visible, sinon son dernier panneau
   resterait masqué derrière la barre fixed (jamais un chevauchement de contenu). */
body.has-save-bar .main { padding-bottom: calc(48px + var(--save-bar-height)); }
@media (max-width: 560px) {
  .console-save-bar { padding: 0 12px; gap: 8px; }
  .console-save-bar-count { font-size: 12px; }
}
details.panel > summary { list-style: revert; }
details.panel summary::-webkit-details-marker { display: revert; }

/* [COMPLÉMENT ⑦ 2026-07-14] Horaires par cours -- ligne compacte + édition inline, repli du champ
   pleine largeur qui explosait le scroll dès ~15 cours. Tokens uniquement. */
.schedule-row { display: flex; align-items: center; gap: 10px; padding: 8px 4px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
.schedule-row:last-child { border-bottom: none; }
.schedule-row-display { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; cursor: pointer; padding: 4px 6px; border-radius: var(--radius-sm); }
.schedule-row-display:hover, .schedule-row-display:focus { background: var(--neutral-soft); outline: none; }
.schedule-row-name { font-weight: 600; }
.schedule-row-value { color: var(--text-muted); font-size: 13px; margin-left: auto; white-space: nowrap; }
.schedule-row-edit { flex: 1; min-width: 200px; }

/* [ONGLET DATES 2026-07-14] Grille calendrier annuelle (parité Bindows AnnualCalendar) -- remplace
   la liste verticale d'un champ par date (repliée par année, cf. .cal-year-grid). Tokens uniquement. */
.cal-year-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 12px; }
.cal-month { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 8px; }
.cal-month-title { font-weight: 600; font-size: 12px; text-transform: uppercase; text-align: center; margin-bottom: 6px; color: var(--text-muted); }
.cal-month-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-weekday { font-size: 10px; text-align: center; color: var(--text-muted); font-weight: 600; }
.cal-day { font-size: 11px; text-align: center; padding: 3px 0; border-radius: var(--radius-sm); cursor: pointer; }
.cal-day:hover { background: var(--neutral-soft); }
.cal-day-blank { cursor: default; }
.cal-day-selected { background: var(--accent); color: var(--bg-white); font-weight: 600; }
.cal-day-selected:hover { background: var(--accent); }
.cal-day-disabled { color: var(--text-muted); opacity: 0.5; cursor: not-allowed; }
.cal-day-disabled:hover { background: none; }
.cal-day-disabled.cal-day-selected { background: var(--neutral-soft); color: var(--text-muted); opacity: 0.85; }

/* [BUG PERTE SILENCIEUSE 2026-07-14] Ligne/carte touchée mais incomplète -- jamais enregistrée
   silencieusement, toujours visuellement signalée (tier-table rows ET fee-cards). Tokens uniquement. */
tr.row-incomplete { background: var(--warning-soft); }

/* [E165 2026-07-15] Disponibilités résidences -- grille hebdomadaire (pas SEMAINE, cf. .cal-day pour
   le pas JOUR de E157/E158). Tokens uniquement, mêmes couleurs sémantiques que .tag-success/-warning/
   -danger/-muted -- jamais une couleur inventée. */
.ra-table { border-collapse: collapse; font-size: 11px; }
.ra-table th, .ra-table td { padding: 2px; text-align: center; }
.ra-table thead th { color: var(--text-muted); font-size: 10px; white-space: nowrap; }
/* [E1395 06/09/2026] Largeur relevée de 24px à 34px -- une case peut désormais afficher "restant
   (brute)" (ex. "4 (5)"), pas seulement la capacité brute seule ; police ramenée à 9px pour ce même
   format sans forcer un retour à la ligne. */
.ra-cell { width: 34px; height: 20px; border-radius: var(--radius-sm); cursor: pointer; font-size: 9px; white-space: nowrap; }
.ra-cell:hover { outline: 2px solid var(--accent); outline-offset: -2px; }
.ra-cell-green { background: var(--success-soft); color: var(--success); }
.ra-cell-amber { background: var(--warning-soft); color: var(--warning); }
.ra-cell-red { background: var(--danger-soft); color: var(--danger); }
.ra-cell-gray { background: var(--neutral-soft); color: var(--text-muted); }
.ra-cell-selected { outline: 2px solid var(--accent); outline-offset: -2px; font-weight: 600; }
.ra-cell-disabled { background: var(--neutral-soft); color: var(--text-muted); opacity: 0.5; cursor: not-allowed; }
/* [E1022 27/08/2026] Marqueur SUPPLÉMENTAIRE "option prise" (pré-réservation active, pending ou
   confirmée) -- se SUPERPOSE au niveau de capacité (vert/ambre/rouge/gris), ne le remplace jamais :
   seuls la bordure et le point sont ajoutés, le fond/texte de la couleur de base restent visibles
   (l'école doit voir À LA FOIS le niveau de stock ET qu'une option y est posée, critère de
   lisibilité du cadrage E1022/DOSSIER-DISPONIBILITES). */
.ra-cell-option { border: 2px solid var(--accent); }
.ra-swatch { display: inline-block; width: 12px; height: 12px; border-radius: var(--radius-sm); margin-right: 4px; vertical-align: middle; }
.ra-swatch-green { background: var(--success-soft); border: 1px solid var(--success); }
.ra-swatch-amber { background: var(--warning-soft); border: 1px solid var(--warning); }
.ra-swatch-red { background: var(--danger-soft); border: 1px solid var(--danger); }
.ra-swatch-gray { background: var(--neutral-soft); border: 1px solid var(--text-muted); }
.ra-swatch-option { background: var(--accent-soft); border: 1px solid var(--accent); }
tr.row-incomplete td { border-color: var(--warning); }
div.row-incomplete { border: 1px solid var(--warning); }
.row-incomplete-hint { color: var(--warning); font-weight: 700; margin-right: 6px; cursor: help; }

/* [ONGLET SAISONS 2026-07-14 ①] Grille calendrier en mode PLAGE -- réutilise .cal-* (E157). Tokens
   uniquement. */
.cal-day-season { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.cal-day-season:hover { background: var(--accent-soft); }
.cal-day-season-overlap { background: repeating-linear-gradient(45deg, var(--accent-soft), var(--accent-soft) 4px, var(--warning-soft) 4px, var(--warning-soft) 8px); }
.cal-day-pending { outline: 2px solid var(--accent); outline-offset: -2px; }
tr.row-armed { outline: 2px solid var(--accent); outline-offset: -2px; }

/* [ORDRE-FIL-CONVERSATION 2026-07-21, E271] Indication du sens de lecture, à côté du filtre de canal.
   Volontairement discrète : c'est un repère, pas un message — il doit se lire au premier coup d'œil
   puis se faire oublier. Mêmes tokens que le reste de la console, aucune couleur inventée. */
.thread-order-hint { margin-left: 10px; color: var(--text-muted); font-size: 12px; white-space: nowrap; }

/* [UX-AFFICHAGE-EMAILS 2026-07-21, E275] Corps d'email en paragraphes compacts.
   Les emails reçus portent un saut de ligne DOUBLE entre chaque phrase (forme de
   `ExtractedMarkdownMessage`) : rendus en <br> bruts, cela produisait une ligne vide pleine hauteur
   partout. Le rendu passe désormais par des <p>, et l'espacement redevient une affaire de CSS —
   0,45 em suffit à séparer deux idées sans faire respirer la page à chaque phrase. */
.thread-card-body .email-para,
.thread-card-draft-body .email-para { margin: 0 0 0.45em; line-height: 1.5; }
.thread-card-body .email-para:last-child,
.thread-card-draft-body .email-para:last-child { margin-bottom: 0; }

/* Le brouillon devient repliable. Son en-tête est le résumé cliquable : même affordance que les
   autres cartes du fil, aucun style inventé. Ouvert, il ne doit rien changer à l'apparence connue. */
.thread-card-draft-fold > summary { cursor: pointer; list-style: revert; }
.thread-card-draft-fold[open] > summary { margin-bottom: 6px; }

/* [CHIFFRAGE-2B2C 2026-07-21, E277] Bandeaux du brouillon porteur de devis. Vert = un devis chiffré
   par le moteur attend un clic. Ambre = une demande de prix est restée sans chiffrage, et il faut
   que ça se voie : c'est la seule information que la fiche ne pouvait pas donner jusqu'ici. */
.thread-card-quote-attached { background: var(--success-soft); color: var(--success); border-radius: 8px;
  padding: 6px 10px; margin-bottom: 8px; font-size: 13px; font-weight: 600; }
.thread-card-quote-failed { background: var(--warning-soft); color: var(--warning); border-radius: 8px;
  padding: 6px 10px; margin-bottom: 8px; font-size: 13px; line-height: 1.45; }

/* [DELAI-ZERO 2026-07-21, E278] Aide d'un réglage qui retire une sécurité : lisible sans crier.
   Mêmes tokens que le reste, aucune couleur inventée. */
.field-hint-warning { color: var(--warning); }

/* [ONBOARDING-PLACEHOLDERS 2026-07-22, E307] « Voir un exemple » sous les champs de prose des
   fiches d'onboarding. <details> natif (aucun JS) ; le bloc déplié est de la LECTURE SEULE pure —
   aucun contrôle de formulaire dedans, rien de soumissible. Tokens existants, aucune couleur
   inventée. */
.onb-example { margin-top: 6px; }
.onb-example-link {
  cursor: pointer; font-size: 12px; color: var(--accent); user-select: none;
  list-style-position: inside;
}
.onb-example-link:hover { text-decoration: underline; }
.onb-example-body {
  margin-top: 6px; padding: 10px 12px; background: var(--bg); border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
}
.onb-example-source {
  font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.04em;
  margin-bottom: 6px;
}
.onb-example-text { font-size: 13px; line-height: 1.5; color: var(--text-muted); white-space: normal; }

/* [E675 2026-08-17] Repli/dépli PAR JOUR de la grille « Programme d'activités » (campus_fiche.php,
   section programme-activites, E674). <details> natif, fermé par défaut (aucun "open", aucun JS) --
   même patron que .thread-card-chat/.onb-example ci-dessus : la disposition flex du résumé vit sur un
   SPAN ENFANT de <summary> (jamais sur <summary> lui-même), pour préserver le chevron natif. */
.pa-day-list { display: flex; flex-direction: column; gap: 6px; }
.pa-day { border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 2px 10px; }
.pa-day > summary { cursor: pointer; padding: 8px 0; list-style: revert; }
.pa-day summary::-webkit-details-marker { display: revert; }
.pa-day-summary-row { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.pa-day-name { min-width: 90px; }
.pa-day-star {
  display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px;
  border-radius: var(--radius-full); background: var(--warning-soft); color: var(--warning);
  font-size: 12px; line-height: 1;
}
.pa-day-highlight { border-left: 3px solid var(--warning); }
.pa-day-condensed { color: var(--text-muted); font-size: 12px; }
.pa-day-table { margin: 4px 0 10px; }
