/* =========================================================
   THOMATHS — PAGE DOCUMENT (cours / TD / DM / DS)
   Sommaire + ressources dans .sidebar (gauche), corps du
   document en blocs colorés par type (définition, prop,
   preuve, remarque/attention, exemple, exercice).
   Consomme uniquement les tokens (tokens.css).
   ========================================================= */

/* La classe .sheet-card reste sur .sheet-detail-info pour le JS
   (closest('.sheet-card')), mais son style de "carte" (fond/bordure/marge,
   de sheet_card.css) est neutralisé ici : le bandeau .doc-header a déjà son
   propre fond, une carte autour créait un double encadré. */
.sheet-detail-info.sheet-card {
    background: transparent;
    border: none;
    margin-bottom: 0;
    border-radius: 0;
}

/* ---------- Panneau gauche (sommaire) ---------- */
.doc-side {
    display: flex;
    flex-direction: column;
    gap: 20px;
    padding: 16px 14px;
}
.doc-side-back {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 0.82rem;
    color: var(--text-muted);
    text-decoration: none;
    margin-bottom: 2px;
}
.doc-side-back svg { width: 14px; height: 14px; }
.doc-side-back:hover { color: var(--text); }

.doc-side-label {
    font-size: 0.7rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-faint);
    margin-bottom: 8px;
}
.doc-side-empty { font-size: 0.82rem; color: var(--text-faint); font-style: italic; }

/* Sommaire : pas de puces. Chaque sous-entrée active devient une zone
   carrée à coins arrondis (fond accent). Le titre de section (l1) porte un
   séparateur horizontal juste en dessous de son nom, qui passe en bleu tant
   qu'on est dans cette section (elle reste "active" tant que son bandeau
   reste épinglé, cf. updateActiveToc) ; un second séparateur, neutre,
   marque la fin de chaque section (au-dessus du titre suivant). */
.doc-toc-jump-btn {
    width: 100%;
    display: flex; align-items: center; justify-content: center; gap: 4px;
    padding: 5px 8px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: 0.78rem;
    cursor: pointer;
}
.doc-toc-jump-btn:hover { background: var(--surface-3); color: var(--text); }
.doc-toc-jump-top { margin-bottom: 8px; }
.doc-toc-jump-bottom { margin-top: 8px; }
/* Cible de dépose "début"/"fin" pendant un drag de bloc (cf. sheet_detail.html) —
   mêmes boutons que la navigation rapide Début/Fin, réutilisés comme zones de
   dépose pour envoyer un bloc en tête/queue de la feuille sans avoir à scroller. */
.doc-toc-jump-btn.doc-toc-drop-target {
    background: var(--accent-soft);
    color: var(--accent);
    outline: 2px dashed var(--accent);
    outline-offset: -2px;
}
.doc-toc { display: flex; flex-direction: column; gap: 0; }
.doc-toc-item {
    display: flex;
    align-items: baseline;
    gap: 7px;
    padding: 6px 10px;
    border-radius: 8px;
    font-size: 0.85rem;
    color: var(--text-muted);
    text-decoration: none;
    line-height: 1.3;
}
.doc-toc-item:hover { background: var(--surface-2); color: var(--text); }
/* Cible de dépose pendant un drag de bloc (cf. sheet_detail.html,
   dragover du sommaire) — distincte de .active pour rester visible même
   sur l'entrée actuellement affichée à l'écran. */
.doc-toc-item.doc-toc-drop-target {
    background: var(--accent-soft);
    outline: 2px dashed var(--accent);
    outline-offset: -2px;
}
.doc-toc-item.active {
    background: var(--accent-soft);
    color: var(--accent);
    font-weight: 600;
}
.doc-toc-num { color: inherit; flex: 0 0 auto; font-variant-numeric: tabular-nums; }
.doc-toc-l1 {
    font-weight: 600;
    padding-bottom: 8px;
    margin-bottom: 6px;
    border-bottom: 1px solid var(--border-soft);
}
.doc-toc-l1.active {
    background: transparent;
    color: var(--text);
    border-bottom-color: var(--accent);
}
.doc-toc-l1:not(:first-child) {
    border-top: 1px solid var(--border-soft);
    padding-top: 10px;
    margin-top: 8px;
}
.doc-toc-l2 { padding-left: 20px; font-size: 0.8rem; }

.doc-resources { display: flex; flex-direction: column; gap: 4px; }
.doc-resource {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 9px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-soft);
    background: var(--surface);
    font-size: 0.82rem;
    color: var(--text-muted);
    text-decoration: none;
}
.doc-resource:hover { border-color: var(--border); color: var(--text); }
.doc-resource-count {
    font-size: 0.72rem;
    font-weight: 600;
    color: var(--text-faint);
    background: var(--surface-3);
    border-radius: 999px;
    padding: 1px 7px;
}

.doc-side-chapter { font-family: var(--font-head); font-size: 0.95rem; color: var(--text); margin-bottom: 8px; }
.doc-side-tracks { display: flex; flex-wrap: wrap; gap: 6px; }

/* ---------- Navigation chapitre précédent/suivant ---------- */
.doc-chapnav {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 18px;
}
.doc-chapnav-bottom { margin-top: 28px; margin-bottom: 0; }
.doc-chapnav-link {
    display: flex;
    flex-direction: column;
    gap: 2px;
    text-decoration: none;
    padding: 4px 0;
    max-width: 45%;
}
.doc-chapnav-prev { text-align: left; }
.doc-chapnav-next { text-align: right; margin-left: auto; }
.doc-chapnav-dir { font-size: 0.74rem; color: var(--text-faint); }
.doc-chapnav-title { font-size: 0.88rem; color: var(--text-muted); }
.doc-chapnav-link:hover .doc-chapnav-title { color: var(--accent); }

/* ---------- Bandeau de métadonnées du document (sous le .doc-header) ---------- */
.doc-title-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.doc-title {
    font-family: var(--font-head);
    font-size: 1.7rem;
    font-weight: 600;
    color: var(--text);
    margin: 0;
    min-width: 0;
}
.doc-title-tracks { display: flex; gap: 6px; margin-left: auto; }

.doc-subtitle-row {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 8px;
    font-size: 0.82rem;
    color: var(--text-faint);
}
.doc-meta-sep { color: var(--border); }
.doc-meta { color: var(--text-muted); }

.doc-meta-empty { color: var(--text-faint); }

@media (max-width: 860px) {
    .doc-meta-grid { max-width: none; gap: 14px 20px; }
}

.doc-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 14px; }
.doc-actions-admin { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.doc-act-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: 0.82rem;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.doc-act-btn svg { width: 14px; height: 14px; }
.doc-act-btn:hover { background: var(--surface-3); color: var(--text); border-color: var(--border); }
.doc-act-pdf { color: var(--danger); border-color: rgba(217, 83, 79, 0.35); }
.doc-act-pdf:hover { background: rgba(217, 83, 79, 0.1); }
.doc-flag {
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: transparent;
    color: var(--text-faint);
    font-size: 0.78rem;
    cursor: pointer;
}
.doc-flag.active { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); }

/* ---------- Corps du document ---------- */
.doc-body { display: flex; flex-direction: column; gap: 16px; }
/* Les blocs d'une section/sous-section sont imbriqués dans ces wrappers
   (regroupForSticky, sheet_detail.html) : ce ne sont donc plus des enfants
   flex DIRECTS de .doc-body, il faut leur redonner le même espacement. */
.doc-section-group, .doc-subsection-group { display: flex; flex-direction: column; gap: 16px; }
.doc-block { position: relative; }
/* Retour visuel du couper — même comportement que .exercise-card.cut-pending
   (theme.css), uniformisé pour tous les types de bloc (section, environnement,
   texte, preuve…), pas seulement les exercices. */
.sheet-block.cut-pending { opacity: 0.4; }
/* Sélection (clic sur le bloc) : recolore la VRAIE bordure existante
   (border-color), pas un box-shadow/outline superposé à côté — donc même
   niveau (le rendu est littéralement la bordure du bloc) et même couleur
   que le liseré gauche déjà là (--kind-border, posée par kind ci-dessous),
   qui se prolonge simplement sur les 3 autres côtés au lieu de rester
   confinée à gauche. Fallback sur l'accent pour les blocs sans couleur de
   kind (exercice, section, texte…), qui n'ont pas de bordure du tout par
   défaut : on leur réserve une bordure transparente 1px pour que la
   sélection ne fasse que la recolorer, sans décaler la mise en page.
   Spécificité volontairement basse (0,1,0, pas de :not() chaîné) : un
   :not() par classe exclue AJOUTE sa propre spécificité, deux :not()
   chaînés faisaient grimper cette règle à 0,3,0 et l'emportaient à tort
   sur .sheet-block.selected (0,2,0) ET sur border-bottom de
   .doc-section/.doc-subsection (0,1,0) — plus rien ne se voyait en
   sélectionnant une section/sous-section/texte, et leur séparateur
   restait invisible même non sélectionnés. */
.sheet-block { border: 1px solid transparent; border-radius: var(--radius); }
/* Placeholder d'un bloc pas encore chargé (cf. views/sheets.py::detail,
   `lazy_enabled`/`eager_count` — chargement différé des blocs hors écran sur
   une feuille dense) : remplacé en place par sheet_detail.html une fois son
   HTML réel rapatrié en arrière-plan. Hauteur approximative pour limiter le
   saut visuel au remplacement (surtout notable pour un exercice, plus haut
   qu'un titre de section). */
.doc-block-lazy { padding: 14px 0; }
.doc-block-lazy-skeleton {
    height: 1.1em;
    width: 60%;
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    animation: doc-block-lazy-pulse 1.4s ease-in-out infinite;
}
.doc-block-lazy-skeleton-title { height: 1.4em; width: 40%; }
.doc-block-lazy-skeleton-exo { height: 5em; width: 100%; }
@keyframes doc-block-lazy-pulse {
    0%, 100% { opacity: 0.5; }
    50% { opacity: 0.9; }
}
/* .doc-exo-embed n'a pas de bordure propre (padding:0, pur wrapper autour
   de .exercise-card, qui a déjà la sienne — cf. exercise_card.css) : la
   bordure transparente réservée ci-dessus lui suffit (jamais recolorée),
   sinon elle ferait un double cadre autour de la carte. La sélection
   recolore donc directement celle de .exercise-card, seul cadre visible. */
.sheet-block.selected:not(.doc-exo-embed) { border-color: var(--kind-border, var(--accent)); }
.sheet-block.doc-exo-embed.selected .exercise-card { border-color: var(--kind-border, var(--accent)); }
/* Section/sous-section : contour carré (pas de border-radius hérité de
   .sheet-block), pour les distinguer visuellement des blocs de contenu. */
.sheet-block.doc-section, .sheet-block.doc-subsection { border-radius: 0; }
/* Multisélection (plage shift+clic, cf. core/card_nav.js) : même traitement
   que .selected (recolore la bordure réservée ci-dessus) + un fond léger
   pour que la plage entière reste lisible d'un coup d'œil, y compris les
   blocs "du milieu" qui n'ont pas le focus clavier (`selected`). */
.sheet-block.multi-selected:not(.doc-exo-embed) { border-color: var(--kind-border, var(--accent)); background: var(--accent-soft); }
.sheet-block.doc-exo-embed.multi-selected .exercise-card { border-color: var(--kind-border, var(--accent)); background: var(--accent-soft); }

/* ---------- Drag groupé (multisélection, cf. core/drag_reorder.js
   getGroup) : un seul bloc flotte réellement (`item`, exactement comme un
   drag solo — jamais reparenté pendant le drag, cf. commentaire dans
   drag_reorder.js), les autres membres du groupe sont juste masqués
   (display:none) le temps du geste. Le badge, posé en enfant de `item`
   (déjà en position:fixed pendant le drag), indique combien de blocs
   suivront au lâcher. ---------- */
.drag-group-badge {
    position: absolute;
    top: -10px;
    right: -10px;
    min-width: 22px;
    height: 22px;
    padding: 0 6px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--surface);
    font-size: 0.75rem;
    font-weight: 600;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}

.doc-section-title, .doc-subsection-title {
    font-family: var(--font-head);
    color: var(--text);
    display: flex;
    align-items: baseline;
    gap: 8px;
}
/* Le .doc-editable ne prend par défaut que la largeur de son texte (item
   flex non étiré) — sans ça, l'<input> d'édition inline (width:100% de son
   parent, cf. .inline-edit-input) hérite de cette étroitesse au lieu de
   profiter de toute la largeur du titre. Sans effet visuel hors édition (le
   texte reste aligné à gauche). Même pattern que .dp-title-text
   (sheet_detail.css). */
.doc-section-title .doc-editable,
.doc-subsection-title .doc-editable {
    flex: 1 1 0;
    min-width: 0;
}
/* Sans box-sizing:border-box, le width:100% de .inline-edit-input (padding
   1px 5px + bordure 1px) déborde de ~12px hors du titre — même correctif
   ponctuel que .doc-header-subtitle-row (doc.css), pas touché à la règle
   globale (utilisée aussi par des champs volontairement étroits ailleurs). */
.doc-section-title .inline-edit-input,
.doc-subsection-title .inline-edit-input {
    width: 100%;
    box-sizing: border-box;
}
/* La section et la sous-section "en cours" restent épinglées en haut au
   scroll (même principe que .doc-semester-title sur la page Cours & TD).
   Chaque section (et chaque sous-section) est enveloppée côté client dans un
   wrapper .doc-section-group/.doc-subsection-group avec tout son contenu
   (regroupForSticky, sheet_detail.html) : le navigateur décolle nativement
   le titre précédent dès que son wrapper se termine (le "push" est donc du
   sticky natif, pas du JS de bascule) — même mécanisme que .doc-semester-group
   + <details> sur Cours & TD, juste construit dynamiquement plutôt qu'au
   template, pour ne pas toucher au modèle de données à plat (positions,
   drag-and-drop, export LaTeX). */
.doc-section, .doc-subsection {
    /* Padding vertical : sans lui, le titre colle au bord haut de la boîte —
       une fois épinglée juste sous la section (top calculé pour un écart
       nul), le box-shadow de 10px de la section (ci-dessous) peint alors
       par-dessus le haut du texte de la sous-section plutôt que sur du vide. */
    padding: 8px 0;
    /* Même fond (couleur + dégradé + bruit) que le body, en background-attachment
       fixed : ça s'aligne pixel pour pixel avec le fond réel de la page, quel
       que soit l'endroit du dégradé où le bandeau se trouve une fois épinglé. */
    background-color: var(--bg);
    background-image:
        radial-gradient(1100px 650px at 100% 100%, rgba(109, 123, 255, 0.035), transparent 60%),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.02 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
        linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 480px, var(--bg) 100%);
    background-attachment: fixed;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    position: sticky;
    /* PAS de transition sur top : --compact-bar-h (posé en JS) doit décaler
       la section/sous-section épinglée EXACTEMENT en même temps que la barre
       compacte apparaît, sinon un écart transitoire laisse entrevoir le
       contenu déjà scrollé pendant que top rattrape sa nouvelle valeur. */
    /* Séparateur visible une fois épinglé, sinon le titre "flotte" au-dessus
       du contenu qui défile juste en dessous, sans limite nette. Un peu
       d'espace après la ligne pour ne pas coller le contenu suivant. */
    border-bottom: 1px solid var(--border);
    margin-bottom: 8px;
}
/* Étend la zone opaque de quelques px sous le bandeau, sur toute sa largeur,
   pour masquer la bordure d'un bloc (sélection couper/copier, environnement…)
   qui défile juste en dessous et pourrait dépasser sous le bandeau épinglé
   au pixel près. Un ::after (plutôt qu'un box-shadow façon la version
   précédente) reprend le même dégradé/bruit que le fond de .doc-section :
   un box-shadow, teinté uniquement en var(--bg) uni, tranchait visiblement
   sur le dégradé linéaire (var(--bg-top) → var(--bg)) quand la section est
   proche du haut de page — d'où une bande sombre visible sous le trait.
   Seulement sur .doc-section : la sous-section, elle, n'a rien d'autre de
   sticky juste après elle pour masquer ce même débordement — ce même bandeau
   y peindrait alors une bande visible en permanence sur le contenu normal
   qui suit. */
.doc-section::after {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 100%;
    height: 10px;
    background-color: var(--bg);
    background-image:
        radial-gradient(1100px 650px at 100% 100%, rgba(109, 123, 255, 0.035), transparent 60%),
        url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.02 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"),
        linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 480px, var(--bg) 100%);
    background-attachment: fixed;
    pointer-events: none;
}
/* --compact-bar-h (posée par sheets.js sur .doc-chapters-scroll) vaut la
   hauteur réelle de la barre compacte quand elle est révélée, 0 sinon : les
   sections/sous-sections épinglées se décalent d'autant pour ne jamais se
   retrouver recouvertes par la barre (même z-index d'empilement : 20 pour
   la barre, cf. .doc-compact-bar-wrap). */
.doc-section {
    margin-top: 2px;
    top: var(--compact-bar-h, 0px);
    z-index: 3;
    scroll-margin-top: 2px;
}
/* La sous-section colle juste sous la hauteur réelle de la section
   (mesurée en JS et posée dans --sub-top) : pas d'espace mort entre les
   deux quand les deux sont épinglées. scroll-margin-top reprend la même
   variable pour que le clic dans le sommaire n'atterrisse pas sous la
   section restée épinglée au-dessus.
   z-index supérieur à .doc-section : une fois les deux épinglées bord à
   bord (sans espace), le box-shadow de la section (qui déborde de 10px
   sous elle, cf. plus haut) chevauche le haut de la sous-section — sans
   ce z-index plus élevé, cette bande peinte passait par-dessus le titre/
   la bordure de la sous-section au lieu de rester dessous. */
.doc-subsection {
    padding-bottom: 0;
    top: calc(var(--compact-bar-h, 0px) + var(--sub-top, 40px));
    z-index: 4;
    scroll-margin-top: var(--sub-top, 40px);
}
.doc-section-title { font-size: 22px; font-weight: 600; margin: 0; }
.doc-subsection-title { font-size: 19px; font-weight: 600; margin: 0; }
.doc-section-num { color: var(--text); font-weight: 600; }

.doc-text { color: var(--text-muted); }
/* Le bloc "Latex brut" n'est pas un .doc-env (pas de cadre coloré), donc son
   en-tête "Latex brut" n'hérite pas du padding 14px 18px de .doc-env — sans
   ça il colle au bord gauche/haut au lieu d'être aligné comme le libellé
   "Exemple" d'un vrai bloc pédagogique. */
.doc-text > .doc-env-head { padding: 14px 18px 0; margin-bottom: 8px; }
/* Le contenu (prose ou CodeMirror en édition) suit le même padding
   horizontal que .doc-env, pour ne pas coller au bord de la card. */
.doc-text > .content.doc-prose { padding: 0 18px 14px; }
.doc-prose { font-family: Charter, 'Bitstream Charter', 'Sitka Text', Cambria, Georgia, serif; font-size: 13px; line-height: 1.65; color: var(--text); }
.doc-prose p:first-child { margin-top: 0; }
.doc-prose p:last-child { margin-bottom: 0; }
.doc-prose.doc-editable:empty { min-height: 1.6em; cursor: text; }
.doc-prose.doc-editable:empty::before {
    content: "À compléter…";
    color: var(--text-muted);
    font-style: italic;
}
/* Mode élève : "À compléter…" est une invite d'édition (comme "+ titre",
   cf. .doc-env-title.empty plus bas) — un élève ne doit jamais la voir,
   ni garder la réserve de hauteur d'un champ vide à éditer. */
body.mode-eleve .doc-prose.doc-editable:empty { min-height: 0; }
body.mode-eleve .doc-prose.doc-editable:empty::before { content: none; }

/* ---------- Blocs pédagogiques colorés (définition / prop / preuve / exemple / remarque) ---------- */
.doc-env {
    border-radius: var(--radius);
    border: 1px solid var(--border-soft);
    background: var(--surface);
    border-left: 3px solid var(--border);
    padding: 14px 18px;
}
.doc-env-head { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.doc-env-icon { display: inline-flex; width: 18px; height: 18px; flex: 0 0 auto; }
.doc-env-icon svg { width: 100%; height: 100%; }
.doc-env-label {
    font-size: 0.72rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.06em;
}
.doc-env-title { font-size: 0.88rem; color: var(--text-muted); font-style: italic; }
/* Placeholder "+ titre" (champ vide) : uniquement visible en mode édition,
   comme "+ sous-titre" pour la feuille (cf. .sheet-subtitle-empty-hint). */
.doc-env-title.empty { display: none; color: var(--text-faint); }
body.mode-admin .doc-env-title.empty { display: inline; }
/* width: fit-content — .doc-env-head est en display:flex donc s'étire par
   défaut sur toute la largeur du bloc ; sans ça, la zone cliquable pour
   changer de type déborde largement au-delà du texte (icône+libellé, ou
   +titre s'il y en a un). */
body.mode-admin .doc-env-head-switchable { cursor: pointer; width: fit-content; }
/* En édition du titre (textarea présente) : le fit-content ci-dessus ne
   laisse aucune place pour grandir, donc on le lève ponctuellement et on
   fait grandir le titre pour occuper toute la largeur restante de la tête. */
body.mode-admin .doc-env-head-switchable:has(.inline-edit-cm-line) { width: 100%; }
.doc-env-title:has(.inline-edit-cm-line) { flex: 1 1 auto; min-width: 0; }
body.mode-admin .doc-env-head-switchable:hover .doc-env-label { text-decoration: underline dotted; }
/* Couleur du type courant dans le menu : celle de son propre ctx-color-*
   (même logique que les icônes), pas systématiquement l'accent — sinon
   "Exemple" (jaune) apparaît en bleu quand il est le type sélectionné. */
.ctx-menu button.ctx-current { font-weight: 700; color: var(--accent); }
.ctx-menu button.ctx-current.ctx-color-definition  { color: var(--content-definition); }
.ctx-menu button.ctx-current.ctx-color-proposition { color: var(--content-proposition); }
.ctx-menu button.ctx-current.ctx-color-hint        { color: var(--hint); }
.ctx-menu button.ctx-current.ctx-color-muted       { color: var(--text-muted); }
.ctx-menu button.ctx-current.ctx-color-danger      { color: var(--danger); }
.ctx-menu button.ctx-current.ctx-color-proof       { color: var(--content-proof); }
.ctx-menu button.ctx-current.ctx-color-exercise    { color: var(--content-exercise-doc); }
.ctx-menu button.ctx-current.ctx-color-intro       { color: var(--text-faint); }
.ctx-menu button.ctx-current.ctx-color-todo        { color: var(--content-exercise); }
/* Au repos (pas le type courant) : le logo reste toujours coloré comme son
   bloc, seul le texte reste blanc — sauf types "muted" (remarque, en
   pratique, divers, latex brut) qui n'ont pas de couleur de bloc propre. */
.ctx-menu button.ctx-color-intro:not(.ctx-current) svg       { color: var(--text-faint); }
.ctx-menu button.ctx-color-todo:not(.ctx-current) svg        { color: var(--content-exercise); }
.ctx-menu button.ctx-color-definition:not(.ctx-current) svg  { color: var(--content-definition); }
.ctx-menu button.ctx-color-proposition:not(.ctx-current) svg { color: var(--content-proposition); }
.ctx-menu button.ctx-color-hint:not(.ctx-current) svg        { color: var(--hint); }
.ctx-menu button.ctx-color-danger:not(.ctx-current) svg      { color: var(--danger); }
.ctx-menu button.ctx-color-proof:not(.ctx-current) svg       { color: var(--content-proof); }
.ctx-menu button.ctx-color-exercise:not(.ctx-current) svg    { color: var(--content-exercise-doc); }

/* ---------- Édition inline (double-clic) du texte des blocs ---------- */
body.mode-admin .doc-editable { cursor: text; border-radius: 4px; transition: background 0.12s; }
body.mode-admin .doc-editable:hover { background: var(--surface-2); }
.doc-editable-textarea {
    display: block;
    width: 100%;
    box-sizing: border-box;
    resize: none;
    overflow: hidden;
    background: var(--inset);
    color: var(--text);
    border: 1px solid var(--accent);
    border-radius: var(--radius-sm);
    padding: 6px 8px;
    font: inherit;
    line-height: inherit;
}
.doc-editable-textarea:focus { outline: none; }

/* Définition — violet */
/* Introduction — gris clair, isolée en tête de feuille */
.doc-env-intro {
    background: var(--surface-2);
    border-left-color: var(--text-faint);
    --kind-border: var(--text-faint);
}
.doc-env-intro .doc-env-icon, .doc-env-intro .doc-env-label { color: var(--text-muted); }

/* Sommaire (TOC) — accent bleu, même couleur que l'icône Sommaire de l'en-tête */
.doc-env-toc {
    background: var(--accent-soft);
    border-left-color: var(--accent);
    --kind-border: var(--accent);
}
.doc-env-toc .doc-env-icon, .doc-env-toc .doc-env-label { color: var(--accent); }

.doc-env-definition, .doc-env-notation {
    background: var(--content-definition-bg);
    border-left-color: var(--content-definition);
    --kind-border: var(--content-definition);
}
.doc-env-definition .doc-env-icon, .doc-env-definition .doc-env-label,
.doc-env-notation .doc-env-icon, .doc-env-notation .doc-env-label { color: var(--content-definition); }

/* Théorème / proposition / lemme / corollaire — bleu accent */
.doc-env-theoreme, .doc-env-proposition, .doc-env-lemme, .doc-env-corollaire {
    background: var(--content-proposition-bg);
    border-left-color: var(--content-proposition);
    --kind-border: var(--content-proposition);
}
.doc-env-theoreme .doc-env-icon, .doc-env-theoreme .doc-env-label,
.doc-env-proposition .doc-env-icon, .doc-env-proposition .doc-env-label,
.doc-env-lemme .doc-env-icon, .doc-env-lemme .doc-env-label,
.doc-env-corollaire .doc-env-icon, .doc-env-corollaire .doc-env-label { color: var(--content-proposition); }

/* Démonstration — bloc décalé (indenté + réduit), même couleur que l'environnement
   démontré (proposition/théorème = bleu, définition = vert, exemple = jaune…) */
.doc-env-proof {
    margin-left: 28px;
    padding: 10px 16px;
    background: var(--content-proof-bg);
    border-left-color: var(--content-proof);
    --kind-border: var(--content-proof);
}
.doc-env-proof .doc-env-icon, .doc-env-proof .doc-env-label { color: var(--content-proof); }

.doc-env-proof-definition {
    background: var(--content-definition-bg);
    border-left-color: var(--content-definition);
    --kind-border: var(--content-definition);
}
.doc-env-proof-definition .doc-env-icon, .doc-env-proof-definition .doc-env-label { color: var(--content-definition); }

.doc-env-proof-theoreme, .doc-env-proof-proposition, .doc-env-proof-lemme, .doc-env-proof-corollaire {
    background: var(--content-proposition-bg);
    border-left-color: var(--content-proposition);
    --kind-border: var(--content-proposition);
}
.doc-env-proof-theoreme .doc-env-icon, .doc-env-proof-theoreme .doc-env-label,
.doc-env-proof-proposition .doc-env-icon, .doc-env-proof-proposition .doc-env-label,
.doc-env-proof-lemme .doc-env-icon, .doc-env-proof-lemme .doc-env-label,
.doc-env-proof-corollaire .doc-env-icon, .doc-env-proof-corollaire .doc-env-label { color: var(--content-proposition); }

.doc-env-proof-exemple {
    background: var(--hint-bg);
    border-left-color: var(--hint);
    --kind-border: var(--hint);
}
.doc-env-proof-exemple .doc-env-icon, .doc-env-proof-exemple .doc-env-label { color: var(--hint); }

.doc-env-proof-remarque {
    background: var(--surface-2);
    border-left-color: var(--text-faint);
    --kind-border: var(--text-faint);
}
.doc-env-proof-remarque .doc-env-icon, .doc-env-proof-remarque .doc-env-label { color: var(--text-muted); }

.doc-env-proof-attention {
    background: rgba(217, 83, 79, 0.08);
    border-left-color: var(--danger);
    --kind-border: var(--danger);
}
.doc-env-proof-attention .doc-env-icon, .doc-env-proof-attention .doc-env-label { color: var(--danger); }

/* Exemple — jaune (hint) */
.doc-env-exemple {
    background: var(--hint-bg);
    border-left-color: var(--hint);
    --kind-border: var(--hint);
}
.doc-env-exemple .doc-env-icon, .doc-env-exemple .doc-env-label { color: var(--hint); }

/* Remarque — gris neutre (distinct des couleurs "de contenu") */
.doc-env-remarque {
    background: var(--surface-2);
    border-left-color: var(--text-faint);
    --kind-border: var(--text-faint);
}
.doc-env-remarque .doc-env-icon, .doc-env-remarque .doc-env-label { color: var(--text-muted); }

/* En pratique — même couleur que Remarque (cf. demande utilisateur) */
.doc-env-pratique {
    background: var(--surface-2);
    border-left-color: var(--text-faint);
    --kind-border: var(--text-faint);
}
.doc-env-pratique .doc-env-icon, .doc-env-pratique .doc-env-label { color: var(--text-muted); }

/* Attention — rouge, seul bloc réellement "alerte" */
.doc-env-attention {
    background: rgba(217, 83, 79, 0.08);
    border-left-color: var(--danger);
    --kind-border: var(--danger);
}
.doc-env-attention .doc-env-icon, .doc-env-attention .doc-env-label { color: var(--danger); }

/* Todo — orange, isolé en fin de feuille */
.doc-env-todo {
    background: var(--content-exercise-bg);
    border-left-color: var(--content-exercise);
    --kind-border: var(--content-exercise);
}
.doc-env-todo .doc-env-icon, .doc-env-todo .doc-env-label { color: var(--content-exercise); }
/* Les Todo sont des pense-bêtes d'auteur (cf. _visible_items, sheets.py,
   qui ne les filtre PAS côté serveur — seuls les items trashés le sont) :
   un élève ne doit jamais les voir, quel que soit le flag hide_todos
   (celui-ci ne pilote que l'export LaTeX \hidetodos, pas l'affichage HTML). */
body.mode-eleve .doc-env-todo { display: none; }

/* Exercice — violet (legacy, non appliqué sur .doc-exo-embed) */
.doc-env-exercise:not(.doc-exo-embed) {
    background: var(--content-exercise-doc-bg);
    border-left-color: var(--content-exercise-doc);
    --kind-border: var(--content-exercise-doc);
}
.doc-env-exercise:not(.doc-exo-embed) .doc-env-icon, .doc-env-exercise:not(.doc-exo-embed) .doc-env-label { color: var(--content-exercise-doc); }
.doc-exo-meta { display: flex; align-items: center; gap: 10px; margin-left: auto; flex-wrap: wrap; }
.doc-exo-time { font-size: 0.78rem; color: var(--text-faint); white-space: nowrap; }
.doc-env-foot { display: flex; justify-content: flex-end; margin-top: 10px; }
.doc-exo-open { color: var(--content-exercise-doc); border-color: rgba(139, 92, 255, 0.35); }

/* Exercice intégré dans une feuille : carte identique à la liste
   d'exercices, pas de cadre violet (cf. .doc-env-exercise legacy).
   border-radius aligné sur .exercise-card (exercise_card.css, 12px) : ce
   wrapper .sheet-block est l'élément qui porte .selected/.cut-pending
   (box-shadow), sans ce même rayon ses coins seraient carrés autour
   d'une carte aux coins arrondis. */
.doc-exo-embed { padding: 0; border-radius: 12px; }
.doc-exo-open:hover { background: var(--content-exercise-doc-bg); }

/* Exercice supprimé (corbeille) — rouge en pointillés, visible en mode édition uniquement */
.sheet-item-trashed {
    background: rgba(220, 38, 38, 0.08);
    border: 1px dashed var(--danger);
    border-left: 3px dashed var(--danger);
}
.sheet-item-trashed .doc-env-icon, .sheet-item-trashed .doc-env-label, .sheet-item-trashed .doc-env-title {
    color: var(--danger);
}
.sheet-item-trashed-hint { font-size: 0.85rem; color: var(--text-muted); margin: 6px 0 0; }
.sheet-item-trashed-hint a { color: var(--danger); text-decoration: underline; }

/* Item masqué (clic droit "Cacher") : affiché en transparence, visible
   uniquement en mode édition (mode élève : l'item est absent du HTML, cf.
   _visible_items côté serveur, donc cette règle ne s'applique jamais là-bas). */
.sheet-item-hidden { opacity: 0.45; }
.sheet-item-hidden:hover { opacity: 0.7; }

/* Icône oeil barré : seul indice + raccourci de réaffichage d'un item masqué
   en dehors du clic droit (cf. sheets.js, .doc-unhide-btn). Même emplacement
   (top:6px, boîte 24px) que .exo-fav/.exo-info-btn (exercise_card.css) —
   toujours à GAUCHE de ces icônes existantes quand elles sont présentes
   (favori/coeur "par coeur", info "métadonnées") : right:6px par défaut
   (aucune autre icône), décalé à 62px dès qu'un favori ou un info cohabite
   dans le même bloc (`:has()`, y compris à travers `_exercise_card.html`
   imbriquée pour un item exercice). Toujours visible (pas besoin du survol
   du bloc) pour rester repérable même hors focus. */
.doc-unhide-btn {
    position: absolute;
    top: 6px;
    right: 6px;
    width: 24px;
    height: 24px;
    min-width: 24px;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px solid var(--border);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    color: var(--star);
    cursor: pointer;
    z-index: 1;
}
.doc-unhide-btn svg { width: 18px; height: 18px; }
.doc-unhide-btn:hover { color: var(--star); background: rgba(245, 179, 1, 0.12); border-color: var(--star); }
/* Le trait diagonal (oeil "barré") s'efface au survol : préfigure l'effet du
   clic (réafficher l'item) en montrant l'oeil "normal" — même mécanique que
   .variant-panel-eye/.context-banner-hide-toggle (opacité du trait, pas de
   second SVG). */
.doc-unhide-btn .eye-slash { transition: opacity 0.12s; }
.doc-unhide-btn:hover .eye-slash { opacity: 0; }
.doc-block:has(.exo-fav) .doc-unhide-btn,
.doc-block:has(.exo-info-btn) .doc-unhide-btn { right: 62px; }
body.mode-eleve .doc-unhide-btn { display: none; }

.sheet-empty-state {
    text-align: center;
    padding: 60px 20px;
    color: var(--text-faint);
}
.sheet-empty-hint { font-size: 0.85rem; }

/* ---------- Outils de bloc (drag, admin) ----------
   Monter/descendre/supprimer au survol ont été retirés : redondants avec
   le clic droit (menu générique) et le drag-and-drop. */
.sheet-block.dragging { opacity: 0.4; }

/* Poignée de déplacement : bande sur tout le bord droit du bloc (dans la
   gouttière réservée par .doc-chapters-scroll, hors du bloc lui-même pour ne
   jamais décaler son contenu), pas une petite icône isolée — seule ELLE porte
   draggable="true" (jamais le bloc entier) pour que le texte du bloc reste
   sélectionnable normalement. */
.doc-block-drag-handle {
    position: absolute;
    right: -16px;
    top: 0;
    bottom: 0;
    width: 16px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--text-faint);
    cursor: grab;
    opacity: 0;
    transition: opacity 0.12s, color 0.12s, background 0.12s;
    z-index: 1;
}
.doc-block:hover .doc-block-drag-handle { opacity: 1; }
.doc-block-drag-handle:hover { color: var(--text); background: var(--surface-3); }
.doc-block-drag-handle:active { cursor: grabbing; }
/* Le drag est piloté en Pointer Events (pas en drag-and-drop natif, cf.
   sheet_detail.html) : sans le comportement natif qui gèle la sélection
   pendant un drag, il faut la désactiver nous-mêmes le temps du geste. */
body.is-dragging-block { cursor: grabbing; user-select: none; -webkit-user-select: none; }
body.mode-eleve .doc-block-drag-handle { display: none; }

/* Bouton poubelle unique, en haut à droite du bloc (théorème, définition,
   section, sous-section, texte, preuve — pas les exercices, qui ont déjà
   leur propre slot d'action adaptatif dans _exercise_card.html). Position,
   couleur et règles d'apparition au survol/focus : voir le groupe partagé
   .exo-delete-btn dans editable.css/exercise_card.css — même bouton. */

@media (max-width: 860px) {
    .doc-title { font-size: 1.35rem; }
    .doc-chapnav-link { max-width: 48%; }
    .doc-block-drag-handle { display: none; }
}

/* =========================================================
   PAGE "COURS & TRAVAUX DIRIGÉS" (liste des chapitres)
   ========================================================= */

.doc-page { padding: 0 4px 40px; }

/* Page "Cours & TD" : le <main> générique perd sa marge/largeur pour que
   .doc-page-scroll occupe une largeur généreuse sous la navbar (68px).
   Layout à deux colonnes : une sidebar étroite faite de cartes indépendantes
   (niveau/recherche/ordre/actions) et une colonne principale qui prend le
   plus de place possible pour la liste des chapitres. */
main:has(.doc-page-scroll) {
    margin: 0 auto;
    max-width: 1180px;
    padding: 0 24px;
    box-sizing: border-box;
    transition: margin-left 0.2s ease, margin-right 0.2s ease;
}
/* Panneau info (ⓘ) ouvert : la position/taille du bloc centré ne doit
   JAMAIS bouger (demande explicite 2026-08-24, "ça décale tout encore" —
   toute tentative de décalage, même partiel/plafonné, a été rejetée). Le
   panneau s'affiche donc en overlay par-dessus le contenu plutôt que de
   pousser/rétrécir quoi que ce soit ; voir la règle de positionnement du
   panneau lui-même (.doc-info-panel ou équivalent) pour le z-index/position.
   sheet_detail.html (page DM/DS/TD, PDF collé à droite) réutilise aussi
   .doc-page-scroll pour hériter de ce centrage (cf. sheet_detail.css) — donc
   toute réservation de marge pour le panneau info doit être scopée à la
   page Devoirs (#doc-devoirs-root, cf. devoirs.css) et non posée ici,
   sous peine de décaler aussi la zone PDF de l'éditeur de feuille (constaté
   2026-08-24). */
/* Cours & TD (#doc-chapters-root, posé par doc_body_attrs dans
   cours_et_td2.html) : plus large que les autres pages "document"
   (Exercices, Devoirs, Corbeille), toujours centré via margin:auto
   ci-dessus. Réduit de 8% (2026-08-13) par rapport aux 1416px d'origine,
   puis à 1100px (2026-08-13). */
main:has(#doc-chapters-root) {
    max-width: 1100px;
}
/* La context bar fait office d'en-tête sur TOUTE page qui en a une : le fil
   d'Ariane nomme déjà la page. Le grand bandeau titre/sous-titre/stats
   (.doc-sticky-top) et sa barre compacte au scroll (.doc-compact-bar-wrap)
   sont donc redondants — on saute directement au contenu.
   C'est Cours & TD qui appliquait déjà ce choix, à lui seul ; il est
   généralisé (2026-08-18) parce qu'un bandeau de 190px avec halo et Σ en
   filigrane pesait trop lourd pour ce qu'il portait.
   Le sélecteur passe par :has(.ctx-bar) et non par une liste de pages : une
   page sans context bar garderait son en-tête, sinon elle n'aurait plus rien
   pour se nommer. (La corbeille, seul cas restant, a reçu une context bar.)
   Les blocs doc_title / doc_subtitle / doc_stats restent dans le socle et
   dans les templates : ils ne coûtent rien et redeviendraient visibles en
   retirant cette règle. */
.doc-page:has(.ctx-bar) .doc-sticky-top,
.doc-page:has(.ctx-bar) .doc-compact-bar-wrap,
/* .context-banner : le bandeau de contexte de la page Exercices (chapitre /
   feuille courants) joue exactement le même rôle de barre supérieure, hors du
   mode « ajouter à une feuille » qui utilise une vraie .ctx-bar. */
.doc-page:has(.context-banner) .doc-sticky-top,
.doc-page:has(.context-banner) .doc-compact-bar-wrap {
    display: none;
}
/* Sans .doc-sticky-top, le contenu (liste Devoirs, Correction, Corbeille)
   démarrait collé au fil d'Ariane. Exclu #doc-chapters-root : Cours & TD gère
   déjà son propre espacement interne (padding par chapitre), un margin-top
   ici s'ajouterait par-dessus. */
.doc-page:has(.ctx-bar) .doc-chapters-scroll:not(#doc-chapters-root),
.doc-page:has(.context-banner) .doc-chapters-scroll:not(#doc-chapters-root) {
    margin-top: var(--space-4);
}
/* Cours & TD : la context bar reste transparente (choix assumé) — donc la
   liste des chapitres ne doit JAMAIS défiler derrière elle, sous peine de
   voir les titres de chapitre passer par transparence sous le fil
   d'Ariane. Avec un scroll de page classique, même un padding-top ne
   suffit pas : ça ne corrige que la position de départ, pas les lignes qui
   remontent sous la barre en scrollant plus loin. Seule solution robuste
   sans rendre la barre opaque : cantonner le défilement à
   #doc-chapters-root lui-même, dont la hauteur est capée à l'espace
   restant sous la navbar (52px) et la context bar mesurée en JS
   (--doc-topbar-h, cf. _leftmenu_base.html), avec son propre overflow-y —
   la liste commence donc physiquement sous la barre et ne peut plus jamais
   passer derrière. min-height:0 sur toute la chaîne flex (sinon un enfant
   flex garde sa taille de contenu et empêche le parent de se limiter à la
   hauteur du viewport, cf. .doc-sidebar-toc-card plus haut pour le même
   piège). */
/* .doc-page a un padding-bottom:40px (plus haut) : sans l'annuler ici,
   #app-body dépasse quand même le viewport de ces 40px malgré la hauteur
   capée de .doc-page-scroll juste en dessous, et récupère donc SA PROPRE
   scrollbar en plus de celle de #doc-chapters-root (double scrollbar). Le
   padding-bottom utile pour la liste vit déjà dans .doc-chapters-scroll
   (cf. plus bas), à l'intérieur de la zone qui défile. */
main:has(#doc-chapters-root) .doc-page {
    padding-bottom: 0;
}
main:has(#doc-chapters-root) .doc-page-scroll {
    /* -6px : margin-bottom de .ctx-bar (context_bar.css), pas inclus dans
       --doc-topbar-h (mesuré via getBoundingClientRect, qui ignore les
       marges) mais bien consommé dans le flux normal. */
    height: calc(100vh - var(--nav-height) - var(--doc-topbar-h, 52px) - 6px);
    min-height: 0;
}
main:has(#doc-chapters-root) .doc-page-main {
    /* .doc-page-scroll a align-items:flex-start (pour la sidebar, sur les
       autres pages) : sans align-self:stretch, cette colonne resterait à
       la hauteur de son contenu au lieu de remplir les 100% capés
       ci-dessus, et #doc-chapters-root (flex:1 1 auto en-dessous) n'aurait
       jamais de hauteur bornée pour son overflow-y. */
    align-self: stretch;
    min-height: 0;
    height: 100%;
}
/* La scrollbar de #doc-chapters-root doit apparaître au bord réel de la
   fenêtre (là où le <html> réserve déjà sa gouttière globale, cf. base.css)
   et pas au bord de la colonne centrée (~1416px) — sinon elle flotte au
   milieu de la page, à côté d'une gouttière globale vide puisque #app-body
   ne défile plus (sa hauteur est capée ci-dessus). Même technique de
   breakout plein écran que .ctx-bar (context_bar.css), avec un padding qui
   ramène le contenu à l'alignement centré habituel (50% de 1416px). */
#doc-chapters-root {
    overflow-y: auto;
    min-height: 0;
    width: 100vw;
    margin-left: calc(-50vw + 50%);
    margin-right: calc(-50vw + 50%);
    padding-left: max(24px, calc(50vw - 550px));
    padding-right: max(24px, calc(50vw - 550px));
    box-sizing: border-box;
}
/* Feuille en mode admin : 3e colonne (prévisu PDF, cf. .doc-page-pdf) — la
   largeur 1180px habituelle de Cours & TD ne laisse pas assez de place pour
   sidebar + HTML + PDF, donc on la lève dans ce cas précis. Le panneau PDF
   est fixe et collé au bord de l'écran (cf. .doc-page-pdf plus bas) : on
   réserve sa largeur ici pour que sidebar+corps HTML ne passent pas dessous. */
/* padding-right suit la même largeur que .doc-page-pdf (cf. plus bas), donc
   la même valeur par défaut min(720px, 42vw) — sinon le corps HTML
   passerait sous un panneau devenu plus étroit que 720px en fenêtre
   réduite. */
/* Scopé à .dp-page (wrapper sheet_detail) : .doc-page-pdf est aussi réutilisé
   tel quel par la page Colles (colles.css, panneau .colles-pdf-panel) mais en
   position:static, PAS en 3e colonne fixe — sans ce scope, ces règles de
   réservation d'espace (pensées pour le panneau fixe) cassaient <main> sur
   /colles/ (padding-right géant, largeur résiduelle écrasée) alors que la
   feuille de style était censée n'affecter que sheet_detail. */
main:has(.dp-page .doc-page-pdf) { max-width: none; padding-right: min(var(--pdf-panel-width, min(720px, 42vw)), calc(100vw - 480px)); padding-left: 8px; }
/* Corps HTML seul défilable dans ce mode (pas la page entière) : sommaire et
   PDF ont déjà chacun leur propre scroll (sticky/fixe), cf. .doc-page-sidebar
   et .doc-page-pdf. */
body:has(.dp-page .doc-page-pdf) { overflow: hidden; }
/* Mode admin (édition) uniquement : le panneau gauche colle à la navbar et
   au bord gauche pour gagner de la place, redonnée au corps HTML (écart
   sidebar/corps réduit en conséquence). */
body:has(.dp-page .doc-page-pdf) .doc-page-scroll { gap: 20px; }
body:has(.dp-page .doc-page-pdf) .doc-page-sidebar { padding-top: 0; }
/* Bandeau plein-largeur (doc_topbar) : simple bloc empilé au-dessus de
   .doc-page-scroll (pas un item de sa ligne flex sidebar/main — testé
   d'abord avec flex-wrap, ça cassait la ligne dès qu'un contenu large la
   faisait "déborder" sur une 2e ligne). Son parent direct, .doc-page,
   contient tout le reste de la page : assez haut pour donner à sa
   position:sticky la marge nécessaire pour "rester accroché" en scrollant
   (un élément sticky ne peut rester accroché que dans la hauteur de SON
   PROPRE PARENT, pas d'un ancêtre plus haut — cf. .context-banner,
   editable.css, pour le détail de ce piège). `:empty` le retire du flux
   (display:none) sur les pages qui ne définissent pas ce bloc. */
.doc-topbar-slot {
    position: sticky;
    top: 0;
    z-index: 19;
}
.doc-topbar-slot:empty { display: none; }
.doc-page-scroll {
    display: flex;
    align-items: flex-start;
    gap: 40px;
    padding: 0;
    box-sizing: border-box;
}

/* ---------- Panneau gauche : cartes indépendantes ----------
   Le scroll vit dans #app-body (cf. nav.css), pas la fenêtre : le panneau
   reste donc fixe à l'écran via position:sticky top:0 (#app-body exclut
   déjà la context bar de sa propre boîte, cf. nav.css — top:0 colle donc
   le panneau juste sous elle, sans le moindre rattrapage de scroll), avec
   son propre scroll interne seulement si son contenu dépasse la hauteur
   disponible (rare, mais évite qu'il ne soit coupé). */
.doc-page-sidebar {
    flex: 0 0 248px;
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-top: 20px;
    box-sizing: border-box;
    position: sticky;
    top: 0;
    max-height: calc(100vh - var(--nav-height) - var(--doc-topbar-h, 0px));
    /* Ce n'est plus le panneau entier qui défile : seul le sommaire a son
       propre scroll interne (cf. .doc-sidebar-toc-card / #doc-toc-wrap
       ci-dessous) — la carte d'outils du haut et les boutons Début/Fin
       restent fixes à l'écran. */
}
.doc-sidebar-card {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    background: #121826;
}
/* Seule carte autorisée à rétrécir/déborder dans la colonne sticky (les
   autres gardent leur taille naturelle) : min-height:0 est indispensable
   pour qu'un enfant flex puisse effectivement rétrécir au lieu de pousser
   son parent hors de la hauteur max de .doc-page-sidebar. */
.doc-sidebar-toc-card { flex: 1 1 auto; min-height: 0; }
/* Barre de scroll collée au bord droit de la carte plutôt qu'en retrait du
   padding : le wrap déborde jusqu'au bord (margin-right négatif annulant le
   padding de .doc-sidebar-card) puis un padding-right plus petit redonne un
   peu d'air au texte sans repousser la scrollbar elle-même. */
#doc-toc-wrap {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    margin-right: -16px;
    padding-right: 8px;
    /* Sans ça, arriver en haut/bas du sommaire en molette fait "déborder" le
       scroll sur la page en dessous (comportement natif du navigateur) —
       gênant ici puisque le sommaire est sensé avoir son propre scroll
       indépendant de celui de la page. */
    overscroll-behavior: contain;
}
.doc-sidebar-label {
    font-size: 0.68rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--text-faint);
}
/* En-tête du sommaire : titre + bouton info (ⓘ), sur la même ligne — le
   bouton réutilise .exo-info-btn (même style que sur les cartes) mais hors
   contexte carte, donc positionné en flux normal plutôt qu'en absolute. */
.doc-sidebar-toc-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
.doc-sidebar-toc-head .doc-sidebar-label { margin-right: auto; }
.doc-sidebar-toc-info-btn {
    position: static;
    width: 20px;
    height: 20px;
    min-width: 20px;
    /* Masqué par défaut, révélé seulement au survol/focus de son en-tête
       (cf. .doc-sidebar-toc-head:hover / .cor-questions-head:hover plus
       bas) — même comportement que les autres icônes flottantes
       (.exo-fav/.exo-info-btn sur les cartes, cf. exercise_card.css),
       demande explicite (l'ⓘ restait visible en permanence auparavant). */
    opacity: 0;
    pointer-events: none;
    /* Neutralise top/right hérités de .exo-info-btn (exercise_card.css,
       pensés pour un bouton position:absolute sur une carte) : inertes tant
       que position reste static, mais [data-tooltip] (doc.css, plus bas)
       force position:relative au survol (nécessaire au ::after du tooltip)
       — sans ce reset, ces offsets redevenaient actifs pile au survol et
       décalaient le bouton de 34px, le sortant de sous le curseur : la
       souris repassait alors hors-hover, le tooltip retombait, le bouton
       revenait à sa place, la souris se retrouvait de nouveau dessus... et
       ainsi en boucle (clignotement/ubiquité au survol). */
    top: auto;
    right: auto;
}
.doc-sidebar-toc-info-btn svg { width: 15px; height: 15px; }
/* Bouton inline (pas flottant sur une carte) : le zoom+ombre au survol de
   .doc-toggle-icon-btn-sm (exercise_card.css) donne l'impression que l'icône
   "bouge" vu le peu d'espace dans la ligne du sommaire — désactivé ici, on
   garde juste le changement de fond/couleur hérité de .exo-info-btn:hover. */
.doc-sidebar-toc-info-btn:hover {
    transform: none;
    box-shadow: none;
}
.doc-sidebar-toc-head:hover .doc-sidebar-toc-info-btn,
.doc-sidebar-toc-head:focus-within .doc-sidebar-toc-info-btn,
.cor-questions-head:hover .doc-sidebar-toc-info-btn,
.cor-questions-head:focus-within .doc-sidebar-toc-info-btn {
    opacity: 1;
    pointer-events: auto;
}
/* PDF : même bouton que l'ancienne barre d'outils, réduit pour tenir sur la
   ligne du titre "Sommaire" à côté du bouton info (i), lui-même compact. */
.doc-sidebar-toc-pdf-btn { width: 22px; height: 22px; min-width: 22px; }
.doc-sidebar-toc-pdf-btn svg { width: 17px; height: 17px; }
.doc-page-sidebar .filiere-selector { display: flex; gap: 8px; }
.doc-page-sidebar .filiere-btn { flex: 1 1 0; text-align: center; }
.doc-page-sidebar .doc-order-select {
    width: 100%;
    box-sizing: border-box;
    background-color: #121826 !important;
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%238b93a8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 14px center;
    background-size: 14px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    padding: 12px 36px 12px 14px;
    font-size: 0.88rem;
}
.doc-page-sidebar .doc-order-select:focus { outline: none; border-color: var(--accent); }
.doc-page-sidebar .doc-order-select option { background-color: #121826 !important; color: var(--text); }
.doc-sidebar-search { position: relative; }
.doc-sidebar-search::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 50%;
    transform: translateY(-50%);
    width: 15px;
    height: 15px;
    background-color: var(--text-faint);
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M21 21l-4.3-4.3'/%3E%3C/svg%3E") no-repeat center / contain;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cpath d='M21 21l-4.3-4.3'/%3E%3C/svg%3E") no-repeat center / contain;
    pointer-events: none;
}
.doc-sidebar-search input {
    box-sizing: border-box;
    width: 100%;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    color: var(--text);
    padding: 8px 12px 8px 34px;
    font-size: 0.85rem;
}
.doc-sidebar-search input:focus { outline: none; border-color: var(--accent); }
.doc-page-sidebar .button-secondary,
.doc-page-sidebar .button { width: 100%; box-sizing: border-box; }
.doc-sidebar-primary-btn {
    padding: 10px 14px;
    font-size: 0.9rem;
    font-weight: 600;
    background-color: var(--accent-strong);
    box-shadow: var(--shadow-glow);
}
.doc-sidebar-primary-btn:hover { background-color: var(--accent-deep, var(--accent-strong)); }
.doc-import-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
}
.doc-import-btn::before {
    content: "";
    width: 15px;
    height: 15px;
    flex: 0 0 auto;
    background-color: currentColor;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 16V4M7 9l5-5 5 5'/%3E%3Cpath d='M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E") no-repeat center / contain;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 16V4M7 9l5-5 5 5'/%3E%3Cpath d='M4 16v3a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-3'/%3E%3C/svg%3E") no-repeat center / contain;
}

/* ---------- Actions déportées dans la sidebar (page feuille) ---------- */
.doc-sidebar-card .doc-actions,
.doc-sidebar-card .doc-actions-admin {
    flex-direction: column;
    align-items: stretch;
    margin-top: 0;
    gap: 6px;
}
.doc-sidebar-card .doc-act-btn,
.doc-sidebar-card .doc-flag {
    width: 100%;
    box-sizing: border-box;
    justify-content: flex-start;
}

/* ---------- Quicklinks Cours/TD/Résumé empilés dans la sidebar ---------- */
.doc-quicklinks-vertical {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 0;
}
.doc-quicklinks-vertical .doc-quicklink { padding: 9px 12px; gap: 10px; }
.doc-quicklinks-vertical .doc-quicklink-icon { width: 26px; height: 26px; }
.doc-quicklink.doc-quicklink-current {
    cursor: default;
    pointer-events: none;
    border-color: var(--kind-cours);
    background: var(--kind-cours-bg);
}
.doc-quicklink.doc-quicklink-current.doc-quicklink-td {
    border-color: var(--kind-td);
    background: var(--kind-td-bg);
}
.doc-quicklink.doc-quicklink-current.doc-quicklink-resume {
    border-color: var(--kind-resume);
    background: var(--kind-resume-bg);
}
.doc-quicklink-current-tag { font-size: 0.72rem; font-weight: 500; color: var(--kind-cours); }
.doc-quicklink-current.doc-quicklink-td .doc-quicklink-current-tag { color: var(--kind-td); }
.doc-quicklink-current.doc-quicklink-resume .doc-quicklink-current-tag { color: var(--kind-resume); }

/* ---------- Colonne principale (titre + liste des chapitres) ---------- */
.doc-page-main {
    flex: 1 1 auto;
    min-width: 0;
    /* width:100% explicite — min-width:0 seul ne suffit pas ici : ce bloc
       est lui-même un conteneur flex (column) contenant du contenu large et
       non compressible (listes/formules LaTeX) ; sans largeur explicite, le
       stretch cross-axis normalement attendu d'un item flex ne l'empêchait
       pas de s'élargir à la taille de son contenu (constaté en pratique),
       ce qui remontait le débordement jusqu'à #app-body (scrollbar
       horizontale sur toute la page). */
    width: 100%;
    display: flex;
    flex-direction: column;
}
.doc-sticky-top { flex: 0 0 auto; }
/* Prévisu PDF (mode admin, cf. .doc-page-pdf) : chaque bloc a sa propre
   barre de scroll (sommaire sticky, corps HTML, PDF fixe) — #app-body lui-
   même ne doit donc pas scroller aussi, sinon double scrollbar.
   scrollbar-gutter:auto (annule le stable de nav.css) : #app-body ne
   scrolle plus jamais dans ce mode, réserver quand même sa place de
   scrollbar (10px) créait un écart visible entre la scrollbar de
   .doc-page-main et le bord du panneau PDF — celui-ci est position:fixed
   par rapport au vrai viewport, donc ne "voit" jamais ce gutter réservé
   sur #app-body (constaté 2026-08-24). */
body:has(.dp-page .doc-page-pdf) #app-body { overflow: hidden; scrollbar-gutter: auto; }
body:has(.dp-page .doc-page-pdf) .doc-page-main {
    max-height: calc(100vh - var(--nav-height));
    overflow-y: auto;
}

/* ---------- Barre compacte de titre, générique (Cours & TD, Devoirs,
   Exercices) ----------
   Même principe que .doc-compact-bar-wrap/.doc-compact-bar de la page
   feuille (juste en dessous), mais réduite au seul titre : ces pages n'ont
   pas d'icônes d'action dans leur en-tête. Le grand bandeau (.doc-header,
   dans .doc-sticky-top) défile normalement ; cette barre fine le remplace
   une fois qu'il a défilé, ancrée sous la navbar fixe (68px). Visibilité
   pilotée par sheets.js (générique, une seule implémentation pour les
   3 pages). */
.doc-page-compact-title { justify-content: flex-start; }
/* Le scroll vit désormais au niveau du site (window) : ce bloc ne scrolle
   plus lui-même, il grandit avec son contenu. Les en-têtes/barres qui
   doivent rester visibles pendant la lecture (barre compacte, titres de
   section/sous-section, cf. plus bas) utilisent position:sticky ancré au
   scroll de la fenêtre, décalé de la hauteur de la navbar fixe (68px). */
.doc-chapters-scroll {
    flex: 1 1 auto;
    /* min-width:0 — sans lui, cet item flex (colonne dans .doc-page-main)
       garde son min-width automatique = min-content de son contenu (cartes
       d'exercice, etc.), qui peut dépasser la largeur disponible ; le
       navigateur laissait alors déborder .doc-chapters-scroll plutôt que de
       le rétrécir, ce qui forçait une scrollbar horizontale sur #app-body
       dès que la fenêtre devenait étroite. */
    min-width: 0;
    /* Pas de overflow-x:hidden ici : avec overflow-y non précisé, la
       spec CSS calculerait overflow-y en "auto" (dès qu'un axe est non
       "visible", l'autre le devient) — ce qui recrée un conteneur de
       scroll interne et casse le position:sticky des enfants (barre
       compacte, sections/sous-sections), qui doit s'ancrer au scroll
       de la fenêtre. */
    padding-bottom: 40px;
    box-sizing: border-box;
}
/* Gouttière pour .doc-block-drag-handle : réservée uniquement sur la page
   feuille (seule à afficher des .doc-block réordonnables), pas sur les
   autres pages qui réutilisent .doc-chapters-scroll (Cours & TD, Devoirs).
   Le bandeau/la barre compacte de cette page compensent avec une marge
   négative pour que leur fond touche le bord droit comme sur les autres
   pages (la scrollbar globale est réservée au niveau du <html>, cf. base.css). */
.doc-chapters-scroll-sheet { padding-right: 16px; }
.doc-chapters-scroll-sheet .doc-header-tall,
.doc-chapters-scroll-sheet .doc-compact-bar-wrap { margin-right: -16px; }

/* ---------- Barre compacte sticky (page feuille) ----------
   Le grand bandeau (.doc-header-tall) fait partie du contenu scrollable :
   il défile normalement et disparaît à la lecture. La barre compacte doit
   apparaître à sa place une fois qu'on l'a dépassée, SANS réserver le
   moindre espace hors du scroller (sinon on perd de la hauteur utile en
   permanence, y compris quand elle est masquée). Le wrapper est donc
   `position: sticky` avec une hauteur nulle — il ne prend jamais de place
   dans le flux — et la barre elle-même est positionnée en absolu dedans ;
   sheets.js bascule .visible dès que le scroll dépasse un seuil. */
.doc-compact-bar-wrap {
    position: sticky;
    top: 0;
    height: 0;
    z-index: 20;
}
.doc-compact-bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 14px 18px;
    border-bottom: 1px solid var(--border);
    background: var(--bg-elev);
    opacity: 0;
    transform: translateY(-6px);
    pointer-events: none;
    transition: opacity 0.15s ease, transform 0.15s ease;
}
.doc-compact-bar.visible { opacity: 1; transform: translateY(0); pointer-events: auto; }
.doc-compact-title {
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--font-head);
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--text);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.doc-compact-actions { flex: 0 0 auto; display: flex; align-items: center; gap: 8px; }

/* ===== Sidebar : bloc navigation (retour, chapitre, actions, TD/Cours/Résumé) ===== */
.doc-sidebar-nav-card { gap: 10px; }
.doc-sidebar-back {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.82rem;
    font-weight: 600;
    color: var(--text-muted);
    text-decoration: none;
}
.doc-sidebar-back svg { width: 16px; height: 16px; flex: 0 0 auto; }
.doc-sidebar-back:hover { color: var(--accent); }
.doc-sidebar-breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
    font-family: var(--font-head);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text);
    line-height: 1.25;
}
.doc-sidebar-breadcrumb-sep { color: var(--text-faint); font-weight: 400; }

.doc-sidebar-sep { border: none; border-top: 1px solid var(--border-soft); margin: 0; }

.doc-sidebar-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}
.doc-sidebar-toolbar .doc-header-icon-btn { width: 32px; height: 32px; }

/* Cours / TD / Résumé : tiennent sur une seule ligne, icône + libellé compacts
   (remplace les grandes cartes verticales .doc-quicklinks-vertical). */
.doc-quicklinks-compact {
    display: flex;
    gap: 6px;
}
.doc-quicklinks-compact .doc-quicklink {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 8px 4px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border-soft);
    background: var(--surface-2);
    color: var(--text-muted);
    text-decoration: none;
    text-align: center;
}
.doc-quicklinks-compact .doc-quicklink-icon { width: 18px; height: 18px; }
.doc-quicklinks-compact .doc-quicklink-icon img { width: 100%; height: 100%; object-fit: contain; }
.doc-quicklinks-compact .doc-quicklink-label { font-size: 0.68rem; font-weight: 600; }
.doc-quicklinks-compact .doc-quicklink-current {
    background: var(--kind-resume);
    border-color: var(--kind-resume);
    color: #fff;
}
.doc-quicklinks-compact .doc-quicklink-current.doc-quicklink-cours {
    background: var(--kind-cours);
    border-color: var(--kind-cours);
}
.doc-quicklinks-compact .doc-quicklink-current.doc-quicklink-td {
    background: var(--kind-td);
    border-color: var(--kind-td);
}
/* Répété explicitement pour "resume" alors que la règle juste au-dessus
   couvre déjà ce cas par défaut : sans lui donner la même spécificité que
   ses équivalents cours/td ci-dessus, la règle .doc-quicklink.doc-quicklink-
   current.doc-quicklink-resume plus haut (variante verticale, 3 classes)
   gagnait le conflit et affichait le fond clair (--kind-resume-bg) au lieu
   du fond plein voulu ici. */
.doc-quicklinks-compact .doc-quicklink-current.doc-quicklink-resume {
    background: var(--kind-resume);
    border-color: var(--kind-resume);
}
/* Page courante : le logo (PNG une seule teinte, cf. icon_cours/td/resume.png)
   passe au blanc plutôt que de garder sa couleur d'origine — filter plutôt
   qu'une variante d'image, puisque ce sont des aplats d'une seule teinte
   (brightness(0) les rend noirs, invert(1) les rend blancs). */
.doc-quicklinks-compact .doc-quicklink-current .doc-quicklink-icon img {
    filter: brightness(0) invert(1);
}
.doc-quicklinks-compact .doc-quicklink-empty { opacity: 0.4; pointer-events: none; }
/* Survol (hors page courante/vide) : seul le contour reprend la couleur du
   logo du bouton — rien d'autre ne change (ni fond ni texte). */
.doc-quicklinks-compact .doc-quicklink-cours:not(.doc-quicklink-empty):not(.doc-quicklink-current):hover {
    border-color: var(--kind-cours);
}
.doc-quicklinks-compact .doc-quicklink-td:not(.doc-quicklink-empty):not(.doc-quicklink-current):hover {
    border-color: var(--kind-td);
}
.doc-quicklinks-compact .doc-quicklink-resume:not(.doc-quicklink-empty):not(.doc-quicklink-current):hover {
    border-color: var(--kind-resume);
}

/* ===== En-tête central : ligne "Modifié le" + onglets HTML/LaTeX ===== */
.doc-header-updated { font-size: 0.85rem; color: var(--text-muted); }
.doc-body-tabs {
    display: flex;
    gap: 6px;
}
.doc-body-tab {
    box-sizing: border-box;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    min-width: 0;
    line-height: 1;
    padding: 4px 7px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-faint);
    font-size: 0.75rem;
    font-weight: 600;
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.doc-body-tab:hover { background: var(--surface-3); color: var(--text); border-color: var(--accent); }
.doc-body-tab.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.doc-body-tab.active .tex-logo, .doc-body-tab.active .html-logo { color: #fff; }

.doc-fullscreen-btn {
    box-sizing: border-box;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 0;
    width: 35px;
    height: 26px;
    padding: 0;
    align-self: center;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-faint);
    cursor: pointer;
    transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.doc-fullscreen-btn:hover { background: var(--surface-3); color: var(--text); border-color: var(--accent); }
.doc-fullscreen-btn svg { width: 16px; height: 16px; }
body.doc-fullscreen .doc-fullscreen-btn { background: var(--accent); border-color: var(--accent); color: #fff; }

/* ---------- Mode plein écran (feuille, cf. sheet_detail.html) ----------
   N'affiche plus que le corps (HTML/LaTeX) + le panneau PDF : navbar et
   sommaire gauche masqués, ce dernier réapparaissant au survol du bord
   gauche de l'écran (cf. .doc-fullscreen-hover-zone, combinateur ~ pur CSS
   — l'élément doit précéder .doc-page-sidebar dans le DOM). Choix EXPLICITE
   de l'utilisateur uniquement (bouton plein écran / Échap) — jamais
   déclenché automatiquement. */
body.doc-fullscreen .topbar { display: none; }
body.doc-fullscreen #app-body { margin-top: 0; height: 100vh; }
body.doc-fullscreen .doc-page-pdf { top: 0; }
body.doc-fullscreen main:has(.dp-page .doc-page-pdf),
body.doc-fullscreen .doc-page-main { max-height: 100vh; }

/* ---------- Repli automatique du seul sommaire (pas de la navbar) ----------
   body.doc-sidebar-compact (posé en JS, sheet_detail.html) : quand le corps
   HTML devient trop étroit pour être lisible confortablement (fenêtre
   étroite, ou panneau PDF élargi en mode prof), le sommaire adopte le MÊME
   comportement visuel que .doc-fullscreen (masqué, réapparaît au survol du
   bord gauche) — d'où les règles partagées ci-dessous — mais SANS masquer
   la navbar : ce repli est automatique/réactif, pas un "mode" choisi par
   l'utilisateur, la navbar doit donc rester le seul repère stable, jusqu'à
   son propre seuil hamburger (nav.css). Distinct de .doc-fullscreen : les
   deux peuvent en théorie cohabiter dans le DOM (classe résiduelle) sans se
   contredire visuellement, cf. règles top/height séparées plus bas. */
.doc-fullscreen-hover-zone { display: none; }
body.doc-fullscreen .doc-fullscreen-hover-zone,
body.doc-sidebar-compact .doc-fullscreen-hover-zone {
    display: block;
    position: fixed;
    left: 0;
    width: 14px;
    z-index: 119;
}
body.doc-fullscreen .doc-fullscreen-hover-zone { top: 0; height: 100vh; }
/* La navbar (52px, cf. nav.css #app-body) reste visible en repli auto : la
   zone de survol et le sommaire fixe démarrent donc en dessous d'elle,
   contrairement au plein écran où ils couvrent toute la hauteur. */
body.doc-sidebar-compact .doc-fullscreen-hover-zone { top: var(--nav-height); height: calc(100vh - var(--nav-height)); }

body.doc-fullscreen .doc-page-sidebar,
body.doc-sidebar-compact .doc-page-sidebar {
    position: fixed;
    left: 0;
    /* La MEME largeur de sommaire que partout ailleurs (--dp-sidebar-w, cf.
       sheet_detail.css), plus le padding du tiroir : la carte a l'interieur
       retombe donc exactement sur la largeur du mode eleve. Un 280px en dur
       ici donnait une carte a 248px, soit 32px de plus que partout ailleurs
       — et comme ce repli ne s'arme qu'en mode prof (le panneau PDF prend la
       place), le sommaire changeait de taille rien qu'en changeant de mode. */
    width: calc(var(--dp-sidebar-w, 248px) + 2 * var(--doc-sidebar-drawer-pad, 16px));
    z-index: 120;
    margin: 0;
    padding: var(--doc-sidebar-drawer-pad, 16px);
    background: var(--bg-elev);
    box-shadow: 4px 0 20px rgba(0, 0, 0, 0.35);
    transform: translateX(-100%);
    transition: transform 0.16s ease;
}
body.doc-fullscreen .doc-page-sidebar { top: 0; height: 100vh; max-height: 100vh; }
body.doc-sidebar-compact .doc-page-sidebar { top: var(--nav-height); height: calc(100vh - var(--nav-height)); max-height: calc(100vh - var(--nav-height)); }

/* Marge dédiée à la zone de survol (cf. .doc-fullscreen-hover-zone
   ci-dessus) : sans elle, le texte du corps peut arriver jusqu'au bord de
   l'écran et la souris "entre" dans le contenu avant d'avoir dépassé les
   blocs — il faut pouvoir passer sa souris à gauche des blocs, pas dessus,
   pour révéler le sommaire. */
body.doc-fullscreen .doc-page-main,
body.doc-sidebar-compact .doc-page-main { padding-left: 24px; }

body.doc-fullscreen .doc-fullscreen-hover-zone:hover ~ .doc-page-sidebar,
body.doc-fullscreen .doc-page-sidebar:hover,
body.doc-sidebar-compact .doc-fullscreen-hover-zone:hover ~ .doc-page-sidebar,
body.doc-sidebar-compact .doc-page-sidebar:hover,
/* Piloté en JS (sheet_detail.html) avec un délai avant de refermer : coller
   la souris à gauche déborde souvent sur la zone de redimensionnement de la
   fenêtre du navigateur, qui fait sortir le curseur de la page — sans ce
   délai, :hover seul referme le sommaire instantanément dans ce cas. */
body.doc-fullscreen.doc-sidebar-revealed .doc-page-sidebar,
body.doc-sidebar-compact.doc-sidebar-revealed .doc-page-sidebar {
    transform: translateX(0);
}

.doc-latex-source .CodeMirror {
    height: auto;
    min-height: 400px;
    border: 1px solid var(--border);
    border-radius: 10px;
    font-size: 0.85rem;
}

/* Prévisu PDF (mode admin) : 3e colonne. Fixe (pas sticky) et collée au bord
   de l'écran — pas au padding de <main> — pour descendre jusqu'en bas et
   jusqu'à l'extrême droite ; sa largeur est réservée en amont sur <main>
   (cf. main:has(.doc-page-pdf) plus haut) puisqu'elle sort du flux normal. */
.doc-page-pdf {
    position: fixed;
    top: var(--nav-height);
    right: 0;
    bottom: 0;
    /* min(720px, 42vw) : largeur par défaut avant tout redimensionnement
       manuel (cf. --pdf-panel-width posé en JS depuis localStorage/drag) —
       se réduit avec la fenêtre plutôt que de rester bloquée à 720px, sans
       quoi le panneau se faisait purement masquer sous 1700px de large (cf.
       ancien @media ci-dessous, retiré). max-width : garde toujours de la
       place pour le corps HTML même si une largeur sauvegardée devient trop
       grande pour la fenêtre (même marge que PANEL_RIGHT_MARGIN en JS). */
    width: var(--pdf-panel-width, min(720px, 42vw));
    max-width: calc(100vw - 480px);
    display: flex;
    flex-direction: column;
    border-left: 1px solid var(--border);
    background: var(--bg-elev);
}

/* Poignée de redimensionnement (cf. sheet_pdf_viewer.js) : bande invisible
   posée côté panneau (left:0, pas à cheval sur le bord comme avant), largeur
   du panneau pilotée par la variable --pdf-panel-width (persistée en
   localStorage, cf. JS).
   Ancien -9px/18px (bande centrée sur le bord, moitié côté corps HTML) :
   revenait à empiéter sur la scrollbar de .doc-page-main une fois l'écart
   entre les deux réduit à ~1px (cf. #app-body scrollbar-gutter:auto
   ci-dessus, 2026-08-24) — la poignée volait alors les clics/drags destinés
   au scroll (constaté : impossible de scroller en cliquant près du bord).
   Toute la largeur passe donc côté panneau, qui a de la place à revendre. */
.doc-pdf-resize-handle {
    position: absolute;
    top: 0;
    /* left:-1px/width +1px : couvre aussi la bordure gauche du panneau
       (border-left:1px de .doc-page-pdf), sinon ce pixel de bordure
       n'est ni dans la zone de resize ni sur la page PDF (align-items:
       safe start, collée contre la poignée) — curseur normal dessus,
       aucune interaction possible. */
    left: -1px;
    width: 15px;
    height: 100%;
    cursor: col-resize;
    z-index: 20;
}
.doc-pdf-resize-handle::after {
    content: "";
    position: absolute;
    left: 0;
    top: 0;
    width: 2px;
    height: 100%;
    background: transparent;
}
.doc-pdf-resize-handle:hover::after,
body.doc-pdf-resizing .doc-pdf-resize-handle::after {
    background: var(--accent);
}
/* Pendant le drag : empêche la sélection de texte et les canvas PDF/iframe
   de capter les événements souris (sinon le drag "décroche" en survolant
   une page rendue). */
body.doc-pdf-resizing { cursor: col-resize; user-select: none; }
body.doc-pdf-resizing .doc-pdf-pages { pointer-events: none; }

/* Barre d'outils maison (remplace le viewer natif du navigateur, imposé et
   non stylisable) : fine, dans le thème du site, seulement les actions
   utiles ici — page préc/suiv, zoom, télécharger. */
.doc-pdf-toolbar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
}
.doc-pdf-toolbar-group {
    display: flex;
    align-items: center;
    gap: 4px;
}
.doc-pdf-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 28px;
    min-width: 28px;
    height: 28px;
    padding: 0;
    margin: 0;
    border: 1px solid transparent;
    border-radius: 6px;
    background: transparent;
    color: var(--text-muted);
    text-decoration: none;
    cursor: pointer;
    font-size: 0.95rem;
    font-family: inherit;
    line-height: 1;
}
.doc-pdf-btn svg { width: 16px; height: 16px; flex: 0 0 auto; }
.doc-pdf-btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.doc-pdf-page-indicator,
.doc-pdf-zoom-level {
    font-size: 0.75rem;
    color: var(--text-muted);
    min-width: 40px;
    text-align: center;
    /* Jamais comprime : sans cela l'indicateur se retrecit a sa min-width
       des que la barre est a l'etroit, et son contenu deborde sur les
       chevrons de part et d'autre. */
    flex: 0 0 auto;
}
/* Symetrie de l'indicateur "10 / 12". Le numero courant est un <input> plus
   large que ses chiffres (pour qu'un nombre a trois chiffres tienne) et
   son texte y est centre : il reste du blanc entre le chevron "<" et le "10".
   Le total de pages, lui, etait un <span> ajuste a son texte, donc colle au
   chevron ">", qui paraissait plus serre que celui de gauche. On lui donne la
   MEME boite qu'a l'input plutot qu'une marge droite arbitraire : la symetrie
   tient alors quel que soit le nombre de chiffres. */
.doc-pdf-page-indicator:has(.doc-pdf-page-input) > span:last-child {
    display: inline-block;
    box-sizing: border-box;
    width: 2.6ch;
    padding: 0 1px;
    border: 1px solid transparent;
    text-align: center;
}
.doc-pdf-page-input {
    box-sizing: border-box;
    width: 2.6ch;
    padding: 0 1px;
    border: 1px solid transparent;
    border-radius: 4px;
    background: transparent;
    color: inherit;
    font: inherit;
    font-size: 0.75rem;
    text-align: center;
    -moz-appearance: textfield;
}
.doc-pdf-page-input:hover { background: var(--surface-2); }
.doc-pdf-page-input:focus {
    outline: none;
    background: var(--surface-2);
    border-color: var(--border);
}
.doc-pdf-toolbar-sep {
    width: 1px;
    height: 16px;
    background: var(--border);
    margin: 0 4px;
}

/* Aperçu Local/Global : au centre de la barre, seul groupe libéré une fois
   la navigation page + le zoom regroupés à droite (cf. doc-pdf-toolbar-
   group-right). */
.doc-pdf-toolbar-group-center {
    display: flex;
    align-items: center;
    gap: 8px;
}
.doc-pdf-apercu-toggle {
    display: flex;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius-sm);
    overflow: hidden;
}
.doc-pdf-apercu-btn {
    display: flex;
    align-items: center;
    gap: 5px;
    min-width: 0;
    padding: 4px 10px;
    border: none;
    background: transparent;
    color: var(--text-muted);
    font-size: 0.75rem;
    font-family: inherit;
    cursor: pointer;
}
.doc-pdf-apercu-btn svg { width: 14px; height: 14px; }
.doc-pdf-apercu-btn:not(.is-active):hover { background: var(--surface-2); color: var(--text); }
.doc-pdf-apercu-btn.is-active { background: var(--accent); color: #fff; }
/* Sans le label "Aperçu :" à côté, le toggle Global/Local est le seul
   élément du groupe central : réduit pour rester visuellement cohérent
   avec sa taille de contenu plutôt que de flotter, surdimensionné, seul
   au milieu de la barre. */
.doc-pdf-apercu-toggle-compact .doc-pdf-apercu-btn {
    padding: 4px 9px;
    gap: 5px;
}

.doc-pdf-btn-recompile {
    position: relative;
    width: auto;
    min-width: 92px;
    padding: 0 8px;
    gap: 6px;
    overflow: hidden;
}
.doc-pdf-recompile-label { font-size: 0.75rem; white-space: nowrap; }
/* Bordure du bouton entier pendant la compilation (accent, vert en Local
   via .is-local ci-dessous) — passe au rouge (--danger) au survol pour
   signaler qu'on peut interrompre, plutôt qu'un second bouton à bordure
   propre imbriqué dans le premier (look "bouton dans le bouton").
   !important : .dp-page .doc-pdf-btn-recompile (sheet_detail.css) fixe
   déjà une border-color permanente avec la même spécificité (2 classes) —
   sans !important, le skin de page l'emportait par ordre de chargement et
   la bordure ne changeait jamais, ni pendant la compilation ni au survol. */
.doc-pdf-btn-recompile.is-loading {
    cursor: default;
    border-color: var(--accent) !important;
}
.doc-pdf-btn-recompile.is-loading.is-local {
    border-color: var(--success) !important;
}
.doc-pdf-btn-recompile.is-loading:hover {
    border-color: var(--danger) !important;
}

/* Flèche accolée au bouton "Recompiler" : ouvre un menu avec les options
   moins fréquentes (recompilation complète latexmk, nettoyage des fichiers
   auxiliaires) sans les imposer par défaut au clic principal (cf.
   sheet_pdf_viewer.js, désormais pdflatex/une passe comme Ctrl+Shift+Entrée). */
.doc-pdf-recompile-group {
    position: relative;
    display: flex;
    align-items: stretch;
}
.doc-pdf-btn-recompile-menu {
    width: 18px;
    min-width: 0;
    padding: 0;
    margin-left: 1px;
}
.doc-pdf-btn-recompile-menu svg { width: 12px; height: 12px; }
.doc-pdf-recompile-dropdown {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    min-width: 220px;
    padding: 4px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.doc-pdf-recompile-dropdown[hidden] { display: none; }
.doc-pdf-recompile-dropdown button {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 8px;
    border: none;
    border-radius: 4px;
    background: transparent;
    color: var(--text);
    font-size: 0.8rem;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
}
.doc-pdf-recompile-dropdown button svg { width: 14px; height: 14px; flex: 0 0 auto; color: var(--text-muted); }
.doc-pdf-recompile-dropdown button:hover { background: var(--surface-2); }
/* Couleurs par nature de l'action, plutôt qu'un gris uniforme : accent pour
   une vraie recompilation (même famille que le bouton "Recompiler"
   principal), succès pour une simple resynchronisation d'affichage (rien
   n'est recalculé), danger pour le nettoyage (efface des fichiers). */
.doc-pdf-recompile-dropdown button.doc-pdf-recompile-opt-rebuild svg { color: var(--accent); }
.doc-pdf-recompile-dropdown button.doc-pdf-recompile-opt-reload svg  { color: var(--success); }
.doc-pdf-recompile-dropdown button.doc-pdf-recompile-opt-clear svg   { color: var(--danger); }
.doc-pdf-btn-recompile.is-loading .doc-pdf-recompile-icon,
.doc-pdf-btn-recompile.is-loading .doc-pdf-recompile-label {
    visibility: hidden;
}
.doc-pdf-recompile-bar {
    position: absolute;
    /* inset horizontal 8px = padding 0 8px du bouton (.doc-pdf-btn-
       recompile) : aligne le centrage du contenu de la barre (Stop) sur
       celui du bouton (icône "Recompiler"), sinon l'icône Stop retombait
       ~2px plus à gauche que l'icône refresh qu'elle remplace. */
    inset: 5px 8px;
    border-radius: 4px;
    overflow: hidden;
    background: var(--surface-2);
}
.doc-pdf-recompile-bar::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -40%;
    width: 40%;
    background: var(--accent);
    border-radius: 4px;
    animation: doc-pdf-progress-sweep 1.1s ease-in-out infinite;
    /* Un pseudo-élément ::after positionné se peint après (donc au-dessus
       de) ses "vrais" frères DOM en z-index:auto — sans ça, ce sweep
       (même invisible à opacity:0 au survol) restait au-dessus du bouton
       Stop et interceptait la souris à sa place, faisant clignoter le
       :hover entre les deux éléments superposés. */
    pointer-events: none;
}
/* Au survol, le bouton Stop remplace l'animation (et non l'inverse) :
   sinon le sweep continue de tourner sous le bouton, visible derrière son
   fond transparent au repos. Coupure nette (pas de transition) : évite
   tout risque d'interférence entre une transition d'opacity et
   l'animation @keyframes infinie déjà en cours sur ce même pseudo-
   élément. Déclenché par :hover sur le BOUTON entier (pas sur la barre,
   inset de 5/6px par rapport au bouton) pour arriver exactement en même
   temps que le changement de bordure ci-dessus (.is-loading:hover) — avec
   :hover sur la barre seule, il y avait une zone morte de quelques px où
   la bordure passait au rouge sans que le sweep cède la place au Stop. */
.doc-pdf-btn-recompile:hover .doc-pdf-recompile-bar::after {
    opacity: 0;
}
/* Compilation "Local" (cf. maybeLocalRecompile, sheet_pdf_viewer.js) :
   même barre que la recompilation globale, en vert pour la distinguer
   visuellement d'une recompilation complète (accent). */
.doc-pdf-btn-recompile.is-local .doc-pdf-recompile-bar::after {
    background: var(--success);
}
@keyframes doc-pdf-progress-sweep {
    0% { left: -40%; }
    100% { left: 100%; }
}
/* Bouton pour interrompre la compilation en cours (cf. data-pdf-action=
   "compile-stop", sheet_pdf_viewer.js) — caché par défaut, remplace le
   texte "Recompiler" au survol du bouton entier (même trigger que le
   changement de bordure et la disparition du sweep ci-dessus, pour que
   les trois arrivent ensemble). Pas de
   bordure/fond propres (contrairement à avant) : la bordure rouge est déjà
   portée par le bouton entier (.doc-pdf-btn-recompile.is-loading:hover
   ci-dessus), un second cadre imbriqué à l'intérieur donnait l'impression
   d'un bouton dans le bouton. Reste contenu dans la barre
   (`.doc-pdf-recompile-bar`, déjà inset 5px 6px dans le bouton parent)
   plutôt que le bouton entier, pour ne jamais déborder sur la flèche du
   menu déroulant juste à côté. */
.doc-pdf-recompile-stop {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 0;
    color: var(--danger);
    opacity: 0;
    cursor: pointer;
}
.doc-pdf-btn-recompile:hover .doc-pdf-recompile-stop {
    opacity: 1;
}
.doc-pdf-recompile-stop-label {
    font-size: 0.75rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Erreur de compilation (latexmk global ou bloc "Local", cf.
   sheet_pdf_viewer.js showCompileError) — reste affichée tant que la
   compilation suivante n'a pas réussi, ne disparaît jamais silencieusement. */
.doc-pdf-error {
    position: relative;
    flex: 0 0 auto;
    margin: 6px;
    border-radius: 4px;
    /* Fond quasi-opaque (mix avec --bg-elev plutôt qu'un simple rgba très
       transparent) : ce bandeau se pose par-dessus des pages PDF ou du code
       LaTeX, un fond trop translucide (0.1 avant) rendait le texte du log
       illisible dès qu'il chevauchait un contenu chargé en dessous. */
    background: color-mix(in srgb, var(--bg-elev) 92%, rgb(var(--latex-error-rgb)) 8%);
    border: 1px solid rgba(var(--latex-error-rgb), 0.35);
    /* Hauteur explicite (pas max-height) : condition nécessaire pour que
       `resize: vertical` fonctionne — 21vh = 30vh (taille d'avant) - 30%,
       cf. demande utilisateur. Reprise ensuite depuis localStorage
       (data-pdf-error-height ci-dessous, sheet_pdf_viewer.js) dès qu'un
       redimensionnement manuel a eu lieu, sinon ce défaut reste appliqué à
       chaque nouvelle apparition du bandeau. `overflow: hidden` (les deux
       axes, pas juste overflow-y) plutôt qu'`auto` : le scroll du texte se
       fait dans .doc-pdf-error-text ci-dessous (seule sa hauteur, hors
       croix, doit défiler) — Chrome n'affiche pas systématiquement la
       poignée de resize si overflow-x reste `visible` par défaut.
       Box-sizing: border-box, sinon le padding s'ajoute par-dessus les
       21vh/etc. et fausse le calcul de la poignée native. */
    box-sizing: border-box;
    height: 21vh;
    /* Assez haut pour garder 4 lignes de .doc-pdf-error-text visibles
       (padding 8px*2 + 4 lignes à 0.75rem/1.4 ≈ 16 + 67px), même si
       l'utilisateur réduit le bandeau au minimum via la poignée resize. */
    min-height: 90px;
    max-height: 70vh;
    resize: vertical;
    overflow: hidden;
}
.doc-pdf-error-close {
    /* Bouton icône compact générique (cf. theme.css, comment sur
       .btn-icon-sm) — ne PAS resurcharger width/height à la main comme
       avant : `button { min-width: 120px }` (form.css) écrasait un
       `width: 20px` seul (min-width prime sur width), d'où une croix à la
       taille d'un bouton normal malgré la déclaration. .btn-icon-sm pose
       `min-width: 0` justement pour ça ; cette classe ne fait plus que
       réduire encore sa taille par défaut (28px) et la sortir du flux
       scrollable (position fixée dans le coin, hors de .doc-pdf-error-text
       qui, elle, défile). */
    position: absolute;
    top: 4px;
    right: 4px;
    width: 18px !important;
    height: 18px !important;
    font-size: 0.85rem !important;
    color: var(--latex-error);
}
.doc-pdf-error-close:hover {
    background: rgba(var(--latex-error-rgb), 0.2);
    color: var(--latex-error);
}
.doc-pdf-error-text {
    height: 100%;
    box-sizing: border-box;
    padding: 8px 28px 8px 10px;
    overflow-y: auto;
    color: var(--latex-error);
    font-family: "Fira Code", "Consolas", monospace;
    font-size: 0.75rem;
    white-space: pre-wrap;
}
/* Numéro de ligne ("l.NN") cliquable dans une erreur de compilation (cf.
   showCompileError, sheet_pdf_viewer.js) : ouvre l'onglet LaTeX au bon
   endroit (Global) ou le champ concerné en édition inline (Local), cf.
   window.gotoCompileErrorLine dans sheet_detail.html. Un <button> inline
   plutôt qu'un <a>, pour ne pas naviguer/laisser une ancre dans l'URL. */
.doc-pdf-error-line-link {
    font: inherit;
    color: inherit;
    background: none;
    border: none;
    padding: 0;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
}
.doc-pdf-error-line-link:hover {
    color: #ff9c9c;
}

/* État "aucun PDF" (cf. sheet_pdf_viewer.js, src vide) : centré plutôt que
   collé en haut-gauche comme le serait du texte brut dans .doc-pdf-pages
   (align-items:safe start, pensé pour des pages de PDF, pas un message). */
.doc-pdf-pages-empty {
    margin: auto;
    padding: 24px;
    color: var(--text-muted, #8b949e);
    font-size: 0.95rem;
    text-align: center;
}

.doc-pdf-pages {
    position: relative;
    flex: 1 1 auto;
    overflow: auto;
    /* Réserve la place de la scrollbar verticale même avant qu'elle
       n'apparaisse : sinon computeFitScale() (sheet_pdf_viewer.js) mesure
       clientWidth avant qu'elle existe, la page est calée sur cette largeur
       "trop grande", puis la scrollbar verticale apparaît une fois le rendu
       fait (page plus haute que large) et grignote quelques px → scrollbar
       horizontale en plus, non voulue. */
    scrollbar-gutter: stable;
    display: flex;
    flex-direction: column;
    /* start (pas center) : la page colle contre la poignée de
       redimensionnement plutôt que de laisser un vide non-interactif entre
       les deux (ni resize ni page) quand elle ne remplit pas toute la
       largeur du panneau — et ça évite au passage le souci de l'ancien
       align-items:center qui poussait l'excédent en scroll négatif
       inatteignable en zoom > 100% (bord gauche de la page coupé). */
    align-items: safe start;
    gap: 8px;
    /* padding-top:0 : sinon le fond sombre du panneau reste visible en
       quelques pixels au-dessus de la 1re page une fois scrollé tout en
       haut (demande explicite) — le PDF doit commencer pile au bord. */
    padding: 0 0 8px;
}

/* Encadré de synchro HTML -> PDF (cf. showSyncBox, sheet_pdf_viewer.js) :
   positionné en absolute sur la boîte SyncTeX exacte du bloc visé, pas un
   flash sur toute la page — apparaît puis s'efface tout seul. */
.doc-pdf-sync-box {
    position: absolute;
    pointer-events: none;
    border: 2px solid var(--accent);
    background: var(--accent-soft, rgba(88, 166, 255, 0.15));
    border-radius: 3px;
    opacity: 0;
    transition: opacity 0.25s ease;
    z-index: 2;
}

/* Couche de texte invisible superposée à chaque page (cf. renderPageTextLayer,
   sheet_pdf_viewer.js) : permet la sélection/copie du texte du PDF sans rien
   changer au rendu visuel (le canvas reste l'unique chose vue). Positionnée
   comme .doc-pdf-sync-box (absolute, calée en JS sur offsetTop/Left du canvas
   correspondant) — CSS standard de la text layer PDF.js (text_layer_builder),
   adaptée au nom de classe de ce projet. */
.doc-pdf-textlayer {
    position: absolute;
    overflow: hidden;
    line-height: 1;
    text-align: initial;
    text-size-adjust: none;
    forced-color-adjust: none;
    transform-origin: 0 0;
    z-index: 1;
}
.doc-pdf-textlayer span,
.doc-pdf-textlayer br {
    color: transparent;
    position: absolute;
    white-space: pre;
    cursor: text;
    transform-origin: 0% 0%;
}
.doc-pdf-textlayer ::selection { background: rgba(88, 166, 255, 0.35); }
.doc-pdf-textlayer .endOfContent {
    display: block;
    position: absolute;
    inset: 100% 0 0;
    z-index: -1;
    cursor: default;
    user-select: none;
}
.doc-pdf-textlayer .endOfContent.active { top: 0; }
.doc-pdf-sync-box.is-visible { opacity: 1; }
/* Surbrillance ponctuelle après une synchro PDF <-> HTML (cf.
   sheet_pdf_viewer.js) : deux classes plutôt qu'une seule animation à durée
   fixe — `.sync-flash` (état statique, allumé dès le clic, AVANT même que le
   scroll ne commence, et qui reste plein tant que le scroll dure, aussi
   long soit-il) puis `.sync-flash-fade` (n'ajoutée qu'une fois le scroll
   terminé, cf. afterScrollSettles) qui déclenche l'extinction sur 1.2s.
   Une seule classe liée à une @keyframes à durée fixe se serait éteinte
   toute seule 1.2s après avoir été posée, qu'on ait fini de scroller ou
   non — indépendant du retrait JS de la classe. */
.sync-flash {
    box-shadow: 0 0 0 3px var(--accent);
}
.sync-flash-fade {
    animation: doc-sync-flash 1.2s ease-out;
}
@keyframes doc-sync-flash {
    0% { box-shadow: 0 0 0 3px var(--accent); }
    100% { box-shadow: 0 0 0 3px transparent; }
}
/* Même pulse que .sync-flash, mais pour une ligne CodeMirror (onglet TeX,
   cf. attachLatexCtrlClick) : pas de box-shadow ici (une ligne de texte n'a
   pas de bord net), un fond qui s'estompe joue le même rôle. */
.cm-sync-flash-line { animation: doc-cm-sync-flash 1.2s ease-out; }
@keyframes doc-cm-sync-flash {
    0% { background: rgba(124, 131, 253, 0.35); }
    100% { background: transparent; }
}

.doc-pdf-page {
    /* Ni max-width ni height:auto ici : la taille est fixée en JS via
       canvas.style.width/height (cf. sheet_pdf_viewer.js) pour refléter
       le zoom réel — un max-width:100% écraserait tout zoom > 100% en
       recadrant visuellement le canvas à la largeur du conteneur. */
    flex: 0 0 auto;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
    background: #fff;
    /* Réglage global (menu compte, cf. account-menu-brightness) : 100% = rendu
       natif, en-dessous on assombrit le blanc du PDF sans re-render PDF.js. */
    filter: brightness(var(--pdf-brightness, 1));
}

/* Le panneau PDF reste affiché en permanence (cf. .doc-page-pdf, largeur
   responsive via min(720px, 42vw)) et se rétrécit avec la fenêtre — masqué
   seulement ici, sous le même seuil que le passage sidebar+corps en colonne
   (.doc-page-scroll), où trois colonnes côte à côte n'ont plus la place. */
@media (max-width: 860px) {
    /* :not(.doc-page-pdf-embedded) : la vraie 3e colonne fixe (mode prof,
       pensée pour desktop) n'a pas la place sur mobile — mais le MÊME
       élément sert aussi de panneau PDF du mode élève une fois "embedded"
       (cf. sheet_detail.html, position statique à la place du corps HTML,
       révélé au clic sur le bouton PDF). Un display:none inconditionnel sur
       .doc-page-pdf masquait aussi cette variante élève : le bouton PDF
       n'avait alors plus aucun effet visible sur mobile (constaté). */
    .doc-page-pdf:not(.doc-page-pdf-embedded) { display: none; }
    /* :has() matche même un descendant display:none — sans ce reset, <main>
       garderait sa réservation de largeur alors que le panneau est masqué. */
    main:has(.dp-page .doc-page-pdf) { padding-right: 24px; }
    .doc-page-scroll { flex-direction: column; }
    .doc-page-sidebar { flex: 0 0 auto; width: 100%; position: static; max-height: none; overflow-y: visible; }
}

/* ---------- En-tête (titre + sous-titre + statistiques) ----------
   Le halo/Σ est un effet de "hero" volontairement confiné à ce bloc :
   il ne doit pas se propager au reste du fond de page (sobre ailleurs).
   Le fondu est fait via un masque (mask-image) plutôt qu'un calque séparé :
   TOUT le contenu du header (fond + halo + Σ) s'estompe ensemble et
   progressivement, au lieu d'une coupure nette suivie d'un dégradé plat
   qui ne reprenait pas la couleur du halo. */
/* En-tête SOBRE (2026-08-18) : c'était un bandeau de 190px sur fond --bg-elev,
   avec un halo en dégradé et un Σ de 13rem en filigrane. Trop présent pour ce
   qu'il porte (un titre, un sous-titre et deux chips). Il est maintenant posé
   à plat sur le fond de page, sans bloc coloré ni décor, et fait la hauteur de
   son contenu.
   Ne concerne PAS la page feuille, qui a son propre en-tête (.dp-title-card,
   sheet_detail.css) et n'utilise de .doc-header* que les boutons icône. */
.doc-header {
    position: relative;
    display: flex;
    flex-direction: column;
    height: auto;
    box-sizing: border-box;
    padding: var(--space-3) 0 var(--space-3);
    margin-bottom: 0;
    background: transparent;
    border: none;
    border-radius: 0;
}
/* Bandeau de la page feuille : une ligne de plus (icônes d'action) que sur
   les autres pages (Cours & TD, Devoirs) qui réutilisent .doc-header. Hauteur
   fixe → auto pour s'adapter au contenu réel (titre + chips + icônes) sans
   risque de chevauchement/écrasement selon la longueur du titre. */
.doc-header-tall { height: auto; min-height: 190px; padding: 24px 14px 20px; }
.doc-header-fade { display: none; }
/* Halo et Σ en filigrane : retirés de l'en-tête sobre. Les éléments restent
   dans le markup de _leftmenu_base.html (un seul socle pour toutes les pages),
   ils sont simplement masqués ici. */
.doc-header-glow { display: none; }

.doc-header-mark { display: none; }
/* Ligne titre : badge de type (gros) à gauche, titre au milieu, icônes
   d'action en haut à droite — page feuille uniquement (les autres pages
   doc-header n'ont pas de badge ni d'icônes). */
/* align-items: flex-start (pas center) : la ligne s'étire quand le titre ou
   le sous-titre grandit (édition inline) — retour/badge/icônes doivent
   rester ancrés en haut, jamais recentrés verticalement. */
.doc-header-title-row { position: relative; z-index: 1; display: flex; align-items: flex-start; gap: 12px; }
.doc-header-back-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 32px; height: 32px;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-faint);
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.doc-header-back-btn svg { width: 17px; height: 17px; }
.doc-header-back-btn:hover { background: var(--surface-3); color: var(--text); border-color: var(--accent); }

/* ---------- Icônes d'action de l'en-tête (preuve/todo/PDF/TeX/note) ----------
   Dans la ligne de titre, poussées en haut à droite du bandeau (margin-left:
   auto dans le flex row .doc-header-title-row) plutôt qu'en bas. */
.doc-header-icons {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 8px;
    margin-left: auto;
}
.doc-header-icon-btn {
    position: relative;
    box-sizing: border-box;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    width: 32px; height: 32px;
    min-width: 0;
    padding: 0;
    margin: 0;
    border-radius: var(--radius-sm);
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text-faint);
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    text-decoration: none;
    transition: background 0.12s, color 0.12s, border-color 0.12s, transform 0.08s ease, box-shadow 0.12s;
}
/* position:absolute + inset:0 + margin:auto : centrage immunisé contre le
   flex-shrink erratique observé sur <button> (le centrage flex seul ne
   suffisait pas, l'icône débordait du cadre). */
.doc-header-icon-btn svg {
    position: absolute;
    inset: 0;
    margin: auto;
    width: 19px; height: 19px;
    flex-shrink: 0;
}
.doc-header-icon-proof svg, .doc-header-icon-todo svg, .doc-header-icon-hint svg, .doc-header-icon-pdf svg, .doc-header-icon-toc svg { width: 26px; height: 26px; }
.doc-header-icon-btn:hover {
    background: var(--surface-3);
    color: var(--text);
    border-color: var(--accent);
    transform: scale(1.03);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
/* .latex-btn (exercise_card.css) apporte son propre padding/bordure : on les
   neutralise ici pour garder la taille uniforme 36x36 de la rangée. */
.doc-header-icon-btn.latex-btn { padding: 0; border-width: 1px; }
.doc-header-icon-btn .tex-logo { font-size: 15px; }

/* ---------- Actions d'en-tête sur les pages "document" génériques
   (Exercices, Devoirs, Cours & TD, Corbeille) — même emplacement/gabarit
   que .doc-header-icons de la page feuille (icônes 32x32 en haut à droite
   du bandeau), pour ne pas avoir de gros bouton texte qui prend toute la
   largeur. ---------- */
.doc-header-actions {
    /* Ancrées en haut à droite de l'en-tête. top:0 et non 16px : le bandeau de
       190px qui justifiait ce décalage n'existe plus. */
    position: absolute;
    top: var(--space-4);
    right: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    gap: 8px;
}
.doc-header-actions:empty { display: none; }
.doc-header-icon-btn.doc-header-icon-danger { color: var(--danger); border-color: var(--danger); background: rgba(217, 83, 79, 0.1); }
.doc-header-icon-btn.doc-header-icon-danger:hover { background: var(--danger); color: #fff; }

/* Bouton "Notions" (sheet_detail.html) : grisé (défaut .doc-header-icon-btn)
   tant que le chapitre n'a pas de plan de notions, coloré (.has-notions) une
   fois qu'il en a un — seul indicateur avant d'ouvrir la modale. Survol
   distinct des deux états : ne doit pas se confondre avec la couleur "a des
   notions" au repos. */
.doc-header-icon-btn.sheet-notions-btn.has-notions {
    color: var(--accent);
    border-color: var(--accent);
    background: color-mix(in srgb, var(--accent) 12%, var(--surface-2));
}
.doc-header-icon-btn.sheet-notions-btn.has-notions:hover {
    background: var(--accent);
    color: var(--on-accent);
    border-color: var(--accent);
}

/* Bouton "Notes" (sheet_detail.html) : même logique — grisé (défaut
   .doc-header-icon-btn) tant qu'aucune note n'est saisie, coloré
   (.has-notes) une fois rempli. Neutralise ici la couleur orange globale
   posée par .notes-icon svg (exercise_card.css), qui s'applique sinon
   sans condition. */
.doc-header-icon-btn.notes-icon:not(.has-notes),
.doc-header-icon-btn.notes-icon:not(.has-notes) svg {
    color: var(--text-faint);
}
.doc-header-icon-btn.notes-icon.has-notes,
.doc-header-icon-btn.notes-icon.has-notes svg {
    color: var(--content-exercise);
}
.doc-header-icon-btn.notes-icon.has-notes {
    border-color: var(--content-exercise);
    background: var(--content-exercise-bg);
}
/* Notes d'une FEUILLE : violet (demande 2026-09-03) plutôt que l'orange
   générique des notes d'exercice — sur la même rangée que le bouton Todos,
   désormais orange lui aussi, les deux ne se distinguaient plus. */
.doc-header-icon-btn.notes-icon.sheet-notes-btn.has-notes,
.doc-header-icon-btn.notes-icon.sheet-notes-btn.has-notes svg { color: var(--violet); }
.doc-header-icon-btn.notes-icon.sheet-notes-btn.has-notes {
    border-color: var(--violet);
    background: var(--violet-soft);
}
/* ---------- Boutons toggle uniformisés (sommaire / preuves / todos) ----------
   Mécanique commune (survol/press/pop) via .doc-toggle-icon-btn — jamais de
   changement de couleur au survol, juste fond/bordure plus marqués, léger
   scale, ombre douce ; la couleur ne change qu'au clic (cf. .icon-btn-press
   / .icon-btn-pop). Couleur "coloré = contenu visible, gris = masqué" pour
   les trois, mais la polarité de ".active" diffère selon le flag posté
   (show_toc : actif = affiché ; hide_proofs/hide_todos : actif = masqué) —
   d'où deux jeux de règles ci-dessous, chacun surchargeant la règle
   générique .flag-toggle-btn.active (bleu accent). */
.doc-toggle-icon-btn {
    transition: background 0.12s, border-color 0.12s, color 0.18s ease, transform 0.08s ease, box-shadow 0.12s;
}
.doc-toggle-icon-btn:hover {
    transform: scale(1.03);
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.doc-toggle-icon-btn.icon-btn-press { transform: scale(0.96); }
@keyframes docIconBtnPop {
    0%   { transform: scale(0.96); }
    60%  { transform: scale(1.04); }
    100% { transform: scale(1); }
}
.doc-toggle-icon-btn.icon-btn-pop { animation: docIconBtnPop 0.2s ease; }

/* Preuves / Indications / Todos : actif = masqué (gris), inactif = visible (coloré). */
.doc-header-icon-proof.active, .doc-header-icon-hint.active, .doc-header-icon-todo.active { color: var(--text-faint); border-color: var(--border); background: var(--surface-2); }
.doc-header-icon-proof:not(.active) { color: var(--success, #22c55e); border-color: var(--success, #22c55e); background: rgba(34, 197, 94, 0.1); }
.doc-header-icon-hint:not(.active) { color: var(--hint, #eab308); border-color: var(--hint, #eab308); background: rgba(234, 179, 8, 0.1); }
.doc-header-icon-todo:not(.active) { color: #e0762c; border-color: #e0762c; background: rgba(224, 118, 44, 0.12); }
.doc-header-icon-proof.active:hover, .doc-header-icon-hint.active:hover, .doc-header-icon-todo.active:hover { background: var(--surface-3); border-color: var(--border); }
.doc-header-icon-proof:not(.active):hover { background: rgba(34, 197, 94, 0.18); border-color: var(--success, #22c55e); }
.doc-header-icon-hint:not(.active):hover { background: rgba(234, 179, 8, 0.18); border-color: var(--hint, #eab308); }
.doc-header-icon-todo:not(.active):hover { background: rgba(224, 118, 44, 0.22); border-color: #e0762c; }

/* Sommaire : polarité inverse (actif = affiché, donc coloré). */
.doc-header-icon-toc.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
.doc-header-icon-toc:not(.active) { color: var(--text-faint); border-color: var(--border); background: var(--surface-2); }
.doc-header-icon-toc.active:hover { background: var(--accent-soft); border-color: var(--accent); }
.doc-header-icon-toc:not(.active):hover { background: var(--surface-3); border-color: var(--border); }
.sheet-kind-badge-big {
    flex: 0 0 auto;
    padding: 0;
    background: transparent;
    border-radius: 0;
}
.sheet-kind-badge-big .sh-kind-icon { width: 44px; height: 44px; }
.doc-header-title { position: relative; z-index: 1; text-align: left; min-width: 0; flex: 1 1 auto; }
.doc-header-subtitle-row {
    font-size: 0.9rem;
    color: var(--text-muted);
    cursor: text;
    margin-top: 2px;
}
/* Champ d'édition du sous-titre : prend toute la largeur disponible (le
   conteneur grandit désormais avec .doc-header-title en flex:1) au lieu de
   rester à la largeur minimale par défaut d'un <input>, ce qui faisait
   défiler le texte au lieu de l'afficher en entier. */
.doc-header-subtitle-row .inline-edit-input { width: 100%; box-sizing: border-box; }
.doc-header-subtitle-row:hover { color: var(--text); }
.sheet-subtitle-empty-hint { color: var(--text-faint); font-style: italic; }
.doc-header h1 {
    font-family: var(--font-head);
    font-size: 1.9rem;
    font-weight: 700;
    color: var(--text);
    margin: 0;
    /* line-height serré : le 1.55 hérité de la page donnait 55px de hauteur
       pour 35px de texte, l'essentiel de la hauteur de l'ancien bandeau. */
    line-height: 1.15;
    letter-spacing: -0.01em;
    text-align: left;
    text-wrap: balance;
}
/* Titre "Variantes de « … »" : le titre de l'exercice cité peut être long,
   contrairement aux titres de page fixes (Exercices, Devoirs…) — taille
   réduite pour rester lisible sans casser la ligne n'importe où. */
.doc-header h1 .doc-title-family { font-size: 0.68em; }
.doc-header-subtitle {
    margin: 6px 0 0;
    font-size: 0.95rem;
    color: var(--text-muted);
    text-align: left;
}
.doc-header-stats { display: flex; align-items: flex-start; gap: 10px; margin-top: 10px; flex-wrap: wrap; justify-content: flex-start; flex: 1 1 auto; min-width: 0; }
.doc-header-tall .doc-header-stats { margin-top: 10px; }
/* Page feuille : "Modifié le" à gauche, onglets HTML/LaTeX à droite sur la
   même ligne (cf. .doc-header-updated / .doc-body-tabs plus haut). */
#sheet-detail-card .doc-header-stats { align-items: center; justify-content: space-between; }
.doc-stat-chip {
    display: inline-flex;
    align-items: center;
    height: 30px;
    box-sizing: border-box;
    gap: 7px;
    padding: 3px 10px;
    border: 1px solid var(--border-soft);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: 0.8rem;
    font-weight: 500;
    line-height: 1.3;
}
.doc-stat-chip svg { width: 14px; height: 14px; color: var(--accent); flex: 0 0 auto; }
.doc-stat-chip-tracks { gap: 6px; padding: 3px 10px; }
.doc-header-actions { display: flex; align-items: center; gap: 10px; }

/* ---------- Groupes par semestre : titre de section, pas de carte ----------
   Refonte "explorateur de cours" (2026-08-13) : on abandonne la carte à
   bordure+fond qui empilait Généralités/Semestre 1/2 comme des panneaux
   administratifs, au profit d'un simple titre de section plus marqué
   (taille/poids) au-dessus d'une liste de lignes — la hiérarchie vient du
   titre, pas d'un cadre. */
.doc-semester-group {
    margin-bottom: 32px;
}
.doc-semester-group:last-child { margin-bottom: 0; }
.doc-semester-title {
    display: flex;
    align-items: center;
    gap: 16px;
    font-family: var(--font-body);
    font-size: 1.4rem;
    font-weight: 400;
    color: var(--text);
    margin: 0;
    padding: 8px 16px;
    border: 1px solid var(--border-soft);
    border-bottom: none;
    border-top-left-radius: var(--radius);
    border-top-right-radius: var(--radius);
    background: var(--bg, #0a0e17);
    position: sticky;
    top: 0;
    z-index: 10;
    cursor: pointer;
    list-style: none;
    user-select: none;
}
.doc-semester-icon {
    display: inline-block;
    width: 28px; height: 28px;
    flex: 0 0 auto;
    background-color: var(--accent);
    mask-repeat: no-repeat;
    mask-position: center;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    -webkit-mask-size: contain;
}
.doc-semester-title::after {
    content: "›";
    margin-left: auto;
    display: inline-block;
    font-size: 2rem;
    font-weight: 400;
    line-height: 1;
    color: var(--text);
    transition: transform 0.15s;
}
.doc-semester-title::-webkit-details-marker { display: none; }
.doc-semester-group[open] > .doc-semester-title::after { transform: rotate(90deg); }
.doc-semester-body {
    padding: 0;
}

/* ---------- Groupes thématiques (ordre = "Par thème") ---------- */
.doc-theme-group { margin-bottom: 8px; }
.doc-theme-title {
    font-family: var(--font-head);
    font-size: 1rem;
    font-weight: 600;
    color: var(--text-muted);
    margin: 20px 0 10px;
}

/* ---------- Chapitre (accordéon) — une ligne dans une liste, pas une carte.
   Pas de bordure ni de fond propres : seule une ligne fine sépare deux
   chapitres consécutifs (au lieu d'une bordure sur les 4 côtés de chaque
   "carte"), le survol est ce qui distingue une ligne active. ---------- */
.doc-chapter {
    /* Pas de wrapper dédié autour de la liste de chapitres (le D&D,
       cours_et_td2.js, insère le placeholder comme enfant direct de
       .doc-semester-body — un wrapper intermédiaire casserait ce drop) :
       chaque ligne dessine son propre bord gauche/droit, qui mis bout à
       bout avec .doc-semester-title (même bord, coins hauts arrondis
       au-dessus) et le bord bas de la dernière ligne (coins bas arrondis,
       cf. :last-of-type) forme un seul cadre "titre + chapitres" — le
       bouton "+ Ajouter un chapitre" qui suit dans le DOM n'a lui aucun de
       ces bords, donc reste visuellement hors du cadre. */
    border: none;
    border-left: 1px solid var(--border-soft);
    border-right: 1px solid var(--border-soft);
    border-bottom: 1px solid var(--border-soft);
    border-radius: 0;
    background: var(--surface);
    margin: 0;
    overflow: hidden;
    transition: background 0.12s;
}
.doc-chapter:last-of-type {
    border-bottom-left-radius: var(--radius);
    border-bottom-right-radius: var(--radius);
}
.doc-chapter:hover,
.doc-chapter[open] {
    background: var(--surface-2);
}
.doc-chapter-head {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 12px 14px;
    cursor: pointer;
    list-style: none;
    user-select: none;
    outline: none;
    position: relative;
}
.doc-chapter-head::-webkit-details-marker { display: none; }
.doc-chapter-head.drop-target-active { background: var(--accent-dim, rgba(109,123,255,0.14)); box-shadow: inset 0 0 0 2px var(--accent); }
.doc-chapter-drag-handle {
    cursor: grab;
    color: var(--text-faint);
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 0.12s, color 0.12s;
    /* Même élargissement de hitbox que .doc-resource-drag-handle. */
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    width: 24px;
    margin: -12px 0 -12px -14px;
}
.doc-chapter:hover .doc-chapter-drag-handle { opacity: 0.6; }
.doc-chapter-drag-handle:hover { opacity: 1 !important; color: var(--accent); }
.doc-chapter.dragging { opacity: 0.4; }
.doc-semester-body.drag-over-target { outline: 2px dashed var(--accent); outline-offset: -2px; }
.doc-quicklink.drop-target-active { border-color: var(--accent); background: var(--accent-dim, rgba(109,123,255,0.14)); }
.doc-quicklink.drop-target-incompatible { border-color: var(--danger-soft); background: rgba(229, 103, 95, 0.1); cursor: no-drop; }
.doc-chapter-chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    color: var(--text-muted);
    transition: transform 0.15s, color 0.12s;
    flex: 0 0 auto;
}
.doc-chapter-chevron svg {
    width: 19px;
    height: 19px;
    display: block;
}
.doc-chapter:hover .doc-chapter-chevron,
.doc-chapter[open] .doc-chapter-chevron { color: var(--text); }
.doc-chapter[open] > .doc-chapter-head .doc-chapter-chevron { transform: rotate(90deg); }
.doc-chapter-number {
    display: inline-block;
    min-width: 2ch;
    color: var(--accent);
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}
.doc-chapter-title {
    font-family: var(--font-body);
    font-size: 0.92rem;
    font-weight: 400;
    color: var(--text);
}
.doc-chapter-menu-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin-left: auto;
    width: 22px;
    height: 22px;
    min-width: 0;
    background: transparent;
    border: none;
    color: var(--text-faint);
    cursor: pointer;
    padding: 0;
    line-height: 1;
    font-size: 1rem;
    border-radius: var(--radius-sm);
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 0.12s, background 0.12s, color 0.12s;
}
.doc-chapter:hover .doc-chapter-menu-btn,
.doc-chapter-menu-btn:focus-visible { opacity: 1; }
.doc-chapter-menu-btn:hover { background: var(--surface-3); color: var(--text); }

.doc-chapter-body { padding: 4px 20px 20px; }

/* ---------- Boutons rapides Cours / Exercices / Résumé ---------- */
.doc-quicklinks {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 10px;
    margin-bottom: 16px;
}
.doc-quicklinks:last-child { margin-bottom: 0; }
.doc-quicklink {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    border: 1px solid var(--border-soft);
    border-radius: var(--radius);
    background: var(--surface-2);
    text-decoration: none;
    color: var(--text);
    transition: border-color 0.12s, background 0.12s;
}
.doc-quicklink:hover { border-color: var(--border); }
.doc-quicklink-cours,
.doc-quicklink-td,
.doc-quicklink-resume { background: var(--surface); }
.doc-quicklink-cours:hover,
.doc-quicklink-td:hover,
.doc-quicklink-resume:hover { background: var(--surface-2); }

/* Infobulle rapide générique : remplace le tooltip natif du navigateur
   (title=), trop lent à apparaître (~1s). Délai réduit ici à 150ms via
   transition-delay, seulement au survol. À l'origine réservée aux tuiles
   Cours/TD/Résumé (.doc-quicklink) ; généralisée à tout élément portant
   data-tooltip — cf. core/fast_tooltip.js, qui bascule automatiquement
   tout title= existant vers data-tooltip au survol (site entier, pas
   besoin de retoucher chaque template). */
/* :where() = spécificité nulle, exprès : tout élément avec un title= passe
   par data-tooltip au survol (fast_tooltip.js) et doit rester positionné
   comme il l'était avant (absolute pour un bouton flottant de carte,
   static pour .doc-sidebar-toc-info-btn, etc.). Avec un simple sélecteur
   `[data-tooltip] { position: relative }`, cette règle avait la même
   spécificité qu'une classe (.exo-info-panel-close, .modal-close-btn...)
   et gagnait dès que doc.css était chargé après le fichier définissant le
   bouton (base.html) : le bouton repassait en position:relative pile au
   survol, retombait en flux normal (décalage bas-gauche, hors du
   curseur) — cf. l'ancienne liste blanche ci-dessous, remplacée par ce
   :where() qui couvre TOUT bouton/icône existant ou futur sans
   entretien : n'importe quelle règle `.xxx { position: absolute/static }`
   ailleurs, quel que soit l'ordre de chargement, l'emporte automatiquement
   sur ce :where() à spécificité 0. */
/* Infobulle flottante : un unique élément enfant direct de <body>
   (position:fixed), créé et repositionné par core/fast_tooltip.js à chaque
   survol via getBoundingClientRect. Remplace l'ancienne approche en ::after
   posée directement sur le déclencheur : en position:absolute, elle se
   faisait tronquer/masquer par le premier ancêtre à overflow:hidden/auto
   (carte, panneau info scrollable, #app-body...) quel que soit son
   z-index — l'overflow d'un ancêtre l'emporte toujours sur le z-index d'un
   descendant. Being fixed on <body>, cette infobulle sort de tous ces
   contextes d'empilement et reste toujours au-dessus, jamais coupée. */
.fast-tooltip-bubble {
    position: fixed;
    /* > .vlink-backdrop (theme.css, z-index 9500) : sinon l'infobulle
       d'un bouton dans une modale (ex. "Supprimer la notion") se retrouve
       coincée derrière le backdrop de sa propre modale. */
    z-index: 9600;
    background: var(--surface-3, #2a2a2a);
    color: var(--text);
    border: 1px solid var(--border-soft);
    border-radius: 6px;
    padding: 4px 8px;
    font-size: 0.78rem;
    white-space: nowrap;
    max-width: 280px;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.1s;
    transition-delay: 0s;
}
.fast-tooltip-bubble.visible {
    opacity: 1;
    transition-delay: 0.15s;
}
/* Poignée de glisser-déposer d'une tuile "principale" (Cours/TD/Résumé) —
   même hitbox élargie que .doc-resource-drag-handle, pour la ressortir vers
   la liste des feuilles du chapitre (cf. cours_et_td.js). */
.doc-quicklink-drag-handle {
    cursor: grab;
    color: var(--text-faint);
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 0.12s, color 0.12s;
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    width: 22px;
    margin: -12px 0 -12px -16px;
}
.doc-quicklink:hover .doc-quicklink-drag-handle { opacity: 0.6; }
.doc-quicklink-drag-handle:hover { opacity: 1 !important; color: var(--accent); }
.doc-quicklink.dragging { opacity: 0.4; }

.doc-quicklink-label { font-weight: 600; font-size: 0.92rem; }
.doc-quicklink-arrow { margin-left: auto; color: var(--text-faint); }
.doc-quicklink-empty {
    opacity: 0.45;
    cursor: default;
    /* PAS de pointer-events: none ici : ce badge vide est justement la cible
       de drop la plus courante (promouvoir une feuille en "principale" là où
       il n'y en a pas encore) ; le désactiver empêchait le survol/drop d'y
       être détecté du tout. L'absence de href suffit à empêcher toute
       navigation au clic, donc rien d'autre à neutraliser. */
}

/* ---------- Icônes des logos (Cours/TD/Résumé/DM/DS/Colle) ----------
   Taille uniformisée : 26x26 dans les listes, 36x36 dans les autres menus. */
.doc-quicklink-icon {
    display: inline-flex;
    width: 36px; height: 36px;
    flex: 0 0 auto;
}
.doc-quicklink-icon img { width: 100%; height: 100%; object-fit: contain; }

/* ---------- Liste des ressources du chapitre ---------- */
.doc-resource-list { display: flex; flex-direction: column; gap: 0; margin-bottom: 12px; }
.doc-resource-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    border: 1px solid var(--border-soft);
    border-top: none;
    border-radius: 0;
    background: var(--surface);
    text-decoration: none;
    color: var(--text);
    position: relative;
}
.doc-resource-drag-handle {
    cursor: grab;
    color: var(--text-faint);
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 0.12s, color 0.12s;
    /* Hitbox élargie à toute la partie gauche de la ligne (pas juste le
       glyphe ⠿) : étend la zone cliquable jusqu'aux bords de la ligne via
       des marges négatives qui annulent le padding du conteneur, plutôt
       que de ne compter que sur la taille du caractère lui-même. */
    display: flex;
    align-items: center;
    justify-content: center;
    align-self: stretch;
    width: 22px;
    margin: -8px 0 -8px -10px;
}
.doc-resource-row:hover .doc-resource-drag-handle { opacity: 0.6; }
.doc-resource-drag-handle:hover { opacity: 1 !important; color: var(--accent); }
.doc-resource-row.dragging { opacity: 0.4; }
.doc-resource-row:first-child {
    border-top: 1px solid var(--border-soft);
    border-top-left-radius: var(--radius-sm);
    border-top-right-radius: var(--radius-sm);
}
.doc-resource-row:last-child {
    border-bottom-left-radius: var(--radius-sm);
    border-bottom-right-radius: var(--radius-sm);
}
.doc-resource-row:hover { background: var(--surface-2); border-color: var(--border); }
.doc-resource-icon { display: inline-flex; width: 26px; height: 26px; flex: 0 0 auto; margin-right: 24px; filter: saturate(0.85); transition: filter 0.15s; }
.doc-resource-icon img { width: 100%; height: 100%; object-fit: contain; }
.doc-resource-row:hover .doc-resource-icon { filter: saturate(1.6) brightness(1.1); }
.doc-resource-kind {
    font-size: 0.85rem;
    font-weight: 600;
    color: var(--text);
    flex: 1 1 auto;
    transition: color 0.15s;
}
.doc-resource-row:hover .doc-resource-kind { color: #fff; }
.doc-resource-arrow { margin-left: auto; color: var(--text-faint); }

.doc-add-item-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 9px;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
    font-size: 0.85rem;
}
.doc-add-item-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }

.doc-add-chapter-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    padding: 9px;
    margin-top: 6px;
    border: 1px dashed var(--border);
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-faint);
    cursor: pointer;
    font-size: 0.85rem;
}
.doc-add-chapter-btn:hover { border-color: var(--accent); color: var(--accent); background: var(--accent-dim); }

/* ---------- Sous-chapitres (popup d'édition d'un chapitre) ---------- */
.subchapter-list { display: flex; flex-direction: column; gap: 0; margin: 6px 0 10px; }
.subchapter-row {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 8px;
    border: 1px solid var(--border-soft);
    border-top: none;
    background: var(--surface);
}
.subchapter-row:first-child { border-top: 1px solid var(--border-soft); border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm); }
.subchapter-row:last-child { border-bottom-left-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
.subchapter-row:hover { background: var(--surface-2); border-color: var(--border); }
.subchapter-drag-handle {
    cursor: grab;
    color: var(--text-faint);
    flex: 0 0 auto;
    opacity: 0;
    transition: opacity 0.12s, color 0.12s;
}
.subchapter-row:hover .subchapter-drag-handle { opacity: 0.6; }
.subchapter-drag-handle:hover { opacity: 1 !important; color: var(--accent); }
.subchapter-row.dragging { opacity: 0.4; }
.subchapter-title { flex: 1 1 auto; min-width: 0; color: var(--text); cursor: text; }
.subchapter-delete-btn {
    flex: 0 0 auto;
    margin-left: auto;
    width: 24px;
    height: 24px;
    min-width: 24px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    border: 1px solid var(--border);
    background: var(--surface-2);
    border-radius: var(--radius-sm);
    color: var(--danger);
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
}
.subchapter-delete-btn svg { width: 15px; height: 15px; }
.subchapter-delete-btn:hover { background: var(--danger); color: #fff; border-color: var(--danger); }
.subchapter-add-row { display: flex; gap: 8px; }
.subchapter-add-row input[type="text"] { flex: 1 1 auto; }
.inline-edit-input { width: 100%; font: inherit; }

/* Mode élève : pas de création/gestion */
body.mode-eleve .admin-only { display: none !important; }

/* ---------- Palette "aller au bloc n°" (Ctrl/Cmd+G, cf. sheet_detail.html) :
   au-dessus de tout, y compris le sommaire flottant plein écran (120). ---- */
.doc-jump-box {
    position: fixed;
    top: 18vh;
    left: 50%;
    transform: translateX(-50%);
    z-index: 500;
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 10px 14px;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    /* Liseré gauche coloré selon le mode (bloc/ligne, cf. data-jump-mode posé
       par sheet_detail.html) — seul repère visuel rapide pour distinguer les
       deux sans avoir à relire le libellé à chaque Ctrl/Cmd+G. */
    border-left: 3px solid var(--accent);
    border-radius: var(--radius-md, 10px);
    box-shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
    transition: border-left-color 0.15s ease;
}
.doc-jump-box[data-jump-mode="line"] { border-left-color: #d9a441; }
.doc-jump-box-label { color: var(--text-muted); font-size: 0.85rem; white-space: nowrap; }
.doc-jump-box input {
    width: 90px;
    font: inherit;
    font-size: 1.1rem;
    text-align: center;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm, 6px);
    color: var(--text);
    padding: 4px 6px;
}
.doc-jump-box input:focus { outline: none; border-color: var(--accent); }
.doc-jump-box[data-jump-mode="line"] input:focus { border-color: #d9a441; }
.doc-jump-box-error { color: var(--danger); font-size: 0.8rem; }

/* ---------- Coeur "question de cours" — RÈGLE UNIQUE ---------- */
/* Un seul endroit pour la couleur "actif" du cœur (Environment.
   is_question_cours), quel que soit l'endroit où il apparaît : le bouton
   statique de la carte de bloc (.exo-fav-heart, page classique), le bouton
   de la barre de survol (.fpdf-action[data-fpdf-block-action="question-
   cours"], page faux PDF), et l'icône décorative du panneau "Détails"
   (exo-meta-icon-row). Les trois posent `.icon-question-cours` (+ `.is-
   active` quand `is_question_cours` est vrai) plutôt que de dupliquer une
   couleur rose dans chaque fichier CSS — ce qui avait déjà divergé une fois
   (2026-09-11 : la barre de survol n'avait jamais reçu la règle posée sur le
   bouton statique). */
/* `.active`/`.is-active` : les deux conventions déjà en usage sur les
   boutons-icône du site (bouton bascule "à l'ancienne" vs bouton de barre
   d'actions) — un cœur porte l'une ou l'autre selon d'où il vient, cette
   règle ne force pas à uniformiser les DEUX pour un seul cœur.

   `fill` répété sur la classe elle-même ET sur `svg` descendant : la classe
   se pose tantôt sur un BOUTON qui CONTIENT le svg (boutons bascule), tantôt
   directement sur le `<svg>` lui-même (icône décorative du panneau info,
   `cls` de `block_icon`) — les deux cas doivent peindre le même cœur.

   `.icon-question-cours` répétée (`.icon-question-cours.icon-question-cours…`)
   plutôt qu'une seule fois : à spécificité ÉGALE (deux classes), la règle
   générique du bouton hôte (`.fpdf-action.is-active`, bleu) gagnait sur
   celle-ci selon l'ORDRE de chargement des feuilles — `sheet_fake_pdf.css`
   arrive après `doc.css` dans le `<head>` de la page, donc après elle dans
   la cascade à spécificité égale (constaté : le cœur de la barre de survol
   restait bleu-contour). Cette répétition monte la spécificité à trois
   classes, qui gagne quel que soit l'ordre des fichiers — plus robuste
   qu'un `!important`, et ça n'a de sens que pour CE cœur, pas pour un site
   entier de boutons "actifs". */
.icon-question-cours.icon-question-cours.active,
.icon-question-cours.icon-question-cours.is-active {
    color: #e0455f;
    fill: #e0455f;
}
.icon-question-cours.icon-question-cours.active svg,
.icon-question-cours.icon-question-cours.is-active svg {
    fill: #e0455f;
}
