/* =====================================================================
   My_IPFPS — Thème UI Branding & Dark Mode
   Charte : bleu électrique + vert école. Clair par défaut, sombre via
   [data-theme="dark"] sur <html>. Toutes les classes historiques sont
   conservées et thémées via variables.
   ===================================================================== */

/* =====================================================================
   Typographie — pairing « crafted » : Fraunces (titres, serif optique) +
   Public Sans (corps, sans humaniste civique). Polices VENDORISÉES en local
   (public/assets/fonts/, woff2 variable subset latin, aucun CDN) → portable
   et hors-ligne, dans l'esprit « PDF.js local ». Le subset latin couvre tout
   le français (accents, œ, €, guillemets, tirets). Repli system-ui.
   ===================================================================== */
@font-face {
    font-family: 'Fraunces';
    font-style: normal;
    font-weight: 300 900;
    font-display: swap;
    src: url('../fonts/fraunces-latin-var.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: 'Public Sans';
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url('../fonts/publicsans-latin-var.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
    /* ----- Charte école ----- */
    --primary-blue: #2563eb;
    --primary-blue-dark: #1d4ed8;
    --primary-green: #16a34a;
    --primary-green-dark: #15803d;

    /* ----- Fonds / cartes / textes (clair & sombre) ----- */
    --bg-light: #f5f8fe;
    --bg-dark: #0b1220;
    --card-light: #ffffff;
    --card-dark: #141f38;
    --text-light: #1e293b;
    --text-dark: #e6edf7;

    /* ----- Tokens actifs (mode clair par défaut) ----- */
    --bg: var(--bg-light);
    --surface: var(--card-light);
    --text: var(--text-light);
    --muted: #586575; /* slate assombri : ≥4.5 sur fonds clairs (bg/hover/th) — WCAG AA */
    --border: #e4e9f2;
    --hover: #f5f8fc;
    --th-bg: #f1f5fb;

    --primary: var(--primary-blue);
    --primary-dark: var(--primary-blue-dark);
    --primary-light: #dbe6ff;

    --success: var(--primary-green-dark); /* vert TEXTE assombri (#15803d) — AA ; les remplissages de marque gardent --primary-green */
    --success-bg: #e9faf0;
    --danger: #b91c1c; /* rouge TEXTE assombri — AA sur blanc & --danger-bg */
    --danger-bg: #fef2f2;
    --warning: #f59e0b; /* conservé pour les REMPLISSAGES/bordures */
    --warning-bg: #fff7e8;
    --warning-text: #8a5a00; /* ambre TEXTE foncé — AA (le jaune vif #f59e0b échoue en texte) */

    /* ----- Habillage ----- */
    /* Sidebar théme-aware : clair en mode light, navy en mode dark (cf. [data-theme="dark"]). */
    --sidebar-bg: #eef2f8;
    --sidebar-text: #334155;
    --sidebar-muted: #64748b;
    --sidebar-section: #8a97ab;             /* libellés de section (PILOTAGE…) */
    --sidebar-brand: #1f2a44;               /* texte du wordmark */
    --sidebar-hover-bg: rgba(37, 99, 235, .08);
    --sidebar-hover-text: var(--primary-blue);
    --sidebar-border: rgba(15, 23, 42, .08);
    --sidebar-chip: rgba(15, 23, 42, .05);  /* fond role-tag */
    --sidebar-glow: rgba(37, 99, 235, .07); /* halo radial du haut */
    --glass: rgba(255, 255, 255, .72);
    --ring: rgba(37, 99, 235, .28);
    --radius: 14px;
    --radius-sm: 9px;
    --shadow: 0 1px 2px rgba(15, 23, 42, .06), 0 10px 28px rgba(15, 23, 42, .07);
    --shadow-hover: 0 6px 14px rgba(15, 23, 42, .10), 0 18px 40px rgba(15, 23, 42, .12);
    --sidebar-w: 252px;

    /* ----- Phase 1 — tokens additifs (aucun renommage) ----- */
    --radius-lg: 20px;
    --ring-success: rgba(22, 163, 74, .26);
    --space-1: 4px;
    --space-2: 8px;
    --space-3: 12px;
    --space-4: 16px;
    --space-5: 24px;
    --space-6: 32px;
    --content-max: 1480px;

    /* ----- Typographie (familles + échelle modulaire ~1.2) ----- */
    --font-head: 'Fraunces', Georgia, 'Times New Roman', serif;
    --font-body: 'Public Sans', 'Segoe UI', Roboto, system-ui, -apple-system, sans-serif;
    --font-mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    /* Échelle de tailles (px → rem-friendly via base 15px) */
    --fs-xs: 12px;
    --fs-sm: 13px;
    --fs-base: 15px;
    --fs-md: 16px;
    --fs-lg: 18px;
    --fs-xl: 21px;
    --fs-2xl: clamp(22px, 1.1rem + 1vw, 26px);
    --fs-3xl: clamp(26px, 1.2rem + 1.8vw, 34px);
    /* Interlignes / graisses / interlettrage */
    --lh-tight: 1.18;
    --lh-snug: 1.32;
    --lh-body: 1.6;
    --tracking-tight: -.018em;
    --tracking-wide: .06em;
}

html[data-theme="dark"] {
    --bg: var(--bg-dark);
    --surface: var(--card-dark);
    --text: var(--text-dark);
    --muted: #93a3bd;
    --border: #243450;
    --hover: #1a2742;
    --th-bg: #172339;

    --primary: #60a5fa; /* lien/accent clairci en sombre : ≥4.5 sur card/bg — WCAG AA */
    --primary-dark: #2563eb;
    --primary-light: rgba(59, 130, 246, .20);

    --success: #34d399;
    --success-bg: rgba(16, 185, 129, .16);
    --danger: #f87171;
    --danger-bg: rgba(239, 68, 68, .16);
    --warning: #fbbf24;
    --warning-bg: rgba(245, 158, 11, .16);
    --warning-text: #fbbf24; /* en sombre, l'ambre vif passe en texte (≥9:1 sur card) */

    --sidebar-bg: #0a1424;
    --sidebar-text: #c5d2e6;
    --sidebar-muted: #8b9bb5;
    --sidebar-section: #6b7d99;
    --sidebar-brand: #ffffff;
    --sidebar-hover-bg: rgba(255, 255, 255, .07);
    --sidebar-hover-text: #ffffff;
    --sidebar-border: rgba(255, 255, 255, .07);
    --sidebar-chip: rgba(255, 255, 255, .06);
    --sidebar-glow: rgba(37, 99, 235, .25);
    --glass: rgba(16, 25, 45, .62);
    --ring: rgba(59, 130, 246, .35);
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 30px rgba(0, 0, 0, .45);
    --shadow-hover: 0 8px 18px rgba(0, 0, 0, .5), 0 22px 46px rgba(0, 0, 0, .55);
}

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

/* Fond thémé aussi sur <html> : sinon l'overscroll iOS et la zone du
   home-indicator (PWA standalone) laissent apparaître une bande blanche. */
html { background: var(--bg); }

body {
    font-family: var(--font-body);
    background: var(--bg);
    color: var(--text);
    min-height: 100vh;   /* repli navigateurs sans dvh */
    min-height: 100dvh;
    touch-action: manipulation; /* pas de zoom double-tap (le pan/scroll reste normal) */
    line-height: var(--lh-body);
    font-size: var(--fs-base);
    font-weight: 400;
    letter-spacing: -.003em;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-rendering: optimizeLegibility;
    font-feature-settings: "kern" 1, "liga" 1;
    transition: background .25s ease, color .25s ease;
}

/* ---------- Titres : Fraunces (serif optique) ---------- */
h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-head);
    font-optical-sizing: auto;
    font-weight: 600;
    line-height: var(--lh-tight);
    letter-spacing: var(--tracking-tight);
    color: var(--text);
}
/* Petits titres « étiquette » (≤ section) : un cran moins serrés pour rester lisibles. */
h4, h5, h6 { letter-spacing: -.008em; line-height: var(--lh-snug); }

a { color: var(--primary); text-decoration: none; transition: color .15s; }
a:hover { text-decoration: underline; }
code {
    font-family: var(--font-mono);
    font-size: .9em; background: var(--hover);
    padding: 1px 6px; border-radius: 6px;
}

/* ---------- Auth layout (aligné sur le login clair ; theme-aware) ---------- */
.auth-wrap {
    position: relative;
    overflow: hidden;
    min-height: 100vh;
    display: flex; align-items: center; justify-content: center;
    padding: 24px;
    /* Base sombre par défaut (dark mode) ; surchargée en clair ci-dessous */
    background:
        radial-gradient(1100px 600px at 12% -10%, rgba(37, 99, 235, .28), transparent 60%),
        radial-gradient(900px 500px at 110% 110%, rgba(22, 163, 74, .22), transparent 55%),
        linear-gradient(135deg, #0c1730 0%, #122544 100%);
}
/* Mode clair : fond blanc + lavis bleu/vert doux (comme .login-page) */
html:not([data-theme="dark"]) .auth-wrap {
    background:
        radial-gradient(900px 600px at 12% 8%, rgba(37, 99, 235, .10), transparent 55%),
        radial-gradient(820px 560px at 92% 96%, rgba(22, 163, 74, .10), transparent 55%),
        #ffffff;
}
/* Orbes flous flottants (clair uniquement, derrière la carte) */
html:not([data-theme="dark"]) .auth-wrap::before,
html:not([data-theme="dark"]) .auth-wrap::after {
    content: ""; position: absolute; border-radius: 50%;
    filter: blur(60px); pointer-events: none; z-index: 0;
}
html:not([data-theme="dark"]) .auth-wrap::before {
    width: 340px; height: 340px; left: -90px; top: -70px;
    background: radial-gradient(circle, rgba(37, 99, 235, .22), transparent 70%);
    animation: loginFloat 9s ease-in-out infinite;
}
html:not([data-theme="dark"]) .auth-wrap::after {
    width: 300px; height: 300px; right: -80px; bottom: -80px;
    background: radial-gradient(circle, rgba(22, 163, 74, .20), transparent 70%);
    animation: loginFloat 11s ease-in-out infinite reverse;
}
.auth-card {
    position: relative; z-index: 2;
    width: 100%; max-width: 420px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: 0 20px 60px rgba(2, 8, 23, .35);
    padding: 40px 36px;
    animation: loginUp .5s ease both;
}
/* Carte glassmorphism en clair (comme .login-card) */
html:not([data-theme="dark"]) .auth-card {
    background: rgba(255, 255, 255, .80);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
    border: 1px solid rgba(37, 99, 235, .14);
    box-shadow: 0 24px 60px rgba(15, 23, 42, .12), 0 2px 6px rgba(15, 23, 42, .05);
}
.auth-brand { text-align: center; margin-bottom: 28px; }
.auth-brand .logo {
    width: 72px; height: 72px; margin: 0 auto 14px;
    border-radius: 18px; overflow: hidden;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-size: 26px; font-weight: 800;
    box-shadow: 0 10px 24px rgba(37, 99, 235, .35);
}
.auth-brand .logo img { width: 100%; height: 100%; object-fit: contain; }
.auth-brand h1 { font-size: 21px; font-weight: 800; letter-spacing: -.01em; }
.auth-brand p { color: var(--muted); font-size: 13px; margin-top: 4px; }

/* ---------- Forms ---------- */
.form-group { margin-bottom: 18px; }
.form-group label { display: block; font-weight: 600; font-size: 13px; margin-bottom: 6px; color: var(--text); }
.form-control {
    width: 100%;
    padding: 11px 13px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    font-size: 15px;
    color: var(--text);
    background: var(--surface);
    transition: border-color .15s, box-shadow .15s, background .25s;
}
.form-control::placeholder { color: var(--muted); }
.form-control:focus {
    outline: none;
    border-color: var(--primary);
    box-shadow: 0 0 0 3px var(--ring);
}
/* Affordance survol (bordure seule → conserve la flèche custom du select) */
.form-control:hover:not(:disabled):not(:focus) {
    border-color: color-mix(in srgb, var(--primary) 30%, var(--border));
}
/* État désactivé lisible (background-color : ne réinitialise pas la flèche du select) */
.form-control:disabled {
    background-color: var(--hover);
    color: var(--muted);
    cursor: not-allowed;
}
select.form-control { background: var(--surface); appearance: none;
    background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
    background-position: calc(100% - 18px) 52%, calc(100% - 13px) 52%;
    background-size: 5px 5px, 5px 5px; background-repeat: no-repeat; padding-right: 34px;
}
textarea.form-control { resize: vertical; min-height: 80px; }
input[type="file"].form-control { padding: 8px 10px; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }
.form-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0 18px; }
@media (max-width: 640px){ .form-row, .form-row-3 { grid-template-columns: 1fr; } }
.form-actions { margin-top: 12px; display: flex; gap: 10px; }
.req { color: var(--danger); }

/* Bouton du sélecteur de fichier (look bouton léger, sans toucher au markup) */
input[type="file"].form-control::-webkit-file-upload-button {
    margin-right: 12px; padding: 7px 12px;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--hover); color: var(--text);
    font-size: 13px; font-weight: 600; cursor: pointer;
    transition: background .15s, border-color .15s;
    -webkit-appearance: none;
}
input[type="file"].form-control::file-selector-button {
    margin-right: 12px; padding: 7px 12px;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--hover); color: var(--text);
    font-size: 13px; font-weight: 600; cursor: pointer;
    transition: background .15s, border-color .15s;
    appearance: none;
}
input[type="file"].form-control:hover::-webkit-file-upload-button {
    border-color: color-mix(in srgb, var(--primary) 35%, var(--border));
}
input[type="file"].form-control:hover::file-selector-button {
    border-color: color-mix(in srgb, var(--primary) 35%, var(--border));
}

/* Cases à cocher / radios aux couleurs de la charte (markup inchangé) */
input[type="checkbox"], input[type="radio"] { accent-color: var(--primary); cursor: pointer; }

/* Séparateurs de sections dans un formulaire (h3 à l'intérieur d'un <form>) */
.card form h3 {
    margin-top: var(--space-5); padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    font-size: 15px; letter-spacing: -.01em;
}
.card form h3:first-of-type { margin-top: 0; padding-top: 0; border-top: none; }

/* Zone d'actions des formulaires (scopée à <form> → exclut les cartes de cours) */
.card form .form-actions {
    margin-top: var(--space-5); padding-top: var(--space-4);
    border-top: 1px solid var(--border);
}
.thumb { width: 40px; height: 40px; border-radius: 50%; object-fit: cover; background: var(--primary-light); box-shadow: 0 0 0 2px var(--surface), 0 1px 4px rgba(15, 23, 42, .12); }

/* ---------- Buttons ---------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 8px;
    padding: 11px 18px;
    border: 1px solid transparent; border-radius: var(--radius-sm);
    font-size: 15px; font-weight: 600; cursor: pointer;
    transition: transform .08s ease, box-shadow .18s ease, background .18s ease, filter .18s ease;
}
.btn:hover { text-decoration: none; box-shadow: var(--shadow); transform: translateY(-1px); }
.btn:active { transform: translateY(0); }
.btn-block { width: 100%; }
.btn-primary { background: linear-gradient(135deg, var(--primary-blue), #1d4ed8); color: #fff; box-shadow: 0 6px 16px rgba(37, 99, 235, .28); }
.btn-primary:hover { filter: brightness(1.06); }
.btn-success { background: linear-gradient(135deg, #166534, #15803d); color: #fff; }
.btn-success:hover { filter: brightness(1.06); }
.btn-danger { background: linear-gradient(135deg, #dc2626, #b91c1c); color: #fff; }
.btn-danger:hover { filter: brightness(1.06); }
.btn-warning { background: linear-gradient(135deg, #f59e0b, #fbbf24); color: #1f2937; }
.btn-warning:hover { filter: brightness(1.04); }
.btn-light { background: var(--surface); color: var(--text); border-color: var(--border); }
.btn-light:hover { background: var(--hover); }
.btn-sm { padding: 6px 11px; font-size: 13px; border-radius: 8px; }
.inline-form { display: inline; margin: 0; }

/* ---------- Alerts ---------- */
.alert { display: flex; align-items: center; gap: 10px; padding: 12px 14px; border-radius: var(--radius-sm); margin-bottom: 18px; font-size: 14px; border: 1px solid transparent; }
.alert svg { width: 18px; height: 18px; flex: 0 0 auto; }
.alert > span { min-width: 0; }
.alert-error { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
.alert-success { background: var(--success-bg); color: var(--success); border-color: color-mix(in srgb, var(--success) 35%, transparent); }

/* ---------- App shell ---------- */
.app { display: flex; min-height: 100vh; }
.sidebar {
    width: var(--sidebar-w);
    background:
        radial-gradient(600px 220px at 20% -5%, var(--sidebar-glow), transparent 60%),
        var(--sidebar-bg);
    color: var(--sidebar-text);
    position: fixed; top: 0; left: 0; bottom: 0;
    display: flex; flex-direction: column;
    padding: 18px 0; overflow-y: auto;
    border-right: 1px solid var(--sidebar-border);
    transition: background .2s ease, border-color .2s ease;
}
.sidebar .brand {
    padding: 0 20px 18px;
    font-family: var(--font-head); font-optical-sizing: auto;
    font-size: 19px; font-weight: 700; letter-spacing: -.01em; color: var(--sidebar-brand);
    display: flex; align-items: center; gap: 11px;
    border-bottom: 1px solid var(--sidebar-border);
    margin-bottom: 14px;
}
.sidebar .brand .logo {
    width: 42px; height: 42px; border-radius: 11px; overflow: hidden; flex: 0 0 42px;
    /* Fond blanc permanent (volontairement codé en dur) : le logo SVG doit rester
       lisible sur fond clair quel que soit le thème (light/dark). */
    background: #fff;
    color: var(--primary-blue); display: flex; align-items: center; justify-content: center;
    font-size: 15px; font-weight: 800;
    box-shadow: 0 6px 16px rgba(37, 99, 235, .3);
    padding: 5px;
    perspective: 600px;
    transition: box-shadow .3s ease;
}
.sidebar .brand .logo img {
    width: 100%; height: 100%; object-fit: contain;
    transform-style: preserve-3d;
    backface-visibility: visible;
    animation: brandLogoFloat3d 10s ease-in-out infinite;
}

/* Effet 3D subtil : rotation lente sur l'axe Y + léger flottement + scale + ombre dynamique. */
@keyframes brandLogoFloat3d {
    0% {
        transform: translateY(0) rotateY(0deg) scale(1);
        filter: drop-shadow(0 3px 5px rgba(15, 23, 42, .25));
    }
    50% {
        transform: translateY(-2px) rotateY(180deg) scale(1.05);
        filter: drop-shadow(0 7px 12px rgba(15, 23, 42, .35));
    }
    100% {
        transform: translateY(0) rotateY(360deg) scale(1);
        filter: drop-shadow(0 3px 5px rgba(15, 23, 42, .25));
    }
}

/* Survol (desktop uniquement) : rotation accélérée + ombre renforcée. */
@media (hover: hover) and (pointer: fine) {
    .sidebar .brand .logo:hover { box-shadow: 0 10px 24px rgba(37, 99, 235, .45); }
    .sidebar .brand .logo:hover img { animation-duration: 5s; }
}

@media (prefers-reduced-motion: reduce) {
    .sidebar .brand .logo img { animation: none; transform: none; filter: none; }
}
.sidebar nav a {
    display: flex; align-items: center; gap: 11px;
    padding: 11px 20px; margin: 1px 10px; border-radius: 10px;
    color: var(--sidebar-text); font-size: 14px; font-weight: 500;
    transition: background .15s, color .15s;
}
.sidebar nav a:hover { background: var(--sidebar-hover-bg); color: var(--sidebar-hover-text); text-decoration: none; }
.sidebar nav a.active {
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff; box-shadow: 0 6px 16px rgba(37, 99, 235, .35);
}
/* Sections repliables (accordéon) : <details class="nav-group"> + <summary class="nav-section">.
   Chaque groupe se déplie au clic ; celui de la page active est ouvert par défaut (attr `open`). */
.sidebar nav .nav-group { margin: 0; }
.sidebar nav summary.nav-section {
    display: flex; align-items: center; gap: 8px;
    margin: 14px 0 2px; padding: 6px 22px;
    font-family: var(--font-body);
    font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
    color: var(--sidebar-section);
    cursor: pointer; list-style: none; user-select: none;
    transition: color .15s;
}
.sidebar nav summary.nav-section::-webkit-details-marker { display: none; }
.sidebar nav .nav-group:first-child summary.nav-section { margin-top: 2px; }
@media (hover: hover) and (pointer: fine) {
    .sidebar nav summary.nav-section:hover { color: var(--sidebar-hover-text); }
}
.sidebar nav summary.nav-section:focus-visible { outline: 2px solid var(--sidebar-hover-text); outline-offset: -2px; border-radius: 6px; }
/* Chevron dessiné en CSS (pas d'icône sprite dédiée) : pointe vers le bas, pivote à l'ouverture. */
.sidebar nav .nav-chev {
    width: 7px; height: 7px; margin-left: auto; flex: 0 0 auto;
    border-right: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: translateY(-1px) rotate(45deg);
    transition: transform .2s ease; opacity: .7;
}
.sidebar nav details[open] > summary.nav-section .nav-chev { transform: translateY(1px) rotate(225deg); }
@media (prefers-reduced-motion: reduce) {
    .sidebar nav .nav-chev { transition: none; }
}
/* Pastille de notification sur l'en-tête de groupe (ex. messages non lus).
   Visible quand le groupe est replié ; masquée à l'ouverture (le compteur
   réapparaît alors sur le lien concerné). */
.sidebar nav .nav-badge {
    display: inline-flex; align-items: center; justify-content: center;
    min-width: 18px; height: 18px; padding: 0 5px; margin-left: 8px;
    border-radius: 9px; background: var(--danger); color: #fff;
    font-family: var(--font-body); font-size: 10px; font-weight: 700; line-height: 1;
    letter-spacing: 0; text-transform: none;
}
.sidebar nav details[open] > summary.nav-section .nav-badge { display: none; }
.sidebar .role-tag {
    display: flex; align-items: center; gap: 10px;
    margin: 14px 12px 0; padding: 8px 10px;
    background: var(--sidebar-chip); border-radius: 12px;
    font-size: 12px; color: var(--sidebar-muted);
}
/* Photo (ou initiale en repli) de l'utilisateur connecté, dans le pied de sidebar. */
.sidebar .role-tag-photo {
    width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
    object-fit: cover; border: 1px solid var(--sidebar-border);
}
.sidebar .role-tag-ph {
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff; font-family: var(--font-head); font-weight: 700; font-size: 14px;
}
.sidebar .role-tag-info {
    flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column;
    line-height: 1.25; text-transform: uppercase; letter-spacing: .04em; font-size: 10px;
}
.sidebar .role-tag-info strong {
    color: var(--sidebar-text); font-size: 13px; text-transform: none; letter-spacing: 0;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sidebar .role-tag-logout { margin: 0; flex: 0 0 auto; }
.sidebar .role-tag-logout button {
    display: flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; border-radius: 9px; cursor: pointer;
    background: transparent; border: 1px solid var(--sidebar-border);
    color: var(--sidebar-muted);
    transition: background .15s, color .15s, border-color .15s;
}
/* Le sélecteur global `.logout-icon { display:none }` (réservé au bouton mobile de la
   topbar) masquerait l'icône ici → on la réaffiche explicitement dans la sidebar. */
.sidebar .role-tag-logout button .logout-icon { display: block; width: 17px; height: 17px; }
@media (hover: hover) and (pointer: fine) {
    .sidebar .role-tag-logout button:hover { color: #fff; background: var(--danger); border-color: var(--danger); }
}
.sidebar .role-tag-logout button:focus-visible { outline: 2px solid var(--sidebar-hover-text); outline-offset: 2px; }

.main { flex: 1; margin-left: var(--sidebar-w); display: flex; flex-direction: column; min-width: 0; }
.topbar {
    height: 64px;
    background: var(--glass);
    -webkit-backdrop-filter: blur(12px) saturate(140%);
    backdrop-filter: blur(12px) saturate(140%);
    border-bottom: 1px solid var(--border);
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: nowrap; column-gap: var(--space-2);
    padding: 0 24px; position: sticky; top: 0; z-index: 10;
}
.topbar h2 { font-size: 17px; }
/* Bloc « compte » (thème + identité + photo + déconnexion) : ne rétrécit JAMAIS.
   Sans cette règle, flexbox le comprime en dernier recours et le bouton de
   déconnexion (dernier enfant) sort de la topbar sticky → invisible. */
.topbar .user { display: flex; align-items: center; gap: 14px; font-size: 14px; flex: 0 0 auto; }
.topbar .user .who { text-align: right; line-height: 1.2; min-width: 0; }
.topbar .user .who small { color: var(--muted); }
/* Un e-mail long ne doit pas gonfler la topbar : tronqué proprement. */
.topbar .user .who strong {
    display: block; max-width: 170px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* La photo de profil est TOUJOURS présente dans la topbar, calibrée sur les deux
   autres pastilles rondes (thème et déconnexion) : les trois se lisent comme
   une même série. Elle ne disparaît à aucune largeur. */
.topbar .thumb, .topbar .avatar {
    width: 38px; height: 38px; flex: 0 0 auto;
    box-shadow: 0 0 0 1px var(--border), 0 1px 4px rgba(15, 23, 42, .12);
}
.avatar {
    width: 38px; height: 38px; border-radius: 50%;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 700;
}
.theme-toggle {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 50%; width: 38px; height: 38px; cursor: pointer;
    font-size: 17px; line-height: 1; display: inline-flex; align-items: center; justify-content: center;
    transition: background .15s, transform .15s, box-shadow .15s; color: var(--text);
}
.theme-toggle:hover { background: var(--hover); transform: rotate(-12deg); box-shadow: var(--shadow); }

/* Titre de la navbar : prend tout l'espace dispo ET le rend en PREMIER quand la
   topbar est à l'étroit (shrink 100 = il cède avant l'année, la recherche et le
   bloc compte). Ellipsis à toutes les largeurs, plus seulement en mobile. */
.topbar-title {
    flex: 1 100 auto; min-width: 0;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Bloc nom/prénom + icône logout = mobile uniquement (cachés en desktop). */
.who-mobile { display: none; }
.logout-icon { display: none; }

.content { padding: 26px; }

/* ---------- Cards ---------- */
.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 22px;
    box-shadow: var(--shadow);
    transition: background .25s, border-color .25s;
}
.card h3 { font-size: 16px; margin-bottom: 6px; }
.card .muted { color: var(--muted); font-size: 14px; }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-5); margin-top: 6px; }
.stat {
    position: relative; overflow: hidden;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 20px;
    box-shadow: var(--shadow);
    transition: transform .18s cubic-bezier(0.23, 1, 0.32, 1), box-shadow .18s ease;
}
.stat::before {
    content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
    background: linear-gradient(180deg, var(--primary-blue), var(--primary-green));
}
.stat::after {
    content: ""; position: absolute; right: -30px; top: -30px; width: 110px; height: 110px;
    background: radial-gradient(circle, rgba(37, 99, 235, .12), transparent 70%);
}
@media (hover: hover) and (pointer: fine) {
    .stat:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
}
.stat .label { color: var(--muted); font-size: 13px; font-weight: 600; }
.stat .value {
    font-family: var(--font-head); font-optical-sizing: auto;
    font-size: 30px; font-weight: 700; margin-top: 8px; line-height: 1.05;
    letter-spacing: var(--tracking-tight);
    font-variant-numeric: tabular-nums;
    /* Texte dégradé clippé : stop vert assombri (#15803d) → AA même en texte normal. */
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green-dark));
    -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
/* Sombre : stops clairs pour un contraste fort du chiffre sur card (#60a5fa→#34d399). */
html[data-theme="dark"] .stat .value {
    background: linear-gradient(135deg, #60a5fa, #34d399);
    -webkit-background-clip: text; background-clip: text;
}
.stat .soon { display: inline-block; margin-top: 8px; font-size: 11px; color: var(--muted); background: var(--hover); padding: 3px 8px; border-radius: 20px; }

.page-head { margin-bottom: 20px; }
.page-head h1 { font-size: var(--fs-3xl); }
.page-head p { color: var(--muted); font-size: 14px; margin-top: 4px; font-family: var(--font-body); }
.hint { font-size: 13px; color: var(--muted); margin-top: 14px; }

/* ---------- Tables ---------- */
.toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 18px; gap: 12px; flex-wrap: wrap; }
/* Groupe d'actions de la toolbar (boutons multiples) — espacement homogène */
.toolbar > div:not(.page-head) { display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
/* overflow-x auto (et non hidden) : une table plus large que l'écran DÉFILE au
   lieu d'être coupée (ex. colonne Actions à 5 boutons) — desktop comme mobile. */
.table-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow-x: auto; overflow-y: hidden; }
table.data { width: 100%; border-collapse: collapse; font-size: 14px; font-variant-numeric: tabular-nums; }
table.data th, table.data td { padding: 12px 14px; text-align: left; border-bottom: 1px solid var(--border); }
table.data th { background: var(--th-bg); color: var(--muted); font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .03em; }
table.data tbody tr { transition: background .12s; }
table.data tbody tr:hover { background: var(--hover); }
table.data td.actions { white-space: nowrap; text-align: right; }
table.data td.actions > * + * { margin-left: var(--space-2); }
/* Variante « repli » pour les colonnes à actions nombreuses (ex. Stagiaires,
   5 boutons) : les boutons passent sur 2 lignes au lieu d'élargir la table. */
table.data td.actions.actions-wrap { white-space: normal; min-width: 300px; }
table.data td.actions.actions-wrap > * { margin: 3px 0 3px var(--space-2); }
.empty { padding: 40px; text-align: center; color: var(--muted); }

/* ---------- Badges ---------- */
.badge { display: inline-block; padding: 3px 10px; border-radius: 20px; font-size: 12px; font-weight: 600; }
.badge-on { background: var(--success-bg); color: var(--success); }
.badge-off { background: var(--hover); color: var(--muted); }
.badge-info { background: var(--primary-light); color: var(--primary-dark); } /* bleu foncé sur fond bleu clair — AA (5.35) */
html[data-theme="dark"] .badge-info { color: var(--primary); } /* en sombre, le bleu clairci (#60a5fa) passe sur le fond translucide */

/* ---------- Détails (fiche) ---------- */
.detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px,1fr)); gap: var(--space-4); }
.detail-grid .item {
    background: var(--hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 10px 12px;
}
.detail-grid .item .k { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing:.03em; }
.detail-grid .item .v { font-size: 15px; margin-top: 4px; }

/* ---------- Emploi du temps (grille) ---------- */
.edt-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; }
.edt-col { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); }
.edt-day { background: linear-gradient(135deg, var(--primary-blue), var(--primary-green)); color: #fff; text-align: center; padding: 10px; font-weight: 700; font-size: 13px; }
.edt-empty { text-align: center; color: var(--muted); padding: 18px 0; }
.edt-slot { border-bottom: 1px solid var(--border); padding: 10px 11px; }
.edt-slot:last-child { border-bottom: none; }
.edt-time { font-size: 12px; font-weight: 700; color: var(--primary); }
.edt-module { font-weight: 600; margin-top: 3px; font-size: 14px; }
.edt-meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
@media (max-width: 900px) { .edt-grid { grid-template-columns: repeat(2, 1fr); } }
/* Téléphone (≤768) : les 6 jours défilent horizontalement (largeur lisible)
   plutôt que d'être tassés sur 2 colonnes — cohérent avec .edt-week-wrap. */
@media (max-width: 768px) {
    .edt-grid {
        grid-template-columns: repeat(6, minmax(150px, 1fr));
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
        padding-bottom: 4px;
    }
}

/* ---------- Dashboard ---------- */
.dash-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: var(--space-5); margin-top: 18px; }
.dash-item { padding: var(--space-3) 0; border-bottom: 1px solid var(--border); display: flex; flex-direction: column; gap: 2px; }
.dash-item:last-child { border-bottom: none; }
.dash-item small { font-size: 12px; }

/* Section header hierarchy inside dashboard panels (scoped — global .card h3 untouched) */
.dash-cols .card h3 {
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-2);
    border-bottom: 1px solid var(--border);
    letter-spacing: -.01em;
}

/* En-tête de panneau de formulaire dans une carte (h3 suivi d'un <form>) —
   scopé via :has(+ form) : affectations + passages, jamais le .card h3 global. */
.card > h3:has(+ form) {
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-3);
    border-bottom: 1px solid var(--border);
    letter-spacing: -.01em;
}

/* Link affordance inside dashboard panels */
.dash-cols .dash-item a { transition: color .15s ease; }
@media (hover: hover) and (pointer: fine) {
    .dash-cols .dash-item a:hover { color: var(--primary); }
    .dash-cols .dash-item:hover { background: var(--hover); border-radius: var(--radius-sm); }
}

/* Entrance motion (one-shot, reduced-motion safe via global kill-switch) */
@keyframes dashFadeUp { to { opacity: 1; transform: translateY(0); } }
.grid .stat, .grid .card, .dash-cols .card {
    opacity: 0; transform: translateY(8px);
    animation: dashFadeUp .26s cubic-bezier(0.23, 1, 0.32, 1) forwards;
}
.grid .stat:nth-child(1), .grid .card:nth-child(1), .dash-cols .card:nth-child(1) { animation-delay: 0ms; }
.grid .stat:nth-child(2), .grid .card:nth-child(2), .dash-cols .card:nth-child(2) { animation-delay: 50ms; }
.grid .stat:nth-child(3), .grid .card:nth-child(3), .dash-cols .card:nth-child(3) { animation-delay: 100ms; }
.grid .stat:nth-child(4), .grid .card:nth-child(4), .dash-cols .card:nth-child(4) { animation-delay: 150ms; }
.grid .stat:nth-child(5), .grid .card:nth-child(5) { animation-delay: 200ms; }
.grid .stat:nth-child(6), .grid .card:nth-child(6) { animation-delay: 250ms; }
.grid .stat:nth-child(7), .grid .card:nth-child(7) { animation-delay: 300ms; }
.grid .stat:nth-child(8), .grid .card:nth-child(8) { animation-delay: 350ms; }
/* Interactivité des cartes en grille (galerie de cours) — gated pointeur fin */
@media (hover: hover) and (pointer: fine) {
    .grid .card:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); }
}
/* Reduced motion: keep content fully visible (global kill-switch disables the animation) */
@media (prefers-reduced-motion: reduce) {
    .grid .stat, .grid .card, .dash-cols .card { opacity: 1; transform: none; }
}

/* ---------- Grille de suivi des paiements ---------- */
/* Zone de défilement BORNÉE à la hauteur visible (pattern Excel/Airtable) :
   les deux barres (horizontale + verticale) vivent dans la zone visible → plus
   besoin de descendre en bas de page pour atteindre le défilement horizontal.
   max() garantit une hauteur de travail minimale sur petits écrans. */
.pay-grid {
    overflow: auto;
    max-height: max(420px, calc(100dvh - 230px)); /* topbar + filtres + paddings */
    -webkit-overflow-scrolling: touch;
}
/* En-têtes collants pendant le défilement vertical interne. Le séparateur bas
   est porté par un box-shadow : avec border-collapse, la bordure d'un th sticky
   défile avec le contenu. */
.pay-grid thead th {
    position: sticky;
    top: 0;
    z-index: 5;
    background: var(--th-bg);
    box-shadow: inset 0 -1px 0 var(--border);
}
@media (max-width: 768px) {
    .pay-grid { max-height: max(360px, calc(100dvh - 190px)); }
}
/* Barre horizontale MIROIR, plaquée en bas de l'écran (créée par app.js).
   La barre native de .pay-grid vit au bas de la boîte, donc hors écran tant
   que la page n'est pas descendue : celle-ci reste TOUJOURS atteignable.
   Elle ne s'affiche (.is-on) que si la grille déborde ET que sa barre native
   est sous la ligne de flottaison → jamais deux barres l'une sur l'autre. */
.pay-hbar {
    position: sticky;
    bottom: 0;
    z-index: 7;
    display: none;
    overflow-x: auto;
    overflow-y: hidden;
    height: 16px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-top-color: var(--border);
    border-radius: 0 0 var(--radius) var(--radius);
    box-shadow: 0 -8px 12px -8px rgba(15, 23, 42, .28);
}
.pay-hbar.is-on { display: block; }
.pay-hbar-inner { height: 1px; }
/* Colonne « Stagiaire » (1re) COLLANTE à gauche : l'identité reste visible
   jusqu'à Juillet. Photo, filière, classe et nom sont REGROUPÉS dans cette
   seule colonne (la filière est abrégée en sigle) → ~230px rendus aux mois.
   Fond opaque obligatoire + ombre à droite pour signaler la superposition. */
.pay-table th:nth-child(1),
.pay-table td:nth-child(1) {
    position: sticky;
    left: 0;
    z-index: 3;
    background: var(--surface);
    box-shadow: inset -1px 0 0 var(--border), 6px 0 8px -6px rgba(15, 23, 42, .18);
}
/* L'en-tête « Stagiaire » cumule top (thead sticky) + left → au-dessus de tout. */
.pay-table thead th:nth-child(1) { z-index: 6; background: var(--th-bg); }
.pay-table tbody tr:hover td:nth-child(1) { background: var(--hover); }
.pay-table th, .pay-table td { white-space: nowrap; }

/* ---- Identité regroupée (photo + nom + sigle filière · classe + CIN) ---- */
/* Double classe : `.pay-name-link` (déclarée plus bas) impose display:inline-block ;
   sans cette spécificité, la photo se retrouverait EMPILÉE au-dessus du nom. */
.pay-name-link.pay-ident {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 230px;
    max-width: 300px;
}
.pay-ident-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.pay-ident-txt strong { line-height: var(--lh-tight, 1.2); }
.pay-ident-meta { display: flex; align-items: center; gap: 6px; }
.pay-badge {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 700;
    letter-spacing: var(--tracking-wide, .02em);
    color: var(--primary);
    background: color-mix(in srgb, var(--primary-blue) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--primary-blue) 24%, transparent);
}
.pay-ident-meta small { font-size: var(--fs-xs); }
/* Mobile : la colonne d'identité collante doit CÉDER la place aux mois.
   ⚠️ Double classe .pay-name-link.pay-ident OBLIGATOIRE : la règle de base
   (230px) porte deux classes — une règle à classe simple (.pay-ident) perd
   TOUJOURS, quelle que soit sa position (c'était le bug : 230px appliqués
   jusqu'à 360px → zéro mois entièrement visible). */
@media (max-width: 768px) {
    .pay-name-link.pay-ident { min-width: 168px; max-width: 196px; gap: var(--space-2); }
    .pay-table td:nth-child(1) { padding-left: 10px; padding-right: 10px; }
    /* nowrap hérité de .pay-table : le nom TRONQUE (ellipsis) au lieu d'élargir la colonne. */
    .pay-ident-txt strong { display: block; overflow: hidden; text-overflow: ellipsis; }
    .payment-avatar, .payment-avatar-placeholder { width: 36px; height: 36px; }
}
@media (max-width: 480px) {
    .pay-name-link.pay-ident { min-width: 148px; max-width: 168px; }
    /* CIN retiré : l'identité est déjà portée par photo + nom + badge (le CIN reste dans la fiche). */
    .pay-ident-meta small { display: none; }
    .payment-avatar, .payment-avatar-placeholder { width: 32px; height: 32px; }
}
/* ---- Arriérés des années ANTÉRIEURES (grille, fiche paiements, fiche stagiaire) ----
   Signale qu'un stagiaire — typiquement un promu — n'a pas soldé une année
   révolue. Tous les écrans étant scopés sur l'année affichée, cette dette est
   sinon totalement invisible depuis l'année en cours.
   Tokens `--danger` / `--danger-bg` : redéfinis dans le bloc sombre, donc
   theme-aware sans override (contrairement à `--bg-light`, piège connu). */
.arr-tag {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    padding: 1px 7px;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 700;
    line-height: var(--lh-tight, 1.2);
    color: var(--danger);
    background: var(--danger-bg);
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
    overflow: hidden;
    text-overflow: ellipsis;
}
.arr-tag .ic { width: 13px; height: 13px; flex: 0 0 auto; }

.arr-alert {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: 12px 14px;
    margin-bottom: 18px;
    border-radius: var(--radius-sm);
    color: var(--danger);
    background: var(--danger-bg);
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
    border-left-width: 4px;
}
.arr-alert > .ic { width: 20px; height: 20px; flex: 0 0 auto; }
.arr-alert-body { display: flex; flex-direction: column; gap: 2px; flex: 1 1 220px; min-width: 0; }
.arr-alert-detail { font-size: var(--fs-xs); opacity: .85; }
.arr-alert-actions { display: flex; flex-wrap: wrap; gap: 8px; }

/* Rappel discret : la saisie en cours porte sur une année archivée. */
.arr-scope {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -6px 0 16px;
    font-size: var(--fs-xs);
    color: var(--muted);
}
.arr-scope .ic { width: 15px; height: 15px; flex: 0 0 auto; }

@media (max-width: 480px) {
    /* Le CIN cède la place (cf. .pay-ident-meta small), jamais l'alerte de dette. */
    .arr-tag { font-size: 10px; padding: 1px 5px; }
    .arr-alert { align-items: flex-start; }
}

/* ---- Rappel de solde (tableau de bord Stagiaire) ----
   Affiché seulement quand un reliquat existe, il disparaît dès que le
   stagiaire est à jour. Ton AMBRE volontaire, pas rouge : c'est un rappel
   adressé à la personne concernée, pas une alerte d'erreur. `--warning-text`
   est le token TEXTE conforme AA (le jaune vif `--warning` reste réservé aux
   remplissages) ; les deux sont redéfinis en thème sombre. */
.solde-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    padding: 14px 16px;
    margin-bottom: 18px;
    border-radius: var(--radius);
    color: var(--warning-text);
    background: color-mix(in srgb, var(--warning) 12%, transparent);
    border: 1px solid color-mix(in srgb, var(--warning) 38%, transparent);
    border-left: 4px solid var(--warning);
}
.solde-bar-ic { width: 22px; height: 22px; flex: 0 0 auto; }
.solde-bar-txt { display: flex; flex-direction: column; gap: 2px; flex: 1 1 220px; min-width: 0; }
.solde-bar-txt strong { font-size: var(--fs-lg); line-height: var(--lh-tight, 1.2); }
.solde-bar-sub { font-size: var(--fs-sm); opacity: .9; }
.solde-bar-cta { flex: 0 0 auto; }

@media (max-width: 480px) {
    .solde-bar { padding: 12px 13px; }
    .solde-bar-txt strong { font-size: var(--fs-base, 15px); }
    .solde-bar-cta { width: 100%; justify-content: center; }
}

/* ---- Dossier financier complet (toutes années, lecture seule) ----
   Écran par défaut d'un lauréat (plus inscrit nulle part) et récapitulatif
   « depuis l'inscription » pour tous. Une année = un <details> ; celles qui
   restent dues s'ouvrent d'office. Tokens uniquement (thème-aware). */
.dos-annee { margin-bottom: 14px; padding: 0; }
.dos-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-3);
    justify-content: space-between;
    padding: 14px 16px;
    cursor: pointer;
    list-style: none;
    border-radius: var(--radius);
}
.dos-head::-webkit-details-marker { display: none; }
.dos-head-main { display: flex; align-items: baseline; gap: 10px; min-width: 0; flex-wrap: wrap; }
.dos-head-main strong { font-family: var(--font-head); font-size: var(--fs-lg); }
.dos-head-chiffres { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
@media (hover: hover) and (pointer: fine) {
    .dos-head:hover { background: var(--hover); }
}
.dos-annee[open] .dos-head { border-bottom: 1px solid var(--border); }
.dos-annee .table-wrap { padding: 0 16px 16px; }
.dos-actions { padding: 14px 16px 0; }

/* Le reste dû se lit comme une alerte, pas comme un badge neutre. */
.dos-du {
    background: var(--danger-bg);
    color: var(--danger);
    border: 1px solid color-mix(in srgb, var(--danger) 35%, transparent);
}
.stat.dos-stat--du .value { color: var(--danger); }

.dos-solde {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -6px 0 18px;
    font-size: var(--fs-sm);
    font-weight: 600;
    color: var(--success);
}
.dos-solde .ic { width: 17px; height: 17px; flex: 0 0 auto; }

.pay-cell { text-align: center; padding: 6px 8px; }
.cell-select {
    padding: 4px 6px; border-radius: 7px; border: 1px solid var(--border);
    font-size: 12px; font-weight: 600; cursor: pointer; background: var(--surface); color: var(--text);
    min-width: 96px;
    transition: filter .15s;
}
.cell-select:hover { filter: brightness(1.03); }
.cell-select.st-paye { background: var(--success-bg); color: var(--success); border-color: color-mix(in srgb, var(--success) 35%, transparent); }
.cell-select.st-part { background: var(--warning-bg); color: var(--warning-text); border-color: color-mix(in srgb, var(--warning) 40%, transparent); }
.cell-select.st-non  { background: var(--danger-bg); color: var(--danger); border-color: color-mix(in srgb, var(--danger) 35%, transparent); }
/* Mois non échu + non payé : apparence neutre « — » (ni « Non payé », ni dette). */
.cell-select.is-future.st-non { background: var(--hover); color: var(--muted); border-color: var(--border); }
.cell-select:disabled { opacity: .6; cursor: wait; }
.cell-empty { color: var(--muted); }
.recu-link { margin-left: 4px; text-decoration: none; font-size: 14px; display: inline-block; transition: transform .12s ease; }
@media (hover: hover) and (pointer: fine) {
    .recu-link:hover { transform: scale(1.12); }
}
/* Montant d'un paiement PARTIEL affiché SOUS le select : le title (survol)
   est inaccessible au tactile — sans cette ligne, impossible de connaître
   le montant depuis la grille sur mobile. Maintenu par paiements.js. */
.pay-amt { display: block; margin-top: 3px; font-size: var(--fs-xs); font-weight: 600; color: var(--warning-text); }

/* Cibles tactiles mobile : le select de statut est L'ACTION principale de la
   page. ⚠️ Ce bloc doit rester APRÈS les règles de base .pay-cell/.cell-select/
   .recu-link (même spécificité → seule la position dans le fichier tranche). */
@media (max-width: 768px) {
    .pay-cell { padding: 6px; }
    .cell-select { min-height: 42px; font-size: 13px; }
    .recu-link { padding: 8px 6px; margin-left: 0; }
}

/* ---- Filtres repliables (mobile) : bouton résumé + compteur de filtres actifs.
   Desktop : bouton masqué, formulaire toujours visible (même replié).
   Sans JS : le bouton reste [hidden] et le formulaire reste visible (comportement d'avant). */
.pay-filtres-toggle { display: none; }
.pay-filtres-toggle[hidden] { display: none !important; } /* [hidden] doit gagner sur display:flex (piège evol24) */
@media (max-width: 768px) {
    .pay-filtres-toggle {
        display: flex; align-items: center; gap: 10px; width: 100%;
        min-height: 48px; padding: 2px 4px;
        background: none; border: 0; color: var(--text);
        font: inherit; font-weight: 700; font-size: var(--fs-md, 15px);
        cursor: pointer; text-align: left;
    }
    .pay-filtres-toggle > .ic { width: 18px; height: 18px; color: var(--primary); flex: 0 0 auto; }
    .pay-filtres-chev { margin-left: auto; color: var(--muted); transition: transform .2s ease; }
    .pay-filtres:not(.is-collapsed) .pay-filtres-chev { transform: rotate(180deg); }
    .pay-filtres.is-collapsed > form { display: none; }
}

/* Nom / photo de stagiaire cliquables → fiche / état complet */
.pay-name-link { color: inherit; text-decoration: none; display: inline-block; }
.pay-name-link strong { transition: color .12s ease; }
.pay-name-link .payment-avatar,
.pay-name-link .payment-avatar-placeholder,
.pay-name-link .thumb,
.pay-name-link .avatar { transition: transform .12s ease, box-shadow .12s ease; }
@media (hover: hover) and (pointer: fine) {
    .pay-name-link:hover strong { color: var(--primary); text-decoration: underline; }
    .pay-name-link:hover .payment-avatar,
    .pay-name-link:hover .payment-avatar-placeholder,
    .pay-name-link:hover .thumb,
    .pay-name-link:hover .avatar { transform: scale(1.06); box-shadow: 0 0 0 2px var(--primary), 0 1px 4px rgba(15, 23, 42, .18); }
}

/* ---------- En-tête fiche absences d'un stagiaire ---------- */
.abs-head { display: flex; align-items: center; gap: var(--space-3); flex-wrap: wrap; }
.abs-head .st-avatar img, .abs-head .st-avatar-ph { width: 64px; height: 64px; font-size: var(--fs-lg); }

/* ---------- Recadrage photo de profil (cropper circulaire) ---------- */
.cropper-modal[hidden] { display: none; }
.cropper-modal { position: fixed; inset: 0; z-index: 1100; display: flex; align-items: center; justify-content: center; padding: var(--space-4); }
.cropper-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, .66); backdrop-filter: blur(2px); }
.cropper-box { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-hover); padding: var(--space-4); max-width: 92vw; text-align: center; }
.cropper-title { margin: 0 0 var(--space-3); font-size: var(--fs-lg); }
.cropper-stage { position: relative; margin: 0 auto; border-radius: var(--radius); overflow: hidden; background: #0b1220 repeating-conic-gradient(#1e293b 0% 25%, #0b1220 0% 50%) 50% / 24px 24px; touch-action: none; }
.cropper-canvas { display: block; cursor: grab; touch-action: none; }
.cropper-canvas:active { cursor: grabbing; }
/* Anneau de cadrage circulaire : assombrit HORS du cercle (zone visible du profil). */
.cropper-ring { position: absolute; inset: 0; pointer-events: none; }
.cropper-ring::after { content: ""; position: absolute; inset: 0; border-radius: 50%;
    box-shadow: 0 0 0 9999px rgba(11, 18, 32, .55), inset 0 0 0 2px rgba(255, 255, 255, .85); }
.cropper-controls { display: flex; align-items: center; gap: 10px; margin: var(--space-3) 0 6px; }
.cropper-range { flex: 1; accent-color: var(--primary); cursor: pointer; }
.cropper-zoom { width: 34px; height: 34px; border: 1px solid var(--border); background: var(--surface); color: var(--text); border-radius: 50%; font-size: 18px; line-height: 1; cursor: pointer; }
.cropper-hint { margin: 0 0 var(--space-3); font-size: var(--fs-xs); color: var(--muted); }
.cropper-actions { display: flex; gap: var(--space-2); justify-content: center; }
body.cropper-open { overflow: hidden; }
@media (hover: hover) and (pointer: fine) { .cropper-zoom:hover { border-color: var(--primary); color: var(--primary); } }

/* ---------- Saisie groupée des absences ---------- */
.abs-saisie .abs-check { text-align: center; width: 52px; }
.abs-saisie .abs-cb { width: 20px; height: 20px; cursor: pointer; accent-color: var(--primary); }
.abs-saisie .abs-cb[disabled] { cursor: not-allowed; opacity: .5; }
.abs-saisie .abs-row-label { color: var(--muted); font-size: var(--fs-xs); cursor: default; }

/* ---------- Grille de saisie des notes ---------- */
.notes-grid .note-input { width: 92px; padding: 6px 8px; text-align: center; }
.notes-grid .note-input.is-empty { background: var(--bg-light); }
.notes-grid .note-input[readonly] { opacity: .7; cursor: not-allowed; }
@media (max-width: 768px) {
    .notes-grid .note-input { width: 100%; }
}

/* ---------- Fiche stagiaire : en-tête profil + modales photo ---------- */
.st-profile { display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-4); padding-bottom: var(--space-4); border-bottom: 1px solid var(--border); }
.st-avatar img, .st-avatar-ph { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; display: flex; align-items: center; justify-content: center; box-shadow: 0 0 0 3px var(--surface), 0 2px 8px rgba(15, 23, 42, .14); }
.st-avatar-ph { background: var(--hover); color: var(--muted); font-weight: bold; font-size: var(--fs-xl); text-transform: uppercase; }
.st-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

.st-modal[hidden] { display: none; }
.st-modal { position: fixed; inset: 0; z-index: 1000; display: flex; align-items: center; justify-content: center; padding: var(--space-4); }
.st-modal-backdrop { position: absolute; inset: 0; background: rgba(15, 23, 42, .62); backdrop-filter: blur(2px); }
.st-modal-box { position: relative; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-hover); padding: var(--space-4); max-width: 92vw; max-height: 88vh; overflow: auto; }
.st-modal-box--wide { width: 760px; }
.st-modal-close { position: absolute; top: 8px; right: 8px; background: var(--surface); border: 1px solid var(--border); border-radius: 50%; width: 34px; height: 34px; display: inline-flex; align-items: center; justify-content: center; cursor: pointer; color: var(--muted); }
.st-modal-photo { display: block; max-width: 80vw; max-height: 78vh; border-radius: var(--radius); }
.st-modal-title { margin: 0 0 var(--space-3); font-size: var(--fs-lg); }
.st-hist-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(130px, 1fr)); gap: var(--space-3); }
.st-hist-item { margin: 0; }
.st-hist-item img { width: 100%; aspect-ratio: 1; object-fit: cover; border-radius: var(--radius); border: 1px solid var(--border); }
.st-hist-item figcaption { display: flex; flex-direction: column; gap: 2px; margin-top: 6px; font-size: var(--fs-xs); }
body.st-modal-open { overflow: hidden; }
@media (hover: hover) and (pointer: fine) {
    .st-modal-close:hover { color: var(--danger); border-color: var(--danger); }
}

/* Avatar dans la grille des paiements */
.payment-avatar { width: 42px; height: 42px; border-radius: 50%; object-fit: cover; display: block; box-shadow: 0 0 0 2px var(--surface), 0 1px 4px rgba(15, 23, 42, .12); }
.payment-avatar-placeholder {
    width: 42px; height: 42px; border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    font-weight: bold; background: var(--hover); color: var(--muted);
    font-size: 14px; text-transform: uppercase;
    box-shadow: 0 0 0 2px var(--surface), 0 1px 4px rgba(15, 23, 42, .12);
}

/* ---------- Menu mobile ---------- */
.menu-toggle {
    display: none; background: var(--surface); border: 1px solid var(--border);
    border-radius: 9px; padding: 7px 11px; font-size: 18px; cursor: pointer; line-height: 1; color: var(--text);
    /* Icône parfaitement centrée quand le bouton passe en inline-flex (mobile),
       y compris avec la cible 44×44 (≤480px) où min-height dépasse le contenu. */
    align-items: center; justify-content: center;
}
.sidebar-overlay { display: none; position: fixed; inset: 0; background: rgba(2, 8, 23, .5); z-index: 19; }

@media (max-width: 768px) {
    .sidebar { transform: translateX(-100%); z-index: 20; transition: transform .2s ease; }
    .sidebar.open { transform: translateX(0); }
    .sidebar-overlay.show { display: block; }
    .main { margin-left: 0; }
    .menu-toggle { display: inline-flex; }
    .content { padding: 18px 16px; }

    /* ---------- Navbar mobile compacte (≤768px) ---------- */
    .topbar {
        height: 60px; padding: 0 12px; gap: 10px; z-index: 30;
        -webkit-backdrop-filter: blur(14px) saturate(150%);
        backdrop-filter: blur(14px) saturate(150%);
    }
    /* La sidebar s'ouvre SOUS la navbar (qui reste visible au-dessus). */
    .sidebar { padding-top: 72px; }
    .menu-toggle { padding: 6px 9px; font-size: 17px; border-radius: 9px; }

    /* Centre : titre sur une seule ligne, tronqué si trop long. */
    .topbar .topbar-title {
        text-align: center; font-size: 16px; font-weight: 700;
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }

    .topbar .user { gap: 8px; }
    .theme-toggle { width: 34px; height: 34px; font-size: 15px; }

    /* Masque identifiant/email/rôle, affiche Nom puis Prénom (2 lignes). */
    .topbar .user .who { display: none; }
    .who-mobile {
        display: flex; flex-direction: column; justify-content: center;
        line-height: 1.12; max-width: 80px;
    }
    .who-mobile span {
        font-size: 11px; white-space: nowrap; overflow: hidden;
        text-overflow: ellipsis; max-width: 80px;
    }
    .who-mobile .wm-nom { font-weight: 700; color: var(--text); }
    .who-mobile .wm-prenom { color: var(--muted); }

    /* Photo de profil : cercle 36px. */
    .topbar .thumb, .topbar .avatar { width: 36px; height: 36px; font-size: 13px; }

    /* Déconnexion = icône rouge uniquement (logique de logout inchangée). */
    .logout-text { display: none; }
    .logout-btn.btn.btn-light {
        display: inline-flex; align-items: center; justify-content: center;
        width: 34px; height: 34px; padding: 0; border-radius: 50%;
        color: var(--danger); border: 1px solid var(--danger);
        background: var(--danger-bg);
    }
    .logout-icon { display: block; }

    /* Filet de sécurité : toute table large défile horizontalement au lieu de
       déformer la page (Mes cours, Mes notes, EDT…). */
    .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

    /* --------- Tables Stagiaire transformées en cartes (.data-cards) --------- */
    .table-wrap--cards {
        background: transparent; border: 0; box-shadow: none;
        border-radius: 0; overflow: visible;
    }
    .data-cards { font-size: 14px; }
    .data-cards thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
    .data-cards, .data-cards tbody { display: block; width: 100%; }
    .data-cards tr {
        display: block;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: 14px;
        box-shadow: var(--shadow);
        padding: 4px 14px;
        margin-bottom: 12px;
    }
    .data-cards td {
        display: flex; align-items: center; justify-content: space-between;
        gap: 14px; padding: 10px 0; border: 0;
        border-bottom: 1px solid var(--border);
        text-align: right;
    }
    .data-cards tr td:last-child { border-bottom: 0; }
    .data-cards td::before {
        content: attr(data-label);
        font-weight: 600; color: var(--muted);
        text-transform: none; letter-spacing: 0;
        text-align: left; flex: 0 0 auto;
    }
    /* 1ʳᵉ cellule = en-tête de carte (mois / date) */
    .data-cards td:first-child {
        justify-content: flex-start;
        font-size: 16px; font-weight: 700;
        padding: 12px 0 10px; border-bottom: 2px solid var(--border);
    }
    .data-cards td:first-child::before { content: ''; }
    /* Message « aucune donnée » */
    .data-cards td.empty {
        display: block; text-align: center; color: var(--muted);
        border: 1px solid var(--border); border-radius: 14px;
        background: var(--surface); padding: 18px;
    }
    .data-cards td.empty::before { content: ''; }

    /* Cartes de paiement (vue Stagiaire) : accent latéral + fond teinté par statut. */
    .data-cards tr.pay-card { border-left-width: 4px; }
    .data-cards tr.pay-card--paye {
        border-left-color: var(--success);
        background: color-mix(in srgb, var(--success-bg) 60%, var(--surface));
    }
    .data-cards tr.pay-card--non {
        border-left-color: var(--danger);
        background: color-mix(in srgb, var(--danger-bg) 60%, var(--surface));
    }
    .data-cards tr.pay-card--part {
        border-left-color: var(--warning);
        background: color-mix(in srgb, var(--warning-bg) 55%, var(--surface));
    }
    .data-cards tr.pay-card--futur { border-left-color: var(--border); }
}

/* =====================================================================
   Login IPFPS — thème CLAIR (page autonome, layout auth)
   Toujours en clair, indépendant du dark mode de l'application.
   ===================================================================== */
.login-page {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    align-items: stretch;
    position: relative;
    overflow: hidden;
    color: #1e293b;
    font-family: var(--font-body);
    background:
        radial-gradient(900px 600px at 12% 8%, rgba(37, 99, 235, .10), transparent 55%),
        radial-gradient(820px 560px at 92% 96%, rgba(22, 163, 74, .10), transparent 55%),
        #ffffff;
}
/* Accents flous bleu / vert (légers, dérive douce) */
.login-page::before, .login-page::after {
    content: ""; position: absolute; border-radius: 50%;
    filter: blur(60px); pointer-events: none; z-index: 0;
}
.login-page::before {
    width: 360px; height: 360px; left: -90px; top: -70px;
    background: radial-gradient(circle, rgba(37, 99, 235, .22), transparent 70%);
    animation: loginFloat 9s ease-in-out infinite;
}
.login-page::after {
    width: 320px; height: 320px; right: -80px; bottom: -80px;
    background: radial-gradient(circle, rgba(22, 163, 74, .20), transparent 70%);
    animation: loginFloat 11s ease-in-out infinite reverse;
}
@keyframes loginFloat { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(22px); } }

/* ----- Panneau de marque ----- */
.login-brand {
    flex: 1 1 50%;
    display: flex; align-items: center; justify-content: center;
    padding: 48px 44px; position: relative; z-index: 2;
}
.login-brand-inner { max-width: 420px; text-align: center; animation: loginUp .6s ease both; }
/* Scène 3D : la perspective vit sur le conteneur pour donner de la profondeur. */
.login-logo {
    perspective: 1100px;
    perspective-origin: 50% 45%;
    display: inline-block;
}
.login-logo img {
    width: min(210px, 50vw);
    /* Rotation 3D lente autour de l'axe Y → objet qui tourne dans l'espace, pas un spin plat. */
    animation: logoSpin3d 14s cubic-bezier(0.45, 0, 0.55, 1) infinite;
    transform-style: preserve-3d;
    transform-origin: center center;
    backface-visibility: visible;
    will-change: transform, filter;
}
/* L'ombre suit la rotation (plus profonde de profil, douce de face) → sensation de volume. */
@keyframes logoSpin3d {
    0%   { transform: rotateY(0deg)   translateZ(0); filter: drop-shadow(0 12px 22px rgba(37, 99, 235, .18)); }
    25%  { transform: rotateY(90deg)  translateZ(14px); filter: drop-shadow(14px 16px 30px rgba(37, 99, 235, .26)); }
    50%  { transform: rotateY(180deg) translateZ(0); filter: drop-shadow(0 12px 22px rgba(22, 163, 74, .18)); }
    75%  { transform: rotateY(270deg) translateZ(14px); filter: drop-shadow(-14px 16px 30px rgba(37, 99, 235, .26)); }
    100% { transform: rotateY(360deg) translateZ(0); filter: drop-shadow(0 12px 22px rgba(37, 99, 235, .18)); }
}
/* Respect du kill-switch global ; on neutralise aussi explicitement la rotation 3D. */
@media (prefers-reduced-motion: reduce) {
    .login-logo img {
        animation: none;
        transform: none;
        filter: drop-shadow(0 12px 24px rgba(37, 99, 235, .18));
    }
}
.login-brand-title {
    font-family: var(--font-head); font-optical-sizing: auto;
    font-size: clamp(28px, 3.4vw, 42px);
    letter-spacing: .12em; font-weight: 700; margin-top: 14px;
    background: linear-gradient(90deg, #2563eb, #15803d); /* stop vert assombri — AA */
    -webkit-background-clip: text; background-clip: text; -webkit-text-fill-color: transparent;
}
.login-brand-sub { margin: 10px auto 0; max-width: 340px; color: #64748b; font-size: 14px; line-height: 1.55; }
.login-points {
    list-style: none; margin: 26px 0 0; padding: 0;
    display: inline-flex; flex-direction: column; gap: 12px; text-align: left;
}
.login-points li { display: flex; align-items: center; gap: 10px; color: #334155; font-size: 14px; font-weight: 500; }
.login-points svg { width: 20px; height: 20px; flex: 0 0 auto; color: #16a34a; }

/* ----- Côté formulaire ----- */
.login-form-side {
    flex: 1 1 50%;
    display: flex; align-items: center; justify-content: center;
    padding: 40px; position: relative; z-index: 2;
}
.login-card {
    width: min(420px, 100%);
    background: rgba(255, 255, 255, .80);
    -webkit-backdrop-filter: blur(14px) saturate(140%);
    backdrop-filter: blur(14px) saturate(140%);
    border: 1px solid rgba(37, 99, 235, .14);
    border-radius: 22px;
    padding: 38px 32px;
    box-shadow: 0 24px 60px rgba(15, 23, 42, .12), 0 2px 6px rgba(15, 23, 42, .05);
    animation: loginUp .5s ease both;
}
.login-card-head { text-align: center; margin-bottom: 24px; }
.login-card-badge {
    display: inline-flex; align-items: center; justify-content: center;
    width: 54px; height: 54px; border-radius: 16px; margin-bottom: 12px;
    color: #fff; background: linear-gradient(135deg, #2563eb, #16a34a);
    box-shadow: 0 10px 24px rgba(37, 99, 235, .28);
}
.login-card-badge svg { width: 26px; height: 26px; }
.login-title { font-size: clamp(22px, 3vw, 28px); font-weight: 800; color: #0f172a; margin: 0; }
.login-sub { color: #64748b; font-size: 14px; margin: 6px 0 0; }

.login-field { margin-bottom: 16px; }
.login-label { display: block; font-size: 13px; font-weight: 600; color: #334155; margin-bottom: 7px; }

.login-input-group {
    display: flex; align-items: center; gap: 10px;
    background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 13px;
    padding: 0 14px;
    transition: border-color .2s, box-shadow .2s, background .2s;
}
.login-input-group:focus-within {
    background: #fff; border-color: #2563eb;
    box-shadow: 0 0 0 4px rgba(37, 99, 235, .14);
}
.login-input-group .ic { width: 20px; height: 20px; color: #2563eb; flex: 0 0 auto; }
.login-input-group input {
    flex: 1 1 auto; min-width: 0;
    background: transparent; border: none; outline: none;
    color: #0f172a; font-size: 15px; padding: 13px 0;
}
.login-input-group input::placeholder { color: #94a3b8; }
.login-input-group input:-webkit-autofill {
    -webkit-text-fill-color: #0f172a;
    -webkit-box-shadow: 0 0 0 1000px #fff inset;
    transition: background-color 9999s ease-out 0s;
}
.password-toggle {
    background: none; border: none; cursor: pointer; color: #94a3b8;
    display: inline-flex; align-items: center; padding: 6px; flex: 0 0 auto;
    transition: color .15s;
}
.password-toggle:hover { color: #2563eb; }
.password-toggle .eye, .password-toggle .eye-off { width: 20px; height: 20px; }
.password-toggle .eye-off { display: none; }
.password-toggle.is-on .eye { display: none; }
.password-toggle.is-on .eye-off { display: inline; }

.login-btn-gradient {
    width: 100%; margin-top: 22px;
    display: inline-flex; align-items: center; justify-content: center; gap: 9px;
    border: none; border-radius: 13px; padding: 14px;
    font-size: 15px; font-weight: 700; letter-spacing: .02em; color: #fff;
    cursor: pointer;
    background: linear-gradient(90deg, #2563eb 0%, #1aa3d8 50%, #16a34a 100%);
    background-size: 180% 100%;
    box-shadow: 0 12px 26px rgba(37, 99, 235, .26);
    transition: background-position .35s ease, transform .12s ease, box-shadow .2s ease;
}
.login-btn-gradient svg { width: 18px; height: 18px; }
.login-btn-gradient:hover { background-position: 100% 0; transform: translateY(-1px); box-shadow: 0 16px 32px rgba(22, 163, 74, .28); }
.login-btn-gradient:active { transform: translateY(0); }

.login-remember { margin-top: 14px; }
.login-remember label { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; font-size: 14px; color: var(--text); user-select: none; }
.login-remember input { width: 17px; height: 17px; cursor: pointer; accent-color: var(--primary); }

/* Lien « Mot de passe oublié ? » (login) + retour connexion (pages oubli/reset) */
.login-forgot { margin-top: var(--space-2); text-align: right; }
.login-forgot a { font-size: var(--fs-sm); color: var(--primary); text-decoration: none; }
.login-forgot-center { text-align: center; margin-top: var(--space-4); }
@media (hover: hover) and (pointer: fine) {
    .login-forgot a:hover { text-decoration: underline; }
}

.login-foot { text-align: center; color: #94a3b8; font-size: 12px; margin: 18px 0 0; }

/* Alerts dans la carte claire */
.login-card .alert { background: #fef2f2; border: 1px solid #fecaca; color: #b91c1c; }
.login-card .alert-success { background: #f0fdf4; border: 1px solid #bbf7d0; color: #15803d; }

@keyframes loginUp { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }

/* ----- Responsive : empilé sur mobile ----- */
@media (max-width: 860px) {
    .login-page {
        flex-direction: column;
        /* Hauteur EXACTE de l'écran (pas min-) : la page ne défile/rebondit
           jamais, elle s'adapte à toute taille d'écran. Si l'écran est trop
           court (SE, clavier ouvert), c'est .login-form-side qui défile en
           interne — la page reste fixe. */
        height: 100vh;   /* repli navigateurs sans dvh */
        height: 100dvh;
        min-height: 0;
        touch-action: manipulation; /* pas de zoom double-tap */
    }
    .login-brand { flex: 0 0 auto; padding: 40px 24px 8px; }
    .login-logo img { width: 130px; }
    .login-points { display: none; }
    .login-form-side {
        flex: 1 1 auto; padding: 14px 18px 40px;
        min-height: 0;
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
    }
}
@media (max-width: 480px) {
    .login-card { padding: 28px 20px; border-radius: 18px; }
    .login-brand { padding: 30px 20px 4px; }
}

/* ----- Mobile + petite hauteur (SE portrait, paysage) : compacter le vertical
   uniquement (padding haut/bas, marges, taille logo) — horizontal/largeurs inchangés ----- */
@media (max-width: 860px) and (max-height: 720px) {
    .login-brand { padding-top: 22px; padding-bottom: 4px; }
    .login-logo img { width: 88px; }
    .login-brand-title { margin-top: 8px; }
    .login-brand-sub { margin-top: 6px; }
    .login-card { padding-top: 22px; padding-bottom: 22px; }
    .login-card-head { margin-bottom: 16px; }
    .login-card-badge { width: 46px; height: 46px; margin-bottom: 8px; }
    .login-field { margin-bottom: 12px; }
    .login-btn-gradient { margin-top: 16px; }
    .login-foot { margin-top: 12px; }
}
@media (max-width: 860px) and (max-height: 560px) {
    .login-brand { padding-top: 14px; padding-bottom: 2px; }
    .login-logo img { width: 64px; }
    .login-card { padding-top: 16px; padding-bottom: 16px; }
    .login-card-head { margin-bottom: 12px; }
    .login-field { margin-bottom: 10px; }
}

/* ----- Bascule de thème (coin haut-droit) — réutilise .theme-toggle/#themeIcon ----- */
.login-theme-toggle { position: absolute; top: 16px; right: 16px; z-index: 3; }

/* ----- Login en mode sombre : suit le thème global (tokens) ; accents bleu/vert conservés ----- */
[data-theme="dark"] .login-page {
    color: var(--text);
    background:
        radial-gradient(900px 600px at 12% 8%, rgba(37, 99, 235, .16), transparent 55%),
        radial-gradient(820px 560px at 92% 96%, rgba(22, 163, 74, .14), transparent 55%),
        linear-gradient(135deg, #0c1730 0%, #122544 100%);
}
[data-theme="dark"] .login-card {
    background: var(--surface); border-color: var(--border);
    box-shadow: 0 24px 60px rgba(0, 0, 0, .45), 0 2px 6px rgba(0, 0, 0, .3);
}
[data-theme="dark"] .login-title { color: var(--text); }
[data-theme="dark"] .login-sub,
[data-theme="dark"] .login-brand-sub,
[data-theme="dark"] .login-foot { color: var(--muted); }
[data-theme="dark"] .login-points li { color: var(--text); }
[data-theme="dark"] .login-label { color: var(--text); }
[data-theme="dark"] .login-input-group { background: var(--hover); border-color: var(--border); }
[data-theme="dark"] .login-input-group input { color: var(--text); }
[data-theme="dark"] .login-input-group input::placeholder { color: var(--muted); }
[data-theme="dark"] .login-input-group input:-webkit-autofill {
    -webkit-text-fill-color: var(--text);
    -webkit-box-shadow: 0 0 0 1000px var(--surface) inset;
}
[data-theme="dark"] .login-input-group:focus-within {
    background: var(--surface); border-color: var(--primary-blue);
}

/* ===================== Ma classe (Stagiaire) : cartes membres ===================== */
.members {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 14px;
}
.member-card {
    display: flex; align-items: center; gap: 12px;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 14px; padding: 12px 14px; box-shadow: var(--shadow);
    transition: transform .18s cubic-bezier(0.23, 1, 0.32, 1), box-shadow .18s ease;
}
@media (hover: hover) and (pointer: fine) {
    .member-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-hover); }
}
.member-avatar {
    width: 48px; height: 48px; border-radius: 50%; object-fit: cover;
    flex: 0 0 48px; background: var(--primary-light);
    box-shadow: 0 0 0 2px var(--surface), 0 1px 4px rgba(15, 23, 42, .12);
}
.member-avatar--initials {
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 700; font-size: 16px;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
}
.member-name { font-weight: 600; font-size: 14px; line-height: 1.25; min-width: 0; }
@media (max-width: 768px) {
    .members { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
}
/* La carte membre est désormais un <button> (ouvre la mini-carte profil) :
   reset des styles natifs du bouton, l'apparence reste celle de .member-card. */
button.member-card { width: 100%; font: inherit; color: inherit; text-align: left; cursor: pointer; }
button.member-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* ---- Mini-carte profil d'un camarade (modale « Ma classe ») ----
   Couverture bord à bord + avatar chevauchant + nom : même vocabulaire visuel
   que la bannière « Mon profil » (evol24), en format carte compacte. */
.st-modal-box.mc-box { padding: 0; width: min(400px, 100%); overflow: hidden; }
.mc-cover {
    height: 118px;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
}
.mc-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.mc-body { padding: 0 var(--space-4) var(--space-4); margin-top: -44px; text-align: center; }
.mc-avatar {
    width: 88px; height: 88px; border-radius: 50%; object-fit: cover;
    display: block; margin: 0 auto; background: var(--surface);
    box-shadow: 0 0 0 4px var(--surface), 0 2px 8px rgba(15, 23, 42, .18);
}
.mc-avatar--initials {
    display: flex; align-items: center; justify-content: center;
    color: #fff; font-weight: 700; font-size: var(--fs-xl);
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
}
.mc-name { font-family: var(--font-head); font-size: var(--fs-xl); font-weight: 700; margin-top: 10px; }
.mc-sub { color: var(--muted); font-size: var(--fs-sm); margin-top: 2px; }
/* [hidden] doit gagner sur display:block/flex (piège documenté evol24). */
.mc-cover img[hidden], .mc-avatar[hidden] { display: none; }
/* Déclencheur discret : la photo d'une LISTE devient cliquable (listes Admin
   Responsables/Formateurs/Encadrants) sans changer son apparence. */
.mc-trigger {
    background: none; border: 0; padding: 0; margin: 0;
    display: inline-flex; border-radius: 50%; cursor: pointer;
    font: inherit; color: inherit; line-height: 0;
}
.mc-trigger:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
    .mc-trigger:hover .thumb,
    .mc-trigger:hover .avatar { box-shadow: 0 0 0 2px var(--primary), 0 1px 4px rgba(15, 23, 42, .18); }
}

/* ===================== Pièce jointe : viewer intégré (message) ===================== */
.att-block { margin-top: 18px; }
.att-viewer { margin-top: 12px; display: flex; flex-direction: column; gap: 10px; }
.att-toolbar { display: flex; gap: 8px; flex-wrap: wrap; }
.att-stage {
    position: relative; width: 100%; height: 70vh;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 12px; box-shadow: var(--shadow); overflow: hidden;
    display: flex; align-items: center; justify-content: center;
}
.att-scroll {
    width: 100%; height: 100%; overflow: auto; padding: 12px;
    display: flex; flex-direction: column; align-items: center; gap: 12px;
    background: #525659;
}
.att-canvas { background: #fff; box-shadow: 0 2px 12px rgba(0,0,0,.35); max-width: 100%; }
.att-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.att-msg { padding: 28px; text-align: center; color: var(--muted); }
.att-msg p { margin: 0; font-size: 15px; }

/* Plein écran : tout le viewer (barre + scène) → boutons restent visibles. */
.att-viewer:fullscreen, .att-viewer:-webkit-full-screen {
    width: 100vw; height: 100vh; box-sizing: border-box;
    padding: 12px 16px; background: var(--bg);
}
.att-viewer:fullscreen .att-stage,
.att-viewer:-webkit-full-screen .att-stage { flex: 1 1 auto; height: auto; min-height: 0; }
/* Repli iOS (voir .is-fs-fallback en fin de fichier) : mêmes styles qu'en natif
   + safe-area (statut bar / home-indicator iPhone). */
.att-viewer.is-fs-fallback {
    /* taille = ancrage 4 bords du bloc .is-fs-fallback (pas de 100vh ici) */
    box-sizing: border-box;
    padding: calc(12px + env(safe-area-inset-top, 0px)) 16px calc(12px + env(safe-area-inset-bottom, 0px));
    background: var(--bg);
}
.att-viewer.is-fs-fallback .att-stage { flex: 1 1 auto; height: auto; min-height: 0; }

@media (max-width: 768px) { .att-stage { height: 58vh; } }

/* ===================== Boîte de réception : liste compacte mobile ===================== */
/* Desktop : la liste mobile est masquée (le tableau reste). */
.mobile-inbox-list { display: none; }

@media (max-width: 768px) {
    .inbox-desktop { display: none; }            /* masque le tableau sur mobile */
    .mobile-inbox-list {
        display: block;
        background: var(--surface); border: 1px solid var(--border);
        border-radius: 14px; overflow: hidden; box-shadow: var(--shadow);
    }
    .mobile-inbox-empty { padding: 24px; text-align: center; color: var(--muted); }

    .mobile-inbox-item {
        display: flex; align-items: center; gap: 12px;
        padding: 12px 14px; min-height: 72px;
        border-bottom: 1px solid var(--border);
        color: var(--text); text-decoration: none;
    }
    .mobile-inbox-item:last-child { border-bottom: 0; }
    .mobile-inbox-item:hover, .mobile-inbox-item:active { background: var(--hover); }

    .inbox-avatar {
        width: 40px; height: 40px; border-radius: 50%; flex: 0 0 40px;
        display: flex; align-items: center; justify-content: center;
        font-weight: 700; color: #fff; font-size: 15px;
        background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    }
    .inbox-content { flex: 1 1 auto; min-width: 0; }
    .inbox-subject {
        font-size: 14px; line-height: 1.25; color: var(--text);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .inbox-item-unread .inbox-subject { font-weight: 700; }
    .inbox-meta {
        margin-top: 2px; font-size: 12px; color: var(--muted);
        white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .inbox-side {
        flex: 0 0 auto; display: flex; flex-direction: column;
        align-items: flex-end; gap: 5px;
    }
    .inbox-date { font-size: 11px; color: var(--muted); white-space: nowrap; }
    .badge-status { font-size: 10px; padding: 2px 8px; }
    .inbox-chevron { flex: 0 0 auto; color: var(--muted); }
}

/* ===================== Emploi du temps hebdomadaire (grille) ===================== */
.edt-week-wrap { overflow-x: auto; }
.edt-week { width: 100%; border-collapse: collapse; min-width: 720px; table-layout: fixed; }
.edt-week th, .edt-week td {
    border: 1px solid var(--border); padding: 6px; text-align: center; vertical-align: top;
}
.edt-week thead th {
    background: var(--th-bg); color: var(--muted); font-size: 12px;
    text-transform: uppercase; letter-spacing: .03em; padding: 10px 6px;
}
.edt-week .edt-day {
    background: var(--th-bg); color: var(--text); font-weight: 700;
    width: 110px; text-align: left; padding-left: 12px;
}
.edt-cell { height: 78px; }
.edt-cell.edt-editable { cursor: pointer; transition: background .12s; }
.edt-cell.edt-editable:hover { background: var(--hover); }
.edt-add { color: var(--muted); font-size: 22px; font-weight: 300; line-height: 78px; display: block; }

/* Saisie directe : deux selects empilés par cellule */
.edt-cell-edit { padding: 6px; vertical-align: middle; }
.edt-cell-edit select.form-control {
    width: 100%; font-size: 12px; padding: 6px 7px; margin: 0 0 5px;
}
.edt-cell-edit select.form-control:last-child { margin-bottom: 0; }
.edt-week-edit td, .edt-week-edit th { vertical-align: middle; }

.seance {
    display: flex; flex-direction: column; gap: 2px; text-align: left;
    border-radius: 8px; padding: 7px 9px; border-left: 4px solid var(--muted);
    background: var(--hover); height: 100%; box-sizing: border-box;
}
.seance-module { font-weight: 700; font-size: 13px; color: var(--text); }
.seance-form { font-size: 12px; color: var(--text); }
.seance-type { font-size: 11px; color: var(--muted); }
.seance-cours  { background: rgba(37, 99, 235, .12);  border-left-color: var(--primary-blue); }
.seance-stage  { background: rgba(22, 163, 74, .14);  border-left-color: var(--primary-green); }
.seance-examen { background: rgba(220, 38, 38, .12);  border-left-color: #dc2626; }
.seance-autre  { background: var(--hover);            border-left-color: var(--muted); }
/* evol26 : type « contrôle » (ambre) + libellé de type + remarque de séance */
.seance-controle { background: rgba(217, 119, 6, .14); border-left-color: var(--warning); }
.seance-kind {
    font-size: 10px; font-weight: 800; text-transform: uppercase;
    letter-spacing: .05em; color: var(--muted);
}
.seance-examen .seance-kind { color: var(--danger); }
.seance-controle .seance-kind { color: var(--warning-text); }
.seance-note { font-size: 11px; color: var(--muted); font-style: italic; }

/* evol26 : champ remarque dans la cellule éditable (mêmes cotes que les selects) */
.edt-cell-edit .cell-remarque {
    width: 100%; font-size: 12px; padding: 6px 7px; margin: 0; box-sizing: border-box;
}
/* evol26 : duplication vers une date choisie (toolbar grille) */
.edt-dup { display: inline-flex; align-items: center; gap: 6px; }
.edt-dup .edt-dup-date { width: auto; font-size: 13px; padding: 8px 9px; }

/* Modale d'édition de cellule */
.edt-modal-overlay {
    position: fixed; inset: 0; z-index: 40; display: flex;
    align-items: center; justify-content: center; padding: 16px;
    background: rgba(2, 8, 23, .55); -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.edt-modal {
    width: 100%; max-width: 460px; background: var(--surface);
    border: 1px solid var(--border); border-radius: 16px;
    box-shadow: var(--shadow-hover); padding: 22px;
}
.edt-modal h3 { margin: 0 0 14px; }
.edt-modal-actions { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 6px; }

@media (max-width: 768px) {
    .edt-week .edt-day { width: 84px; padding-left: 8px; font-size: 13px; }
}

/* Respect des préférences de mouvement réduit */
@media (prefers-reduced-motion: reduce) {
    * { transition: none !important; animation: none !important; }
}

/* ===================== Lecteur de cours (PDF / image / vidéo) ===================== */
.reader { display: flex; flex-direction: column; gap: 14px; }
.reader-bar {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 16px; flex-wrap: wrap;
}
.reader-head h1 { margin: 0 0 4px; }
.reader-head p { margin: 0; color: var(--muted); font-size: 14px; }
.reader-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.reader-group { display: inline-flex; gap: 6px; align-items: center; }
.reader-pageinfo { font-size: 14px; color: var(--muted); min-width: 56px; text-align: center; }
.reader-desc { margin: 0; }

.reader-stage {
    position: relative;
    width: 100%;
    height: 80vh;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow);
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* Zone de défilement du rendu PDF (canvas PDF.js) */
.pdf-scroll {
    width: 100%; height: 100%;
    overflow: auto;
    display: flex; align-items: flex-start; justify-content: center;
    padding: 12px;
    background: #525659;
}
#pdfCanvas { background: #fff; box-shadow: 0 2px 12px rgba(0,0,0,.35); max-width: 100%; }
.reader-loading {
    position: absolute; inset: 0; display: flex; gap: 10px;
    align-items: center; justify-content: center;
    color: var(--muted); font-size: 14px; background: var(--surface);
}
.reader-loading .spinner {
    width: 18px; height: 18px; border-radius: 50%;
    border: 2px solid var(--border); border-top-color: var(--primary);
    display: inline-block; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.reader-img { max-width: 100%; max-height: 100%; object-fit: contain; }
.reader-video { width: 100%; height: 100%; background: #000; }

/* Boutons à icône (expand/compress…) */
.btn-icon { display: inline-flex; align-items: center; gap: 6px; }
.btn-icon svg { display: block; }

/* Bouton-icône carré (colonnes d'actions denses) : 1 rangée compacte et
   alignée, libellé porté par title + aria-label. */
.btn-ic {
    display: inline-flex; align-items: center; justify-content: center;
    width: 36px; height: 36px; padding: 0;
    vertical-align: middle;
}
.btn-ic .ic { width: 16px; height: 16px; }

/* Plein écran : c'est TOUT le lecteur (.reader) qui passe en plein écran.
   Mode « reader » immersif : le document occupe l'écran ENTIER ; la barre
   d'outils devient une pilule flottante compacte (pages / zoom / sortie)
   centrée en bas ; titre, description, « Retour » et hint sont masqués
   (markup intouché — uniquement du re-style scopé à l'état plein écran). */
.reader:fullscreen,
.reader:-webkit-full-screen {
    position: relative;
    width: 100vw; height: 100vh; box-sizing: border-box;
    padding: 0; gap: 0;
    background: var(--bg);
}
/* Barre → pilule flottante au-dessus du document */
.reader:fullscreen .reader-bar,
.reader:-webkit-full-screen .reader-bar {
    position: absolute;
    left: 50%; transform: translateX(-50%);
    bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
    z-index: 5;
    flex-wrap: nowrap; justify-content: center; align-items: center;
    gap: var(--space-2);
    width: max-content; max-width: calc(100% - 2 * var(--space-3));
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: var(--shadow-hover);
}
/* Masqués en plein écran : titre, Retour, libellé du bouton sortie, desc, hint */
.reader:fullscreen .reader-head,
.reader:-webkit-full-screen .reader-head,
.reader:fullscreen .reader-actions > a.btn-primary,
.reader:-webkit-full-screen .reader-actions > a.btn-primary,
.reader:fullscreen #fsExit span,
.reader:-webkit-full-screen #fsExit span,
.reader:fullscreen .reader-desc,
.reader:-webkit-full-screen .reader-desc,
.reader:fullscreen .hint,
.reader:-webkit-full-screen .hint { display: none; }
.reader:fullscreen .reader-actions,
.reader:-webkit-full-screen .reader-actions { flex-wrap: nowrap; gap: var(--space-2); }
/* Boutons compacts, cibles tactiles conservées (≥44px) */
.reader:fullscreen .reader-actions .btn,
.reader:-webkit-full-screen .reader-actions .btn {
    min-width: 44px; min-height: 44px;
    padding: 8px 12px;
    border-radius: 999px;
}
.reader:fullscreen .reader-pageinfo,
.reader:-webkit-full-screen .reader-pageinfo { min-width: 0; white-space: nowrap; color: var(--text); }
/* Document plein cadre (plus de carte : ni bordure, ni coins, ni ombre) */
.reader:fullscreen .reader-stage,
.reader:-webkit-full-screen .reader-stage {
    flex: 1 1 auto; height: auto; min-height: 0;
    border: none; border-radius: 0; box-shadow: none;
}
/* La fin du document reste lisible sous la pilule flottante */
.reader:fullscreen .pdf-scroll,
.reader:-webkit-full-screen .pdf-scroll { padding-bottom: 84px; }
/* Repli iOS (voir .is-fs-fallback en fin de fichier) : mêmes styles qu'en natif
   + safe-area (statut bar / home-indicator iPhone). */
.reader.is-fs-fallback {
    /* taille = ancrage 4 bords du bloc .is-fs-fallback (pas de 100vh ici) */
    position: fixed;
    box-sizing: border-box;
    padding: 0; gap: 0;
    background: var(--bg);
}
.reader.is-fs-fallback .reader-bar {
    position: absolute;
    left: 50%; transform: translateX(-50%);
    bottom: calc(var(--space-3) + env(safe-area-inset-bottom, 0px));
    z-index: 5;
    flex-wrap: nowrap; justify-content: center; align-items: center;
    gap: var(--space-2);
    width: max-content; max-width: calc(100% - 2 * var(--space-3));
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    box-shadow: var(--shadow-hover);
}
.reader.is-fs-fallback .reader-head,
.reader.is-fs-fallback .reader-actions > a.btn-primary,
.reader.is-fs-fallback #fsExit span,
.reader.is-fs-fallback .reader-desc,
.reader.is-fs-fallback .hint { display: none; }
.reader.is-fs-fallback .reader-actions { flex-wrap: nowrap; gap: var(--space-2); }
.reader.is-fs-fallback .reader-actions .btn {
    min-width: 44px; min-height: 44px;
    padding: 8px 12px;
    border-radius: 999px;
}
.reader.is-fs-fallback .reader-pageinfo { min-width: 0; white-space: nowrap; color: var(--text); }
.reader.is-fs-fallback .reader-stage {
    flex: 1 1 auto; height: auto; min-height: 0;
    border: none; border-radius: 0; box-shadow: none;
}
.reader.is-fs-fallback .pdf-scroll { padding-bottom: 84px; }
/* Très petits écrans : pilule resserrée pour tenir sur une seule ligne */
@media (max-width: 380px) {
    .reader:fullscreen .reader-bar,
    .reader:-webkit-full-screen .reader-bar,
    .reader.is-fs-fallback .reader-bar { gap: 4px; padding: 4px; }
    .reader:fullscreen .reader-actions,
    .reader:-webkit-full-screen .reader-actions,
    .reader.is-fs-fallback .reader-actions { gap: 4px; }
    .reader:fullscreen .reader-actions .btn,
    .reader:-webkit-full-screen .reader-actions .btn,
    .reader.is-fs-fallback .reader-actions .btn { padding: 8px 10px; }
    .reader:fullscreen .reader-group,
    .reader:-webkit-full-screen .reader-group,
    .reader.is-fs-fallback .reader-group { gap: 4px; }
}

.reader-fallback {
    padding: 28px; text-align: center; color: var(--muted);
    max-width: 460px;
}
.reader-fallback p { margin: 0; font-size: 15px; }

@media (max-width: 640px) {
    .reader-stage { height: 68vh; }
}

/* =====================================================================
   Phase 1 — Fondation & shell (polish global, additif)
   Aucune classe existante n'est renommée/supprimée ; seules des propriétés
   sont affinées et de nouveaux utilitaires ajoutés.
   ===================================================================== */

/* Accessibilité : focus clavier visible (ne change aucun comportement) */
:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; border-radius: 4px; }
.btn:focus-visible, a:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }
.form-control:focus-visible { outline: none; } /* l'anneau box-shadow suffit */
.sidebar nav a:focus-visible { outline: 2px solid var(--sidebar-hover-text); outline-offset: -2px; }

/* Lien d'évitement (skip-link) pour la navigation clavier */
.skip-link {
    position: absolute; left: -9999px; top: 0; z-index: 100;
    background: var(--primary); color: #fff; padding: 10px 16px;
    border-radius: 0 0 10px 0; font-weight: 600; box-shadow: var(--shadow);
}
.skip-link:focus { left: 0; }

/* Barres de défilement premium (discrètes) */
* { scrollbar-width: thin; scrollbar-color: var(--border) transparent; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
    background: var(--border); border-radius: 8px;
    border: 2px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted); background-clip: padding-box; }

/* Canevas de contenu : largeur de lecture confortable + accents très légers
   bleu/vert UNIQUEMENT en mode clair (le dark mode reste inchangé). */
.content { max-width: var(--content-max); margin-inline: auto; width: 100%; }
html:not([data-theme="dark"]) .main {
    background:
        radial-gradient(720px 380px at 100% 0%, rgba(22, 163, 74, .045), transparent 60%),
        radial-gradient(640px 360px at 0% 100%, rgba(37, 99, 235, .05), transparent 60%);
}

/* Hiérarchie : titres de page plus affirmés (Fraunces display, serré). */
.page-head h1 { font-weight: 800; letter-spacing: var(--tracking-tight); }

/* Topbar : liseré bas net + cohérence verre */
.topbar { box-shadow: 0 1px 0 var(--border); }

/* Cartes : légère élévation au survol (les cartes cliquables/interactives) */
.card { transition: background .25s, border-color .25s, box-shadow .2s, transform .12s; }

/* Tables : dernière ligne sans liseré (conteneur déjà arrondi) */
table.data tbody tr:last-child td { border-bottom: 0; }

@media (max-width: 768px) {
    .content { padding: 18px 14px; }
}
@media (max-width: 480px) {
    .content { padding: 16px 12px; }
}

/* ============================================================
   Historique — détails lisibles (bloc déroulant « Voir détails »)
   ============================================================ */
.hist-details { font-size: 13px; }
.hist-details > summary {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: var(--radius-sm);
    background: var(--hover);
    color: var(--primary-blue);
    font-weight: 600;
    font-size: 12px;
    white-space: nowrap;
    list-style: none;
    user-select: none;
}
.hist-details > summary::-webkit-details-marker { display: none; }
.hist-details > summary::before { content: "▸"; font-size: 11px; }
.hist-details[open] > summary::before { content: "▾"; }
@media (hover: hover) and (pointer: fine) {
    .hist-details > summary:hover { background: var(--border); }
}

.hist-detail-body {
    margin-top: 10px;
    padding: 12px 14px;
    background: var(--bg-light);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    min-width: 240px;
}
.hist-detail-body dl.hist-dl {
    display: grid;
    grid-template-columns: minmax(120px, max-content) 1fr;
    gap: 4px 14px;
    margin: 0;
}
.hist-dl dt { color: var(--muted); font-weight: 600; }
.hist-dl dd { margin: 0; color: var(--text); font-variant-numeric: tabular-nums; }

.hist-diff {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-top: 12px;
}
.hist-diff-col {
    padding: 10px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface);
}
.hist-diff-col h5 {
    margin: 0 0 8px;
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--muted);
}
.hist-diff-col.hist-before { border-left: 3px solid var(--danger); }
.hist-diff-col.hist-after { border-left: 3px solid var(--primary-green); }

.hist-tech { margin-top: 12px; }
.hist-tech > summary {
    cursor: pointer;
    font-size: 11px;
    color: var(--muted);
    user-select: none;
}
.hist-tech-label {
    margin-top: 8px;
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--muted);
}
.hist-tech pre {
    margin: 4px 0 0;
    padding: 8px 10px;
    background: var(--th-bg);
    border-radius: var(--radius-sm);
    font-size: 11px;
    line-height: 1.45;
    overflow-x: auto;
    white-space: pre-wrap;
    word-break: break-word;
}

@media (max-width: 768px) {
    .hist-diff { grid-template-columns: 1fr; }
    .hist-detail-body dl.hist-dl { grid-template-columns: 1fr; gap: 2px; }
    .hist-dl dt { margin-top: 6px; }
}

/* ============================================================
   Quiz Santé (espace Stagiaire) — style ludique / moderne
   ============================================================ */
.quiz-anim {
    opacity: 0;
    transform: translateY(8px);
    animation: dashFadeUp .26s cubic-bezier(0.23, 1, 0.32, 1) forwards;
}

/* --- Écran d'introduction --- */
.quiz-intro {
    max-width: 560px;
    margin-inline: auto;
    text-align: center;
}
.quiz-intro-icon {
    font-size: 56px;
    line-height: 1;
    margin-bottom: var(--space-2);
}
.quiz-intro h2 { margin: 0 0 var(--space-2); }
.quiz-rules {
    list-style: none;
    margin: var(--space-4) auto;
    padding: 0;
    max-width: 340px;
    text-align: left;
    display: grid;
    gap: var(--space-2);
}
.quiz-rules li {
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 10px 14px;
    font-weight: 600;
}
.quiz-start { margin-top: var(--space-3); }

/* --- Carte question --- */
.quiz-card { max-width: 620px; margin-inline: auto; }
.quiz-topbar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    margin-bottom: var(--space-3);
}
.quiz-progress {
    flex: 1;
    height: 10px;
    background: var(--bg-light);
    border: 1px solid var(--border);
    border-radius: 999px;
    overflow: hidden;
}
.quiz-progress-bar {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--primary-blue), var(--primary-green));
    transition: width .35s cubic-bezier(0.23, 1, 0.32, 1);
}
.quiz-step { font-size: 13px; color: var(--muted); white-space: nowrap; }

/* --- Timer --- */
.quiz-timer {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-bottom: var(--space-4);
}
.quiz-timer-track {
    flex: 1;
    height: 6px;
    background: var(--bg-light);
    border-radius: 999px;
    overflow: hidden;
}
.quiz-timer-fill {
    display: block;
    height: 100%;
    width: 100%;
    border-radius: 999px;
    background: linear-gradient(90deg, var(--primary-green), var(--warning));
    transition: width 1s linear;
}
.quiz-timer-text {
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    color: var(--primary-blue);
    min-width: 38px;
    text-align: right;
}

.quiz-question {
    font-size: 19px;
    line-height: 1.4;
    margin: 0 0 var(--space-4);
}

/* --- Choix --- */
.quiz-choices { display: grid; gap: var(--space-3); }
.quiz-choice {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    width: 100%;
    text-align: left;
    padding: 14px 16px;
    border: 2px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--text);
    font-size: 15px;
    font-weight: 600;
    cursor: pointer;
    transition: border-color .18s ease, background .18s ease, transform .18s cubic-bezier(0.23, 1, 0.32, 1);
}
.quiz-choice-letter {
    flex: 0 0 auto;
    width: 34px;
    height: 34px;
    display: grid;
    place-items: center;
    border-radius: 50%;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff;
    font-weight: 800;
}
.quiz-choice-text { flex: 1; }
@media (hover: hover) and (pointer: fine) {
    .quiz-choice:hover {
        border-color: var(--primary-blue);
        background: var(--hover);
        transform: translateY(-2px);
    }
}
.quiz-choice:active { transform: translateY(0); }
.quiz-choice:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--ring); }

/* États de réponse (feedback après clic / timeout) */
.quiz-choice:disabled { cursor: default; }
.quiz-choice:disabled:not(.correct):not(.wrong) { opacity: .5; }
.quiz-choice.correct {
    border-color: var(--success);
    background: var(--success-bg);
    color: var(--success);
    animation: quizPop .35s cubic-bezier(0.23, 1, 0.32, 1);
}
.quiz-choice.correct .quiz-choice-letter { background: var(--success); }
.quiz-choice.wrong {
    border-color: var(--danger);
    background: var(--danger-bg);
    color: var(--danger);
}
.quiz-choice.wrong .quiz-choice-letter { background: var(--danger); }
.quiz-choice.shake { animation: quizShake .4s cubic-bezier(0.36, 0.07, 0.19, 0.97); }

.quiz-feedback {
    margin-top: var(--space-4);
    text-align: center;
    font-weight: 700;
    font-size: 15px;
    min-height: 1.2em;
    opacity: 0;
    transform: translateY(4px);
    transition: opacity .2s ease, transform .2s ease;
}
.quiz-feedback.show { opacity: 1; transform: none; }
.quiz-feedback.correct { color: var(--success); }
.quiz-feedback.wrong { color: var(--danger); }
.quiz-feedback.timeout { color: var(--warning-text); }

@keyframes quizPop {
    0% { transform: scale(1); }
    50% { transform: scale(1.04); }
    100% { transform: scale(1); }
}
@keyframes quizShake {
    0%, 100% { transform: translateX(0); }
    20% { transform: translateX(-6px); }
    40% { transform: translateX(6px); }
    60% { transform: translateX(-4px); }
    80% { transform: translateX(4px); }
}

/* --- Résultat --- */
.quiz-result {
    max-width: 560px;
    margin-inline: auto;
    text-align: center;
}
.quiz-score-ring {
    --quiz-pct: 0;
    width: 160px;
    height: 160px;
    margin: var(--space-2) auto var(--space-4);
    border-radius: 50%;
    background: conic-gradient(var(--primary-green) calc(var(--quiz-pct) * 1%), var(--border) 0);
    display: grid;
    place-items: center;
}
.quiz-score-inner {
    width: 124px;
    height: 124px;
    border-radius: 50%;
    background: var(--surface);
    display: grid;
    place-items: center;
    box-shadow: var(--shadow);
}
.quiz-score-value { font-size: 30px; font-weight: 800; font-variant-numeric: tabular-nums; }
.quiz-score-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; }
.quiz-message { font-size: 18px; font-weight: 700; margin: 0 0 var(--space-4); }
.quiz-result--success .quiz-message { color: var(--primary-green); }
.quiz-result--good .quiz-message { color: var(--primary-blue); }
.quiz-result--low .quiz-message { color: var(--danger); }
.quiz-stats {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin-bottom: var(--space-5);
}
.quiz-stat {
    background: var(--bg-light);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 14px 8px;
}
.quiz-stat-value { display: block; font-size: 26px; font-weight: 800; font-variant-numeric: tabular-nums; }
.quiz-stat-label { font-size: 12px; color: var(--muted); }
.quiz-stat--good .quiz-stat-value { color: var(--primary-green); }
.quiz-stat--bad .quiz-stat-value { color: var(--danger); }
.quiz-actions {
    display: flex;
    gap: var(--space-3);
    justify-content: center;
    flex-wrap: wrap;
}

/* --- Carte promo (dashboard) --- */
.quiz-promo {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-top: var(--space-4);
    background: linear-gradient(120deg, rgba(37, 99, 235, .08), rgba(22, 163, 74, .08));
}
.quiz-promo-text h3 { margin: 0 0 4px; }
.quiz-promo-text p { margin: 0; max-width: 60ch; }

@media (max-width: 480px) {
    .quiz-stats { grid-template-columns: 1fr; }
    .quiz-actions { flex-direction: column; }
    .quiz-actions .btn, .quiz-actions form { width: 100%; }
    .quiz-actions form .btn { width: 100%; }
}

@media (prefers-reduced-motion: reduce) {
    .quiz-anim { opacity: 1; transform: none; animation: none; }
    .quiz-progress-bar, .quiz-choice { transition: none; }
    .quiz-choice.correct, .quiz-choice.shake { animation: none; }
    .quiz-feedback { transition: none; }
}

/* ============================================================
   Stagiaire — fiche « Mes paiements » simplifiée
   ============================================================ */
/* Reste à payer affiché uniquement pour un statut « Partiel ». */
.pay-reste {
    display: block;
    margin-top: 4px;
    font-size: 13px;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* ============================================================
   Top Étudiants — gestion Admin (liste) + page Stagiaire (carousel 3D)
   ============================================================ */

/* ---- Admin : liste / formulaire ---- */
.ts-order { display: inline-flex; gap: 4px; }
.ts-order .inline-form { margin: 0; }
.ts-thumb {
    width: 46px;
    height: 46px;
    border-radius: 50%;
    object-fit: cover;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--th-bg);
    color: var(--muted);
    font-weight: 700;
    box-shadow: 0 0 0 2px var(--surface), 0 1px 4px rgba(15, 23, 42, .12);
}
.ts-thumb--initials { font-size: 15px; }
.ts-form-current { display: flex; align-items: center; gap: var(--space-3); margin-bottom: var(--space-2); }
.ts-check { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.ts-check input { accent-color: var(--primary-blue); }

/* ---- Page Stagiaire : Circular Gallery (galerie 3D circulaire) ----
   Port vanilla fidèle du composant React « circular-gallery » : anneau 3D
   piloté au défilement (scroll → rotateY) + auto-rotation lente au repos ;
   profondeur rendue par l'opacité de chaque carte (calculée en JS, voir
   ipfpsCircularGallery). Markup + rotation = JS ; tailles & rayon responsives
   via custom props `--cgal-*`. Sans JS / mouvement réduit : grille statique.
   Couleurs via tokens (thème clair/sombre automatique). */
.cgal {
    --cgal-radius: 500px;
    --cgal-card-w: 300px;
    --cgal-card-h: 400px;
    --cgal-h: 520px;
    position: relative;
    width: 100%;
    height: var(--cgal-h);
    margin-top: var(--space-4);
    display: flex;
    align-items: center;
    justify-content: center;
    perspective: 2000px;
}

/* Anneau : porte les cartes ; rotateY appliqué en inline par le JS. */
.cgal-ring {
    position: relative;
    width: var(--cgal-card-w);
    height: var(--cgal-card-h);
    transform-style: preserve-3d;
    will-change: transform;
}

/* Carte : posée sur le cercle (angle serveur + rayon CSS). */
.cgal-card {
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--cgal-card-w);
    height: var(--cgal-card-h);
    margin-left: calc(var(--cgal-card-w) / -2);
    margin-top: calc(var(--cgal-card-h) / -2);
    transform: rotateY(var(--cgal-angle, 0deg)) translateZ(var(--cgal-radius));
    border-radius: var(--radius-lg);
    overflow: hidden;
    border: 1px solid var(--border);
    background: var(--surface);
    box-shadow: var(--shadow-hover);
    transition: opacity .3s linear;
}

/* Photo plein cadre. */
.cgal-photo {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
}
.cgal-photo--initials {
    display: flex;
    align-items: center;
    justify-content: center;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff;
    font-size: 64px;
    font-weight: 800;
    letter-spacing: .02em;
}

/* Overlay nom / sous-titre (description) / position en bas de carte. */
.cgal-overlay {
    position: absolute;
    left: 0;
    bottom: 0;
    width: 100%;
    z-index: 2;
    padding: 30px 16px 16px;
    background: linear-gradient(to top, rgba(0, 0, 0, .82), rgba(0, 0, 0, 0));
    color: #fff;
}
.cgal-name {
    margin: 0;
    font-size: 20px;
    font-weight: 700;
    line-height: 1.2;
    color: #fff;
    text-shadow: 0 1px 6px rgba(0, 0, 0, .45);
}
.cgal-sub {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    font-style: italic;
    opacity: .85;
}
.cgal-by {
    margin: 6px 0 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: .02em;
    opacity: .75;
}

.cgal-hint {
    margin: var(--space-4) 0 0;
    text-align: center;
    font-size: 12px;
    color: var(--muted);
}

/* Responsive : rayon + cartes plus compacts (desktop / tablette / mobile). */
@media (max-width: 900px) {
    .cgal { --cgal-radius: 340px; --cgal-card-w: 230px; --cgal-card-h: 310px; --cgal-h: 440px; }
    .cgal-photo--initials { font-size: 54px; }
    .cgal-name { font-size: 18px; }
}
@media (max-width: 560px) {
    .cgal { --cgal-radius: 210px; --cgal-card-w: 168px; --cgal-card-h: 232px; --cgal-h: 360px; }
    .cgal-photo--initials { font-size: 44px; }
    .cgal-overlay { padding: 22px 12px 12px; }
    .cgal-name { font-size: 15px; }
    .cgal-sub { font-size: 12px; }
}

/* Accessibilité : pas de mouvement → grille statique lisible (le JS s'abstient). */
@media (prefers-reduced-motion: reduce) {
    .cgal { perspective: none; height: auto; }
    .cgal-ring {
        position: static;
        width: 100%;
        height: auto;
        transform: none !important;
        display: flex;
        flex-wrap: wrap;
        justify-content: center;
        gap: var(--space-4);
    }
    .cgal-card {
        position: static;
        left: auto;
        top: auto;
        margin: 0;
        transform: none !important;
        opacity: 1 !important;
    }
}

/* =====================================================================
   Icônes Lucide — sprite vendorisé local (public/assets/icons/lucide.svg),
   référencé par <svg class="ic"><use href="…lucide.svg#nom"/></svg>.
   Trait 2px hérité du <symbol> ; couleur via currentColor (suit le texte).
   Remplacent les anciens emojis ; même emplacement / contexte de taille.
   ===================================================================== */
.ic {
    width: 1.15em;
    height: 1.15em;
    flex: 0 0 auto;
    vertical-align: -0.16em;
    stroke-width: 2;
    pointer-events: none;
}
/* Tailles cohérentes par contexte. */
.sidebar nav a .ic { width: 19px; height: 19px; }
.btn .ic { width: 18px; height: 18px; }
.btn-sm .ic { width: 15px; height: 15px; }
h1 .ic, h2 .ic, .page-head h1 .ic { width: .82em; height: .82em; vertical-align: -0.08em; }
.card h3 .ic, h3 .ic { width: 1.05em; height: 1.05em; vertical-align: -0.16em; }
.dash-cols .dash-item a .ic { width: 17px; height: 17px; }
.menu-toggle .ic, .theme-toggle .ic { width: 20px; height: 20px; vertical-align: middle; }
.quiz-intro-icon .ic { width: 56px; height: 56px; }
.quiz-rules li .ic { width: 18px; height: 18px; vertical-align: -0.18em; }

/* --- Mes absences (Stagiaire) : liste repliable (date + « Voir détail ») --- */
.abs-list { display: grid; gap: var(--space-2); }
.abs-item {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow);
    overflow: hidden;
}
.abs-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 12px 16px;
    cursor: pointer;
    list-style: none;
    font-weight: 600;
    color: var(--text);
    min-height: 44px;
}
.abs-row::-webkit-details-marker { display: none; }
.abs-date { display: inline-flex; align-items: center; gap: 8px; }
.abs-date .ic { width: 18px; height: 18px; color: var(--muted); }
.abs-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: var(--fs-sm);
    color: var(--primary);
    white-space: nowrap;
}
.abs-toggle .ic { width: 16px; height: 16px; }
.abs-item[open] .abs-toggle-show { display: none; }
.abs-item:not([open]) .abs-toggle-hide { display: none; }
.abs-detail {
    border-top: 1px solid var(--border);
    padding: 12px 16px;
    display: grid;
    gap: 8px;
}
.abs-field { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.abs-actions { border-top: 1px dashed var(--border); padding-top: 8px; margin-top: 2px; }
.abs-actions .actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.abs-label {
    min-width: 90px;
    color: var(--muted);
    font-size: var(--fs-sm);
    font-weight: 600;
}
@media (hover: hover) and (pointer: fine) {
    .abs-row:hover { background: var(--hover); }
}

/* --- Vitrine « IPFPS » (Stagiaire) : grand nom animé + photo + fond shader --- */
/* La vitrine suit désormais le THÈME (le shader WebGL aussi, via son uniform
   uDark) : fond blanc en clair, fond bleu nuit en sombre. Tout ce qui dépend du
   fond passe par ces quatre variables — texte, vignette, ombres, anneau photo —
   de sorte que le thème sombre reste EXACTEMENT ce qu'il était (ses valeurs sont
   reprises telles quelles dans le bloc [data-theme="dark"] plus bas). */
.ipfps-hero {
    /* accent du nom (choix : vert de marque) + palette du logo (fond/repli) */
    --ipfps-accent: var(--primary-green);
    --ipfps-logo-teal: #1076A1;
    --ipfps-logo-lime: #96C93F;
    --ipfps-logo-sky: #8CC0D2;
    --ipfps-elec: #1f6bff; /* bleu électrique de l'effet */

    /* --- Variables dépendantes du fond (valeurs du thème CLAIR) --- */
    --ipfps-ink: var(--text);                   /* texte lisible sur fond blanc */
    --ipfps-veil-1: rgba(255, 255, 255, 0.42);  /* vignette : vers le BLANC */
    --ipfps-veil-2: rgba(255, 255, 255, 0.30);
    --ipfps-veil-3: rgba(255, 255, 255, 0.16);
    --ipfps-shadow-name: rgba(15, 23, 42, 0.16);
    --ipfps-shadow-name-2: rgba(15, 23, 42, 0.10);
    --ipfps-shadow-logo: rgba(15, 23, 42, 0.18);
    --ipfps-shadow-text: rgba(15, 23, 42, 0.10);
    --ipfps-photo-ring: rgba(15, 23, 42, 0.10);

    position: relative;
    overflow: hidden;
    min-height: 78dvh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start; /* contenu ancré en haut → logo visible en entier */
    border-radius: var(--radius-lg);
    padding: clamp(28px, 7vh, 90px) var(--space-3) var(--space-5);
    /* repli si WebGL indisponible : dégradé clair, assorti au shader */
    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--ipfps-elec) 6%, #fff),
            color-mix(in srgb, var(--ipfps-elec) 14%, #fff));
}

/* Thème SOMBRE : valeurs d'origine de la vitrine, reprises à l'identique. */
[data-theme="dark"] .ipfps-hero {
    --ipfps-ink: var(--text-dark);
    --ipfps-veil-1: rgba(0, 0, 0, 0.34);
    --ipfps-veil-2: rgba(0, 0, 0, 0.28);
    --ipfps-veil-3: rgba(0, 0, 0, 0.10);
    --ipfps-shadow-name: rgba(0, 0, 0, 0.55);
    --ipfps-shadow-name-2: rgba(0, 0, 0, 0.35);
    --ipfps-shadow-logo: rgba(0, 0, 0, 0.45);
    --ipfps-shadow-text: rgba(0, 0, 0, 0.5);
    --ipfps-photo-ring: rgba(255, 255, 255, 0.10);

    background:
        linear-gradient(135deg,
            color-mix(in srgb, var(--ipfps-elec) 22%, #000),
            color-mix(in srgb, var(--ipfps-elec) 45%, #000));
}
.ipfps-shader {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    display: block;
    z-index: 0;
}
/* Voile de contraste (vignette) au-dessus du shader, sous le contenu : améliore
   la lisibilité du texte sur l'animation sans en masquer les couleurs. */
.ipfps-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background:
        radial-gradient(130% 90% at 50% 38%, transparent 42%, var(--ipfps-veil-1) 100%),
        linear-gradient(to bottom, var(--ipfps-veil-3), transparent 30%, var(--ipfps-veil-2));
}
.ipfps-content {
    position: relative;
    z-index: 1;
    width: 100%;
    max-width: 920px;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
}

/* Boutons plein écran (coin haut-droit, au-dessus du shader) */
.ipfps-fs {
    position: absolute;
    top: var(--space-3);
    right: var(--space-3);
    z-index: 2;
    display: flex;
    gap: 8px;
}

/* Logo de l'école en tête, avec animation « wave » (balancement continu) */
.ipfps-logo {
    margin-top: 0; /* ancré dans la carte (plus de marge négative qui débordait) */
    margin-bottom: clamp(8px, 2vh, 20px);
    animation: ipfpsLogoWave 3.2s ease-in-out infinite;
    transform-origin: 50% 90%;
}
.ipfps-logo img {
    width: clamp(120px, 18vw, 200px); /* encore agrandi */
    height: auto;
    display: block;
    filter: drop-shadow(0 6px 18px var(--ipfps-shadow-logo));
}
@keyframes ipfpsLogoWave {
    0%, 100% { transform: translateY(0) rotate(-3.5deg); }
    50%      { transform: translateY(-10px) rotate(3.5deg); }
}

/* Nom : plus grand que la photo, monospace, accent vert + lisibilité sur fond sombre */
.ipfps-name {
    margin: 0;
    font-family: var(--font-mono);
    font-weight: 800;
    line-height: 0.82;
    letter-spacing: -0.045em;
    text-transform: uppercase;
    color: var(--ipfps-accent);
    text-shadow: 0 2px 30px var(--ipfps-shadow-name), 0 0 2px var(--ipfps-shadow-name-2);
    max-width: 100%;
}
/* nowrap pour garder chaque prénom/nom sur une ligne ; taille bornée pour que
   même un nom long tienne dans la largeur utile (sidebar comprise) sans coupure. */
.ipfps-line { display: block; white-space: nowrap; max-width: 100%; }
.ipfps-letter {
    display: inline-block;
    font-size: clamp(2.2rem, 8vw, 8.5rem);
    /* fade-in (opacité/flou) + wave continue (translation) : 2 propriétés
       distinctes → pas de conflit d'animation. */
    animation:
        ipfpsLetterIn 0.5s ease-out both,
        ipfpsWave 2.6s ease-in-out infinite;
    animation-delay: calc(var(--i) * 55ms), calc(var(--i) * 110ms);
}
@keyframes ipfpsLetterIn {
    from { opacity: 0; filter: blur(8px); }
    to   { opacity: 1; filter: blur(0); }
}
@keyframes ipfpsWave {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-0.13em); }
}

/* Photo : sous le nom, descendue, plus petite que le nom */
.ipfps-photo { margin-top: clamp(18px, 4vh, 48px); }
.ipfps-photo-inner {
    width: clamp(64px, 10vw, 112px);
    height: clamp(108px, 17vw, 188px);
    border-radius: 999px;
    overflow: hidden;
    box-shadow: var(--shadow-hover), 0 0 0 3px var(--ipfps-photo-ring);
    background: color-mix(in srgb, var(--ipfps-logo-teal) 35%, transparent);
    animation: ipfpsPhotoIn 0.7s cubic-bezier(0.23, 1, 0.32, 1) both;
    animation-delay: 0.4s;
}
@keyframes ipfpsPhotoIn {
    from { opacity: 0; filter: blur(8px); }
    to   { opacity: 1; filter: blur(0); }
}
.ipfps-photo-inner img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ipfps-initials {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    font-family: var(--font-head);
    font-weight: 700;
    font-size: clamp(26px, 5vw, 52px);
    color: var(--text-dark);
    background: color-mix(in srgb, var(--ipfps-logo-teal) 45%, #000);
}
@media (hover: hover) and (pointer: fine) {
    .ipfps-photo-inner { transition: transform 0.25s cubic-bezier(0.23, 1, 0.32, 1); }
    .ipfps-photo-inner:hover { transform: scale(1.06); }
}
.ipfps-tagline {
    margin-top: clamp(20px, 5vh, 56px);
    font-family: var(--font-body);
    font-size: var(--fs-lg);
    letter-spacing: 0.02em;
    color: var(--ipfps-ink);
    opacity: 0.82;
    text-shadow: 0 1px 12px var(--ipfps-shadow-text);
}

/* --- Bouton « partager en story » ---------------------------------------
   Réservé au TACTILE : sur ordinateur, le partage de fichiers n'aboutit qu'à un
   téléchargement — sans intérêt ici. Il reste donc caché par défaut, et n'est
   révélé (`[hidden]` retiré par la vue) que sur un appareil qui sait réellement
   partager un fichier ET qui est tactile. Les deux gardes sont volontaires :
   le JS teste la CAPACITÉ, le CSS teste le TYPE d'appareil. */
.ipfps-share { display: none; }

@media (hover: none) and (pointer: coarse) {
    .ipfps-share:not([hidden]) {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        margin-top: clamp(18px, 3.5vh, 34px);
        width: 52px;
        height: 52px;
        border-radius: 999px;
        cursor: pointer;
        color: var(--ipfps-ink);
        background: color-mix(in srgb, var(--surface) 62%, transparent);
        border: 1px solid color-mix(in srgb, var(--ipfps-ink) 18%, transparent);
        -webkit-backdrop-filter: blur(8px);
        backdrop-filter: blur(8px);
        box-shadow: var(--shadow);
        transition: transform .15s ease, opacity .15s ease;
    }
    .ipfps-share .ic { width: 22px; height: 22px; }
    .ipfps-share:active { transform: scale(0.92); }
    /* Pendant la composition de l'image (~1 s) : bouton inerte et estompé. */
    .ipfps-share.is-busy { opacity: 0.5; pointer-events: none; }
}
/* En plein écran : occuper tout l'écran, sans coins arrondis, et re-centrer
   verticalement (composition équilibrée sur toute la hauteur). */
.ipfps-hero:fullscreen { border-radius: 0; min-height: 100vh; justify-content: center; padding-top: var(--space-5); }
.ipfps-hero:-webkit-full-screen { border-radius: 0; min-height: 100vh; justify-content: center; padding-top: var(--space-5); }
/* Repli iOS : ancrage 4 bords AUTONOME (spécificité 0,2,0 → insensible à toute
   règle .ipfps-hero, y compris les media queries standalone) ; jamais de
   100vh/100dvh ; contenu + boutons décalés sous la statut bar (safe-area). */
.ipfps-hero.is-fs-fallback {
    position: fixed;
    top: 0; left: 0; right: 0; bottom: 0;
    width: 100%; height: 100%;
    min-height: 0; max-height: none;
    margin: 0;
    border-radius: 0;
    display: flex; flex-direction: column; align-items: center;
    justify-content: center;
    padding-top: calc(var(--space-5) + env(safe-area-inset-top, 0px));
    padding-bottom: max(var(--space-2), env(safe-area-inset-bottom, 0px));
}
.ipfps-hero.is-fs-fallback .ipfps-fs { top: calc(var(--space-3) + env(safe-area-inset-top, 0px)); }

/* --- Responsive vitrine IPFPS --- */
@media (max-width: 768px) {
    .ipfps-hero {
        /* Comme .gal : hauteur EXACTE (pas min-) calée sur la hauteur visible
           (viewport − topbar 60 − paddings contenu) → la page ne défile JAMAIS.
           Une page défilable déclenche le décalage iOS des position:fixed
           (bande en bas au plein écran de repli). */
        height: calc(100dvh - 60px - 18px - max(18px, env(safe-area-inset-bottom)));
        min-height: 0;
        border-radius: var(--radius);
        padding: var(--space-4) var(--space-2);
    }
    .ipfps-logo img { width: clamp(110px, 30vw, 168px); }
    .ipfps-fs { top: var(--space-2); right: var(--space-2); }

    /* Photo de profil : les clamp() de bureau retombaient sur leur MINIMUM en
       mobile (10vw/17vw → 64×108px), la rendant minuscule alors que la place ne
       manque pas. On repart des vw avec un plancher plus généreux, à ratio égal
       (≈ 0.60, la pilule du bureau). */
    .ipfps-photo-inner {
        width: clamp(88px, 24vw, 112px);
        height: clamp(148px, 40vw, 188px);
    }

    /* Tagline : descendue (elle chevauchait les vagues du shader) et en gras,
       pour tenir face au fond animé. */
    .ipfps-tagline {
        margin-top: clamp(28px, 6vh, 64px);
        font-weight: 700;
        opacity: 0.9;
    }
}
@media (max-width: 480px) {
    .ipfps-hero {
        height: calc(100dvh - 60px - 16px - max(18px, env(safe-area-inset-bottom)));
        min-height: 0;
    }
    .ipfps-photo { margin-top: clamp(14px, 3vh, 28px); }
}

@media (prefers-reduced-motion: reduce) {
    .ipfps-letter,
    .ipfps-photo-inner,
    .ipfps-logo {
        animation: none;
        opacity: 1;
        filter: none;
        transform: none;
    }
}

/* --- Top Étudiants (Stagiaire) : vidéo de fond (loop, muette) --- */
.ts-page {
    position: relative;
    isolation: isolate;
    min-height: calc(100dvh - 140px);
    border-radius: var(--radius-lg);
    overflow: hidden;
    /* repli (vidéo masquée / non chargée) */
    background: linear-gradient(135deg, #0b1f29, #10303a);
}
.ts-video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 0;
    pointer-events: none;
    opacity: 0.4; /* vidéo à 40 % (atténuée) → contenu plus lisible */
}
/* Voile assombri pour garder le texte/cartes lisibles par-dessus la vidéo */
.ts-overlay {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0.45), rgba(0, 0, 0, 0.22) 40%, rgba(0, 0, 0, 0.55));
}
.ts-fg {
    position: relative;
    z-index: 1;
    padding: var(--space-4) var(--space-3);
    color: var(--text-dark);
}
.ts-fg .page-head h1 { color: var(--text-dark); }
.ts-fg .page-head p,
.ts-fg .cgal-hint { color: rgba(230, 237, 247, 0.82); }
@media (prefers-reduced-motion: reduce) {
    .ts-video { display: none; } /* pas de vidéo animée en mouvement réduit (repli dégradé) */
}

/* Bascule de thème : lune (clair → clic = sombre) / soleil (sombre → clic = clair).
   Visibilité pilotée par data-theme → bon état dès le <head>, aucun flash, sans JS. */
#themeIcon { display: inline-flex; align-items: center; }
#themeIcon .ic-sun { display: none; }
html[data-theme="dark"] #themeIcon .ic-moon { display: none; }
html[data-theme="dark"] #themeIcon .ic-sun { display: inline-flex; }

/* =====================================================================
   Mobile natif — correction viewport (dvh), encoches (safe-area), cibles
   tactiles ≥44px, tables ancrées, actions plein-pouce. Additif : aucune
   classe/markup/logique touché ; ne fait que surcharger/affiner l'existant
   (l'ordre source garantit la priorité sur les règles mobiles plus haut).
   ===================================================================== */

/* --- Hauteur réelle du viewport mobile (évite le saut de la barre d'URL) --- */
.app { min-height: 100vh; min-height: 100dvh; }
.auth-wrap { min-height: 100vh; min-height: 100dvh; }

@media (max-width: 768px) {
    /* --- Zones sûres (encoche / indicateur d'accueil iOS/Android) --- */
    .topbar {
        padding-left: max(12px, env(safe-area-inset-left));
        padding-right: max(12px, env(safe-area-inset-right));
    }
    .content {
        padding-left: max(14px, env(safe-area-inset-left));
        padding-right: max(14px, env(safe-area-inset-right));
        padding-bottom: max(18px, env(safe-area-inset-bottom));
    }
    .sidebar { padding-bottom: max(18px, env(safe-area-inset-bottom)); }

    /* --- Cibles tactiles : minimum confortable 44×44 (recommandation WCAG/iOS) --- */
    .menu-toggle { min-width: 44px; min-height: 44px; padding: 0 12px; }
    .theme-toggle { width: 44px; height: 44px; font-size: 17px; }
    .topbar .thumb, .topbar .avatar { width: 40px; height: 40px; font-size: 14px; }
    .logout-btn.btn.btn-light { width: 44px; height: 44px; }

    /* Navigation latérale : lignes plus hautes = plus faciles au pouce. */
    .sidebar nav a { padding: 13px 20px; margin: 2px 10px; font-size: 15px; }

    /* Boutons d'action dans les tableaux : plus de surface tactile + espacement. */
    table.data td.actions .btn-sm,
    .btn-icon.btn-sm { min-height: 38px; padding: 8px 12px; }
    table.data td.actions > * + * { margin-left: var(--space-3); }

    /* --- Tables larges (non transformées en cartes) : 1ʳᵉ colonne ancrée ---
       La colonne d'identité reste visible pendant le défilement horizontal.
       On exclut .pay-table (bug-hardened) et les .data-cards (déjà en cartes). */
    table.data:not(.pay-table):not(.data-cards) th:first-child,
    table.data:not(.pay-table):not(.data-cards) td:first-child {
        position: sticky;
        left: 0;
        z-index: 1;
        box-shadow: 1px 0 0 var(--border);
    }
    table.data:not(.pay-table):not(.data-cards) td:first-child { background: var(--surface); }
    table.data:not(.pay-table):not(.data-cards) th:first-child { background: var(--th-bg); }
    table.data:not(.pay-table):not(.data-cards) tbody tr:hover td:first-child { background: var(--hover); }

    /* --- Barre d'outils : actions en pleine largeur, faciles au pouce --- */
    .toolbar > div:not(.page-head) { width: 100%; }
    .toolbar > div:not(.page-head) > .btn,
    .toolbar > div:not(.page-head) > form { flex: 1 1 auto; }
    .toolbar > div:not(.page-head) > form > .btn { width: 100%; }
}

@media (max-width: 480px) {
    /* Actions de formulaire empilées et pleine largeur (pouce-friendly). */
    .form-actions { flex-direction: column; align-items: stretch; }
    .form-actions > .btn,
    .form-actions > form,
    .form-actions > form > .btn { width: 100%; }

    /* Barre d'outils : titre + actions empilés proprement. */
    .toolbar { gap: var(--space-3); }
    .toolbar > div:not(.page-head) { flex-direction: column; align-items: stretch; }
    .toolbar > div:not(.page-head) > .btn,
    .toolbar > div:not(.page-head) > form,
    .toolbar > div:not(.page-head) > form > .btn { width: 100%; }

    /* Boutons globaux : un cran plus grands pour le tactile sur très petit écran. */
    .btn { padding: 12px 18px; }

    /* Page Paiements : Excel + Stats rappels côte à côte (secondaires),
       « Envoyer un rappel » (action principale) garde la pleine largeur.
       Après la règle générique .toolbar > div:not(.page-head) pour la battre. */
    .toolbar > div.pay-actions { flex-direction: row; flex-wrap: wrap; }
    .toolbar > div.pay-actions > .btn { flex: 1 1 40%; width: auto; }
    .toolbar > div.pay-actions > form { flex: 1 1 100%; }
}

/* =====================================================================
   Notifications « toast » (remplacent la barre flash statique)
   Rendu par flash.php (messages session inchangés) + animé par ipfpsToasts().
   Coin haut-droit (desktop) / bas (mobile), auto-fermeture 4s + barre de
   progression, fermeture manuelle, empilage, tokens AA, clair/sombre,
   prefers-reduced-motion. z-index au-dessus de la modale (40) et navbar (30).
   ===================================================================== */
.toast-container {
    position: fixed;
    top: calc(74px + env(safe-area-inset-top, 0px));
    right: 16px;
    z-index: 60;
    display: flex;
    flex-direction: column;
    gap: 10px;
    width: min(380px, calc(100vw - 32px));
    pointer-events: none;
}
.toast {
    pointer-events: auto;
    position: relative;
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 13px 14px;
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 4px solid var(--muted);
    box-shadow: var(--shadow-hover);
    color: var(--text);
    overflow: hidden;
    opacity: 0;
    transform: translateX(18px);
    transition: opacity .28s ease, transform .28s cubic-bezier(0.23, 1, 0.32, 1);
}
.toast--in { opacity: 1; transform: none; }
.toast--out { opacity: 0; transform: translateX(18px); }
.toast-ic { width: 20px; height: 20px; flex: 0 0 auto; margin-top: 1px; }
.toast-msg { flex: 1 1 auto; min-width: 0; font-size: 14px; line-height: 1.4; word-break: break-word; }
.toast-close {
    flex: 0 0 auto; background: none; border: none; cursor: pointer;
    color: var(--muted); padding: 3px; border-radius: 6px; line-height: 0;
    transition: color .15s, background .15s;
}
.toast-close:hover { color: var(--text); background: var(--hover); }
.toast-close .ic { width: 16px; height: 16px; }
.toast-progress {
    position: absolute; left: 0; bottom: 0; height: 3px; width: 100%;
    transform-origin: left center; transform: scaleX(1);
    background: currentColor; opacity: .55;
}
/* Types : accent (bordure gauche, icône, barre) via tokens contrastés AA. */
.toast--success { border-left-color: var(--success); }
.toast--success .toast-ic, .toast--success .toast-progress { color: var(--success); }
.toast--error { border-left-color: var(--danger); }
.toast--error .toast-ic, .toast--error .toast-progress { color: var(--danger); }
.toast--warning { border-left-color: var(--warning); }
.toast--warning .toast-ic { color: var(--warning-text); }
.toast--warning .toast-progress { color: var(--warning); }
.toast--info { border-left-color: var(--primary); }
.toast--info .toast-ic, .toast--info .toast-progress { color: var(--primary); }

@media (max-width: 560px) {
    .toast-container {
        top: auto; left: 12px; right: 12px; width: auto;
        bottom: calc(12px + env(safe-area-inset-bottom, 0px));
    }
    .toast, .toast--out { transform: translateY(18px); }
    .toast--in { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
    .toast { transition: opacity .01s linear; opacity: 1; transform: none; }
    .toast--out { opacity: 0; }
    .toast-progress { display: none; }
}

/* =====================================================================
   Squelettes de chargement (shimmer) — primitives + application page-load.
   Shimmer = balayage gauche→droite via tokens (aucune couleur en dur).
   prefers-reduced-motion : bloc gris statique (le pré-masquage n'est même pas
   activé — voir <head> app.php). Clair/sombre via tokens.
   ===================================================================== */
.skel {
    position: relative;
    display: block;
    background: var(--hover);
    border-radius: 6px;
    overflow: hidden;
}
.skel::after {
    content: "";
    position: absolute; inset: 0;
    transform: translateX(-100%);
    background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--surface) 70%, transparent), transparent);
    animation: skelShimmer 1.3s ease-in-out infinite;
}
@keyframes skelShimmer { 100% { transform: translateX(100%); } }
.skel-line { height: 12px; width: 100%; }
.skel-line--lg { height: 22px; border-radius: 8px; }

/* Squelette de tableau : tbody injecté par le JS, calqué sur le <thead>. */
table.data tbody.skel-body td { border-bottom: 1px solid var(--border); padding: 14px; }
table.data tbody.skel-body .skel-line { margin: 1px 0; }

/* Squelette KPI : overlay dans la carte .stat (déjà position:relative/overflow:hidden). */
.skel-stat { position: absolute; inset: 20px; display: flex; flex-direction: column; gap: 12px; z-index: 2; }

/* Pré-masquage du contenu réel pendant <html class="is-loading"> (posé en head). */
html.is-loading .table-wrap table.data > tbody:not(.skel-body) { visibility: hidden; }
html.is-loading .grid .stat .label,
html.is-loading .grid .stat .value,
html.is-loading .grid .stat .soon { visibility: hidden; }

/* Filet de sécurité : révélation auto après 4s si le JS ne s'exécute pas. */
html.is-loading .table-wrap table.data > tbody:not(.skel-body),
html.is-loading .grid .stat .label,
html.is-loading .grid .stat .value {
    animation: skelFailsafe 0s linear 4s forwards;
}
@keyframes skelFailsafe { to { visibility: visible; } }

/* Révélation douce au chargement (<html class="is-loaded">). */
html.is-loaded .table-wrap table.data > tbody:not(.skel-body),
html.is-loaded .grid .stat .label,
html.is-loaded .grid .stat .value { animation: skelFadeIn .32s ease both; }
@keyframes skelFadeIn { from { opacity: 0; } to { opacity: 1; } }

@media (prefers-reduced-motion: reduce) {
    .skel::after { animation: none; }
}

/* État « en cours » d'un <select> async (cascade, cellule paiement) :
   pulsation discrète — ne touche PAS l'image de fond (flèche custom préservée). */
select.form-control.is-loading,
.cell-select.is-loading {
    pointer-events: none;
    opacity: .6;
    animation: skelPulse 1s ease-in-out infinite;
}
@keyframes skelPulse { 0%, 100% { opacity: .5; } 50% { opacity: .85; } }

/* =====================================================================
   Barre de progression de navigation (ressenti de chargement MPA).
   Affichée au clic lien interne / submit POST, masquée à l'affichage de la
   nouvelle page. z-index au-dessus des toasts.
   ===================================================================== */
.nav-progress {
    position: fixed; top: 0; left: 0; right: 0; height: 3px;
    z-index: 70; pointer-events: none;
    opacity: 0; transition: opacity .2s ease;
}
.nav-progress.is-active { opacity: 1; }
.nav-progress-bar {
    display: block; height: 100%; width: 0;
    background: linear-gradient(90deg, var(--primary-blue), var(--primary-green));
    box-shadow: 0 0 8px color-mix(in srgb, var(--primary) 55%, transparent);
    transition: width .24s ease;
}
@media (prefers-reduced-motion: reduce) {
    .nav-progress, .nav-progress-bar { transition: none; }
}

/* ============================================================
   Interrupteur (toggle switch) — checkbox stylé, tokens, clair/sombre.
   Utilisé page Admin « Gestion des accès classes » (et réutilisable).
   ============================================================ */
.switch {
    position: relative;
    display: inline-flex;
    align-items: center;
    width: 46px;
    height: 26px;
    flex: 0 0 auto;
    cursor: pointer;
    vertical-align: middle;
}
.switch-input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    opacity: 0;
    cursor: pointer;
}
.switch-slider {
    position: absolute;
    inset: 0;
    border-radius: 999px;
    background: var(--border);
    border: 1px solid var(--border);
    transition: background .2s ease, border-color .2s ease;
}
.switch-slider::before {
    content: "";
    position: absolute;
    top: 2px;
    left: 2px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    background: #fff; /* pastille toujours blanche : lisible sur piste verte ET grise, clair/sombre */
    box-shadow: 0 1px 3px rgba(15, 23, 42, .35);
    transition: transform .2s cubic-bezier(0.23, 1, 0.32, 1);
}
.switch-input:checked + .switch-slider {
    background: var(--primary-green);
    border-color: var(--primary-green);
}
.switch-input:checked + .switch-slider::before { transform: translateX(20px); }
.switch-input:focus-visible + .switch-slider { outline: 2px solid var(--primary); outline-offset: 2px; }
.switch-input:disabled + .switch-slider { opacity: .6; cursor: not-allowed; }
@media (prefers-reduced-motion: reduce) {
    .switch-slider, .switch-slider::before { transition: none; }
}

/* ============================================================
   Pages d'erreur (404 / 500) — layout auth, centré, tokens
   ============================================================ */
.err-page {
    min-height: 100dvh;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-5);
    background: var(--bg-light);
}
.err-card {
    width: 100%;
    max-width: 440px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: var(--space-6) var(--space-5);
    text-align: center;
}
.err-icon {
    width: 56px;
    height: 56px;
    color: var(--primary-blue);
    margin-bottom: var(--space-3);
}
.err-icon--danger { color: var(--danger); }
.err-code {
    font-family: var(--font-head);
    font-size: var(--fs-3xl);
    font-weight: 600;
    line-height: var(--lh-tight);
    color: var(--muted);
    margin: 0;
}
.err-title {
    font-size: var(--fs-xl);
    margin: var(--space-2) 0 var(--space-3);
}
.err-text {
    color: var(--muted);
    line-height: var(--lh-body);
    margin: 0 0 var(--space-5);
}
.err-card .btn { justify-content: center; }

/* ============================================================
   Tableau de bord financier — barre de recouvrement (lecture seule)
   ============================================================ */
.fin-bar {
    display: block;
    width: 100%;
    min-width: 90px;
    height: 8px;
    border-radius: 999px;
    background: var(--hover);
    border: 1px solid var(--border);
    overflow: hidden;
}
.fin-bar-fill {
    display: block;
    height: 100%;
    border-radius: 999px;
    background: var(--primary-green);
    transition: width .3s cubic-bezier(0.23, 1, 0.32, 1);
}
.fin-rate {
    display: inline-block;
    margin-top: 4px;
    font-size: var(--fs-xs);
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}
.fin-sort {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: var(--space-3);
    font-size: var(--fs-sm);
    color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
    .fin-bar-fill { transition: none; }
}

/* ---- Filtres (Année → Filière → Classe) ---- */
.fin-filters {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--space-4);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}
.fin-active-year { font-size: var(--fs-lg); font-weight: 800; color: var(--primary); }
.fin-filters-form {
    display: flex;
    align-items: flex-end;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin: 0;
}
.fin-field { display: flex; flex-direction: column; gap: 4px; position: relative; }
.fin-field label { font-size: var(--fs-xs); font-weight: 600; color: var(--muted); }
.fin-field .form-control { min-width: 180px; }
/* Hint hors flux : ne grossit pas la colonne → les champs restent alignés (flex-end). */
.fin-field .hint {
    position: absolute;
    top: 100%;
    left: 0;
    margin: 4px 0 0;
    font-size: var(--fs-xs);
    color: var(--muted);
    white-space: nowrap;
}
.fin-reset { align-self: flex-end; }

/* ---- Bloc carte générique ---- */
.fin-block { margin-top: var(--space-4); }
.fin-block-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.fin-block-head h3 { margin: 0; }

/* ---- Légende du graphique ---- */
.fin-legend { display: flex; gap: var(--space-3); flex-wrap: wrap; font-size: var(--fs-xs); color: var(--muted); }
.fin-legend-item { display: inline-flex; align-items: center; gap: 6px; }
.fin-key { width: 12px; height: 12px; border-radius: 3px; display: inline-block; }
.fin-key--att { background: var(--hover); border: 1px solid var(--border); }
.fin-key--enc { background: var(--primary-green); }
.fin-key--fut { background: repeating-linear-gradient(45deg, var(--hover), var(--hover) 3px, var(--border) 3px, var(--border) 6px); }

/* ---- Graphique mensuel (barres verticales, rendu serveur, sans JS) ---- */
.fin-chart {
    display: flex;
    align-items: flex-end;
    gap: var(--space-2);
    margin-top: var(--space-4);
    padding-top: var(--space-3);
    min-height: 220px;
    overflow-x: auto;
}
.fin-col {
    flex: 1 1 0;
    min-width: 52px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
}
.fin-col-bar {
    width: 100%;
    height: 180px;
    display: flex;
    align-items: flex-end;
    justify-content: center;
}
.fin-track {
    width: 60%;
    max-width: 34px;
    min-height: 4px;
    background: var(--hover);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    display: flex;
    align-items: flex-end;
    overflow: hidden;
    position: relative;
}
.fin-fill {
    width: 100%;
    background: var(--primary-green);
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
    transition: height .35s cubic-bezier(0.23, 1, 0.32, 1);
}
.fin-col.is-future .fin-track {
    background: repeating-linear-gradient(45deg, var(--hover), var(--hover) 4px, var(--border) 4px, var(--border) 8px);
}
.fin-col.is-future .fin-fill { opacity: .6; }
.fin-col-val {
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.fin-col.is-future .fin-col-val { color: var(--muted); font-weight: 600; }
.fin-col-label { font-size: var(--fs-xs); color: var(--muted); text-align: center; }

/* ---- Étiquettes d'état (mois échu / à venir) ---- */
.fin-tag {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 600;
}
.fin-tag--due { background: color-mix(in srgb, var(--primary-green) 14%, transparent); color: var(--success); }
.fin-tag--fut { background: var(--hover); color: var(--muted); }

.fin-tag--cash { background: color-mix(in srgb, var(--primary-blue) 14%, transparent); color: var(--primary); }
.fin-tag--never { background: color-mix(in srgb, var(--warning) 20%, transparent); color: var(--warning-text); }

/* ---- « Reste » mensuel cliquable → modale « qui doit quoi » ---- */
.fin-reste-btn {
    background: none; border: 0; padding: 0; margin: 0;
    font: inherit; font-weight: 700; color: var(--danger);
    text-decoration: underline dotted; text-underline-offset: 3px;
    cursor: pointer; border-radius: 4px;
}
.fin-reste-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
    .fin-reste-btn:hover { text-decoration-style: solid; }
}
.st-modal-box.fin-md-box { width: min(560px, 96vw); }
.fin-md-list {
    max-height: 55vh; overflow: auto;
    border: 1px solid var(--border); border-radius: var(--radius-sm);
    margin-top: var(--space-2);
}
.fin-md-row {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-3);
    padding: 10px 12px; text-decoration: none; color: inherit;
    border-bottom: 1px solid var(--border);
    min-height: 48px; /* cible tactile */
}
.fin-md-row:last-child { border-bottom: 0; }
@media (hover: hover) and (pointer: fine) {
    .fin-md-row:hover { background: var(--hover); }
    .fin-md-row:hover strong:first-child { color: var(--primary); }
}
.fin-md-who { display: flex; flex-direction: column; gap: 2px; min-width: 0; text-align: left; }
.fin-md-who small { color: var(--muted); font-size: var(--fs-xs); }
.fin-md-amt { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 auto; }
.fin-md-amt strong { color: var(--danger); }
.fin-md-amt small { color: var(--muted); font-size: var(--fs-xs); white-space: nowrap; }
.fin-md-total {
    display: flex; justify-content: space-between; align-items: center;
    margin-top: var(--space-3); padding-top: var(--space-3);
    border-top: 2px solid var(--border); font-size: var(--fs-md, 15px);
}
.fin-md-total strong { color: var(--danger); font-size: var(--fs-lg); }
.fin-md-actions { margin-top: var(--space-3); }
.fin-md-actions .hint { margin-top: 6px; text-align: center; }

/* ---- Variation vs l'année précédente (posée dans le .soon d'un .stat) ---- */
.fin-delta { font-weight: 700; }
.fin-delta.is-up   { color: var(--success); background: color-mix(in srgb, var(--primary-green) 12%, transparent); }
.fin-delta.is-down { color: var(--danger);  background: color-mix(in srgb, var(--danger) 12%, transparent); }
.fin-delta.is-flat { color: var(--muted); }
.fin-note { margin: var(--space-3) 0 0; }

/* ---- Deux cartes côte à côte (Objectif annuel / Stagiaires) ---- */
.fin-duo {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
    gap: var(--space-4);
}
.fin-duo > .fin-block { margin-top: var(--space-4); }

/* ---- Objectif de l'année ---- */
.fin-bar--lg { height: 14px; margin: var(--space-3) 0; }
.fin-goal {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
    gap: var(--space-3);
}
.fin-goal-item { display: flex; flex-direction: column; gap: 2px; }
.fin-goal-label { font-size: var(--fs-xs); color: var(--muted); font-weight: 600; }
.fin-goal-item strong { font-size: var(--fs-lg); font-variant-numeric: tabular-nums; }
.fin-goal-ok { color: var(--success); }
.fin-goal-todo { color: var(--warning-text); }

/* ---- Segmentation des stagiaires (recouvrement en têtes) ---- */
.fin-seg {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--space-3);
    margin: var(--space-3) 0 var(--space-4);
}
.fin-seg-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: var(--space-3) var(--space-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--hover);
    text-align: center;
}
.fin-seg-val { font-family: var(--font-head); font-size: var(--fs-2xl); font-weight: 800; line-height: 1; }
.fin-seg-label { font-size: var(--fs-xs); color: var(--muted); font-weight: 600; }
.fin-seg--ok   .fin-seg-val { color: var(--success); }
.fin-seg--warn .fin-seg-val { color: var(--warning-text); }
.fin-seg--bad  .fin-seg-val { color: var(--danger); }

/* ---- Split Inscription / Mensualités ---- */
.fin-split { display: flex; flex-direction: column; gap: var(--space-3); }
.fin-split-row {
    display: grid;
    grid-template-columns: 100px 1fr auto;
    align-items: center;
    gap: var(--space-3);
}
.fin-split-label { font-size: var(--fs-sm); font-weight: 700; }
.fin-split-row .fin-rate { margin-top: 0; white-space: nowrap; }

/* ---- Niveau de risque (balance âgée + liste des impayés) ---- */
.fin-risk {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: var(--fs-xs);
    font-weight: 700;
    white-space: nowrap;
}
.fin-risk--low  { background: color-mix(in srgb, var(--primary-green) 14%, transparent); color: var(--success); }
.fin-risk--mid  { background: color-mix(in srgb, var(--warning) 20%, transparent); color: var(--warning-text); }
.fin-risk--high { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
.fin-risk--crit { background: var(--danger); color: #fff; }

/* ---- Trésorerie : mêmes barres, couleur de marque distincte du recouvrement ---- */
.fin-col--cash .fin-fill { background: var(--primary-blue); }
[data-theme="dark"] .fin-col--cash .fin-fill { background: var(--primary); }

@media (prefers-reduced-motion: reduce) {
    .fin-fill { transition: none; }
}
@media (max-width: 768px) {
    .fin-filters-form { width: 100%; }
    .fin-field { flex: 1 1 140px; }
    .fin-field .form-control { min-width: 0; width: 100%; }
    .fin-col { min-width: 44px; }
    .fin-seg { grid-template-columns: 1fr; }
    .fin-split-row { grid-template-columns: 1fr; gap: 4px; }
    .fin-split-row .fin-rate { white-space: normal; }
}

/* =====================================================================
   Galerie (module Galerie)
   - Gestion Admin : grille de vignettes (.gal-admin-*)
   - Consultation : mur « masonry » draggable (.gal / .gal-canvas / .gal-item)
     + lightbox (.gal-lightbox). Tout via tokens ; fond sombre constant
     (var(--sidebar-bg)) pour mettre les photos en valeur.
   ===================================================================== */

/* --- Gestion Admin --- */
.gal-admin-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: var(--space-3);
}
.gal-admin-item {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    box-shadow: var(--shadow);
    display: flex;
    flex-direction: column;
}
.gal-admin-item img {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    display: block;
    background: var(--hover);
}
.gal-admin-titre { padding: 8px 10px 0; font-size: var(--fs-sm); color: var(--text); font-weight: 600; }
.gal-admin-actions { display: flex; gap: 6px; padding: 10px; flex-wrap: wrap; margin-top: auto; }
/* Barre d'outils (statut + activer/désactiver + aperçu) */
.gal-admin-tools { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.gal-status {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 6px 12px;
    border-radius: 999px;
    font-size: var(--fs-sm);
    font-weight: 600;
    border: 1px solid var(--border);
}
.gal-status::before {
    content: "";
    width: 8px; height: 8px;
    border-radius: 50%;
    background: currentColor;
}
.gal-status.is-on { color: var(--success); background: color-mix(in srgb, var(--success) 12%, var(--surface)); border-color: color-mix(in srgb, var(--success) 35%, var(--border)); }
.gal-status.is-off { color: var(--muted); background: var(--hover); }

/* --- Mur futuriste draggable (Stagiaire / aperçu) ---
   Panneau « HUD » sombre : grille animée, liserés néon, plein écran.
   Accent néon scopé (--gal-neon*) indépendant du thème clair/sombre. */
.gal {
    --gal-neon: #38bdf8;
    --gal-neon-2: #22d3ee;
    /* offset = topbar + paddings verticaux du .content → le panneau remplit la
       hauteur visible sans laisser de vide en bas. `.is-preview` compense en
       plus la barre d'outils admin au-dessus. Surchargé par breakpoint. */
    --gal-off: 116px;
    position: relative;
    overflow: hidden;
    height: calc(100dvh - var(--gal-off));
    min-height: 360px;
    border-radius: var(--radius-lg);
    background:
        radial-gradient(1200px 600px at 16% -12%, rgba(56, 189, 248, 0.16), transparent 60%),
        radial-gradient(900px 520px at 112% 118%, rgba(34, 211, 238, 0.14), transparent 55%),
        linear-gradient(160deg, #0b1220 0%, #0a0f1c 55%, #060a12 100%);
    border: 1px solid rgba(56, 189, 248, 0.22);
    box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55), inset 0 0 0 1px rgba(255, 255, 255, 0.02);
    cursor: grab;
    touch-action: none; /* drag 2D géré en JS */
    user-select: none;
    isolation: isolate;
}
/* grille de fond façon HUD */
.gal::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        linear-gradient(rgba(56, 189, 248, 0.06) 1px, transparent 1px),
        linear-gradient(90deg, rgba(56, 189, 248, 0.06) 1px, transparent 1px);
    background-size: 46px 46px;
    -webkit-mask-image: radial-gradient(130% 130% at 50% 0%, #000 28%, transparent 82%);
    mask-image: radial-gradient(130% 130% at 50% 0%, #000 28%, transparent 82%);
    animation: galGrid 24s linear infinite;
}
@keyframes galGrid { to { background-position: 46px 46px, 46px 46px; } }
.gal:active { cursor: grabbing; }
.gal.is-preview { --gal-off: 188px; } /* + barre d'outils « Aperçu » au-dessus */

/* Vrai masonry : colonnes CSS → photos empilées et serrées (pas de trous).
   Largeur explicite = cols × largeur de colonne + gouttières → le canvas
   draggable garde une taille connue. */
.gal-canvas {
    --gal-colw: clamp(170px, 23vw, 250px);
    --gal-colgap: 16px;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
    column-count: var(--gal-cols, 4);
    column-gap: var(--gal-colgap);
    width: calc(var(--gal-cols, 4) * var(--gal-colw) + (var(--gal-cols, 4) - 1) * var(--gal-colgap));
    padding: 28px;
    will-change: transform;
}
.gal-item {
    position: relative;
    display: block;
    width: 100%;
    margin: 0 0 var(--gal-colgap, 16px);
    break-inside: avoid;
    -webkit-column-break-inside: avoid;
    border-radius: 16px;
    overflow: hidden;
    background: #0e1626;
    border: 1px solid rgba(148, 163, 184, 0.14);
    box-shadow: 0 14px 34px rgba(0, 0, 0, 0.5);
    cursor: pointer;
    animation: galItemIn 0.6s var(--d, 0ms) cubic-bezier(0.18, 0.71, 0.11, 1) both;
}
/* liseré néon révélé au survol */
.gal-item::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    padding: 1px;
    background: linear-gradient(140deg, rgba(56, 189, 248, 0), rgba(56, 189, 248, 0.6), rgba(34, 211, 238, 0));
    -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    -webkit-mask-composite: xor;
    mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
    mask-composite: exclude;
    opacity: 0;
    transition: opacity 0.25s;
    pointer-events: none;
}
.gal-item img { display: block; width: 100%; height: auto; object-fit: cover; pointer-events: none; }
.gal-caption {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 26px 12px 12px;
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: 0.02em;
    color: #eaf6ff;
    background: linear-gradient(to top, rgba(3, 8, 18, 0.9), transparent);
    transition: transform 0.28s, opacity 0.28s;
}
@keyframes galItemIn {
    from { opacity: 0; transform: translateY(18px) scale(0.82); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}
@media (hover: hover) and (pointer: fine) {
    .gal-item { transition: transform 0.28s cubic-bezier(0.23, 1, 0.32, 1), box-shadow 0.28s; }
    .gal-item:hover {
        box-shadow: 0 22px 50px rgba(0, 0, 0, 0.6), 0 0 30px rgba(56, 189, 248, 0.35);
    }
    .gal-item:hover::after { opacity: 1; }
    /* légende révélée au survol (cachée par défaut sur appareils pointeur) */
    .gal-caption { transform: translateY(100%); opacity: 0; }
    .gal-item:hover .gal-caption { transform: translateY(0); opacity: 1; }
}

/* --- Barre d'outils en surimpression (titre + plein écran) --- */
.gal-bar {
    position: absolute;
    top: 14px; left: 14px; right: 14px;
    z-index: 3;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    pointer-events: none; /* laisse passer le drag, sauf sur le bouton */
}
.gal-bar-title {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 8px 15px;
    border-radius: 999px;
    background: rgba(8, 13, 24, 0.55);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(56, 189, 248, 0.25);
    color: #dff1ff;
    font-weight: 700;
    font-size: var(--fs-sm);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}
.gal-bar-title::before {
    content: "";
    width: 8px; height: 8px;
    border-radius: 50%;
    background: var(--gal-neon-2);
    box-shadow: 0 0 10px var(--gal-neon-2);
    animation: galPulse 1.8s ease-in-out infinite;
}
@keyframes galPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
.gal-fs {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    gap: 7px;
    padding: 9px 15px;
    border-radius: 999px;
    background: rgba(8, 13, 24, 0.6);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(56, 189, 248, 0.4);
    color: #eaf6ff;
    font-size: var(--fs-sm);
    font-weight: 600;
    cursor: pointer;
    transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
}
.gal-fs:hover {
    background: rgba(56, 189, 248, 0.22);
    box-shadow: 0 0 22px rgba(56, 189, 248, 0.4);
    transform: translateY(-1px);
}
.gal-fs .ic { width: 18px; height: 18px; }
.gal-fs .gal-fs-exit { display: none; }
.gal:fullscreen .gal-fs .gal-fs-enter,
.gal:-webkit-full-screen .gal-fs .gal-fs-enter { display: none; }
.gal:fullscreen .gal-fs .gal-fs-exit,
.gal:-webkit-full-screen .gal-fs .gal-fs-exit { display: inline-flex; }
.gal.is-fs-fallback .gal-fs .gal-fs-enter { display: none; }
.gal.is-fs-fallback .gal-fs .gal-fs-exit { display: inline-flex; }

/* Plein écran */
.gal:fullscreen,
.gal:-webkit-full-screen {
    width: 100vw;
    height: 100vh;
    border-radius: 0;
    border: none;
}
/* Repli iOS (voir .is-fs-fallback en fin de fichier) : mêmes styles qu'en natif ;
   la barre (titre + bouton Quitter) descend sous la statut bar iPhone. */
.gal.is-fs-fallback {
    /* taille = ancrage 4 bords du bloc .is-fs-fallback (pas de 100vh ici) */
    border-radius: 0;
    border: none;
}
.gal.is-fs-fallback .gal-bar { top: calc(14px + env(safe-area-inset-top, 0px)); }
.gal.is-fs-fallback .gal-hint { bottom: calc(16px + env(safe-area-inset-bottom, 0px)); }

.gal-hint {
    position: absolute;
    left: 50%;
    bottom: 16px;
    transform: translateX(-50%);
    z-index: 3;
    pointer-events: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: rgba(8, 13, 24, 0.55);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(148, 163, 184, 0.2);
    color: #cfe6fb;
    font-size: var(--fs-sm);
    padding: 7px 16px;
    border-radius: 999px;
    white-space: nowrap;
    max-width: calc(100% - 28px);
    overflow: hidden;
    text-overflow: ellipsis;
    animation: galHintFade 0.8s 4.5s forwards; /* s'efface après lecture */
}
@keyframes galHintFade { to { opacity: 0; visibility: hidden; } }

/* --- Lightbox futuriste --- */
.gal-lightbox {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: none;
    align-items: center;
    justify-content: center;
    padding: 30px;
    background: radial-gradient(1000px 600px at 50% 50%, rgba(8, 20, 40, 0.75), rgba(2, 4, 10, 0.94));
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}
.gal-lightbox.open { display: flex; animation: galLbIn 0.25s ease both; }
@keyframes galLbIn { from { opacity: 0; } to { opacity: 1; } }
.gal-lightbox img {
    max-width: 92vw;
    max-height: 86vh;
    border-radius: 14px;
    border: 1px solid rgba(56, 189, 248, 0.4);
    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.04), 0 30px 80px rgba(0, 0, 0, 0.7), 0 0 60px rgba(56, 189, 248, 0.25);
}
.gal-lightbox-close {
    position: absolute;
    top: 18px;
    right: 22px;
    width: 46px;
    height: 46px;
    font-size: 28px;
    line-height: 1;
    color: #eaf6ff;
    background: rgba(56, 189, 248, 0.14);
    border: 1px solid rgba(56, 189, 248, 0.4);
    border-radius: 50%;
    cursor: pointer;
    transition: background 0.2s, box-shadow 0.2s;
}
.gal-lightbox-close:hover {
    background: rgba(56, 189, 248, 0.3);
    box-shadow: 0 0 22px rgba(56, 189, 248, 0.5);
}
.gal-lightbox-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 52px;
    height: 52px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #eaf6ff;
    background: rgba(56, 189, 248, 0.14);
    border: 1px solid rgba(56, 189, 248, 0.4);
    border-radius: 50%;
    cursor: pointer;
    z-index: 2;
    transition: background 0.2s, box-shadow 0.2s, transform 0.2s;
}
.gal-lightbox-prev { left: 18px; }
.gal-lightbox-next { right: 18px; }
.gal-lightbox-nav .ic { width: 24px; height: 24px; }
.gal-lightbox-nav:hover {
    background: rgba(56, 189, 248, 0.3);
    box-shadow: 0 0 22px rgba(56, 189, 248, 0.5);
    transform: translateY(-50%) scale(1.06);
}
.gal-lightbox-nav[hidden] { display: none; }
/* Compteur « n / total » de la lightbox (pilule bas-centre). */
.gal-lightbox-counter {
    position: absolute;
    bottom: calc(18px + env(safe-area-inset-bottom, 0px));
    left: 50%;
    transform: translateX(-50%);
    z-index: 2;
    color: #eaf6ff;
    background: rgba(56, 189, 248, 0.14);
    border: 1px solid rgba(56, 189, 248, 0.4);
    padding: 5px 14px;
    border-radius: 999px;
    font-size: var(--fs-sm);
    font-weight: 600;
    letter-spacing: 0.03em;
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    pointer-events: none;
}
.gal-lightbox-counter[hidden] { display: none; }
/* Verrouille le défilement de la page pendant que la lightbox est ouverte. */
.gal-lb-lock, .gal-lb-lock body { overflow: hidden; }

@media (max-width: 768px) {
    .gal { --gal-off: 96px; }            /* topbar 60 + paddings .content 18×2 */
    .gal.is-preview { --gal-off: 168px; }
}
@media (max-width: 480px) {
    .gal { --gal-off: 92px; }            /* topbar 60 + paddings .content 16×2 */
    .gal.is-preview { --gal-off: 164px; }
    .gal-canvas { --gal-colw: clamp(140px, 42vw, 200px); --gal-colgap: 12px; padding: 18px; }
    .gal-bar-title { letter-spacing: 0.03em; padding: 7px 12px; }
    .gal-fs span:not(.ic) { display: none; } /* bouton plein écran = icône seule */
    .gal-lightbox-nav { width: 44px; height: 44px; }
    .gal-lightbox-prev { left: 8px; }
    .gal-lightbox-next { right: 8px; }
}

@media (prefers-reduced-motion: reduce) {
    .gal-item { animation: none; opacity: 1; transform: none; }
    .gal::before { animation: none; }
    .gal-bar-title::before { animation: none; }
    .gal-hint { animation: none; }
}

/* =====================================================================
   Sondages — vote (dans la messagerie) + résultats (Admin) + création
   ===================================================================== */
.sond-card { margin-top: var(--space-4); }
.sond-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; margin-bottom: 12px; }
.sond-badge {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 4px 11px; border-radius: 20px; font-size: var(--fs-xs); font-weight: 700;
    background: var(--primary-light); color: var(--primary-dark);
}
.sond-badge .ic { width: 15px; height: 15px; }
.sond-deadline { font-size: var(--fs-xs); color: var(--muted); }
.sond-q { font-size: var(--fs-lg); font-weight: 600; margin: 0 0 14px; line-height: var(--lh-snug); }
.sond-q-big { font-family: var(--font-head); font-size: var(--fs-xl); font-weight: 600; }

/* --- Options à voter --- */
.sond-options { display: flex; flex-direction: column; gap: 9px; margin-bottom: 8px; }
.sond-opt {
    display: flex; align-items: center; gap: 11px;
    padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface); cursor: pointer; transition: border-color .15s, background .15s;
}
.sond-opt input { width: 18px; height: 18px; flex: 0 0 18px; accent-color: var(--primary); cursor: pointer; }
.sond-opt-label { font-size: var(--fs-base); }
@media (hover: hover) and (pointer: fine) {
    .sond-opt:hover { border-color: var(--primary); background: var(--hover); }
}
.sond-voted {
    display: flex; align-items: center; gap: 8px; font-weight: 600; color: var(--success);
    margin: 0 0 12px;
}
.sond-voted .ic { width: 18px; height: 18px; }

/* --- Barres de résultats --- */
.sond-bars { display: flex; flex-direction: column; gap: 12px; margin-top: 6px; }
.sond-bar-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 5px; }
.sond-bar-label { font-size: var(--fs-sm); font-weight: 600; }
.sond-bar-val { font-size: var(--fs-xs); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
.sond-track { height: 12px; border-radius: 20px; background: var(--th-bg); overflow: hidden; }
.sond-fill {
    display: block; height: 100%; border-radius: 20px;
    background: linear-gradient(90deg, var(--primary-blue), var(--primary-green));
    transition: width .4s cubic-bezier(0.23, 1, 0.32, 1);
}
.sond-bar.is-top .sond-fill { background: linear-gradient(90deg, var(--primary-green), var(--primary-blue)); }
.sond-bar.is-mine .sond-bar-label { color: var(--primary-dark); }
.sond-mine-tag {
    font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
    color: var(--primary-dark); background: var(--primary-light); padding: 1px 7px; border-radius: 10px; margin-left: 6px;
}

/* --- KPIs résultats (Admin) --- */
.sond-kpis { display: flex; flex-wrap: wrap; gap: 12px; margin: 16px 0; }
.sond-kpi {
    flex: 1 1 120px; display: flex; flex-direction: column; gap: 2px;
    padding: 12px 16px; background: var(--bg-light); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
html[data-theme="dark"] .sond-kpi { background: var(--hover); }
.sond-kpi-val { font-family: var(--font-head); font-size: var(--fs-2xl); font-weight: 700; color: var(--primary-dark); line-height: 1; }
.sond-kpi-lbl { font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.sond-sec { font-size: var(--fs-lg); margin: 0 0 14px; display: flex; align-items: center; gap: 10px; }

/* --- Puces non-votants --- */
.sond-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.sond-chip {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 12px; border-radius: 20px; font-size: var(--fs-sm);
    background: var(--th-bg); border: 1px solid var(--border);
}
.sond-chip small { color: var(--muted); font-size: 11px; }

/* --- Création : choix dynamiques --- */
.sond-edit-list { display: flex; flex-direction: column; gap: 9px; margin-bottom: 10px; }
.sond-edit-row { display: flex; align-items: center; gap: 9px; }
.sond-edit-grip {
    flex: 0 0 26px; height: 26px; display: flex; align-items: center; justify-content: center;
    border-radius: 50%; background: var(--th-bg); color: var(--muted);
    font-size: var(--fs-xs); font-weight: 700;
}
.sond-edit-row .form-control { flex: 1 1 auto; }
.sond-edit-del { flex: 0 0 auto; }
.sond-edit-del:disabled { opacity: .4; cursor: not-allowed; }
.sond-toggles { display: flex; flex-direction: column; gap: 10px; }
.sond-check { display: flex; align-items: flex-start; gap: 10px; font-size: var(--fs-base); cursor: pointer; }
.sond-check input { width: 18px; height: 18px; flex: 0 0 18px; margin-top: 1px; accent-color: var(--primary); cursor: pointer; }

/* ===================== Plein écran de repli (iOS/WebKit) =====================
   Sur iPhone, l'API Fullscreen n'existe pas pour les éléments HTML :
   ipfpsFullscreen (app.js) pose .is-fs-fallback sur l'élément ciblé (+
   fs-fallback-lock sur <html>) → « faux plein écran » fixe couvrant tout le
   viewport. Les déclinaisons par composant (.att-viewer/.reader/.ipfps-hero/
   .gal, plus haut) mirrorent leurs règles :fullscreen. Bloc volontairement en
   FIN de fichier : ses hauteurs (100dvh) doivent gagner la cascade sur les
   hauteurs calculées des composants (.gal, .ipfps-hero…). */
.is-fs-fallback {
    position: fixed;
    /* Ancrage aux 4 bords (PAS de 100vh/100dvh : iOS les calcule mal en PWA
       standalone → bande claire en bas). L'élément épouse toujours l'écran. */
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    /* 100% sur un élément fixed = taille du viewport (fiable iOS, s'adapte à
       tout écran) ; l'ancrage 4 bords reste en ceinture de sécurité. */
    width: 100%;
    height: 100%;
    max-width: none;
    max-height: none;
    margin: 0;
    border-radius: 0;
    z-index: 1200;   /* au-dessus de tout le chrome fixe (cropper = 1100) */
    /* Couche de compositing dédiée : force iOS à repositionner le fixed
       correctement (sinon rendu décalé vers le haut → bande en bas). */
    -webkit-transform: translateZ(0);
    transform: translateZ(0);
}
html.fs-fallback-lock,
html.fs-fallback-lock body { overflow: hidden; height: 100%; }
/* Verrou iOS renforcé : body figé (position:fixed) → la page derrière ne peut
   plus être défilée ni rendue décalée ; masquée par l'élément plein écran. */
html.fs-fallback-lock body {
    position: fixed;
    top: 0; left: 0; right: 0;
    width: 100%;
}

/* ===================== PWA standalone (installée à l'écran d'accueil) =====================
   iOS + barre de statut « black-translucent » : le contenu passe SOUS l'encoche.
   viewport-fit=cover (layouts) expose env(safe-area-inset-*) → on abaisse la
   topbar/sidebar de la hauteur de l'encoche et on la retranche des hauteurs
   pleine-page calculées. Sans effet dans un navigateur classique (inset = 0,
   les calc() retombent sur les valeurs d'origine). */
@media (display-mode: standalone) {
    .topbar {
        padding-top: env(safe-area-inset-top, 0px);
        height: calc(64px + env(safe-area-inset-top, 0px));
    }
    .sidebar { padding-top: calc(18px + env(safe-area-inset-top, 0px)); }
    /* :not(.is-fs-fallback) : en plein écran de repli, la taille vient de
       l'ancrage 4 bords (.is-fs-fallback), pas de ce calc. */
    .gal:not(.is-fs-fallback) { height: calc(100dvh - var(--gal-off) - env(safe-area-inset-top, 0px)); }
    /* Pages auth (sans topbar) : logo + bouton thème sous la statut bar. */
    .login-page {
        padding-top: env(safe-area-inset-top, 0px);
        padding-bottom: env(safe-area-inset-bottom, 0px);
    }
    .login-theme-toggle { top: calc(16px + env(safe-area-inset-top, 0px)); }
    .auth-wrap { padding-top: calc(24px + env(safe-area-inset-top, 0px)); }
}
@media (display-mode: standalone) and (max-width: 768px) {
    .topbar { height: calc(60px + env(safe-area-inset-top, 0px)); }
    /* La sidebar mobile s'ouvre sous la navbar : 72px + encoche. */
    .sidebar { padding-top: calc(72px + env(safe-area-inset-top, 0px)); }
    .ipfps-hero:not(.is-fs-fallback) { height: calc(100dvh - 60px - 18px - max(18px, env(safe-area-inset-bottom)) - env(safe-area-inset-top, 0px)); }
}
@media (display-mode: standalone) and (max-width: 480px) {
    .ipfps-hero:not(.is-fs-fallback) { height: calc(100dvh - 60px - 16px - max(18px, env(safe-area-inset-bottom)) - env(safe-area-inset-top, 0px)); }
}

/* ---------- Saisie rapide des absences (.abs-quick-*) ----------
   Mobile-first : cartes larges tappables (toute la ligne coche/décoche),
   chips de séance, résumé + compteurs dynamiques, barre Enregistrer sticky.
   Additif : les classes .abs-saisie/.abs-check/.abs-cb existantes restent. */
.abs-quick-chips { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.abs-quick-chip {
    display: inline-flex; align-items: center; justify-content: center;
    min-height: 44px; padding: 8px 16px;
    border: 1px solid var(--border); border-radius: 999px;
    background: var(--surface); color: var(--text);
    font-weight: 700; font-size: var(--fs-sm); cursor: pointer; user-select: none;
}
.abs-quick-chip input { position: absolute; opacity: 0; pointer-events: none; }
.abs-quick-chip.is-on {
    border-color: var(--primary); color: var(--primary);
    background: color-mix(in srgb, var(--primary) 10%, var(--surface));
}
@media (hover: hover) and (pointer: fine) {
    .abs-quick-chip:hover { border-color: var(--primary); }
}

.abs-quick-summary { margin-bottom: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-4); align-items: center; justify-content: space-between; }
.abs-quick-facts { display: flex; flex-wrap: wrap; gap: var(--space-4); }
.abs-quick-facts > div { display: flex; flex-direction: column; gap: 2px; }
.abs-quick-facts span { color: var(--muted); font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.abs-quick-counts { display: flex; gap: var(--space-3); }
.abs-quick-count { text-align: center; min-width: 72px; padding: 8px 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--bg); }
.abs-quick-count span { display: block; color: var(--muted); font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.abs-quick-count strong { font-size: var(--fs-xl); font-variant-numeric: tabular-nums; }
.abs-quick-count--abs strong { color: var(--danger); }

.abs-quick-bar { margin-bottom: var(--space-3); display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center; }
.abs-quick-bar .form-control { flex: 1 1 220px; min-height: 44px; }
.abs-quick-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
.abs-quick-actions .btn { min-height: 44px; }

.abs-quick-list { display: flex; flex-direction: column; gap: var(--space-2); }
.abs-quick-row {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); overflow: hidden;
    transition: border-color .15s, background .15s;
}
.abs-quick-main {
    display: flex; align-items: center; gap: var(--space-3);
    padding: 10px 14px; min-height: 56px; cursor: pointer;
    flex-wrap: wrap;
}
.abs-quick-row.is-deja .abs-quick-main { cursor: default; }
.abs-quick-main .aq-cb { width: 20px; height: 20px; flex: 0 0 20px; accent-color: var(--danger); cursor: pointer; }
.abs-quick-id { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.abs-quick-id strong { overflow-wrap: anywhere; }
/* Retour visuel immédiat : ligne teintée + badge basculé quand cochée. */
.abs-quick-b-absent { display: none; }
.abs-quick-row.is-abs { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 7%, var(--surface)); }
.abs-quick-row.is-abs .abs-quick-b-present { display: none; }
.abs-quick-row.is-abs .abs-quick-b-absent { display: inline-flex; color: var(--danger); border-color: currentColor; }
@media (hover: hover) and (pointer: fine) {
    .abs-quick-row:not(.is-deja):hover { border-color: var(--primary); }
}
/* Justification + remarque : visibles seulement quand la ligne est cochée. */
.abs-quick-extra { display: none; }
.abs-quick-row.is-abs .abs-quick-extra {
    display: flex; flex-wrap: wrap; gap: var(--space-2); align-items: center;
    padding: 10px 14px; border-top: 1px dashed var(--border);
}
.abs-quick-justif { display: flex; align-items: center; gap: var(--space-2); font-size: var(--fs-sm); }
.abs-quick-justif label { display: inline-flex; align-items: center; gap: 5px; cursor: pointer; min-height: 44px; }
.abs-quick-extra .form-control { flex: 1 1 200px; }

.abs-quick-save {
    position: sticky; bottom: 0; z-index: 20;
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap;
    margin-top: var(--space-3); padding: 10px 14px;
    padding-bottom: max(10px, env(safe-area-inset-bottom));
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-hover);
}
.abs-quick-save .btn { min-height: 46px; }
.abs-quick-save-count strong { color: var(--danger); font-size: var(--fs-lg); }
.abs-quick-noresult { padding: 20px; }

/* ---------- Tableau de bord Admin : tuiles cliquables + bloc Finances ---------- */
a.stat { display: block; text-decoration: none; color: inherit; cursor: pointer; transition: border-color .15s, box-shadow .15s; }
@media (hover: hover) and (pointer: fine) {
    a.stat:hover { border-color: var(--primary); box-shadow: var(--shadow-hover); }
}
.stat .label .ic { width: 15px; height: 15px; vertical-align: -2px; margin-right: 4px; color: var(--primary); }

.dash-fin { margin-bottom: var(--space-4); }
.dash-fin-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); flex-wrap: wrap; margin-bottom: var(--space-3); }
.dash-fin-head h3 { margin: 0; display: inline-flex; align-items: center; gap: 8px; }
.dash-fin-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-3); }
.dash-fin-item { background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius); padding: var(--space-3); }
.dash-fin-item .label { color: var(--muted); font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
.dash-fin-item .value { font-size: var(--fs-xl); font-weight: 800; margin-top: 4px; font-variant-numeric: tabular-nums; }
.dash-fin-item.is-alert .value { color: var(--danger); }
.dash-fin-bar { height: 6px; border-radius: 999px; background: var(--hover); margin-top: 10px; overflow: hidden; }
.dash-fin-bar > span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, var(--primary-blue), var(--primary-green)); }

/* ---------- Liste Emplois du temps : regroupée par filière / classe ----------
   Dernière semaine visible par classe ; les précédentes dans un <details>
   repliable (natif, sans JS — même approche que .abs-* / .nav-group). */
.edt-fil { margin: var(--space-4) 0 var(--space-2); font-size: var(--fs-xl); }
.edt-classe { margin-bottom: var(--space-3); }
.edt-classe-title { margin: 0 0 var(--space-2); display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }
.edt-hist { margin-top: var(--space-2); }
.edt-hist > summary {
    cursor: pointer; user-select: none;
    color: var(--muted); font-size: var(--fs-sm); font-weight: 600;
    padding: 6px 0;
}
.edt-hist > summary:hover { color: var(--text); }
.edt-hist[open] > summary { margin-bottom: var(--space-2); }

/* ---------- Recherche globale topbar (.gsearch, Admin/Responsable) ----------
   Navigation uniquement (AJAX lecture seule). Desktop : champ dans la topbar ;
   mobile ≤768 : icône loupe → champ pleine largeur en surimpression (style
   Gmail). NB : .topbar a un backdrop-filter → les descendants position:fixed
   sont positionnés par rapport à ELLE (comportement voulu ici). */
.gsearch { position: relative; flex: 0 1 360px; min-width: 0; margin: 0 16px; }
.gsearch-toggle { display: none; }
.gsearch-box { position: relative; display: flex; align-items: center; }
.gsearch-ic {
    position: absolute; left: 11px; width: 16px; height: 16px;
    color: var(--muted); pointer-events: none;
}
.gsearch-input {
    width: 100%; min-height: 38px; padding: 0 40px 0 34px;
    border: 1px solid var(--border); border-radius: 999px;
    background: var(--bg); color: var(--text);
    font-family: var(--font-body); font-size: var(--fs-sm);
}
.gsearch-input:focus { outline: none; border-color: var(--primary); background: var(--surface); }
.gsearch-input::-webkit-search-cancel-button { -webkit-appearance: none; }
.gsearch-kbd {
    position: absolute; right: 12px; pointer-events: none;
    padding: 1px 7px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    color: var(--muted); font-family: var(--font-mono); font-size: var(--fs-xs);
    background: var(--surface);
}
.gsearch-close { display: none; }

.gsearch-drop {
    position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 30;
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius); box-shadow: var(--shadow-hover);
    max-height: min(60vh, 480px); overflow-y: auto; overscroll-behavior: contain;
    animation: dashFadeUp .2s cubic-bezier(0.23, 1, 0.32, 1) both;
}
@media (prefers-reduced-motion: reduce) {
    .gsearch-drop { animation: none; opacity: 1; }
}
.gsearch-empty { padding: 16px; color: var(--muted); font-size: var(--fs-sm); }
.gsearch-item {
    display: flex; align-items: flex-start; gap: 10px;
    padding: 10px 12px; text-decoration: none; color: var(--text);
    border-bottom: 1px solid var(--border);
}
.gsearch-item:last-child { border-bottom: 0; }
.gsearch-item.is-active { background: var(--hover); }
@media (hover: hover) and (pointer: fine) {
    .gsearch-item:hover { background: var(--hover); }
}
.gsearch-photo, .gsearch-ph {
    width: 34px; height: 34px; flex: 0 0 34px; border-radius: 50%;
    object-fit: cover; margin-top: 2px;
}
.gsearch-ph {
    display: flex; align-items: center; justify-content: center;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
    color: #fff; font-weight: 700; font-size: var(--fs-sm);
}
.gsearch-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.gsearch-name { font-weight: 700; font-size: var(--fs-sm); overflow-wrap: anywhere; }
.gsearch-name .badge { margin-left: 4px; vertical-align: 1px; }
.gsearch-sub { color: var(--muted); font-size: var(--fs-xs); overflow-wrap: anywhere; }
/* Actions rapides = redirections vers des pages existantes. */
.gsearch-acts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 5px; }
.gsearch-act {
    display: inline-flex; align-items: center;
    padding: 3px 10px; border: 1px solid var(--border); border-radius: 999px;
    color: var(--primary); text-decoration: none;
    font-size: var(--fs-xs); font-weight: 600; background: var(--bg);
}
@media (hover: hover) and (pointer: fine) {
    .gsearch-act:hover { border-color: var(--primary); background: var(--hover); }
}

@media (max-width: 768px) {
    /* Loupe seule dans la topbar ; champ déplié en surimpression. */
    .gsearch { flex: 0 0 auto; margin: 0 6px; }
    .gsearch-box, .gsearch-kbd { display: none; }
    .gsearch-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 38px; height: 38px; border-radius: 50%; cursor: pointer;
        background: var(--surface); border: 1px solid var(--border); color: var(--text);
    }
    /* fixed = relatif à la topbar (backdrop-filter), pinnée en haut → OK. */
    .gsearch--open .gsearch-box {
        display: flex; position: fixed; z-index: 60;
        top: calc(9px + env(safe-area-inset-top, 0px)); left: 8px; right: 8px;
    }
    .gsearch--open .gsearch-input { min-height: 44px; padding-right: 48px; background: var(--surface); }
    .gsearch--open .gsearch-close {
        display: inline-flex; align-items: center; justify-content: center;
        position: absolute; right: 4px; width: 40px; height: 40px;
        border: 0; background: none; color: var(--muted); cursor: pointer;
    }
    .gsearch--open .gsearch-drop {
        position: fixed; z-index: 60;
        top: calc(58px + env(safe-area-inset-top, 0px)); left: 8px; right: 8px;
        max-height: min(70vh, 480px);
    }
    .gsearch-acts { gap: 8px; }
    .gsearch-act { min-height: 30px; padding: 5px 12px; }
}

/* ===================== Examens (evol15) : countdown =====================
   Page « Mes examens » (.exam-*) : hero centré à atmosphère radiale, timer
   4 cellules FLUIDES (grid + clamp — une seule ligne jusqu'à 320px), signature
   « ligne de vie » ECG animée (univers paramédical). Dark mode : overrides
   EXPLICITES [data-theme="dark"] — ne jamais compter sur --bg-light (non
   redéfini en sombre). Tokens uniquement ; reduced-motion neutralise tout. */
.exam-page { display: flex; justify-content: center; }
.exam-hero {
    width: 100%;
    max-width: 760px;
    text-align: center;
    background: radial-gradient(120% 70% at 50% 0%, var(--primary-light) 0%, transparent 55%), var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: clamp(20px, 6vw, 32px) clamp(14px, 4.5vw, 32px);
    opacity: 0;
    transform: translateY(14px);
    animation: dashFadeUp .25s cubic-bezier(0.23, 1, 0.32, 1) forwards;
}
.exam-logo {
    width: clamp(84px, 24vw, 116px);
    height: clamp(84px, 24vw, 116px);
    margin: 0 auto var(--space-3);
    border-radius: 50%;
    display: flex; align-items: center; justify-content: center;
    background: var(--surface);
    border: 1px solid var(--border);
    box-shadow: 0 0 0 8px var(--primary-light), var(--shadow);
}
.exam-logo img { width: 62%; height: 62%; object-fit: contain; }
.exam-brand {
    margin: 0 auto var(--space-5);
    max-width: 420px;
    color: var(--muted);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.exam-kicker {
    display: inline-block;
    margin: 0 0 var(--space-2);
    padding: 4px 14px;
    border-radius: 999px;
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 700;
    font-size: var(--fs-sm);
}
.exam-title {
    margin: 0 0 var(--space-1);
    font-family: var(--font-head);
    font-size: clamp(24px, 6.4vw, 34px);
    letter-spacing: var(--tracking-tight);
}
.exam-date { margin: 0; color: var(--muted); font-size: clamp(14px, 3.6vw, 16px); }

/* Signature : « ligne de vie » ECG — trait de base discret + battement accent
   qui parcourt le tracé en boucle lente (pathLength=1000 côté SVG). */
.exam-ecg {
    display: block;
    width: min(440px, 86%);
    height: 40px;
    margin: var(--space-2) auto 0;
}
.exam-ecg path {
    fill: none;
    stroke-width: 2;
    vector-effect: non-scaling-stroke;
    stroke-linecap: round;
    stroke-linejoin: round;
}
.exam-ecg-base { stroke: var(--border); }
.exam-ecg-beat {
    stroke: var(--primary);
    stroke-dasharray: 90 910;
    stroke-dashoffset: 1000;
    animation: examEcg 6s linear infinite;
}
@keyframes examEcg { to { stroke-dashoffset: 0; } }

.exam-count {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 112px));
    justify-content: center;
    gap: clamp(6px, 2vw, 12px);
    margin: var(--space-4) 0 var(--space-5);
}
.exam-cell {
    min-width: 0;
    padding: clamp(10px, 3vw, 16px) clamp(4px, 1.5vw, 8px);
    border-radius: var(--radius-lg);
    background: var(--bg-light);
    border: 1px solid var(--border);
    display: flex; flex-direction: column; gap: 4px;
}
.exam-num {
    font-family: var(--font-head);
    font-size: clamp(24px, 8vw, 40px);
    font-weight: 700;
    line-height: 1.05;
    color: var(--text);
    font-variant-numeric: tabular-nums;
}
.exam-num.is-tick { animation: examTick .25s cubic-bezier(0.23, 1, 0.32, 1); }
@keyframes examTick {
    from { transform: scale(1.12); color: var(--primary); }
    to   { transform: scale(1);    color: var(--text); }
}
.exam-lbl {
    font-size: clamp(9px, 2.4vw, 12px);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.exam-today {
    margin: var(--space-4) 0 var(--space-5);
    font-family: var(--font-head);
    font-size: var(--fs-2xl);
    font-weight: 700;
    color: var(--success);
}
.exam-msg {
    max-width: 540px;
    margin: 0 auto;
    padding: var(--space-3) var(--space-4);
    text-align: start;
    background: var(--bg-light);
    border-inline-start: 3px solid var(--primary);
    border-radius: var(--radius-sm, 8px);
    color: var(--text);
    line-height: 1.65;
    font-size: clamp(14px, 3.6vw, 15px);
}
/* Responsable : sessions suivantes (toutes classes) */
.exam-next {
    max-width: 540px;
    margin: var(--space-5) auto 0;
    padding-top: var(--space-4);
    border-top: 1px solid var(--border);
    text-align: start;
}
.exam-next h2 {
    margin: 0 0 var(--space-2);
    font-size: var(--fs-xs);
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}
.exam-next-row {
    display: flex; justify-content: space-between; align-items: baseline;
    gap: var(--space-2);
    padding: var(--space-2) 0;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.exam-next-row:last-child { border-bottom: none; }
.exam-next-classe { font-weight: 700; }
.exam-next-date { color: var(--muted); font-size: var(--fs-sm); }
.exam-foot {
    margin: var(--space-5) 0 0;
    color: var(--muted);
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide);
}

/* Dark mode : --bg-light n'existe pas en sombre → surfaces explicites, relief
   par hairline claire, halo logo assombri. Contraste chiffres/labels garanti. */
[data-theme="dark"] .exam-cell {
    background: var(--hover);
    border-color: var(--border);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, .05);
}
[data-theme="dark"] .exam-logo { background: var(--hover); }
[data-theme="dark"] .exam-msg { background: var(--hover); }
[data-theme="dark"] .exam-kicker { color: var(--primary); }

@media (max-width: 480px) {
    .exam-count { margin: var(--space-3) 0 var(--space-4); }
    .exam-brand { margin-bottom: var(--space-4); }
}
/* Accessibilité : toute entrée en opacity:0 DOIT être neutralisée ici. */
@media (prefers-reduced-motion: reduce) {
    .exam-hero { opacity: 1; transform: none; animation: none; }
    .exam-num.is-tick { animation: none; }
    .exam-ecg-beat { animation: none; stroke-dasharray: none; stroke-dashoffset: 0; opacity: .5; }
}

/* ===================== Expéditeur avec photo (messagerie) =====================
   Boîte de réception (desktop .msg-from / mobile .inbox-avatar) + fiche
   message : photo de profil de l'expéditeur, initiale en repli (.avatar). */
.msg-from { display: inline-flex; align-items: center; gap: 10px; }
.msg-from .avatar,
.msg-from .thumb { flex: 0 0 auto; }
.inbox-avatar { overflow: hidden; }
.inbox-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; display: block; }

/* Pièce jointe image : chargement auto + zoom. margin:auto = centrée quand
   plus petite que le cadre, défilable SANS rognage quand zoomée (le piège
   align-items:center + overflow coupe le bord gauche). */
.att-scroll-img { align-items: flex-start; justify-content: flex-start; }
.att-scroll-img .att-img { margin: auto; }

/* ===================== Ordre des stagiaires (evol16) =====================
   Page stagiaires/ordre (.st-order-*) : liste réordonnable — poignée de
   glisser-déposer, numéro de position, flèches ↑/↓. Tokens uniquement. */
.st-order-list { list-style: none; margin: 0 0 var(--space-4); padding: 0; display: grid; gap: var(--space-2); }
.st-order-item {
    display: flex; align-items: center; gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    cursor: grab;
}
.st-order-item.is-dragging { opacity: .55; cursor: grabbing; border-color: var(--primary); }
.st-order-grip { color: var(--muted); font-size: 18px; line-height: 1; user-select: none; flex: 0 0 auto; }
.st-order-pos {
    flex: 0 0 auto;
    min-width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 700; font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
}
.st-order-name { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.st-order-name strong { overflow-wrap: anywhere; }
.st-order-btns { flex: 0 0 auto; display: inline-flex; gap: 6px; }
.st-order-btns .btn-ic:disabled { opacity: .35; cursor: default; }
.st-order-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.st-order-reset { margin-top: var(--space-3); padding-top: var(--space-3); border-top: 1px solid var(--border); }
@media (max-width: 480px) {
    .st-order-item { padding: var(--space-2); }
    .st-order-name small { display: none; } /* CIN masqué sur très petit écran */
}

/* ===================== Badge « compte vérifié » (messagerie) =====================
   Sceau rempli style Instagram après le nom de l'expéditeur : Admin = OR
   (--warning, vif = remplissage de marque), Responsable = BLEU (--primary-blue).
   La coche blanche est dans le symbole SVG (#badge-check). */
.vbadge { display: inline-flex; vertical-align: -3px; margin-left: 4px; }
.vbadge .ic { width: 16px; height: 16px; }
.vbadge-gold { color: var(--warning); }
.vbadge-blue { color: var(--primary-blue); }
.vbadge-green { color: var(--primary-green); }
/* En sombre, le bleu vif de marque reste lisible ; l'or passe sur le token clair. */
[data-theme="dark"] .vbadge-blue { color: var(--primary); }

/* Badge vérifié sur la vitrine IPFPS : proportionnel au grand nom animé,
   apparition en fondu APRÈS la vague des lettres. */
.ipfps-name .vbadge { margin-left: .14em; vertical-align: .04em; }
.ipfps-name .vbadge .ic {
    width: .5em; height: .5em;
    opacity: 0;
    animation: ipfpsVseal .4s ease-out 1.1s forwards;
}
@keyframes ipfpsVseal { to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
    .ipfps-name .vbadge .ic { opacity: 1; animation: none; }
}

/* ===================== Direction & Administration (evol17) =====================
   Carrousel 3D « coverflow » dédié (.adm-*) : carte active de face, voisines
   inclinées en perspective avec profondeur et désaturation douce. Transformations
   posées par le JS inline de la vue ; ici uniquement l'apparence. Tokens only. */
.adm-page { max-width: 900px; margin: 0 auto; }
.adm-head { text-align: center; margin-bottom: var(--space-4); }
.adm-kicker {
    display: inline-block;
    margin: 0 0 var(--space-2);
    padding: 4px 14px;
    border-radius: 999px;
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 700;
    font-size: var(--fs-sm);
}
.adm-title {
    margin: 0 0 var(--space-1);
    font-family: var(--font-head);
    font-size: clamp(24px, 6vw, 34px);
    letter-spacing: var(--tracking-tight);
}
.adm-sub { margin: 0 auto; max-width: 460px; color: var(--muted); font-size: clamp(13px, 3.4vw, 15px); }
.adm-empty { margin-top: var(--space-5); }

/* Scène 3D : perspective + halo doux derrière la carte active */
.adm-stage {
    position: relative;
    height: clamp(380px, 108vw, 505px);
    perspective: 1400px;
    touch-action: pan-y; /* swipe horizontal capté, défilement vertical préservé */
}
.adm-stage::before {
    content: "";
    position: absolute;
    inset: 8% 12%;
    background: radial-gradient(50% 50% at 50% 46%, var(--primary-light) 0%, transparent 70%);
    pointer-events: none;
}
.adm-track { position: absolute; inset: 0; transform-style: preserve-3d; }

/* Carte responsable */
.adm-card {
    position: absolute;
    left: 50%; top: 50%;
    width: clamp(215px, 56vw, 290px);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    overflow: hidden;
    box-shadow: var(--shadow);
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
    transition: transform .55s cubic-bezier(0.23, 1, 0.32, 1), opacity .55s cubic-bezier(0.23, 1, 0.32, 1), box-shadow .3s ease;
    will-change: transform, opacity;
}
.adm-card.is-active { box-shadow: var(--shadow-hover); cursor: default; }
.adm-card:focus-visible { outline: 2px solid var(--primary); outline-offset: 3px; }

.adm-photo { position: relative; aspect-ratio: 4 / 5; background: var(--bg-light); }
.adm-photo img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    transition: filter .55s ease;
}
/* Profondeur : les cartes en retrait sont légèrement éteintes */
.adm-card:not(.is-active) .adm-photo img { filter: saturate(.6) brightness(.88); }
.adm-ph {
    width: 100%; height: 100%;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-head);
    font-size: clamp(48px, 13vw, 72px);
    font-weight: 700;
    color: #fff;
    background: linear-gradient(135deg, var(--primary-blue), var(--primary-green));
}
/* Sceau vérifié incrusté (coin bas-droit, anneau surface = lisible sur toute photo) */
.adm-seal {
    position: absolute;
    right: 10px; bottom: 10px;
    display: flex;
    padding: 4px;
    border-radius: 50%;
    background: var(--surface);
    box-shadow: var(--shadow);
}
.adm-seal .vbadge { margin: 0; }
.adm-seal .vbadge .ic { width: 24px; height: 24px; display: block; }

.adm-plate { padding: var(--space-3) var(--space-2); text-align: center; }
.adm-name {
    margin: 0;
    font-family: var(--font-head);
    font-size: clamp(17px, 4.6vw, 21px);
    letter-spacing: var(--tracking-tight);
}
.adm-role { margin: 3px 0 0; color: var(--muted); font-size: var(--fs-sm); }

/* Flèches */
.adm-nav {
    position: absolute;
    top: 50%; transform: translateY(-50%);
    z-index: 300;
    width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    border: 1px solid var(--border);
    background: var(--surface);
    color: var(--text);
    font-size: 24px; line-height: 1;
    cursor: pointer;
    box-shadow: var(--shadow);
}
.adm-prev { left: 2px; }
.adm-next { right: 2px; }
@media (hover: hover) and (pointer: fine) {
    .adm-nav:hover { background: var(--hover); }
}

/* Points de navigation (actif = pilule allongée) */
.adm-dots { display: flex; justify-content: center; gap: 8px; margin-top: var(--space-3); }
.adm-dot {
    width: 9px; height: 9px;
    padding: 0; border: none; border-radius: 999px;
    background: var(--border);
    cursor: pointer;
    transition: width .25s ease, background .25s ease;
}
.adm-dot.is-on { width: 24px; background: var(--primary); }

@media (max-width: 480px) {
    .adm-nav { width: 40px; height: 40px; font-size: 21px; }
}
@media (prefers-reduced-motion: reduce) {
    .adm-card, .adm-photo img, .adm-dot { transition: none; }
}

/* Téléphone du responsable (carrousel Direction & Administration) : pilule
   distincte sous le rôle, tappable (tel:) — cible tactile confortable. */
.adm-tel {
    display: inline-flex; align-items: center; gap: 7px;
    margin-top: var(--space-2);
    padding: 7px 14px;
    border-radius: 999px;
    background: var(--primary-light);
    color: var(--primary);
    font-weight: 700;
    font-size: var(--fs-sm);
    font-variant-numeric: tabular-nums;
    text-decoration: none;
    border: 1px solid transparent;
}
.adm-tel .ic { width: 15px; height: 15px; flex: 0 0 auto; }
@media (hover: hover) and (pointer: fine) {
    .adm-tel:hover { border-color: var(--primary); }
}
/* Cartes en retrait : la pilule s'éteint avec la photo (cohérence de profondeur). */
.adm-card:not(.is-active) .adm-tel { opacity: .55; }

/* ===================== Module Stages (evol18) : .stg-* =====================
   Groupes (cartes membres), sélecteur de membres (picker), planning (matrice),
   saisie rapide des absences de stage (cartes tactiles). Tokens only. */
/* Cartes groupes */
.stg-groupes { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: var(--space-3); }
.stg-groupe-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); margin-bottom: var(--space-2); }
.stg-groupe-head h3 { margin: 0; }
.stg-membres { list-style: none; margin: 0 0 var(--space-3); padding: 0; display: grid; gap: 8px; }
.stg-membres li { display: flex; align-items: center; gap: 10px; }
.stg-membre-info { display: flex; flex-direction: column; min-width: 0; }
.stg-membre-info small { font-size: var(--fs-xs); }
.stg-groupe-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Sélecteur de membres (3-5) */
.stg-count { font-weight: 700; color: var(--danger); }
.stg-count.is-ok { color: var(--success); }
.stg-picker {
    margin-top: var(--space-2);
    max-height: 380px; overflow: auto;
    border: 1px solid var(--border); border-radius: var(--radius-sm, 8px);
    display: grid; gap: 2px; padding: 6px;
    background: var(--surface);
}
.stg-pick {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 10px; border-radius: var(--radius-sm, 8px);
    cursor: pointer;
}
.stg-pick input { width: 18px; height: 18px; accent-color: var(--primary); flex: 0 0 auto; }
.stg-pick-info { display: flex; flex-direction: column; min-width: 0; }
.stg-pick.is-checked { background: var(--primary-light); }
.stg-pick.is-locked { opacity: .45; cursor: not-allowed; }
@media (hover: hover) and (pointer: fine) {
    .stg-pick:not(.is-locked):hover { background: var(--hover); }
}

/* Services : formulaire d'ajout en ligne + renommage inline */
.stg-service-add { display: flex; gap: var(--space-2); align-items: flex-end; }
.stg-rename { display: flex; gap: 6px; align-items: center; }
.stg-rename .form-control { min-width: 180px; }

/* Planning : matrice groupes × mois */
.stg-matrix td, .stg-matrix th { white-space: nowrap; }
.stg-svc {
    display: inline-block; padding: 3px 10px; border-radius: 999px;
    background: var(--primary-light); color: var(--primary);
    font-weight: 700; font-size: var(--fs-xs);
}
.stg-mois-courant { background: var(--th-bg); }
.stg-rotations { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-2) var(--space-3); }
.stg-rotation.is-current label { color: var(--primary); }
@media (max-width: 560px) { .stg-rotations { grid-template-columns: 1fr; } }

/* Saisie rapide absences de stage : cartes tactiles */
.stg-abs-filtres { margin-bottom: var(--space-3); }
.stg-abs-cards { display: grid; gap: var(--space-2); max-width: 640px; }
.stg-abs-card {
    display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
    padding: var(--space-3);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    cursor: pointer;
}
.stg-abs-card.is-deja { opacity: .65; cursor: default; }
.stg-abs-card.is-absent { border-color: var(--danger); background: var(--danger-bg); }
.stg-abs-check { width: 22px; height: 22px; accent-color: var(--danger); flex: 0 0 auto; }
.stg-abs-info { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.stg-abs-detail { display: flex; flex-direction: column; gap: 6px; width: 100%; }
.stg-abs-just { display: inline-flex; align-items: center; gap: 6px; font-size: var(--fs-sm); cursor: pointer; }
.stg-abs-just input { width: 16px; height: 16px; accent-color: var(--primary); }
.stg-abs-bar {
    position: sticky; bottom: 0; z-index: 5;
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
    max-width: 640px; margin-top: var(--space-3);
    padding: var(--space-2) var(--space-3);
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 999px; box-shadow: var(--shadow-hover);
}
.stg-abs-resume { font-weight: 700; }

/* Mon stage (Stagiaire) : héro « service du mois » */
.stg-mois-hero { text-align: center; margin-bottom: var(--space-3); }
.stg-mois-hero-label { margin: 0; color: var(--muted); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--tracking-wide); }
.stg-mois-hero-svc { margin: 6px 0 4px; font-family: var(--font-head); font-size: var(--fs-2xl); font-weight: 700; color: var(--primary); }

/* ============================================================
   Board « Répartition des groupes » de stage (drag & drop).
   Colonnes défilables horizontalement ; jetons stagiaires déplaçables.
   JS : ipfpsStageBoard. Server-authoritative (max 5, 1 groupe/stagiaire).
   ============================================================ */
.stg-board-actions { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.stg-newgroup { max-width: 420px; margin-bottom: var(--space-3); }
.stg-board-filters { margin-bottom: var(--space-3); }

/* Le board n'est PLUS une bande horizontale : une bande obligeait à scroller
   loin pour atteindre les derniers groupes — et le pool « Non affectés »,
   ancré tout à gauche, sortait alors de l'écran. Deux zones à la place :
     • le POOL, collé (sticky) : il reste sous la main quel que soit le scroll ;
     • les GROUPES, en grille qui passe à la ligne (4 max par rangée).
   Le défilement redevient vertical, la distance pool → n'importe quel groupe
   reste courte, et plus rien ne sort de l'écran. */
.stg-board {
    display: grid;
    grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
    gap: var(--space-3);
    align-items: start;
    padding-bottom: var(--space-2);
}

/* Grille des groupes.
   `calc(25% - gap)` plafonne à 4 colonnes sur grand écran (au-delà, les cartes
   s'élargiraient sans rien gagner) ; le plancher de 225px est calibré pour que
   3 colonnes tiennent encore sur un portable 1280 (zone utile 704px : 3×225 +
   2×12 = 699). En dessous, la grille retombe seule à 2 puis 1. */
.stg-groups {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(max(225px, calc(25% - var(--space-3))), 1fr));
    gap: var(--space-3);
    align-items: start;
    min-width: 0;
}

.stg-col {
    display: flex;
    flex-direction: column;
    min-width: 0;
    max-height: 72vh;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    overflow: hidden;
}

/* Pool collé sous la topbar (64px) : c'est LUI qu'on ne doit jamais perdre. */
.stg-col--pool {
    position: sticky;
    top: calc(64px + env(safe-area-inset-top, 0px) + var(--space-2));
    max-height: calc(100dvh - 64px - env(safe-area-inset-top, 0px) - var(--space-5));
    background: var(--bg-light);
    border-style: dashed;
}
/* ⚠️ `--bg-light` n'est PAS redéfini en thème sombre (piège connu) : sans cet
   override, le pool restait BLANC sur fond sombre et son titre devenait
   illisible. On reprend la teinte de survol, qui, elle, suit le thème. */
[data-theme="dark"] .stg-col--pool { background: var(--hover); }

.stg-col-head {
    display: flex; align-items: center; justify-content: space-between; gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border-bottom: 1px solid var(--border);
    position: sticky; top: 0; background: inherit; z-index: 1;
}
.stg-col-head h2 { margin: 0; font-family: var(--font-head); font-size: var(--fs-base); font-weight: 700; }
.stg-col-count {
    display: inline-flex; align-items: center; gap: 4px;
    font-size: var(--fs-xs); font-weight: 700; color: var(--muted);
    background: var(--hover); padding: 2px 8px; border-radius: 999px; white-space: nowrap;
}
.stg-col-count .ic { width: 14px; height: 14px; }
.stg-col-count.is-ok { color: var(--success); background: color-mix(in srgb, var(--success) 14%, transparent); }
.stg-col-count.is-warn { color: var(--warning-text); background: color-mix(in srgb, var(--warning-text) 14%, transparent); }

.stg-col-body {
    display: flex; flex-direction: column; gap: var(--space-2);
    padding: var(--space-2);
    overflow-y: auto;
    min-height: 90px;
    transition: background .15s ease;
}
.stg-col-body.is-drop { background: var(--primary-light); outline: 2px dashed var(--primary); outline-offset: -4px; }
.stg-col-empty { margin: auto; text-align: center; color: var(--muted); font-size: var(--fs-xs); padding: var(--space-3) var(--space-2); }

.stg-token {
    display: flex; align-items: center; gap: var(--space-2);
    padding: 8px 10px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    cursor: grab;
    user-select: none;
}
/* Le filtre/recherche masque via [hidden] : forcer display:none (sinon
   .stg-token{display:flex}, de même spécificité, l'emporte → jeton resté visible). */
.stg-token[hidden] { display: none !important; }
.stg-col--pool .stg-token { background: var(--surface); }
.stg-token:active { cursor: grabbing; }
.stg-token.is-dragging { opacity: .45; }
.stg-token.is-picked { border-color: var(--primary); box-shadow: 0 0 0 2px var(--primary-light); }
.stg-token .thumb, .stg-token .avatar { width: 34px; height: 34px; flex: 0 0 34px; font-size: var(--fs-xs); }
.stg-token-info { display: flex; flex-direction: column; min-width: 0; line-height: 1.25; }
.stg-token-info strong { font-size: var(--fs-sm); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.stg-token-info small { font-size: var(--fs-xs); }
.stg-token-grip { margin-left: auto; color: var(--muted); font-size: 16px; letter-spacing: -2px; }

/* Mode « tap-to-move » actif : on invite à choisir une colonne cible. */
.stg-picking .stg-col-body { outline: 1px dashed var(--border); outline-offset: -4px; }
.stg-picking .stg-col-head { cursor: pointer; }

@media (hover: hover) and (pointer: fine) {
    .stg-token:hover { border-color: var(--primary); background: var(--hover); }
}

/* 1200–1400px (laptops) : le pool est resserré à 260px — sinon il ampute la
   zone des groupes au point de la faire retomber à 2 colonnes. */
@media (max-width: 1400px) {
    .stg-board { grid-template-columns: minmax(230px, 260px) minmax(0, 1fr); }
}

/* ≤1200px : deux zones côte à côte ne tiennent plus — la colonne des groupes
   descendrait à 1 seule carte. Mesuré : empilé, la même largeur en loge 3.
   Le pool passe donc AU-DESSUS, mais reste COLLÉ en haut : combiné au
   « tap-to-move », on garde le pool et les cibles visibles ensemble (on tape un
   stagiaire, on descend, on tape le groupe). */
@media (max-width: 1200px) {
    .stg-board { grid-template-columns: minmax(0, 1fr); }
    .stg-col--pool {
        top: calc(64px + env(safe-area-inset-top, 0px) + var(--space-1));
        max-height: 42vh;   /* laisse toujours voir des groupes en dessous */
    }
}
@media (max-width: 768px) {
    /* La topbar mesure 60px en mobile. */
    .stg-col--pool { top: calc(60px + env(safe-area-inset-top, 0px) + var(--space-1)); max-height: 38vh; }
    .stg-col { max-height: 60vh; }
}
@media (max-width: 480px) {
    .stg-groups { grid-template-columns: minmax(0, 1fr); }
    .stg-col--pool { max-height: 36vh; }
}

/* ============================================================
   Sélecteur d'année GLOBAL (topbar) + bannière « année archivée ».
   Consultation/modification multi-année (Admin/Responsable). Tokens only.
   ============================================================ */
/* Pilule d'état compacte : [● 25/26 ⌄]. Le libellé est abrégé côté vue
   (« 2025/2026 » → « 25/26 », complet dans le title de chaque option) et le mot
   « (active) » est remplacé par une PASTILLE — verte = année active, ambre =
   année archivée. On gagne ~90px dans une topbar qui aligne cinq contrôles.
   flex: 0 1 auto + min-width: 0 → c'est CE bloc qui absorbe le manque de place
   sur les très petits écrans, jamais les actions de compte. */
.year-switch {
    position: relative;
    display: inline-flex; align-items: center; gap: 7px;
    flex: 0 1 auto; min-width: 0;
    margin: 0; padding: 4px 22px 4px 10px;
    background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
    transition: border-color .15s, box-shadow .15s;
}
/* L'icône calendrier fait double emploi avec la pastille : masquée (la classe
   reste, elle est référencée par la vue). */
.year-switch-ic { display: none; }
.year-dot {
    flex: 0 0 auto; width: 7px; height: 7px; border-radius: 50%;
    background: var(--primary-green);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary-green) 22%, transparent);
}
/* Chevron dessiné en CSS : sa largeur est maîtrisée (le chevron natif du
   <select> coûte ~18px, trop cher ici) et il suit le thème. */
.year-switch::after {
    content: ""; position: absolute; right: 9px; top: 50%;
    width: 5px; height: 5px; margin-top: -4px;
    border-right: 1.6px solid var(--muted); border-bottom: 1.6px solid var(--muted);
    transform: rotate(45deg); pointer-events: none;
}
.year-switch select {
    -webkit-appearance: none; -moz-appearance: none; appearance: none;
    border: none; background: transparent; color: var(--text);
    font: inherit; font-size: var(--fs-sm); font-weight: 700; cursor: pointer;
    padding: 2px 0; min-width: 0; max-width: 120px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.year-switch select:focus { outline: none; }
/* Le focus clavier vit sur la pilule, puisque l'outline du select est retiré. */
.year-switch:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px var(--ring); }
.year-switch.is-archive { border-color: var(--warning-text); background: color-mix(in srgb, var(--warning-text) 12%, var(--surface)); }
.year-switch.is-archive .year-dot {
    background: var(--warning);
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--warning) 28%, transparent);
}
.year-switch.is-archive::after { border-color: var(--warning-text); }
.year-switch.is-archive:focus-within {
    border-color: var(--warning-text);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--warning-text) 30%, transparent);
}

.archive-bar {
    display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
    margin-bottom: var(--space-3);
    padding: 10px 16px;
    background: color-mix(in srgb, var(--warning-text) 12%, var(--surface));
    border: 1px solid color-mix(in srgb, var(--warning-text) 35%, var(--border));
    border-radius: var(--radius);
    color: var(--text); font-size: var(--fs-sm);
}
.archive-bar > .ic { width: 18px; height: 18px; color: var(--warning-text); flex: 0 0 auto; }
.archive-bar span { flex: 1 1 auto; min-width: 200px; }
.archive-bar-back { margin: 0; }
.archive-bar-back button {
    border: 1px solid var(--warning-text); background: transparent; color: var(--warning-text);
    padding: 5px 12px; border-radius: var(--radius-sm); font-size: var(--fs-xs); font-weight: 700; cursor: pointer;
    white-space: nowrap;
}
@media (hover: hover) and (pointer: fine) {
    .archive-bar-back button:hover { background: var(--warning-text); color: var(--surface); }
}
@media (max-width: 600px) {
    .year-switch { padding: 4px 20px 4px 8px; gap: 6px; }
    .year-switch::after { right: 8px; }
    .year-switch select { font-size: var(--fs-xs); }
}

/* ============================================================
   Topbar — compression progressive (tablette & petit portable).
   Depuis l'ajout du sélecteur d'année, cinq blocs se disputent une seule
   ligne : hamburger, titre, année, recherche, compte. Ordre de sacrifice
   assumé : titre → champ de recherche → libellé « Déconnexion » → identité
   → libellé de l'année. Le bloc compte (.user) reste toujours entier.

   Mesuré : au-delà de ~1245px seulement, tout tient avec le champ de recherche
   déployé ET « Déconnexion » en toutes lettres. En dessous, on dégraisse par
   paliers plutôt que d'écraser le titre à zéro.
   ============================================================ */

/* ≤1340px : « Déconnexion » passe en icône (le libellé coûte ~94px pour aucune
   information — l'icône porte déjà title/aria-label). */
@media (min-width: 769px) and (max-width: 1340px) {
    .logout-text { display: none; }
    .logout-icon { display: block; }
    .logout-btn.btn.btn-light {
        display: inline-flex; align-items: center; justify-content: center;
        width: 38px; height: 38px; padding: 0; border-radius: 50%;
        color: var(--danger); border: 1px solid var(--danger);
        background: var(--danger-bg);
    }
}

/* ≤1180px : l'identité (e-mail + rôle) cède à son tour — elle reste lisible
   dans le pied de la sidebar (photo + rôle + déconnexion). */
@media (min-width: 769px) and (max-width: 1180px) {
    .topbar .user .who { display: none; }
}

/* ≤1100px : la recherche repasse en LOUPE — même mécanique que le mobile
   (classe .gsearch--open posée par ipfpsGlobalSearch, qui ne teste aucun point
   de rupture : aucune modification JS nécessaire). On récupère ~350px, le titre
   de page redevient pleinement lisible. */
@media (min-width: 769px) and (max-width: 1100px) {
    .gsearch { flex: 0 0 auto; margin: 0 var(--space-1); }
    .gsearch-box, .gsearch-kbd { display: none; }
    .gsearch-toggle {
        display: inline-flex; align-items: center; justify-content: center;
        width: 38px; height: 38px; border-radius: 50%; cursor: pointer;
        background: var(--surface); border: 1px solid var(--border); color: var(--text);
    }
    /* position: fixed = relatif à .topbar (backdrop-filter) : topbar de 64px ici,
       (64 − 44) / 2 = 10px pour centrer le champ ; le drop se pose juste dessous. */
    .gsearch--open .gsearch-box {
        display: flex; position: fixed; z-index: 60;
        top: calc(10px + env(safe-area-inset-top, 0px)); left: 8px; right: 8px;
    }
    .gsearch--open .gsearch-input { min-height: 44px; padding-right: 48px; background: var(--surface); }
    .gsearch--open .gsearch-close {
        display: inline-flex; align-items: center; justify-content: center;
        position: absolute; right: 4px; width: 40px; height: 40px;
        border: 0; background: none; color: var(--muted); cursor: pointer;
    }
    .gsearch--open .gsearch-drop {
        position: fixed; z-index: 60;
        top: calc(62px + env(safe-area-inset-top, 0px)); left: 8px; right: 8px;
        max-height: min(70vh, 480px);
    }
}

/* ≤600px : le bloc nom/prénom cède (il coûte 88px et l'identité figure déjà
   dans le pied de la sidebar). Le titre de la topbar sticky, lui, reste le seul
   repère de page une fois le <h1> défilé : c'est lui qu'on récupère. Conservé
   sur 601–768px (tablette portrait), où la place ne manque pas. */
@media (max-width: 600px) {
    .who-mobile { display: none; }
}

/* ≤768px : la photo s'aligne sur la cible tactile des deux boutons ronds
   voisins (44px) — thème · photo · déconnexion forment une série homogène.
   Après le bloc « safe-area » (40px) pour l'emporter à spécificité égale. */
@media (max-width: 768px) {
    .topbar .thumb, .topbar .avatar { width: 44px; height: 44px; font-size: 15px; }
}

/* ≤480px : cinq contrôles + la photo dans 375px. Le titre est le seul retrait :
   comprimé, il ne rendait plus qu'un « … » entre le hamburger et la pilule,
   alors que le <h1> de la page l'affiche en entier juste dessous.
   Placé APRÈS les blocs ≤768px pour l'emporter à spécificité égale. */
@media (max-width: 480px) {
    .topbar {
        column-gap: 6px;
        padding-left: max(10px, env(safe-area-inset-left));
        padding-right: max(10px, env(safe-area-inset-right));
    }
    .topbar-title { display: none; }
    .topbar .user { gap: 6px; }
    .gsearch { margin: 0 2px; }
    .gsearch-toggle { width: 44px; height: 44px; }
}

/* ≤400px (iPhone SE 320, Pixel 360, iPhone 375/390) : les derniers pixels se
   prennent sur les écarts, jamais sur le millésime — « 25/26 » doit rester
   entier, c'est lui qui détermine l'année de saisie. Les cibles tactiles
   (menu · loupe · thème · déconnexion) et la photo restent à 44px. */
@media (max-width: 400px) {
    .topbar {
        column-gap: 4px;
        padding-left: max(8px, env(safe-area-inset-left));
        padding-right: max(8px, env(safe-area-inset-right));
    }
    .topbar .user { gap: 4px; }
    .gsearch { margin: 0; }
    .year-switch { padding: 4px 18px 4px 7px; gap: 5px; }
    .year-switch::after { right: 7px; }
    .year-switch select { font-size: 11px; }
}

/* ≤344px (iPhone SE, Galaxy Fold replié) : on gratte les derniers pixels sur les
   écarts et les marges internes, jamais sur « 25/26 » ni sur les pastilles
   rondes (thème · photo · déconnexion restent à 44px). */
@media (max-width: 344px) {
    .topbar {
        column-gap: 3px;
        padding-left: max(6px, env(safe-area-inset-left));
        padding-right: max(6px, env(safe-area-inset-right));
    }
    .topbar .user { gap: 3px; }
    .year-switch { padding: 4px 15px 4px 6px; gap: 4px; }
    .year-switch::after { right: 6px; }
}

/* ============================================================
   Historique — bandeau KPI, groupes par jour, badges par catégorie,
   résumé en ligne (AJOUTS uniquement — rien d'existant n'est modifié)
   ============================================================ */
/* Séparateur de jour. La 2e règle neutralise la 1re colonne collante mobile
   (table.data … td:first-child) qui donnerait un fond décalé à ce td colspan. */
tr.hist-day td,
table.data:not(.pay-table):not(.data-cards) tr.hist-day td:first-child {
    position: static;
    background: var(--th-bg);
    color: var(--muted);
    font-size: var(--fs-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide, .04em);
    padding: 6px 12px;
}
.hist-time { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* Résumé une-ligne (qui / quoi / combien) au-dessus de « Voir détails ». */
.hist-sum {
    font-size: var(--fs-xs);
    color: var(--text);
    margin-bottom: 4px;
    max-width: 420px;
    white-space: normal;
    line-height: var(--lh-body);
}
/* Badges d'action par catégorie (le .badge-info historique reste intact). */
.hist-badge { white-space: nowrap; }
.hist-badge--add     { background: var(--success-bg); color: var(--success); }
.hist-badge--edit    { background: var(--primary-light); color: var(--primary-dark); }
.hist-badge--del     { background: color-mix(in srgb, var(--danger) 12%, transparent); color: var(--danger); }
.hist-badge--val     { background: color-mix(in srgb, var(--warning) 20%, transparent); color: var(--warning-text); }
.hist-badge--warn    { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); border: 1px solid color-mix(in srgb, var(--danger) 32%, transparent); }
.hist-badge--neutral { background: var(--hover); color: var(--muted); }
/* Raccourcis de période + pagination numérotée. */
.hist-chips {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
    font-size: var(--fs-sm);
}
.hist-reset { margin-left: auto; }
.hist-pages {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    flex-wrap: wrap;
    margin-top: var(--space-4);
}
/* KPI en alerte (échecs de connexion > 0). */
.stat .value.is-danger { color: var(--danger); }
/* Fix dark mode (bug préexistant, révélé par l'audit de la page) : le panneau
   « Voir détails » utilisait --bg-light, jamais redéfini en sombre → panneau
   BLANC avec du texte clair illisible. Override explicite, pattern CLAUDE.md. */
[data-theme="dark"] .hist-detail-body { background: var(--hover); }

/* ============================================================
   Mon profil — bannière de couverture (evol24) + carte identité
   Partagé par les 3 espaces (Stagiaire / Encadrant / Admin-Resp).
   Mobile-first ; AJOUTS uniquement, aucune classe existante touchée.
   ============================================================ */
.profil-hero { padding: 0; overflow: hidden; }
/* Variante consultation (fiche Admin) : bannière seule, espacée du bloc suivant. */
.profil-hero--view { margin-bottom: var(--space-4); }
.profil-cover {
    position: relative;
    height: clamp(120px, 24vw, 200px);
    background: linear-gradient(115deg, var(--primary-blue), var(--primary-green));
    background-size: cover;
    background-position: center;
}
.profil-cover.has-img { background-color: var(--hover); }
.profil-cover-form { position: absolute; top: 10px; right: 10px; display: flex; gap: 8px; }
/* L'input reste accessible aux lecteurs d'écran / no-JS via son label. */
.profil-cover-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}
.profil-cover-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-height: 34px;
    padding: 6px 12px;
    border-radius: 999px;
    border: 1px solid rgba(255, 255, 255, .35);
    background: rgba(15, 23, 42, .55);
    color: #fff;
    font-size: var(--fs-xs);
    font-weight: 700;
    cursor: pointer;
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
}
.profil-cover-btn .ic { width: 15px; height: 15px; }
/* display:inline-flex ci-dessus battrait le [hidden] du navigateur (règle UA) :
   sans cet override, le ✓ « fallback no-JS » resterait VISIBLE après que le JS
   l'a masqué. */
.profil-cover-btn[hidden] { display: none; }
@media (hover: hover) and (pointer: fine) {
    .profil-cover-btn:hover { background: rgba(15, 23, 42, .78); }
}
.profil-cover-del, .profil-cover-save { padding: 6px 11px; }
.profil-head {
    display: flex;
    align-items: flex-end;
    gap: var(--space-4);
    padding: 0 var(--space-5) var(--space-4);
}
/* Dimensions EXPLICITES sur le wrap : les initiales internes sont en
   position:absolute — sans elles, le wrap s'effondre à 0 (avatar invisible)
   dès que l'utilisateur n'a pas de photo. */
.profil-avatar-wrap { position: relative; margin-top: -46px; flex: 0 0 auto; width: 92px; height: 92px; }
.profil-avatar {
    width: 92px;
    height: 92px;
    border-radius: 50%;
    object-fit: cover;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 32px;
    font-weight: 800;
    background: var(--primary-light);
    color: var(--primary);
    box-shadow: 0 0 0 4px var(--surface), var(--shadow);
}
.profil-avatar[hidden] { display: none; }
.profil-initials { position: absolute; inset: 0; }
.profil-id { min-width: 0; padding-top: var(--space-3); }
.profil-name {
    font-family: var(--font-head);
    font-size: var(--fs-xl);
    font-weight: 800;
    line-height: var(--lh-tight);
    overflow-wrap: anywhere;
}
.profil-sub { color: var(--muted); font-size: var(--fs-sm); }
.profil-cols { margin-top: var(--space-4); }
.profil-card-photo { max-width: 340px; }
.profil-sep { border: none; border-top: 1px solid var(--border); margin: 18px 0; }
.profil-security {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.profil-security-title { margin: 0 0 2px; font-size: var(--fs-md, 15px); }
.profil-security-title .ic { width: 15px; height: 15px; vertical-align: -2px; color: var(--primary); }

/* Habillage français des inputs fichier (JS ipfps — .ffr ajouté au chargement). */
.ffr { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ffr-name { font-size: var(--fs-xs); overflow-wrap: anywhere; }
input.ffr-input {
    position: absolute;
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;
}

@media (max-width: 768px) {
    .profil-head { padding: 0 var(--space-4) var(--space-4); }
    .profil-avatar-wrap { margin-top: -38px; width: 78px; height: 78px; }
    .profil-avatar { width: 78px; height: 78px; font-size: 26px; }
    .profil-card-photo { max-width: none; }
    .profil-cover-btn-txt { display: none; }         /* pastille icône seule */
    .profil-cover-btn { min-width: 44px; min-height: 44px; justify-content: center; }
    .profil-security { align-items: flex-start; flex-direction: column; }
    .profil-security .btn { width: 100%; justify-content: center; }
}
@media (max-width: 480px) {
    .profil-head { flex-direction: column; align-items: flex-start; gap: 4px; }
    .profil-id { padding-top: 0; }
}

/* ============================================================
   Messagerie — outils de boîte (recherche + chips), accusés de
   lecture, cartes mobiles des Envoyés (AJOUTS uniquement)
   ============================================================ */
.msg-tools {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: var(--space-4);
}
.msg-search { display: flex; gap: 8px; flex: 1 1 260px; max-width: 420px; margin: 0; }
.msg-search .form-control { flex: 1; min-width: 0; }
.msg-chips { display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap; }

/* Accusés de lecture (expéditeur) */
.msg-reads { margin-top: var(--space-4); }
.msg-reads-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
}
.msg-reads-head h3 { margin: 0; }
.msg-reads-head .ic { width: 16px; height: 16px; vertical-align: -2px; color: var(--primary); }
.msg-reads-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.msg-reads-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    padding: 8px 2px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.msg-reads-item:last-child { border-bottom: 0; }
.msg-reads-nom { font-weight: 600; min-width: 0; overflow-wrap: anywhere; }

/* Cartes mobiles des « Envoyés » : réutilise le patron .mobile-inbox-item. */
.msg-sent-item { cursor: default; }
.msg-sent-actions { display: flex; gap: 8px; margin-top: 8px; align-items: center; }

/* =====================================================================
   Bulletins PRO (evol29) — page /bulletins
   ===================================================================== */
.bul-ident { display: flex; align-items: center; gap: 10px; min-width: 0; }
.bul-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.bul-actions .inline-form { display: inline-flex; }
.bul-motifs { margin: 4px 0 0; max-width: 260px; font-size: var(--fs-xs); }
.btn.btn-disabled { opacity: .5; pointer-events: none; }
.bul-manuel { margin-top: 6px; }
.bul-manuel summary { cursor: pointer; }
/* Ligne d'une saisie manuelle existante (libellé + moyennes + suppression). */
.bul-manuel-ligne {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    max-width: 520px;
}
.bul-manuel-ligne .hint { margin: 0; flex: 1 1 auto; min-width: 0; }
.bul-manuel-form {
    display: grid;
    grid-template-columns: minmax(110px, 1.4fr) repeat(3, minmax(70px, 1fr)) auto;
    gap: 6px;
    align-items: center;
    margin-top: 8px;
    max-width: 520px;
}
@media (max-width: 768px) {
    .bul-manuel-form { grid-template-columns: 1fr 1fr; }
    .bul-manuel-form input[name="libelle"] { grid-column: 1 / -1; }
    .bul-actions { width: 100%; }
    .bul-actions .btn { flex: 1 1 auto; justify-content: center; }
}

/* Validation groupée des notes (Admin) — bandeau au-dessus de la liste. */
.val-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: 18px;
    border-left: 4px solid var(--primary-green);
}
.val-bar-info { display: flex; align-items: center; gap: 10px; min-width: 0; }
.val-bar-info .ic { color: var(--primary-green); flex: 0 0 auto; }
@media (max-width: 768px) {
    .val-bar .btn { width: 100%; justify-content: center; }
}

/* Groupement par stagiaire (filtre Décision de la page Notes). La 2e règle
   neutralise la 1re colonne collante mobile — même piège que tr.hist-day. */
tr.note-grp td,
table.data:not(.pay-table):not(.data-cards) tr.note-grp td:first-child {
    position: static;
    background: var(--th-bg);
    font-weight: 700;
    padding: 8px 12px;
    border-top: 2px solid var(--border);
}
tr.note-grp .ic { width: 15px; height: 15px; vertical-align: -2px; color: var(--primary); margin-right: 4px; }
.note-grp-cls { font-weight: 400; color: var(--muted); font-size: var(--fs-xs); margin-left: 10px; }
.note-grp-count {
    float: right;
    font-size: var(--fs-xs);
    font-weight: 600;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 2px 10px;
}
.note-grp-count.is-bad { color: var(--danger); border-color: var(--danger); }

/* Suivi de saisie des notes (page Notes, classe choisie). */
.suivi-card { margin-bottom: 18px; }
.suivi-head { cursor: pointer; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.suivi-head .ic { color: var(--primary); flex: 0 0 auto; }
.suivi-alert {
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--danger);
    border: 1px solid var(--danger);
    border-radius: 999px;
    padding: 2px 10px;
}
.suivi-ok { font-size: var(--fs-xs); font-weight: 600; color: var(--success); }
.suivi-list { list-style: none; margin: var(--space-3) 0 0; padding: 0; }
.suivi-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: 8px 2px;
    border-bottom: 1px solid var(--border);
    flex-wrap: wrap;
}
.suivi-item:last-child { border-bottom: 0; }
.suivi-nom { font-weight: 600; flex: 1 1 220px; min-width: 0; overflow-wrap: anywhere; }
.suivi-badge { flex: 0 0 auto; }
.badge-warn { background: var(--warning); color: #1f2937; }

/* Tranches d'ancienneté de la dette cliquables (page Finances). */
.fin-age-btn { background: none; border: 0; padding: 0; margin: 0; cursor: pointer; border-radius: 999px; }
.fin-age-btn:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
@media (hover: hover) and (pointer: fine) {
    .fin-age-btn:hover .fin-risk { box-shadow: var(--shadow); text-decoration: underline; }
}

/* Envoyés : rangées de chips étiquetées + progression de lecture. */
.msg-chips-row { align-items: center; }
.msg-chips-lbl {
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--muted);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wide, .04em);
    margin-right: 2px;
}
.msg-chips-sep { width: 1px; height: 18px; background: var(--border); margin: 0 6px; }
.msg-prog { display: inline-flex; align-items: center; gap: 8px; min-width: 110px; }
.msg-prog-bar {
    flex: 1 1 64px; height: 7px; border-radius: 999px;
    background: var(--hover); overflow: hidden; min-width: 56px;
}
.msg-prog-fill { display: block; height: 100%; border-radius: 999px; background: var(--muted); }
.msg-prog.is-full .msg-prog-fill { background: var(--primary-green); }
.msg-prog.is-part .msg-prog-fill { background: var(--warning); }
.msg-prog-txt { font-size: var(--fs-xs); font-weight: 700; white-space: nowrap; }

/* =====================================================================
   Loader « ECG » (moniteur cardiaque) — voile plein écran affiché à
   l'OUVERTURE de l'app (PWA, nouvel onglet, F5, arrivée après connexion) et
   sur les navigations depuis le MENU LATÉRAL uniquement. Logo IPFPS original
   au centre, tracé qui se dessine de gauche à droite derrière lui, micro
   pulsation du logo au passage de la grande pointe.

   N'ANNULE RIEN : .nav-progress et les squelettes .skel* restent en place et
   gardent leur rôle (attente serveur / arrivée du contenu dans la page).
   Le minimum d'affichage du voile (1200 ms, une boucle complète) étant
   supérieur au minimum des squelettes (450 ms), ceux-ci terminent leur cycle
   DERRIÈRE le voile et ne sont jamais perçus — donc aucun double chargement
   et rien à désactiver.

   Palette : couleurs réelles du fichier logo.svg, déclarées en variables sur
   .ldr (même convention que .ipfps-hero) — aucune couleur en dur dans les
   règles. Fond thème-aware : blanc très légèrement bleuté en clair, bleu nuit
   en sombre (un flash blanc en thème sombre serait agressif). Ni plaque ni
   halo derrière le logo : vérifié au rendu, il se lit seul sur le fond sombre
   (#1076A1 sur #0b1220 = 3.67:1, au-dessus du seuil de 3:1 applicable à un
   élément graphique) — toute pastille se lisait comme un bloc en trop.
   ===================================================================== */
.ldr {
    --ldr-teal: #1076A1;   /* bleu du logo */
    --ldr-sky:  #8CC0D2;   /* cyan du logo */
    --ldr-lime: #96C93F;   /* vert du logo */
    --ldr-bg: #f6fafd;
    --ldr-base: rgba(16, 118, 161, .12);

    position: fixed;
    inset: 0;
    height: 100dvh;        /* barres mobiles : évite le décalage de 100vh (ignoré si non supporté → inset:0) */
    z-index: 2000;         /* au-dessus de tout le chrome fixe (galerie plein écran = 1200) */
    display: none;
    align-items: center;
    justify-content: center;
    background: var(--ldr-bg);
    overscroll-behavior: contain;
    padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
html[data-theme="dark"] .ldr {
    --ldr-bg: var(--bg-dark);
    --ldr-base: rgba(140, 192, 210, .15);
    --ldr-glow-op: .3;
}

/* Affichage piloté par <html class="ldr-on"> (posée en <head>, donc AVANT le
   premier rendu : aucun flash de contenu). Filet de sécurité : le voile se
   retire tout seul après 8 s si le JS n'a pas pu le faire — l'utilisateur
   n'est jamais bloqué. */
html.ldr-on .ldr {
    display: flex;
    animation: ldrFailsafe 0s linear 8s forwards;
}
/* Sortie (posée par app.js quand la page est prête). Placée APRÈS la règle
   ci-dessus : même spécificité, l'ordre tranche. */
html.ldr-out .ldr {
    display: flex;
    animation: ldrFade .22s ease forwards;
}
@keyframes ldrFailsafe { to { opacity: 0; visibility: hidden; } }
@keyframes ldrFade { to { opacity: 0; visibility: hidden; } }

/* --- Tracé ------------------------------------------------------------
   preserveAspectRatio="none" + vector-effect:non-scaling-stroke : le tracé
   occupe toute la largeur de l'écran, tandis que l'épaisseur du trait ne
   varie jamais. La grande pointe est à x=350 du viewBox, soit 29 % de la
   largeur : au centre elle serait masquée par le logo. Marge mesurée entre
   la pointe et le bord du logo : 19 px à 320 px de large, 31 px à 390 px,
   175 px à 1280 px — elle n'est jamais recouverte.
   ⚠️ Trois valeurs sont liées et doivent bouger ensemble : la position de la
   pointe dans le chemin (loader.php), le plancher de .ldr-logo ci-dessous et
   le pic de @keyframes ldrPulse. */
.ldr-ecg {
    position: absolute;
    left: 0; right: 0;
    top: 50%;
    transform: translateY(-50%);
    width: 100%;
    height: clamp(88px, 19vh, 190px);
    display: block;
}
.ldr-ecg path {
    fill: none;
    stroke-linecap: round;
    stroke-linejoin: round;
    vector-effect: non-scaling-stroke;
}
.ldr-ecg-base { stroke: var(--ldr-base); stroke-width: 1.5; }

/* Segment qui parcourt le chemin : le tracé n'est JAMAIS visible en entier.
   pathLength=1000 sur le <path> → 300 visibles, 1300 de vide : une seule
   passe par boucle, entrée par la gauche et sortie par la droite. */
.ldr-ecg-beat,
.ldr-ecg-glow {
    stroke: url(#ldrGrad);
    stroke-dasharray: 300 1300;
    stroke-dashoffset: 300;
    animation: ldrSweep 1.2s linear infinite;
}
.ldr-ecg-beat { stroke-width: 3.2; }
.ldr-ecg-glow { stroke-width: 12; opacity: var(--ldr-glow-op, .2); }
@keyframes ldrSweep { to { stroke-dashoffset: -1300; } }

/* --- Logo -------------------------------------------------------------
   Pulsation calée sur la boucle du tracé (même durée, mêmes départs → aucune
   synchronisation JS). Le segment franchit la grande pointe vers 19 % de la
   boucle (position ~295/1000 du chemin, segment de 300 sur une course de
   1600) : le pic est calé là, ~240 ms au total, volontairement à peine
   perceptible. Jamais de rotation ni de déformation. */
.ldr-logo {
    position: relative;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    width: clamp(96px, 26vw, 184px);
    height: clamp(96px, 26vw, 184px);
    padding: clamp(10px, 2.4vw, 16px);
    animation: ldrPulse 1.2s ease-in-out infinite;
    will-change: transform;
}
.ldr-logo img { width: 100%; height: 100%; object-fit: contain; display: block; }
@keyframes ldrPulse {
    0%, 12%   { transform: scale(1); }
    19%       { transform: scale(1.035); }
    32%, 100% { transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
    .ldr-ecg-beat {
        animation: none;
        stroke-dasharray: none;
        stroke-dashoffset: 0;
        opacity: .3;
    }
    .ldr-ecg-glow { display: none; }
    .ldr-logo { animation: none; }
    html.ldr-out .ldr { animation-duration: .01s; }
}
