/* ═══════════════════════════════════════════════════════════════════
   Equidesk — composants partagés (C1 audit restructuration)
   Source unique : design/components.css — synchronisé par
   scripts/sync-design.sh (voir tokens.css pour la règle).
   Objectif : remplacer les ~700 styles inline du portail. Une page
   migrée (C2→C5) n'utilise QUE ces classes, zéro style inline.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Page ── */
.page { max-width: 760px; margin: 0 auto; padding: 32px 20px; }
.page-wide { max-width: 860px; }

.back-link { font-size: 13px; color: var(--sky-500); text-decoration: none; }
.back-row { margin-bottom: 20px; }

.page-head { margin: 0 0 18px; }
.page-head h1 { font-size: 22px; color: var(--navy-600); margin: 0 0 6px; }
.sub { font-size: 13px; color: var(--muted); margin: 0; }

.section-title {
  font-size: 15px; text-transform: uppercase; letter-spacing: .05em;
  color: var(--muted); margin: 32px 0 12px;
}

/* ── Cartes ── */
.card {
  background: #fff; border: 1px solid var(--border);
  padding: 24px; box-shadow: var(--shadow-card);
}
.card-flat { box-shadow: none; }
.card-accent-ok   { border-left: 5px solid #10B981; }
.card-accent-warn { border-left: 5px solid #F59E0B; }
.card-accent-err  { border-left: 5px solid var(--signal-err); }

/* ── Alertes ── */
.alert { padding: 14px 18px; font-size: 14px; border: 1px solid var(--border); background: #fff; }
.alert a { color: var(--sky-500); font-weight: 600; }
.alert-warn { background: #FFF8E1; border-color: #E6C567; color: #6B4F00; }
.alert-err  { background: #FDF2F2; border-color: var(--signal-err); color: var(--signal-err); }
.alert-ok   { border-left: 5px solid #10B981; }

/* ── Formulaires ── */
.field-label { display: block; font-size: 12px; color: var(--muted); margin: 0 0 6px; }
.field-input, .field-select {
  width: 100%; box-sizing: border-box; padding: 12px;
  border: 1px solid var(--border); font-size: 15px;
  font-family: inherit; background: #fff; color: var(--text);
}
.field-input:focus, .field-select:focus { outline: 2px solid var(--sky-300); outline-offset: -1px; }
.field-row { margin-bottom: 14px; }
.field-grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.field-error { color: var(--signal-err); font-size: 13px; margin: 12px 0 0; }
.field-hint  { color: #94A3B8; font-size: 12px; margin-top: 16px; }

/* ── Boutons ── */
.btn {
  /* inline-flex plutôt qu'inline-block : seul un conteneur flex peut centrer
     verticalement le libellé quand min-height dépasse la hauteur de ligne.
     Mesuré à 41 px avec le seul padding — 3 px sous la cible tactile de 44. */
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px;
  padding: 12px 20px; border: none; cursor: pointer;
  font-size: 14px; font-weight: 700; font-family: inherit;
  text-decoration: none; text-align: center; transition: filter .15s;
}
.btn:hover { filter: brightness(1.08); }
.btn:disabled { opacity: .6; cursor: default; }
.btn-primary { background: var(--navy-600); color: #fff; }
.btn-accent  { background: var(--sky-500); color: #fff; }
.btn-outline { background: #fff; color: var(--navy-600); border: 1px solid var(--border); }
.btn-full { display: block; width: 100%; }
.btn-lg { padding: 14px; font-size: 15px; }

/* ── Badges & stats ── */
.badge {
  display: inline-block; font-size: 12px; font-weight: 600;
  padding: 4px 10px; border-radius: var(--radius-pill);
  background: #F1F5FF; color: var(--sky-500);
}
.eyebrow {
  font-size: 11px; text-transform: uppercase; letter-spacing: .05em;
  font-weight: 700; margin: 0; color: var(--muted);
}
.eyebrow-accent { color: var(--sky-500); }
.stat-value { font-size: 22px; font-weight: 800; margin: 4px 0 0; }

/* ── Lignes de données (listes factures, chevaux…) ── */
.data-list { background: #fff; border: 1px solid var(--border); overflow: hidden; }
.data-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 16px; border-bottom: 1px solid #F1F4FA;
}
.data-row:last-child { border-bottom: none; }
.data-row .grow { flex: 1; min-width: 0; }
.data-row .title { font-weight: 600; font-size: 13.5px; }
.data-row .meta { font-size: 12px; color: var(--muted); }
.data-row .amount { font-weight: 800; font-size: 15px; white-space: nowrap; }
.mono { font-family: "Consolas", monospace; }

/* ── Grille d'offres ── */
.offer-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; }
.offer-card {
  background: #fff; border: 1px solid var(--border);
  padding: 18px; display: flex; flex-direction: column; gap: 10px;
}
.offer-card.featured { border-color: var(--sky-400); }
.offer-card .name { font-weight: 800; font-size: 16px; margin: 0; }
.offer-card .desc { font-size: 12.5px; color: var(--muted); margin: 0; flex: 1; }

/* D3 — saisie du tarif d'engagement (/espace/concours) */
.fee-input { width: 110px; flex: none; }

/* ── C3-C5 — utilitaires de migration (patterns récurrents des pages) ── */
.row-flex { display: flex; align-items: center; gap: 12px; }
.row-wrap { display: flex; gap: 10px; flex-wrap: wrap; }
.grow-1 { flex: 1; min-width: 0; }
.spacer { flex: 1; }
.mt-0 { margin-top: 0; } .mt-16 { margin-top: 16px; } .mb-16 { margin-bottom: 16px; }
.text-sm { font-size: 13px; } .text-xs { font-size: 12px; }
.muted { color: var(--muted); }
.hint { color: #94A3B8; font-size: 12px; }
.strong { font-weight: 700; }

/* Pastilles de statut (remplace les couleurs inline conditionnelles) */
.pill { display: inline-block; font-size: 12px; font-weight: 600; border-radius: var(--radius-pill); padding: 4px 10px; }
.pill-ok   { background: #EAF9F1; color: #10B981; }
.pill-warn { background: #FFF8E1; color: #B45309; }
.pill-err  { background: #FDF2F2; color: var(--signal-err); }
.pill-info { background: #F1F5FF; color: var(--sky-500); }
.pill-neutral { background: #F1F4FA; color: var(--muted); }

/* Tuiles d'accès rapide (Espace) */
.tile {
  display: inline-flex; align-items: center; gap: 8px;
  text-decoration: none; background: #fff; border: 1px solid var(--border);
  padding: 12px 16px; font-weight: 600; font-size: 14px; color: var(--navy-600);
}
.tile.disabled { opacity: .45; pointer-events: none; }

/* Tables simples (engagements, admin) */
.table { width: 100%; border-collapse: collapse; background: #fff; }
.table thead tr { background: var(--navy-600); color: #fff; }
/* text-align: start et non left — en arabe, « left » désalignait les en-têtes
   de leurs cellules, qui suivent elles la direction du document. */
.table th { padding: 10px; text-align: start; font-size: 13px; }
.table td { padding: 10px; font-size: 13.5px; border-bottom: 1px solid #F1F4FA; }
.ta-right { text-align: right; } .ta-center { text-align: center; }

/* ── i18n (D5) — sélecteur de langue FR / عربي / EN (les 2 layouts) ── */
.lang-switch { display: inline-flex; border: 1px solid var(--border); background: #fff; }
.lang-switch-item {
  padding: 6px 11px; font-size: 12px; font-weight: 600; line-height: 1;
  color: var(--muted); text-decoration: none; border-inline-end: 1px solid var(--border);
}
.lang-switch-item:last-child { border-inline-end: none; }
.lang-switch-item:hover { color: var(--sky-500); }
.lang-switch-item.active { background: var(--navy-600); color: #fff; }

/* ── Retour visuel des actions (anti double-clic) ──
   En Blazor Server, désactiver un bouton demande un aller-retour réseau :
   pendant ce délai l'utilisateur peut recliquer. Le vrai garde-fou est la
   garde de ré-entrance dans le handler ; ces styles rendent l'attente VISIBLE
   pour qu'il n'ait plus envie de recliquer. */
@keyframes ed-spin { to { transform: rotate(360deg); } }
.btn-busy { pointer-events: none; opacity: .8; }
.btn-busy::after {
  content: ""; display: inline-block; width: 12px; height: 12px;
  margin-inline-start: 8px; vertical-align: -1px;
  border: 2px solid currentColor; border-top-color: transparent;
  border-radius: 50%; animation: ed-spin .6s linear infinite;
}
.btn-danger { background: var(--signal-err); color: #fff; }

/* Message de confirmation après une action réussie.
   Entrée douce, puis estompage automatique après 6 s : purement visuel —
   côté Blazor l'état reste posé et se nettoie au prochain clic, comme avant. */
.flash {
  margin-bottom: 16px;
  animation: flash-in .25s ease-out, flash-fade .6s ease-in 6s forwards;
}
@keyframes flash-in  { from { opacity: 0; transform: translateY(-4px); } }
@keyframes flash-fade {
  to {
    opacity: 0; visibility: hidden; margin: 0; padding-block: 0;
    max-height: 0; overflow: hidden; border-width: 0;
  }
}
@media (prefers-reduced-motion: reduce) { .flash { animation: none; } }

/* Bouton compact (dans une ligne de liste ou une pastille) */
.btn-mini { padding: 4px 10px; font-size: 12px; }

/* Chip de code (invitation, licence) : carré, foncé, monospace */
.code-chip {
  display: inline-block; background: var(--navy-600); color: #fff;
  padding: 2px 8px; font-size: 12px; font-weight: 600;
}

/* ── Onglets de section (second niveau de navigation) ──────────────────
   Le rail latéral porte les rubriques et reste figé ; la sous-section se
   choisit ici. Ce sont de vrais liens vers des sous-routes, donc la section
   active est partageable et le retour arrière fonctionne.
   Propriétés logiques partout : en arabe la rangée s'inverse d'elle-même. */
.page-tabs {
    display: flex; flex-wrap: wrap; gap: 2px;
    border-bottom: 1px solid var(--hairline);
    margin-bottom: 20px;
}
.page-tab {
    padding: 9px 16px;
    font-size: 14px; font-weight: 500; color: var(--ink-500);
    border-bottom: 2px solid transparent;
    /* Compense la bordure du conteneur pour que l'onglet actif la recouvre. */
    margin-bottom: -1px;
    white-space: nowrap;
    transition: color .15s, border-color .15s;
}
.page-tab:hover { color: var(--navy-600); }
.page-tab.is-active {
    color: var(--navy-600); font-weight: 600;
    border-bottom-color: var(--navy-600);
}

/* ── Rail de navigation du portail ─────────────────────────────────────
   Icône au-dessus du libellé (motif EquiCity) : dense, lisible, et tient
   dans 112 px même avec des libellés français. Le second niveau vit dans
   la page (.page-tabs), jamais en accordéon ici — le rail ne doit pas se
   dérober sous le curseur au moment du clic.
   Tout est en propriétés logiques : en arabe, le rail passe à droite seul. */
.portal-shell { display: flex; align-items: stretch; min-height: 60vh; }
.portal-shell > .site-main { flex: 1; min-width: 0; }

.portal-rail {
    flex: 0 0 112px; width: 112px;
    padding: 16px 0 40px;
    background: var(--paper-100);
    border-inline-end: 1px solid var(--hairline);
    /* Le rail suit la lecture sans quitter l'écran sur les pages longues. */
    position: sticky; top: 64px; align-self: flex-start;
    max-height: calc(100vh - 64px); overflow-y: auto;
}
.rail-group {
    margin: 18px 0 6px; padding: 0 8px;
    font-size: 9.5px; letter-spacing: .12em; text-transform: uppercase;
    color: var(--ink-400); text-align: center;
}
.rail-group:first-child { margin-top: 0; }

.rail-item {
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    padding: 10px 4px;
    font-size: 11px; font-weight: 500; line-height: 1.25; text-align: center;
    color: var(--ink-700);
    border-inline-start: 3px solid transparent;
    transition: background .15s, color .15s, border-color .15s;
}
.rail-item svg { width: 20px; height: 20px; }
.rail-item:hover { background: #fff; color: var(--navy-600); }
.rail-item.is-active {
    background: #fff; color: var(--navy-600); font-weight: 600;
    border-inline-start-color: var(--navy-600);
}
/* Verrouillé : visible mais inerte. Masquer ferait croire que la
   fonctionnalité n'existe pas ; la montrer dit qu'elle s'achète. */
.rail-item.is-locked { opacity: .38; cursor: not-allowed; }

/* Le bandeau horizontal qui servait ici de repli mobile a été retiré : à dix
   entrées il faisait ~850 px et se parcourait au doigt à l'aveugle. Le tiroir
   du bloc « max-width: 900px » plus bas le remplace, et couvre aussi la
   tablette. */

/* Texte réservé aux lecteurs d'écran : retiré du flux visuel sans être retiré
   de l'arbre d'accessibilité (« display:none » le supprimerait des deux). */
.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%);
    overflow: hidden; white-space: nowrap;
}


/* ══════════════════════════════════════════════════════════════
   Téléphone — le portail s'utilise DEBOUT, dans les écuries
   ══════════════════════════════════════════════════════════════
   Le rail avait bien un mode mobile ; le travail s'était arrêté là. Tout ce
   qui vient après — les lignes de données et les formulaires, c'est-à-dire
   l'application elle-même — reposait sur deux briques qui rendaient le repli
   structurellement impossible :

     .data-row  : display:flex SANS flex-wrap ;
     .grow-1    : flex:1 avec min-width:0, donc une base flex nulle qui se
                  comprime au lieu de passer à la ligne.

   Mesuré à 375 px avant correction : une ligne d'adhérent réclamait 601 px
   pour 289 px disponibles, et 3 boutons sur 4 — dont « Modifier » et
   « Supprimer » — étaient rognés par le « overflow:hidden » de .data-list,
   donc inatteignables. Sur les blocs posés en .card, le débordement remontait
   jusqu'au document : 62 px de décalage de la page entière. Les champs du
   formulaire « nouveau propriétaire » tombaient à 53 px de large.
   ══════════════════════════════════════════════════════════════ */
@media (max-width: 720px) {
    /* Les lignes se replient au lieu de déborder. */
    .data-row { flex-wrap: wrap; }
    .data-row .grow { flex: 1 1 100%; }

    /* Les actions passent sous le contenu, sur toute la largeur. */
    .data-row .btn,
    .data-row button,
    .data-row .field-select { flex: 1 1 auto; }

    /* Les champs cessent de se comprimer : sous 200 px ils passent à la ligne. */
    .row-wrap > .grow-1,
    .row-wrap > .field-input,
    .row-flex > .grow-1 { flex: 1 1 200px; min-width: 200px; }
    .row-flex { flex-wrap: wrap; }

    /* Deux colonnes de formulaire ne tiennent pas sur un téléphone. */
    .field-grid-2 { grid-template-columns: 1fr; }

    /* Largeur fixe pensée pour le bureau. */
    .fee-input { width: 100%; flex: 1 1 auto; }

    /* Filet : plus aucune liste ne peut décaler la page entière. */
    .data-list { overflow-x: auto; }

}

/* Lignes à contrôles nombreux (.row-actions) — planning des cours : libellé +
   jauge + menu déroulant + 4 boutons. Elles ne tiennent pas sur une ligne dès
   la TABLETTE, pas seulement sur téléphone : sans repli, le libellé
   « Samedi 09h00 – 10h00 · debutant » s'écrasait mot par mot. On les empile
   jusqu'à 1024 px — le libellé prend toute la largeur, les actions passent
   dessous et s'étalent. */
@media (max-width: 1024px) {
    .data-row.row-actions { flex-wrap: wrap; }
    .data-row.row-actions .grow { flex: 1 1 100%; }
    .data-row.row-actions .title { white-space: normal; }
    .data-row.row-actions .field-select { max-width: none; flex: 1 1 160px; }
    .data-row.row-actions .btn,
    .data-row.row-actions button { flex: 1 1 auto; }
}

/* Cibles tactiles : bascule de langue, lien de retour et onglets étaient à
   24-38 px de haut. Le seuil suit celui du tiroir (900 px) — l'audit mesuré
   les trouvait encore petits sur tablette, couverte par l'ancien 720. */
@media (max-width: 900px) {
    .lang-switch-item { padding: 10px 12px; }
    .back-link { display: inline-block; padding: 8px 0; }
    .page-tab { padding: 12px 16px; }
    .btn-mini { padding: 10px 12px; }
}


/* ══════════════════════════════════════════════════════════════
   Navigation — rail permanent au bureau, TIROIR sous 900 px
   ══════════════════════════════════════════════════════════════
   Le bandeau horizontal qui remplaçait le rail ne pouvait pas marcher : dix
   entrées réclament environ 850 px, contre 375 disponibles. Rien ne faisait
   défiler l'entrée active dans le champ de vision, et les trois titres de
   groupe — qui distinguent ce que la structure ENCAISSE de ce qu'elle PAIE —
   étaient masqués.

   Un tiroir montre les dix entrées ET leurs groupes, libère toute la hauteur
   pour le contenu, et reprend le geste que les utilisateurs connaissent. La
   coupure est à 900 px : une tablette en portrait (768) reçoit le tiroir, en
   paysage (1024) le rail permanent.
   ══════════════════════════════════════════════════════════════ */

.menu-bouton { display: none; }
.rail-voile { display: none; }

@media (max-width: 900px) {
    .menu-bouton {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 44px;
        height: 44px;
        margin-inline-end: 4px;
        background: none;
        border: 1px solid var(--hairline);
        color: var(--navy-600);
        cursor: pointer;
    }

    /* Le rail sort du flux et glisse depuis le bord de début de ligne :
       inset-inline-start bascule donc de lui-même à droite en arabe. */
    .portal-rail {
        position: fixed;
        inset-block: 0;
        inset-inline-start: 0;
        /* inset-block seul laisse la boîte se dimensionner sur son contenu :
           le panneau s'arrêtait sous la dernière entrée et laissait le voile
           apparaître dessous. dvh suit aussi la barre d'URL mobile. */
        height: 100vh;
        height: 100dvh;
        z-index: 60;
        width: 272px;
        max-width: 84vw;
        /* Le rail sticky du bureau se plafonne à la hauteur du volet visible ;
           ici il occupe toute la colonne, donc on lève ce plafond. */
        max-height: none;
        overflow-y: auto;
        background: #fff;
        border-inline-end: 1px solid var(--hairline);
        transform: translateX(-102%);
        transition: transform .22s ease;
        padding-top: 12px;
    }
    html[dir="rtl"] .portal-rail { transform: translateX(102%); }

    .menu-bascule:checked ~ .portal-shell .portal-rail,
    html[dir="rtl"] .menu-bascule:checked ~ .portal-shell .portal-rail { transform: translateX(0); }

    .rail-voile {
        display: block;
        position: fixed;
        inset: 0;
        z-index: 55;
        background: rgba(15, 27, 54, .45);
        opacity: 0;
        pointer-events: none;
        transition: opacity .22s ease;
    }
    .menu-bascule:checked ~ .portal-shell .rail-voile { opacity: 1; pointer-events: auto; }

    /* Dans le tiroir, l'icône revient à gauche du libellé : une liste se lit
       plus vite qu'une grille de tuiles quand elle est verticale. */
    .rail-item {
        flex-direction: row;
        justify-content: flex-start;
        gap: 12px;
        padding: 12px 18px;
        text-align: start;
    }
    .rail-group { display: block; }

    /* L'en-tête doit tenir sur 375 px : l'email et le libellé de zone sont
       du confort, le bouton de sortie ne l'est pas. */
    .zone-libelle,
    .user-libelle { display: none; }

    /* ── En-tête du portail sur une seule ligne ────────────────────────────
       Mesuré à 375 px : bouton de menu 44 + wordmark 75 + bascule de langue
       132 + « Déconnexion » 116, plus les écarts, demandent 407 px pour
       349 disponibles. Le groupe de gauche, rendu flexible par .grow-1, se
       comprimait donc à 79 px et repliait le wordmark SOUS le bouton de menu.

       Deux corrections, dans cet ordre. D'abord on interdit le repli : un
       en-tête qui passe à trois lignes n'est plus un en-tête. Ensuite on rend
       la place — la bascule de langue part dans le tiroir, où elle est à sa
       place : on choisit sa langue une fois, on se déconnecte souvent.

       Reste 44 + 75 + 116 = 235 px de contenu pour 349 : tient encore sur un
       écran de 320 px, le plus étroit encore en service.

       « .avec-tiroir » et non « .site-header » tout court : les pages publiques
       partagent la même classe d'en-tête mais n'ont AUCUN tiroir. Sans ce
       marqueur, la bascule de langue disparaîtrait aussi des pages publiques
       sur téléphone, sans que rien ne la recueille — l'arabe et l'anglais y
       deviendraient inatteignables. */
    .site-header .inner > .row-flex { flex-wrap: nowrap; }
    .site-header.avec-tiroir .lang-switch { display: none; }

    /* Rendue au tiroir. Séparée du dernier lien par un filet, sinon elle se
       lit comme une entrée de navigation de plus.

       Sélecteur volontairement plus spécifique que la règle de repos plus bas :
       une media query n'ajoute AUCUNE spécificité, et cette règle-ci est écrite
       avant l'autre dans le fichier. À spécificité égale, la dernière écrite
       l'emporterait — y compris sous 900 px, où elle masquerait justement ce
       qu'on cherche à montrer. */
    .portal-rail .rail-langue {
        display: block;
        margin-top: 8px;
        padding: 12px 18px;
        border-top: 1px solid var(--hairline);
    }
}

/* Au repos (au-dessus de 900 px) la bascule vit dans l'en-tête, pas ici. */
.rail-langue { display: none; }

@media (prefers-reduced-motion: reduce) {
    .portal-rail, .rail-voile { transition: none; }
}

/* ══ Micro-interactions (audit UX 2026-08) ═══════════════════════════
   L'app paraissait figée : sur ~15 classes interactives, 4 seulement
   avaient un :hover. Ici, tout ce qui se clique répond — en CSS pur,
   compatible Blazor Server (aucun aller-retour réseau pour un survol). */

/* Tuiles d'accès rapide : premier geste de chaque session. */
.tile { transition: border-color .15s, box-shadow .15s, transform .15s; }
.tile:hover {
  border-color: var(--sky-500);
  box-shadow: 0 2px 8px rgba(15, 27, 54, .10);
  transform: translateY(-1px);
}
.tile:hover svg { color: var(--sky-500); }
.tile:active { transform: translateY(0); }

/* Navigation clavier : visible partout, pas seulement sur les champs. */
.btn:focus-visible, .tile:focus-visible, .page-tab:focus-visible,
.rail-item:focus-visible, .back-link:focus-visible, .data-row a:focus-visible,
.lang-switch-item:focus-visible, a.card:focus-visible {
  outline: 2px solid var(--sky-500); outline-offset: 2px;
}

/* Le clic se sent, même sur les formulaires SSR où le busy n'existe pas. */
.btn:active:not(:disabled) { transform: scale(.98); }

/* Lignes de liste : l'équivalent du hover que les tables publiques
   avaient déjà — les listes du portail, elles, restaient inertes. */
.data-list .data-row { transition: background .12s; }
.data-list .data-row:hover { background: #FAFBFE; }

/* Pastilles d'état : la bascule ok→warn→err devient perceptible. */
.pill { transition: background .2s, color .2s; }

/* Carte-lien (abonnement sur l'accueil…) : cliquable ET l'air cliquable. */
a.card, .card-link {
  text-decoration: none; color: inherit; display: block;
  transition: border-color .15s, box-shadow .15s;
}
a.card:hover, .card-link:hover {
  border-color: var(--sky-500);
  box-shadow: 0 3px 10px rgba(15, 27, 54, .10);
}

/* Entrée des blocs conditionnels (@if Blazor) : la sortie ne s'anime pas
   sans JS, mais adoucir l'entrée suffit à casser l'effet « toc ». */
.card-reveal { animation: card-in .18s ease-out; }
@keyframes card-in { from { opacity: 0; transform: translateY(-6px); } }

/* ── Skeletons de chargement ──
   Remplace le « Chargement… » en texte nu : la page garde sa silhouette
   pendant que Blazor établit le circuit et charge les données. */
.skl { background: var(--paper-100); position: relative; overflow: hidden; }
.skl::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .65), transparent);
  animation: skl-sweep 1.2s infinite;
}
@keyframes skl-sweep { from { transform: translateX(-100%); } to { transform: translateX(100%); } }
html[dir="rtl"] .skl::after { animation-name: skl-sweep-rtl; }
@keyframes skl-sweep-rtl { from { transform: translateX(100%); } to { transform: translateX(-100%); } }
.skl-row  { height: 44px; margin-bottom: 1px; }
.skl-card { height: 88px; margin-bottom: 16px; }

/* ── États vides ──
   Une icône du domaine, une phrase, et l'action primaire SUR PLACE au
   lieu d'un renvoi vers un bouton à l'autre bout de l'écran. */
.empty-state { padding: 40px 24px; text-align: center; }
.empty-state svg { width: 32px; height: 32px; color: #B9C4DA; margin-bottom: 8px; }
.empty-state p { margin: 0 0 14px; }

/* ── Hiérarchie : les chiffres sont l'information, ils dominent. ── */
.page-head h1 { font-size: 26px; }
.stat-value { font-size: 28px; }

/* Bloc mis en avant : un fond teinté crée un niveau, là où un énième
   bord 1px ne faisait qu'ajouter une carte au damier. */
.card-highlight {
  background: linear-gradient(0deg, #fff 60%, #EFF6FF);
  border-inline-start: 4px solid var(--sky-500);
}

@media (prefers-reduced-motion: reduce) {
  .tile, .btn, .card-reveal { animation: none; transition: none; }
  .skl::after { animation: none; }
}

/* Pastille « en direct » : un point plein qui pulse dit mieux le live
   que n'importe quel pictogramme trait (remplace l'emoji 🟢). */
.live-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 50%;
  background: #10B981; vertical-align: 1px;
  animation: live-pulse 1.6s ease-out infinite;
}
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(16, 185, 129, .45); }
  100% { box-shadow: 0 0 0 7px rgba(16, 185, 129, 0); }
}
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

/* ── Tableau de bord (/espace) ──
   Visualisations sans bibliothèque : une jauge et des barres en pur CSS,
   les hauteurs venant du serveur en % inline. */
.jauge { height: 8px; background: #E8ECF4; margin-top: 10px; }
.jauge-remplie { height: 100%; background: var(--sky-500); }

.barres { display: flex; gap: 10px; align-items: flex-end; }
.barres .mois {
  flex: 1; display: flex; flex-direction: column;
  align-items: center; gap: 6px; min-width: 0;
}
.barres .colonnes {
  display: flex; align-items: flex-end; gap: 3px;
  height: 90px; width: 100%; justify-content: center;
}
.barres .b-emis, .barres .b-paye { width: 14px; transition: height .3s ease; }
.barres .b-emis { background: #D6E2F5; }
.barres .b-paye { background: var(--sky-500); }

.legende {
  display: inline-block; width: 10px; height: 10px;
  vertical-align: -1px; margin-inline: 6px 4px;
}
.legende-paye { background: var(--sky-500); }
.legende-emis { background: #D6E2F5; }

/* Une ligne « À traiter » est un lien : elle garde la grammaire data-row. */
a.data-row { display: flex; }

/* ══ Zone /admin (ADM-P3) ═══════════════════════════════════════════
   Le back-office était en styles inline (81 lignes) et blocs <style>
   locaux, avec ses propres hex hors charte — alors que les tokens sont
   chargés pour toutes les pages. Ces classes remplacent l'ensemble. */

.admin-header {
  display: flex; justify-content: space-between; align-items: center;
  flex-wrap: wrap; gap: 8px;
  padding: 14px 20px;
  background: var(--navy-600); color: #fff;
}
.admin-header a { color: var(--navy-200); text-decoration: none; padding-block: 6px; }
.admin-header a:hover { color: #fff; }
/* « width: 100% » n'est pas décoratif, il est structurel. body est en
   flex-column ; or une marge auto sur l'axe croisé neutralise le stretch du
   flex item, qui retombe alors sur la largeur de son CONTENU. Résultat mesuré
   avant correctif : 589 px de page dans un écran de 375, sur les six pages
   /admin — et .table-scroll rendu inopérant, puisqu'il faisait exactement la
   largeur de son tableau au lieu d'être borné. .site-main déclare déjà cette
   ligne, c'est ici qu'elle manquait. */
/* Le padding vit désormais dans .admin-contenu, sinon il s'ajouterait à celui
   de la barre latérale. La largeur passe de 980 à 1320 px : la sidebar en
   consomme 232, et les tables du back-office font jusqu'à sept colonnes. */
.admin-main { padding: 0; max-width: 1320px; margin: 0 auto; width: 100%; min-width: 0; }

.admin-nav a.actif { font-weight: 800; color: var(--navy-600); }

/* Cartes KPI : l'accent porte le sens (bleu neutre, vert argent,
   ambre attention, violet volume). */
.kpi-card {
  background: #fff; border: 1px solid var(--border);
  padding: 18px; border-inline-start: 4px solid var(--sky-500);
}
.kpi-green  { border-inline-start-color: #10B981; }
.kpi-orange { border-inline-start-color: #F59E0B; }
.kpi-purple { border-inline-start-color: #A78BFA; }
.kpi-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.kpi-value { font-size: 28px; font-weight: 800; color: var(--navy-600); margin: 6px 0; }
.kpi-sub   { font-size: 11px; color: #94A3B8; }
.kpi-grid  { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 14px; margin: 18px 0; }

/* Filtres de liste (dashboard, revenus) */
.filter-btn {
  padding: 8px 14px; border: 1px solid var(--border); background: #fff;
  cursor: pointer; font-size: 13px; font-family: inherit; color: var(--navy-600);
  transition: border-color .15s, background .15s;
}
.filter-btn:hover { border-color: var(--sky-500); }
.filter-btn-active { background: var(--sky-500); color: #fff; border-color: var(--sky-500); }

/* Badges d'état des licences */
.status-badge { display: inline-block; padding: 3px 8px; font-size: 11px; font-weight: 600; }
.status-active  { background: #EAF9F1; color: #10B981; }
.status-issued  { background: #F1F5FF; color: var(--sky-500); }
.status-expired { background: #FFF8E1; color: #B45309; }
.status-revoked { background: #FDF2F2; color: var(--signal-err); }

/* Actions en ligne dans les tables admin (Renouveler, Révoquer…) */
.admin-lien-action {
  font-size: 11px; background: none; border: none; cursor: pointer;
  text-decoration: underline; font-family: inherit; padding: 6px 2px;
}
.admin-lien-ok    { color: #10B981; }
.admin-lien-danger { color: var(--signal-err); }
.admin-lien-action:disabled { opacity: .5; cursor: default; }

/* Boutons d'action colorés du workflow engagements */
.admin-btn-etape {
  font-size: 11px; padding: 8px 10px; color: #fff; border: none;
  cursor: pointer; font-family: inherit; margin-inline-end: 4px;
}
.admin-btn-valider { background: var(--sky-500); }
.admin-btn-payer   { background: #F59E0B; }
.admin-btn-confirmer { background: #10B981; }
.admin-btn-refuser { background: var(--signal-err); }
.admin-btn-etape:disabled { opacity: .5; cursor: default; }

.admin-note { font-size: 11px; color: var(--muted); }
.nowrap { white-space: nowrap; }

/* ══ Audit /admin (08/2026) ══════════════════════════════════════════
   Cinq classes étaient employées dans le markup sans exister nulle part :
   les deux champs de connexion admin s'affichaient en input navigateur brut
   (21 px de haut), et l'iframe Umami tombait à sa hauteur par défaut de
   154 px — un tableau de bord d'audience inexploitable. Trois d'entre elles
   ont été remplacées dans le markup par des classes qui existaient déjà
   (field-input, card) : le design system n'avait pas besoin d'un huitième
   style de champ, seulement qu'on s'en serve. Restent celles-ci, qui ne
   correspondaient à rien d'existant. */

/* Le tableau de bord d'audience est l'objet principal de sa page : il mérite
   la hauteur d'un écran, bornée pour ne pas repousser le reste hors de vue. */
.umami-frame {
  width: 100%; height: min(78vh, 900px); min-height: 420px;
  border: 1px solid var(--border); background: #fff;
}

/* ══ Barre latérale du back-office ═══════════════════════════════════
   Le shell est un flex : navigation à gauche, contenu à droite. */
.admin-shell { display: flex; align-items: flex-start; gap: 0; }

.admin-sidebar {
  flex: 0 0 232px;
  align-self: stretch;
  display: flex; flex-direction: column; justify-content: space-between;
  gap: 24px;
  padding: 20px 14px;
  background: #fff;
  border-inline-end: 1px solid var(--border);
  /* Le tableau de bord est long : la navigation reste sous la main sans
     avoir à remonter. 64px = hauteur de l'en-tête admin. */
  position: sticky; top: 0; min-height: calc(100dvh - 64px);
}
.admin-sidebar-liens { display: flex; flex-direction: column; gap: 2px; }

.admin-sidebar-lien {
  display: flex; align-items: center; gap: 10px;
  min-height: 44px; padding: 8px 12px;
  color: var(--text); text-decoration: none;
  font-size: 14px; font-weight: 500;
  border-inline-start: 3px solid transparent;
  transition: background .15s, color .15s, border-color .15s;
}
.admin-sidebar-lien:hover { background: var(--paper-50); color: var(--sky-600, #2563EB); }
.admin-sidebar-lien.actif {
  background: #F1F5FF; color: var(--sky-600, #2563EB);
  font-weight: 700; border-inline-start-color: var(--sky-500);
}

.admin-sidebar-pied { border-top: 1px solid var(--border); padding-top: 14px; }
.admin-sidebar-compte {
  margin: 0 0 8px; padding-inline: 12px;
  font-size: 12px; color: var(--muted);
  /* Une adresse longue ne doit pas élargir la colonne ni déborder. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.admin-sidebar-sortie {
  display: flex; align-items: center; gap: 10px; width: 100%;
  min-height: 44px; padding: 8px 12px;
  background: none; border: none; cursor: pointer;
  font-family: inherit; font-size: 13px; color: var(--muted); text-align: start;
}
.admin-sidebar-sortie:hover { color: var(--signal-err); background: var(--paper-50); }

/* « min-width: 0 » : sans lui, un flex item ne descend jamais sous la largeur
   de son contenu — c'est précisément ce qui faisait déborder /admin de 589 px
   dans un écran de 375. Une table large repousserait ici la sidebar hors de
   l'écran au lieu de défiler dans son propre conteneur. */
.admin-contenu { flex: 1 1 auto; min-width: 0; padding: 24px; }

/* Sous 900 px, une colonne fixe mangerait la moitié de l'écran : la
   navigation repasse en rangée, qui défile horizontalement si besoin. */
@media (max-width: 900px) {
  .admin-shell { display: block; }
  .admin-sidebar {
    position: static; min-height: 0; flex-direction: row; align-items: center;
    gap: 12px; padding: 8px 12px;
    border-inline-end: none; border-bottom: 1px solid var(--border);
  }
  .admin-sidebar-liens {
    flex-direction: row; flex: 1 1 auto;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
  }
  .admin-sidebar-lien {
    white-space: nowrap;
    border-inline-start: none; border-bottom: 3px solid transparent;
  }
  .admin-sidebar-lien.actif { border-inline-start: none; border-bottom-color: var(--sky-500); }
  .admin-sidebar-pied { border-top: none; padding-top: 0; flex: 0 0 auto; }
  /* L'adresse connectée passe à la trappe avant la sortie : sur un écran
     étroit, savoir se déconnecter compte plus que relire son propre email. */
  .admin-sidebar-compte { display: none; }
  .admin-sidebar-sortie span { display: none; }
  .admin-contenu { padding: 18px 14px; }
}

/* Écrans de connexion et de refus : centrés, largeur de lecture. */
.admin-connexion { max-width: 460px; margin: 8vh auto; padding: 0 18px; }

/* Bloc de configuration à recopier. « pre » ne se replie pas par défaut :
   c'est lui qui poussait la page Analytics à 728 px de large sur mobile. */
.code-bloc {
  background: var(--navy-600); color: #E8EDF7;
  padding: 14px 16px; margin: 12px 0;
  font-family: "Consolas", monospace; font-size: 12px; line-height: 1.7;
  white-space: pre-wrap; word-break: break-word; overflow-x: auto;
}

/* Bandeau de totaux au-dessus d'une liste. Générique, pas nommé d'après la
   page où il est apparu : le même besoin existe sur revenus et engagements. */
.stats-inline {
  display: flex; flex-wrap: wrap; gap: 10px 28px;
  padding: 14px 18px; margin: 16px 0;
  background: #fff; border: 1px solid var(--border);
  font-size: 14px; color: var(--text);
}
.stats-inline strong { font-size: 17px; font-weight: 800; color: var(--navy-600); }

/* La classe était employée trois fois sans exister. Le bleu était déjà le
   défaut de .kpi-card, donc l'effet était juste par accident — on l'écrit,
   pour que lire le markup ne mente plus. */
.kpi-blue { border-inline-start-color: var(--sky-500); }

/* ── Cibles tactiles ──────────────────────────────────────────────────
   Mesuré : 18 cibles sous 44 px sur le seul tableau de bord. Les plus
   graves sont « Renouveler » et « Révoquer » à 25 px — deux actions à
   conséquence financière, dont une irréversible, côte à côte. */
.admin-lien-action {
  min-height: 44px; padding-inline: 8px;
  display: inline-flex; align-items: center;
}
.filter-btn { min-height: 44px; }
.admin-nav a, .admin-header a { min-height: 44px; display: inline-flex; align-items: center; }

/* ── Contrastes ───────────────────────────────────────────────────────
   Trois textes étaient sous le seuil AA (4,5). Le pire, .kpi-sub à 2,56,
   porte justement le contexte qui donne son sens au chiffre au-dessus
   (« 6 abonnements web actifs ») : c'était le texte le moins lisible de
   l'écran alors qu'il est le plus explicatif. */
.kpi-sub { color: var(--muted); }
.status-active { color: #047857; }
.admin-lien-ok { color: #047857; }

/* Révoquer est irréversible ; Renouveler ne l'est pas. Elles avaient le
   même poids visuel — deux liens soulignés de 11 px. La destructive perd
   son soulignement permanent : elle ne s'annonce qu'au survol, et reste
   parfaitement atteignable. */
.admin-lien-danger { text-decoration: none; font-weight: 600; }
.admin-lien-danger:hover { text-decoration: underline; }
