/* =====================================================================
   THOMATHS — PAGE FEUILLE (sheet_detail.html)
   Refonte de la page de détail cours/TD/résumé (ex-espace de test
   /design-preview/, fusionné dans la production — cf. sheet_detail.html).
   Fichier isolé de doc.css (préfixe `dp-` partout) pour ne pas interférer
   avec les autres pages document (Exercices, Devoirs, Corbeille), qui
   restent sur doc.css seul. Consomme les tokens de tokens.css comme le
   reste du site.
   ===================================================================== */

/* `.spdf-page` (nouvelle page feuille) déclare la MÊME variable de largeur
   de sommaire : c'est la source unique des deux pages, changer cette valeur
   les change ensemble. Elle est déclarée sur les deux enveloppes plutôt que
   sur `:root` — c'est une mesure propre aux pages feuille, pas au site. */
.dp-page,
.spdf-page {
    /* Largeur du sommaire — SOURCE UNIQUE (cf. plus bas). */
    --dp-sidebar-w: 216px;
}

.dp-page {
    display: flex;
    flex-direction: column;
    min-height: 100%;
}

/* Vert de test pour le type "Cours" : surcharge LOCALE de --kind-cours
   (défini dans tokens.css, utilisé site entier pour l'icône/logo Cours),
   uniquement à l'intérieur de cet espace de test — ne touche pas
   tokens.css, donc aucun effet sur la production.
   Posée sur <body> (pas .dp-page) : une variable CSS personnalisée
   n'est visible que dans le sous-arbre où elle est (re)définie — il faut
   qu'elle englobe aussi la navbar (le Σ du logo, cf. plus bas), qui est
   un sibling de .dp-page, pas un descendant. */
body.dp-kind-cours {
    --kind-cours: #1e6d45;
    /* Vert "texte" unifié : même teinte (hue) que --kind-cours (#1e6d45),
       mais éclaircie/avivée (HSL, L 50% au lieu de ~27%) pour rester
       nettement lisible sur le fond très sombre de la barre — le ton
       médian du dégradé du bouton (#337a56, contraste 3.68:1) et même son
       stop clair (#478766, 4.42:1) ont été jugés encore trop sombres.
       Réutilisée pour le nom de la feuille (fil d'Ariane), le Σ du logo et
       l'accent du sommaire : même famille de vert que les boutons, juste
       plus lumineuse là où c'est du texte fin plutôt qu'un aplat de bouton. */
    --kind-cours-text: #39c687;
}

.dp-page .doc-chapters-scroll-sheet {
    padding-bottom: 0;
    box-sizing: border-box;
}

/* ---- doc-context-bar : le fond est plein écran (100vw, .dp-context-bar) ;
   .dp-context-bar-inner reproduit la grille de .doc-page-scroll en dessous
   (doc.css) — MÊMES pistes (248px sommaire / gap / contenu), MÊME centrage
   (max-width 1180px, padding 24px) — au lieu d'un pourcentage d'écran
   arbitraire. Le fil d'Ariane/switch/affichage tombent donc pile sur les
   bords réels du sommaire et du contenu par construction, sans mesure JS :
   les deux boîtes ont la même géométrie parce qu'elles utilisent
   littéralement les mêmes valeurs, pas parce qu'un script les recopie. */
.dp-context-bar {
    /* Sticky sous la navbar : #app-body (le conteneur qui défile, cf.
       nav.css) a déjà margin-top:52px pour se caler sous la navbar fixe —
       le "top" du sticky se mesure DEPUIS le haut d'#app-body, pas depuis
       le haut de l'écran. top:0 colle donc la barre juste sous la navbar. */
    position: sticky;
    top: 0;
    z-index: 35;
    /* Plein écran (fond) : casse le max-width:1180px hérité de <main> (cf.
       la règle main:has(.doc-page-scroll) dans doc.css) via l'astuce
       classique "100vw + marges négatives" — seulement pour le FOND/la
       bordure, le vrai centrage/padding est reproduit sur
       .dp-context-bar-inner ci-dessous, pas ici. */
    width: 100vw;
    margin-left: calc(-50vw + 50%);
    margin-right: calc(-50vw + 50%);
    margin-bottom: 20px;
    background: var(--surface-2, #12161d);
    /* Séparation avec la navbar juste au-dessus (même fond sinon) : bordure
       claire, en plus de la bordure basse déjà présente vers le contenu. */
    border-top: 1px solid rgba(255, 255, 255, 0.18);
    border-bottom: 1px solid var(--border-soft, rgba(255,255,255,0.08));
}

.dp-context-bar-inner {
    /* Container query (pas @media) : en mode prof, la piste "contenu" (1fr)
       ne dépend pas QUE de la largeur d'écran mais aussi de la largeur du
       panneau PDF (--pdf-panel-width, redimensionnable par l'utilisateur,
       cf. main:has(.doc-page-pdf) dans doc.css) — un PDF élargi rétrécit
       cette piste sans que le viewport ne change. @media ne voit que la
       fenêtre ; @container mesure la largeur RÉELLE de cette boîte, donc
       réagit correctement aux deux causes (fenêtre étroite ET PDF large). */
    container-type: inline-size;
    container-name: dp-bar;
    display: grid;
    /* Mêmes 3 pistes que .doc-page-scroll en dessous : --dp-sidebar-w (même
       variable que .doc-page-sidebar, cf. .dp-page plus haut — PAS 248px,
       la vraie largeur du sommaire ICI est réduite à 216px, cf. plus bas
       dans ce fichier), un gap, puis le contenu/page PDF en 1fr. --dp-gap
       vaut 40px par défaut (mode élève, comme .doc-page-scroll) et 20px en
       mode admin (cf. body:has(.doc-page-pdf) ci-dessous, même sélecteur/
       valeur que doc.css). */
    grid-template-columns: var(--dp-sidebar-w) var(--dp-gap, 40px) 1fr;
    align-items: center;
    box-sizing: border-box;
    /* max-width/margin/padding : copie EXACTE de main:has(.doc-page-scroll)
       (doc.css) — .dp-context-bar a cassé le max-width de <main> pour son
       fond plein écran (cf. plus haut), donc cette boîte doit reproduire
       elle-même ce centrage pour que ses 3 pistes tombent au même endroit
       que .doc-page-scroll juste en dessous. */
    max-width: 1180px;
    margin: 0 auto;
    padding: 0 24px;
}
/* Mode prof : même correctif que doc.css pour .doc-page-scroll (gap réduit
   à 20px) — cf. body.mode-admin .dp-context-bar plus bas, qui annule le
   breakout 100vw en même temps : dans ce mode .dp-context-bar occupe déjà
   exactement la boîte (déjà paddée) de <main>, donc son padding/margin
   propre doit s'annuler pour ne pas le doubler.
   body.mode-admin, PAS body:has(.doc-page-pdf) (copié un peu vite de
   doc.css) : ICI, contrairement à la page de production, l'aside
   .doc-page-pdf est TOUJOURS dans le DOM même en mode élève (juste caché/
   embedded, cf. sheet_detail.html) — :has() le détectait donc aussi en
   mode élève et appliquait le gap 20px partout, faisant chuter le switch/
   l'affichage hors de leur vraie position (constaté : décalage de 18px). */
body.mode-admin .dp-context-bar-inner {
    --dp-gap: 20px;
}
body.mode-admin .dp-context-bar-inner {
    max-width: none;
    margin: 0;
    padding: 0;
}
/* Le breakout "100vw + marges négatives" (cf. .dp-context-bar plus haut)
   suppose que <main> est centré (margin:0 auto), vrai en mode élève
   (main:has(.doc-page-scroll), max-width:1180px). En mode prof, <main>
   (main:has(.doc-page-pdf), cf. doc.css) devient asymétrique — padding-left
   fixe (8px) à gauche, padding-right réservé pour le panneau PDF fixe à
   droite — donc pas centré du tout : le calcul 50vw/50% du breakout devient
   faux et envoie la barre soit trop loin à droite (par-dessus le PDF), soit
   trop courte. On annule ici le breakout : la barre reprend simplement la
   largeur de <main> (déjà correctement dimensionnée pour s'arrêter net au
   bord du panneau PDF, cf. comment plus haut sur .dp-switch-btn-empty),
   au lieu de tenter de calculer une largeur écran indépendante. */
body.mode-admin .dp-context-bar {
    width: auto;
    margin-left: 0;
    margin-right: 0;
}

/* ---- Les 3 éléments de la barre sont maintenant de VRAIS items de la
   grille ci-dessus, chacun sur la piste qui correspond à ce qu'il doit
   suivre en dessous — plus de position:absolute, plus de mesure JS, plus
   de custom properties pilotées en script :
   - fil d'Ariane : piste 1+2 (sommaire+gap), aligné à gauche — même bord
     gauche que le sommaire, par construction.
   - switch Cours/TD/Résumé : piste 3 (contenu/page PDF), centré — même
     centre que le contenu, par construction.
   - affichage HTML/PDF : piste 3, aligné à droite — même bord droit que
     le contenu/la page PDF et que la barre flottante du bas. */
/* grid-row:1 explicite sur les 3 : switch ET affichage ciblent tous les
   deux la piste 3 (justify-self différent — centre / droite — c'est ce qui
   les sépare visuellement, pas des cellules différentes) — sans grid-row
   posé, l'auto-placement considère "piste 3, ligne 1" comme déjà pris par
   le switch et repousse l'affichage sur une 2e ligne implicite (constaté :
   affichage passé SOUS le switch au lieu d'à côté). */
.dp-switch {
    grid-column: 3;
    grid-row: 1;
    justify-self: center;
}
.dp-breadcrumb {
    /* Positionné en absolu depuis .dp-context-bar (bord RÉEL de la fenêtre,
       cf. plus haut : 100vw + marges négatives), et non plus placé dans la
       grille centrée de .dp-context-bar-inner : le sommaire/switch/
       affichage doivent rester alignés sur le contenu centré en dessous,
       mais le fil d'Ariane, lui, doit tomber au même endroit QUEL QUE SOIT
       le mode (prof/élève) ou la largeur d'écran — exactement comme sur la
       page de revue d'import (.review-topbar-inner, imports.css : 20px de
       .review-topbar + 24px de padding propre = 44px depuis le bord de
       fenêtre). Un item de grille centrée ne peut pas offrir cette garantie
       (son offset dépend de max-width/margin:auto, donc de la largeur de
       fenêtre) — l'ancien padding-left différent par mode (12px prof / 0
       élève, posé sur des pistes elles-mêmes à des offsets différents) ne
       tombait donc JAMAIS au même endroit malgré des valeurs ajustées à la
       main (constaté).
       49px et non 44px : en mode élève, .dp-context-bar garde son breakout
       100vw (cf. plus bas dans ce fichier), dont le calcul -50vw+50% ne
       retombe pas pile sur le bord réel de la fenêtre à cause du
       scrollbar-gutter:stable posé sur #app-body (nav.css) — 5px d'écart
       mesurés (constaté), compensés ici pour retomber sur les mêmes 44px
       que le mode prof (qui n'a pas ce breakout, cf. body.mode-admin
       .dp-breadcrumb plus bas) et que la page d'import. */
    position: absolute;
    left: 49px;
    /* top:0/bottom:0 (plutôt que top:50%+translateY, comme avant) : donne à
       .dp-breadcrumb la hauteur RÉELLE de la barre, pas juste celle de son
       contenu — nécessaire pour que .dp-breadcrumb-doc-wrap (align-self:
       stretch, cf. plus bas) puisse remplir toute la hauteur de la context
       bar au survol. Le contenu reste centré verticalement comme avant,
       via align-items:center ci-dessous (inchangé) : aucun changement
       visuel, seule la boîte elle-même grandit. AUSSI la raison pour
       laquelle .dp-breadcrumb-doc-menu doit être déplacé dans <body> plutôt
       que rester ici (cf. script en bas de sheet_detail.html) : garder un
       transform sur cet ancêtre en ferait le containing block de tout
       descendant position:fixed, qui se retrouverait alors découpé par
       l'overflow:hidden ci-dessous (constaté : menu vide malgré position:
       fixed). Sans transform ici, ce risque disparaît, mais le déplacement
       en JS reste la protection robuste si jamais un transform revenait. */
    top: 0;
    bottom: 0;
    /* Largeur bornée par calc plutôt que par la piste de grille (qui
       n'existe plus pour cet élément) : laisse la place au switch/
       affichage (piste 3) à droite, ~300px suffisant même en libellés
       complets vu leur largeur mesurée (cf. plus bas dans ce fichier). Les
       unités cqw visent .dp-context-bar-inner (container-type:inline-size,
       cf. plus haut), donc réagissent aussi bien à un redimensionnement de
       fenêtre qu'à l'élargissement du panneau PDF en mode prof. */
    max-width: calc(100cqw - 49px - 300px);
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: 13.5px;
    color: var(--text-muted, #9aa4b2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Mode prof : .dp-context-bar renonce à son breakout 100vw (cf. règle
   body.mode-admin .dp-context-bar plus bas — le calcul 50vw/50% devient
   faux à côté du panneau PDF) et reprend donc la largeur de <main>, qui a
   lui-même 8px de padding-left (main:has(.doc-page-pdf), doc.css) : le
   fil d'Ariane, positionné depuis .dp-context-bar, hérite de ces 8px en
   plus de son propre `left` — il faut les retrancher ici pour retomber
   exactement sur les mêmes 44px depuis le bord de fenêtre qu'en mode
   élève et sur la page d'import (constaté : sans ce correctif, 52px au
   lieu de 44px). */
body.mode-admin .dp-breadcrumb {
    left: 36px;
    max-width: calc(100cqw - 36px - 300px);
}
.dp-display {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
    /* Léger espace avec le bord droit de la barre (demande : "ça colle la
       bordure") — justify-self: end colle sinon le groupe pile contre la
       piste de grille, qui coïncide avec le bord réel du conteneur. */
    margin-right: 6px;
}

.dp-breadcrumb-link {
    /* Blanc, pas gris muted : plus lisible sur le fond sombre de la barre. */
    color: #fff;
    text-decoration: none;
    flex-shrink: 0;
    white-space: nowrap;
}

.dp-breadcrumb-link:hover {
    color: var(--text-primary, #e8ecf1);
    text-decoration: underline;
}

.dp-breadcrumb-sep {
    color: #fff;
    opacity: 0.55;
    flex-shrink: 0;
}

.dp-breadcrumb-current {
    color: var(--text-primary, #e8ecf1);
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Seul élément qui peut réellement rétrécir (et donc se tronquer en
       "…" à la fin) : les maillons précédents ne se compressent jamais
       individuellement, cf. règle de repli plus bas qui les masque tous
       d'un coup plutôt que de les grignoter lettre par lettre. */
    flex-shrink: 1;
    min-width: 2ch;
}

/* ---- Dernier maillon cliquable : liste des autres documents du même
   chapitre (cf. breadcrumb_documents, ordre identique à Cours & TD). Le
   <button> reprend exactement l'apparence du <span> qu'il remplace
   (même classe .dp-breadcrumb-current) — seul ajout visuel : le chevron. ---- */
.dp-breadcrumb-doc-wrap {
    position: relative;
    display: flex;
    align-items: stretch;
    /* Remplit toute la hauteur de .dp-breadcrumb (elle-même sur toute la
       hauteur de la context bar depuis top:0/bottom:0, cf. plus haut) —
       sans ça le wrap ne prend que la hauteur de son texte, comme les
       autres maillons du fil d'Ariane, et le fond de survol ne couvrirait
       qu'une bande étroite au milieu de la barre au lieu de toute sa
       hauteur (demande explicite). */
    align-self: stretch;
    min-width: 0;
    flex-shrink: 1;
}
button.dp-breadcrumb-doc-btn {
    display: inline-flex;
    align-items: center;
    height: 100%;
    gap: 4px;
    background: none;
    border: none;
    padding: 0 8px;
    margin: 0 -8px;
    /* Pas de `font: inherit` (shorthand) : ça réinitialiserait aussi
       font-weight, écrasant le 600 hérité de .dp-breadcrumb-current —
       seules les propriétés qu'on veut vraiment reprendre du <span> sont
       explicitées ici. */
    font-family: inherit;
    font-size: inherit;
    color: inherit;
    cursor: pointer;
    transition: background-color .12s ease;
}
/* Fond au survol (pas la couleur du texte, qui reste celle du kind de
   feuille, cf. .dp-kind-* .dp-breadcrumb-current plus bas) — cohérent
   avec .ctx-menu button:hover (theme.css). Pas de border-radius : le fond
   touche le haut et le bas RÉELS de la context bar (demande explicite),
   un radius y créerait un coin visiblement coupé contre les bordures de
   la barre. */
button.dp-breadcrumb-doc-btn:hover { background: rgba(255, 255, 255, 0.12); }
.dp-breadcrumb-doc-caret {
    width: 14px;
    height: 14px;
    flex-shrink: 0;
    opacity: 0.7;
}
button.dp-breadcrumb-doc-btn[aria-expanded="true"] .dp-breadcrumb-doc-caret { transform: rotate(180deg); }

.dp-breadcrumb-doc-menu {
    /* position:fixed + coordonnées posées en JS (cf. sheet_detail.html) et
       non position:absolute ancré sur .dp-breadcrumb-doc-wrap : ce wrap vit
       dans .dp-breadcrumb, qui a overflow:hidden (nécessaire à l'ellipsis
       du fil d'Ariane) — un enfant absolu y était découpé/invisible dès
       qu'il dépassait la boîte du lien (constaté : menu ouvert mais vide à
       l'écran). Même technique que .ctx-menu (theme.css). */
    position: fixed;
    z-index: 9000;
    min-width: 240px;
    max-width: 360px;
    max-height: 60vh;
    overflow-y: auto;
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0,0,0,.35);
    padding: 0 0 4px;
    display: flex;
    flex-direction: column;
}
.dp-breadcrumb-doc-menu[hidden] { display: none; }
.dp-breadcrumb-doc-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 14px;
    color: var(--text, #e8ecf1);
    text-decoration: none;
    font-weight: 500;
    font-size: 0.88rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.dp-breadcrumb-doc-item:hover { background: var(--surface-3); }
.dp-breadcrumb-doc-item-current {
    /* Couleur posée en JS (pas ici) : elle doit reprendre exactement celle
       du fil d'Ariane, propre au kind de LA PAGE (vert Cours, violet TD...
       cf. .dp-kind-* .dp-breadcrumb-current plus bas) — or ce menu est
       déplacé dans <body> (cf. script, sheet_detail.html) donc hors de
       portée de la classe .dp-kind-* posée sur .dp-page, qui pilote cette
       couleur par cascade CSS. */
    background: var(--surface-3);
    cursor: default;
}
.dp-breadcrumb-doc-icon {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    object-fit: contain;
}
.dp-breadcrumb-doc-title {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- Manque de place : on ne tronque JAMAIS les maillons "Accueil" /
   filière lettre par lettre (résultat jugé illisible, ex. "Acc…") — on les
   masque tous d'un bloc et on les remplace par un unique "…", ne laissant
   que le titre de la page courante (lui-même tronqué normalement à la fin
   s'il est trop long, cf. règle ci-dessus). Même seuil que le passage en
   icônes seules des boutons switch/affichage : à cette largeur le fil
   d'Ariane complet n'a de toute façon plus la place de tenir sur une ligne
   à côté d'eux. ---- */
@container dp-bar (max-width: 900px) {
    .dp-breadcrumb-link,
    .dp-breadcrumb-sep {
        display: none;
    }
    .dp-breadcrumb-current::before {
        /* Blanc et pleine opacité (pas 0.5 comme .dp-breadcrumb-sep) : le
           "… ›" doit rester bien visible, c'est lui qui indique qu'il y a
           du contenu masqué avant le titre — demande explicite. */
        content: "… › ";
        color: #fff;
        font-weight: 400;
    }
}

/* ---- Sous ~900px, la barre (fil d'Ariane + switch + affichage) manque de
   place et se superpose : on réduit les boutons à leurs icônes seules
   (label texte masqué, cf. <span class="dp-btn-label"> dans le template) et
   on laisse le titre de la page courante rétrécir un peu à son tour (dernier
   à céder, cf. règle ci-dessus le protégeant en priorité). Le switch
   Cours/TD/Résumé passe de centré à aligné à gauche du contenu : évite qu'il
   ne vienne chevaucher HTML/TeX/PDF à droite une fois les 3 blocs resserrés. ---- */
@container dp-bar (max-width: 900px) {
    .dp-breadcrumb-current {
        flex-shrink: 1;
        min-width: 2ch;
    }
    .dp-switch-btn .dp-btn-label,
    .dp-display-btn .dp-btn-label {
        display: none;
    }
    .dp-switch {
        justify-self: start;
    }
    /* L'alinéa (44px, cf. règles ci-dessus) cède de la place au fil
       d'Ariane une fois les boutons réduits à leurs icônes : pur confort
       visuel en large, mais superflu quand chaque pixel de la piste
       contenu compte. Mode prof : même correctif -8px qu'en largeur normale
       (cf. règle body.mode-admin .dp-breadcrumb plus haut), sinon les deux
       modes redivergent sous ce seuil. */
    .dp-breadcrumb {
        left: 25px;
        max-width: calc(100cqw - 25px - 220px);
    }
    body.mode-admin .dp-breadcrumb {
        left: 12px;
        max-width: calc(100cqw - 12px - 220px);
    }
}

/* ---- Sous 580px : les boutons (Cours/TD/Résumé, HTML/TeX/PDF), déjà réduits
   à leurs icônes (~138px + ~139px, cf. règle 900px plus haut), ne rentrent
   plus dans la piste "contenu" (1fr) telle quelle — celle-ci vaut largeur du
   conteneur - 216px de sommaire - 20/40px de gap, donc quasi rien à 580px.
   Plutôt que les empiler sur deux lignes (essayé, rejeté : JAMAIS de bouton
   sous un autre, quelle que soit la largeur — demande explicite), on les
   sort ici de la grille (position:absolute, ancrés sur .dp-context-bar comme
   .dp-breadcrumb juste au-dessus — MÊME technique, pas une nouvelle) pour ne
   plus dépendre de la piste "contenu" figée à 216px de sommaire + gap.
   Pourquoi pas simplement remettre --dp-sidebar-w à 0 ici (comme pour
   .doc-sidebar-compact, cf. doc.css) : .dp-context-bar-inner est LUI-MÊME le
   conteneur de la requête (container-name: dp-bar, plus haut) — changer une
   propriété qui affecte SA PROPRE piste de grille depuis une de ses propres
   container queries est une dépendance circulaire que les navigateurs
   ignorent silencieusement (constaté : la variable ne changeait jamais,
   grid-template-columns restait figé sur 216px). position:absolute
   contourne le problème : ces boutons ne sont alors plus des items de la
   grille, leur taille/position ne dépend donc plus de ses pistes.
   Largeurs ~139px (mesurées, cf. commentaire ci-dessus) + 10px de respiration
   entre les deux groupes pour calculer où poser le switch (justste à gauche
   de l'affichage). Le fil d'Ariane, lui, garde son texte complet si la place
   suffit ; sinon l'ellipse ("…") mange le surplus AVANT de toucher aux
   boutons — jamais l'inverse (cf. max-width réduit ci-dessous). */
@container dp-bar (max-width: 580px) {
    .dp-switch,
    .dp-display {
        position: absolute;
        top: 50%;
        transform: translateY(-50%);
        grid-column: unset;
        justify-self: unset;
    }
    .dp-display { right: 13px; }
    .dp-switch { right: 162px; }
    body.mode-admin .dp-display { right: 6px; }
    body.mode-admin .dp-switch { right: 149px; }
    /* Mode élève : .dp-display (HTML/TeX/plein écran, admin_mode only) n'existe pas
       dans le DOM — right:162px ci-dessus laissait alors un vide à droite du switch
       (place réservée pour un groupe absent). Sans .dp-display comme sibling, on colle
       directement le switch au bord droit (demande explicite "collé à droite"). */
    .dp-context-bar-inner:not(:has(.dp-display)) .dp-switch { right: 13px; }

    .dp-breadcrumb {
        left: 13px;
        /* Doit toujours garder assez de place pour le titre de la page
           courante (quitte à le tronquer en "…", cf. .dp-breadcrumb-current
           plus haut) — jamais disparaître entièrement. Espace réellement
           disponible = largeur du conteneur moins switch (droite:162px +
           ~139px de large) et son propre offset gauche (13px), cf. calcul
           dans le commentaire au-dessus de ce bloc @container. */
        max-width: calc(100cqw - 13px - 301px);
    }
    body.mode-admin .dp-breadcrumb {
        left: 0px;
        max-width: calc(100cqw - 288px);
    }
}

/* ---- Segmented control (Cours/TD/Résumé, HTML/TeX/PDF) — un vrai contrôle
   segmenté (façon macOS/Linear/Raycast) plutôt que des boutons indépendants
   posés côte à côte : un seul fond translucide commun par groupe, des
   séparateurs fins ENTRE les boutons (pas un gap qui coupe visuellement le
   groupe en morceaux), coins arrondis seulement sur les bords extérieurs,
   un seul bouton "élevé" (celui actif). Dimensions (hauteur/rayon/padding)
   posées une fois en variables et partagées par les deux groupes ET par la
   toolbar du panneau PDF (cf. plus bas) — une seule famille de composants. */
/* `.spdf-ctx-bar` (nouvelle page feuille) partage CETTE déclaration plutôt
   que d'en recopier les valeurs : elle n'a pas de `.dp-context-bar-inner`,
   et sans ces variables le contrôle segmenté Cours/TD/Résumé retombait sur
   des dimensions par défaut — même markup, mais plus la même allure. */
.dp-context-bar-inner,
.spdf-ctx-bar {
    --dp-btn-h: 30px;
    --dp-btn-radius: 7px;
    --dp-btn-pad-x: 13px;
    --dp-btn-icon-gap: 7px;
}
.dp-switch,
.dp-display {
    display: flex;
    align-self: center;
    box-sizing: border-box;
    /* Hauteur totale bornée EXPLICITEMENT (border-box : le padding/bordure
       ne s'ajoutent plus par-dessus) — sans ça le groupe finissait à peu
       près à la même hauteur que .dp-context-bar-inner (min-height 44px)
       par coïncidence, donnant l'impression à tort qu'il s'étirait sur
       toute la barre alors que c'était juste une somme qui y ressemblait.
       +4px de padding (2px haut+bas) +2px de bordure (1px haut+bas, cf.
       `border` plus bas) : les DEUX sont "dans" les 34px en box-sizing:
       border-box — en oubliant la bordure (juste +4px avant), le bouton de
       30px à l'intérieur (lui aussi border-box depuis peu, cf. .dp-switch-
       btn/.dp-display-btn) débordait de 2px de la zone de contenu réelle
       (34 - 2 bordure - 4 padding = 28, pas 30). */
    height: calc(var(--dp-btn-h) + 6px);
    padding: 2px;
    gap: 0;
    /* rayon extérieur = rayon intérieur + padding (2px) pour que les 2
       coins soient concentriques et épousent vraiment la même courbe — pas
       +3px, qui laissait un exterieur légèrement trop généreux par rapport
       au bouton actif à l'intérieur. */
    border-radius: calc(var(--dp-btn-radius) + 2px);
    background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015));
    border: 1px solid rgba(255,255,255,0.08);
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 1px 3px rgba(0,0,0,0.18);
    flex: 0 0 auto;
}

.dp-switch-btn,
.dp-display-btn {
    appearance: none;
    position: relative;
    /* border-box : sans lui, la bordure de 1px ci-dessous s'ajoutait PAR-
       DESSUS la height:30px explicite (comportement par défaut, content-
       box) — le bouton faisait alors réellement 32px de haut alors que le
       conteneur ne lui réserve que 30px (34px de hauteur - 2×2px de
       padding, cf. .dp-switch/.dp-display plus haut), d'où 1-2px de
       débordement/décalage constaté entre le contour du bouton et celui
       du pill autour. */
    box-sizing: border-box;
    /* border:1px transparent (pas none) : réserve la place pour la bordure
       colorée du bouton actif (cf. .dp-kind-td/.dp-kind-resume plus bas)
       sans décalage de layout au passage actif/inactif. */
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-muted, #9aa4b2);
    font-size: 13px;
    font-weight: 600;
    /* .dp-switch-btn est maintenant un <a> (navigable, cf. template) sur
       le switch Cours/TD/Résumé — sans ça, pas de centrage vertical/
       horizontal natif (contrairement à <button>) et un soulignement par
       défaut. */
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--dp-btn-icon-gap);
    text-decoration: none;
    /* `button { min-width: 120px }` (form.css, cf. le piège déjà documenté
       dans doc.css ligne ~1493) s'appliquait ici et forçait TOUS ces
       boutons à 120px quel que soit leur texte — d'où la largeur figée
       malgré le padding réduit. min-width:0 le neutralise. */
    min-width: 0;
    height: var(--dp-btn-h);
    padding: 0 var(--dp-btn-pad-x);
    border-radius: var(--dp-btn-radius);
    cursor: pointer;
    transition: background-color .15s ease, color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.dp-switch-btn svg,
.dp-display-btn svg {
    width: 16px;
    height: 16px;
    flex: 0 0 auto;
}
/* Cours/TD/Résumé : texte blanc en permanence, actif ou pas (demande
   explicite) — contrairement à .dp-display-btn (HTML/TeX/PDF), qui garde
   la nuance muted/actif habituelle du segmented control. */
.dp-switch-btn { color: #fff; }
.dp-switch-btn:hover { color: #fff; }
/* Logo PNG du kind (icon_cours/td/resume.png, cf. template) : même logo que
   partout ailleurs sur le site pour ce type de feuille (carte de titre,
   sommaire Cours & TD...) plutôt qu'une icône générique — demande explicite,
   pas une icône "au hasard" qui matcherait juste le champ lexical.
   Le PNG est coloré (vert/violet/bleu selon le kind, cf. icon_cours.png) —
   brightness(0) invert(1) force n'importe quelle teinte non transparente en
   blanc uni (demande : blanc tout le temps, actif ou pas), sans dépendre
   d'un SVG recolorable en currentColor. */
.dp-switch-icon {
    width: 16px;
    height: 16px;
    object-fit: contain;
    filter: brightness(0) invert(1);
    flex: 0 0 auto;
}
/* Le logo TeX (cf. exercise_card.css, .tex-logo) fixe sa propre couleur
   (var(--text)) — sans cette surcharge il resterait figé dans cette teinte
   au lieu de suivre l'état du bouton (muted/actif) comme les autres icônes. */
.dp-display-btn .tex-logo { color: inherit; font-size: 14px; }

/* Séparateur fin entre deux boutons adjacents (pas sur le premier) — porté
   par un ::before plutôt qu'un border-left, pour pouvoir le faire
   disparaître proprement de part et d'autre du bouton actif ci-dessous
   (un vrai segmented control efface la ligne contre la pastille élevée,
   il ne la laisse jamais la traverser). */
.dp-switch-btn:not(:first-child)::before,
.dp-display-btn:not(:first-child)::before {
    content: "";
    position: absolute;
    left: -1px;
    top: 22%;
    bottom: 22%;
    width: 1px;
    background: rgba(255,255,255,0.1);
}
.dp-switch-btn.active::before,
.dp-display-btn.active::before,
.dp-switch-btn.active + *::before,
.dp-display-btn.active + *::before {
    display: none;
}

.dp-switch-btn:hover,
.dp-display-btn:hover {
    color: var(--text-primary, #e8ecf1);
    background: rgba(255,255,255,0.07);
    transform: translateY(-1px);
}
.dp-switch-btn:active,
.dp-display-btn:active {
    transform: translateY(0);
}
.dp-switch-btn:focus-visible,
.dp-display-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--surface-2, #12161d), 0 0 0 4px var(--accent, #6d7bff);
}

/* Chapitre sans feuille TD/Résumé principale : pas de href (cf. template),
   rendu visuellement désactivé plutôt que de sembler cliquable pour rien. */
.dp-switch-btn-empty {
    opacity: 0.35;
    cursor: default;
    pointer-events: none;
}

/* Bouton actif : dégradé raffiné (pas un aplat) + fine bordure claire en
   haut (façon "glossy" discret) + ombre portée légère qui le détache du
   fond du groupe — c'est LUI qui doit sembler "élevé", tous les autres
   restent plats. */
.dp-switch-btn.active,
.dp-display-btn.active {
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d7bff) 82%, white), var(--accent, #6d7bff));
    color: #f4f8ff;
    /* Reflet interne haut + ombre interne basse (relief discret, pas
       glossy) + ombre portée légère qui détache le bouton du fond du
       groupe — commun aux trois couleurs de kind (cours/td/résumé) pour
       qu'elles se lisent comme une seule famille, cf. plus bas. */
    box-shadow:
        inset 0 1px 0 rgba(255,255,255,.15),
        inset 0 -1px 0 rgba(0,0,0,.15),
        0 1px 2px rgba(0,0,0,.25);
}

.dp-switch-btn.active:hover,
.dp-display-btn.active:hover {
    filter: brightness(1.08);
    transform: translateY(-1px);
}

/* ---- Couleur par type de feuille : Cours = vert, TD = violet,
   Résumé = bleu (mêmes tokens --kind-* que les icônes du site, cf.
   tokens.css) — appliquée au bouton actif du switch, au bouton HTML actif
   de l'affichage, et au nom de la feuille dans le fil d'Ariane. Même
   traitement "glossy" que le dégradé générique ci-dessus, juste teinté
   par kind — color-mix() (pas de nouvelle variable) pour dériver la teinte
   claire directement du token --kind-*, garantissant qu'un futur
   changement de couleur reste cohérent sans avoir à maintenir une 2e
   valeur à la main. */
.dp-kind-cours .dp-switch-btn.active,
.dp-kind-cours .dp-display-btn.active {
    background: linear-gradient(180deg, color-mix(in srgb, var(--kind-cours) 82%, white), var(--kind-cours));
    color: #fff;
}
.dp-kind-cours .dp-switch-btn.active:hover,
.dp-kind-cours .dp-display-btn.active:hover { filter: brightness(1.12); }
.dp-kind-cours .dp-breadcrumb-current { color: var(--kind-cours-text, #22c55e); }

/* Violet (TD) : dégradé dédié (pas dérivé de --kind-td via color-mix comme
   avant) — le token brut était trop vif/saturé ("plastique"). Même
   philosophie que le vert de référence : clair en haut, profond en bas,
   même intensité lumineuse/saturation/contraste que le bleu ci-dessous,
   pour que les trois couleurs se lisent comme une seule palette. */
.dp-kind-td .dp-switch-btn.active,
.dp-kind-td .dp-display-btn.active {
    background: linear-gradient(
        180deg,
        #7A5CF5 0%,
        #7353F0 20%,
        #6B4BE8 55%,
        #5B3BD1 100%
    );
    border-color: #8B74FA;
    color: #f4f8ff;
}
.dp-kind-td .dp-switch-btn.active:hover,
.dp-kind-td .dp-display-btn.active:hover { filter: brightness(1.12); }
.dp-kind-td .dp-breadcrumb-current { color: #8B74FA; }

/* Bleu (Résumé/HTML) : même logique — dégradé dédié plus froid/profond que
   --kind-resume brut, jugé trop saturé ("Bootstrap/Windows"). */
.dp-kind-resume .dp-switch-btn.active,
.dp-kind-resume .dp-display-btn.active {
    background: linear-gradient(
        180deg,
        #4D8FE8 0%,
        #4487E3 20%,
        #3C7FDC 55%,
        #316FC8 100%
    );
    border-color: #5B9AF0;
    color: #f4f8ff;
}
.dp-kind-resume .dp-switch-btn.active:hover,
.dp-kind-resume .dp-display-btn.active:hover { filter: brightness(1.12); }
.dp-kind-resume .dp-breadcrumb-current { color: #5B9AF0; }

/* Rose (Banque d'exercices) : même logique — dégradé dédié, même intensité
   lumineuse/saturation/contraste que le vert/violet/bleu ci-dessus, calé sur
   --kind-banque (tokens.css, #e0219e) mais avivé pour rester lisible en
   texte fin sur fond sombre (cf. --kind-cours-text plus haut). */
.dp-kind-banque .dp-switch-btn.active,
.dp-kind-banque .dp-display-btn.active {
    background: linear-gradient(
        180deg,
        #F45FC0 0%,
        #EA4EB5 20%,
        #DE3AA8 55%,
        #C22090 100%
    );
    border-color: #F27CD0;
    color: #f4f8ff;
}
.dp-kind-banque .dp-switch-btn.active:hover,
.dp-kind-banque .dp-display-btn.active:hover { filter: brightness(1.12); }
.dp-kind-banque .dp-breadcrumb-current { color: #F27CD0; }

/* Bouton plein écran (dans .dp-display, à côté de HTML/TeX) : même habillage
   que ses voisins .dp-display-btn — repos discret, survol/actif identiques
   (dégradé teinté par le kind de la feuille quand actif) — SEULE la couleur/
   le survol changent ici, pas la taille (doc-fullscreen-btn garde ses
   dimensions propres de doc.css). */
.dp-display .doc-fullscreen-btn {
    border: 1px solid transparent;
    background: transparent;
    color: var(--text-muted, #9aa4b2);
    transition: background-color .15s ease, color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.dp-display .doc-fullscreen-btn:hover {
    color: var(--text-primary, #e8ecf1);
    background: rgba(255,255,255,0.07);
    border-color: transparent;
    transform: translateY(-1px);
}
body.doc-fullscreen .dp-display .doc-fullscreen-btn {
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d7bff) 82%, white), var(--accent, #6d7bff));
    border-color: transparent;
    color: #fff;
}
body.doc-fullscreen.dp-kind-cours .dp-display .doc-fullscreen-btn {
    background: linear-gradient(180deg, color-mix(in srgb, var(--kind-cours) 82%, white), var(--kind-cours));
}
body.doc-fullscreen.dp-kind-td .dp-display .doc-fullscreen-btn {
    background: linear-gradient(180deg, #7A5CF5 0%, #7353F0 20%, #6B4BE8 55%, #5B3BD1 100%);
}
body.doc-fullscreen.dp-kind-resume .dp-display .doc-fullscreen-btn {
    background: linear-gradient(180deg, #4D8FE8 0%, #4487E3 20%, #3C7FDC 55%, #316FC8 100%);
}
body.doc-fullscreen.dp-kind-banque .dp-display .doc-fullscreen-btn {
    background: linear-gradient(180deg, #F45FC0 0%, #EA4EB5 20%, #DE3AA8 55%, #C22090 100%);
}
body.doc-fullscreen .dp-display .doc-fullscreen-btn:hover { filter: brightness(1.12); }

/* ---- Sommaire (sidebar) : les sections (.doc-toc-l1) en blanc, tout le
   temps — pas seulement actives (cf. doc.css, .doc-toc-l1 sans "active"
   reste gris/muted par défaut).
   Sous-section sélectionnée (.doc-toc-l2 actif) : le fond plein
   (var(--kind-*) directement) était trop éclatant — inversé : fond
   sombre/teinté (10% de la couleur du type sur le fond de carte), et
   c'est la BORDURE gauche qui porte la couleur pleine du type, comme les
   encarts Définition/Théorème du contenu (bg soft + border-left plein). */
.doc-toc-item.doc-toc-l1 { color: #fff; }

/* Pas de bordure/padding sur l'état actif : uniquement le fond + la couleur
   changent au clic, aucune géométrie — sinon le texte du sommaire se décale
   (padding-left différent entre inactif/actif, cf. historique ci-dessus). */
.doc-toc-item.doc-toc-l2.active {
    color: #fff;
    /* pas de gras ici : doc.css (.doc-toc-item.active) met font-weight:600
       pour toute entrée active (l1 et l2) — on l'annule pour la sous-section
       seule, la section (l1) reste en gras via cette règle générique. */
    font-weight: 400;
}
.dp-kind-cours .doc-toc-item.doc-toc-l2.active { background: color-mix(in srgb, var(--kind-cours) 18%, var(--surface-2, #12161d)); }
.dp-kind-td .doc-toc-item.doc-toc-l2.active { background: color-mix(in srgb, var(--kind-td) 18%, var(--surface-2, #12161d)); }
.dp-kind-resume .doc-toc-item.doc-toc-l2.active { background: color-mix(in srgb, var(--kind-resume) 18%, var(--surface-2, #12161d)); }
.dp-kind-banque .doc-toc-item.doc-toc-l2.active { background: color-mix(in srgb, var(--kind-banque) 18%, var(--surface-2, #12161d)); }

/* ---- Bordure des sections (.doc-toc-l1, ligne de séparation entre
   groupes du sommaire) : adopte elle aussi la couleur du type de feuille
   quand la section est active, au lieu du violet fixe --accent de
   doc.css. ---- */
.dp-kind-cours .doc-toc-l1.active { border-bottom-color: var(--kind-cours-text); }
.dp-kind-td .doc-toc-l1.active { border-bottom-color: var(--kind-td); }
.dp-kind-resume .doc-toc-l1.active { border-bottom-color: var(--kind-resume); }
.dp-kind-banque .doc-toc-l1.active { border-bottom-color: #F27CD0; }

/* ---- Σ du logo (navbar) : couleur du type de feuille au lieu de
   --accent fixe (cf. body.dp-kind-* plus haut, posé sur <body> pour
   englober la navbar). ---- */
.dp-kind-cours .brand-mark { color: var(--kind-cours-text); }
.dp-kind-td .brand-mark { color: #8B74FA; }
.dp-kind-resume .brand-mark { color: #5B9AF0; }
.dp-kind-banque .brand-mark { color: #F27CD0; }

/* ---- Lien actif de la navbar ("Cours & TD") : même logique — couleur du
   type de feuille au lieu du bleu --accent fixe (nav.css,
   .nav-link.is-active), pour rester cohérent avec le Σ/fil d'Ariane/
   boutons juste en dessous. ---- */
.dp-kind-cours .nav-link.is-active { color: var(--kind-cours-text); border-bottom-color: var(--kind-cours-text); }
.dp-kind-td .nav-link.is-active { color: #8B74FA; border-bottom-color: #8B74FA; }
.dp-kind-resume .nav-link.is-active { color: #5B9AF0; border-bottom-color: #5B9AF0; }
.dp-kind-banque .nav-link.is-active { color: #F27CD0; border-bottom-color: #F27CD0; }

/* Le sommaire/panneau PDF sont désormais les classes de production
   (.doc-page-sidebar/.doc-page-main/.doc-page-pdf, cf. sheet_detail.html)
   réutilisées telles quelles — plus besoin de .dp-body/.dp-sidebar/.dp-main
   ici, tout leur espacement vient directement de doc.css. */

/* ---- Mode élève : bouton PDF (cf. script inline de sheet_detail.html) —
   le panneau PDF (aside.doc-page-pdf) est TOUJOURS présent dans le DOM
   (caché par défaut, attribut `hidden`), pas seulement en mode prof :
   sheet_pdf_viewer.js référence ses boutons sans garde de nullité
   (recompileBtn.querySelector plante si absent), et il faut de toute façon
   pouvoir basculer HTML <-> PDF sans recharger la page. Problème : sa seule
   PRÉSENCE dans le DOM suffit à déclencher les règles structurelles de
   doc.css ciblant `:has(.doc-page-pdf)` (3e colonne fixe pensée pour le mode
   prof — réservation de largeur sur <main>, overflow bloqué sur #app-body,
   donc scrollbar de PAGE remplacée par un scroll interne planté au milieu de
   l'écran au lieu du vrai bord droit du site...), qu'il soit affiché ou non.
   On neutralise ICI UNIQUEMENT la largeur/l'espacement (pas le scroll,
   cf. plus bas) quand le panneau est encore caché (sélecteur `[hidden]`,
   spécificité > `.doc-page-pdf` seul dans doc.css) : dès que le bouton PDF
   est cliqué (JS retire l'attribut hidden), ces sélecteurs ne matchent plus
   et les règles normales de doc.css reprennent la main seules.
   Le scroll, lui, N'EST PLUS neutralisé ici (contrairement à avant) : le
   corps HTML garde TOUJOURS son propre scroll interne borné (cf.
   sheet_detail.html, mainCol.style.maxHeight/overflowY posés
   inconditionnellement) — #app-body reste donc overflow:hidden (règle de
   base doc.css, `body:has(.doc-page-pdf)`) que le panneau soit affiché ou
   non, pour un comportement identique en mode élève et en mode prof
   (repli/dépliage du menu du bas, alignement de la barre, dimensions du
   panneau PDF cohérentes avec le corps HTML). */
/* `:has(.doc-page-pdf[hidden])` à lui seul ne suffit pas : une fois le
   panneau affiché (bouton PDF cliqué), il perd `[hidden]` mais porte
   TOUJOURS `.doc-page-pdf-embedded` en mode élève (jamais la vraie 3e
   colonne fixe du mode prof) — sans le second sélecteur, <main> retombait
   sur la réservation de largeur pensée pour cette 3e colonne fixe
   (`main:has(.doc-page-pdf)`, doc.css) dès qu'on affichait le PDF, alors
   que le panneau embedded est un simple item flex qui remplace le corps
   HTML à l'identique : largeur du corps HTML (876px) et largeur du PDF
   affiché (488px, calculée comme si une vraie colonne fixe devait encore
   se caser à droite) n'avaient plus rien de commun. */
main:has(.dp-page .doc-page-pdf[hidden]),
main:has(.dp-page .doc-page-pdf-embedded) { max-width: 1180px; padding-right: 24px; padding-left: 24px; }
body:has(.dp-page .doc-page-pdf[hidden]) .doc-page-scroll,
body:has(.dp-page .doc-page-pdf-embedded) .doc-page-scroll { gap: 40px; }
body:has(.dp-page .doc-page-pdf[hidden]) .doc-page-sidebar,
body:has(.dp-page .doc-page-pdf-embedded) .doc-page-sidebar { padding-top: 10px; }

/* Panneau PDF "embedded" : remplace le corps HTML (.doc-page-main, caché en
   JS au clic sur PDF) au lieu d'être une 3e colonne position:fixed collée au
   bord de l'écran — un vrai item flex de .doc-page-scroll, même largeur que
   le corps HTML qu'il remplace. La hauteur (fixe, pas juste max-height : le
   panneau est display:flex/column, .doc-pdf-pages a besoin d'un flex parent
   de hauteur déterminée pour flex:1 et défiler en interne) est posée en JS
   (updateContentMaxHeight, même valeur que .doc-page-main). Pas de poignée
   de redimensionnement (largeur non ajustable ici) ni de recompilation/
   aperçu Local (fonctions prof) — cachées, pas retirées du DOM (cf. plus
   haut). */
.doc-page-pdf.doc-page-pdf-embedded {
    /* relative (pas static) : sert de conteneur de positionnement à la
       toolbar flottante ci-dessous (position:absolute, cf. plus bas). Mais
       contrairement à static, relative respecte top/bottom hérités de
       .dp-page .doc-page-pdf (pensés pour la vraie colonne fixe du mode
       prof) — sans les neutraliser ici, le panneau se décalait de 52px
       vers le bas (symptôme observé : le PDF démarrait trop bas en mode
       élève). auto annule cet héritage, seul le flux flex détermine sa
       position désormais. */
    position: relative;
    top: auto;
    bottom: auto;
    flex: 1 1 auto;
    min-width: 0;
    width: auto;
    max-width: none;
    border-left: none;
}
/* ---- Plein écran DU PANNEAU PDF (bouton commun prof/élève dans la barre
   flottante, cf. sheet_pdf_viewer.js toggleFullscreen) : position:fixed +
   inset:0 s'affranchit du flux normal (3e colonne fixe en mode prof, item
   flex "embedded" en mode élève) sans avoir besoin de distinguer les deux —
   la même règle marche dans les deux modes. z-index au-dessus de la navbar
   (300/310, cf. nav.css) pour qu'elle disparaisse bien sous le panneau. */
.doc-page-pdf.pdf-fullscreen {
    position: fixed;
    inset: 0;
    /* top: --ctx-bar-height (44px), pas 0 : le panneau touchait le haut réel
       de l'écran (par-dessus la navbar, cf. z-index ci-dessous), ce qui le
       faisait changer de taille au moment de passer en plein écran (demande
       explicite : garder la même taille qu'en mode normal, où le panneau
       touche déjà la navbar via top:var(--nav-height), cf. doc.css). 44px
       et non 52px (--nav-height) : la navbar reste couverte (z-index 400 >
       300, cf. commentaire plus haut), donc caler sur sa propre hauteur
       recréerait un vide visible au-dessus du panneau — caler sur la
       hauteur de la context bar (8px de moins) donne le meilleur compromis
       visuel entre "ne pas bouger" et "ne pas laisser de bande vide". */
    top: var(--ctx-bar-height, 44px);
    bottom: 0;
    left: 0;
    right: 0;
    width: 100vw;
    height: calc(100vh - var(--ctx-bar-height, 44px));
    max-width: none;
    max-height: none;
    z-index: 400;
    border: none;
    border-radius: 0;
}
/* La zone PDF doit occuper TOUTE la hauteur de l'écran en plein écran, quel
   que soit le type de feuille (demande explicite : l'ancien letterboxing en
   ratio A4 pour Cours/Résumé laissait de grandes bandes noires en haut/bas
   au lieu de remplir la fenêtre) — même règle qu'avant pour les TD
   (paysage), désormais appliquée à tous les kinds. position:fixed + inset
   vertical à 0 s'affranchit du flux (donc de la réservation de hauteur par
   la barre statique du mode prof, déjà masquée en plein écran de toute
   façon) plutôt que height:100% qui ne remplirait que l'espace restant. */
.doc-page-pdf.pdf-fullscreen {
    background: #000;
    display: flex;
    align-items: stretch;
    justify-content: center;
}
.doc-page-pdf.pdf-fullscreen .doc-pdf-viewport {
    aspect-ratio: auto;
    position: fixed;
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
    width: 90%;
    height: 100vh;
    max-width: none;
    max-height: none;
    margin: 0;
}
/* Croix de fermeture du plein écran : masquée hors plein écran, toujours
   visible (pas de fondu par inactivité, contrairement à .doc-pdf-toolbar)
   une fois dedans — coin haut-droit de l'écran entier, au-dessus des bandes
   noires comme de la page elle-même (z-index supérieur à la toolbar). */
.doc-pdf-fullscreen-close {
    display: none;
}
.doc-page-pdf.pdf-fullscreen .doc-pdf-fullscreen-close {
    display: flex;
    align-items: center;
    justify-content: center;
    position: fixed;
    top: 16px;
    right: 16px;
    width: 44px;
    height: 44px;
    min-width: 0; /* écrase button{min-width:120px} de form.css */
    padding: 0; /* écrase button{padding:8px 16px} de form.css */
    box-sizing: border-box;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.15);
    background: rgba(18, 22, 29, 0.72);
    color: #fff;
    cursor: pointer;
    z-index: 20;
    transition: background 0.15s, transform 0.15s;
}
.doc-page-pdf.pdf-fullscreen .doc-pdf-fullscreen-close svg {
    width: 22px;
    height: 22px;
}
.doc-page-pdf.pdf-fullscreen .doc-pdf-fullscreen-close:hover {
    background: rgba(220, 50, 50, 0.85);
    transform: scale(1.06);
}
/* La barre statique (Recompiler/Global-Local, mode prof) reste au-dessus du
   PDF en plein écran comme en temps normal — seule la zone de pages change
   de taille, pas la présence des contrôles. */
/* ---- Menu flottant au survol du haut de la zone PDF — IDENTIQUE mode prof
   ET élève, aucune branche par mode (ni markup, ni CSS de mise en page) :
   la toolbar ne réserve plus sa hauteur en continu dans le flux (elle est
   position:absolute, sortie du flex du panneau), la zone PDF utilise donc
   toute la hauteur disponible au repos, boutons flottant par-dessus
   seulement au survol du haut. Recompiler/Global-Local (propres au mode
   prof) vivent maintenant dans une barre à part, .doc-pdf-static-toolbar
   (toujours visible, jamais flottante, cf. plus bas) — ce qui reste ici
   (télécharger/ouvrir, pagination, zoom) est donc rigoureusement le même
   contenu dans les 2 modes, d'où une seule mise en page partagée, scopée
   sur .doc-page-pdf tout court (pas de variante -embedded). Seul ce qui
   DOIT rester différent entre les 2 modes (position du panneau lui-même
   dans la page — 3e colonne fixe en prof, remplaçant le corps HTML en
   élève, cf. .doc-page-pdf-embedded plus haut) reste scopé à part. */
/* Conteneur positionné pour la barre flottante ci-dessous (cf. commentaire
   dans le template) — étend flex:1 pour occuper l'espace restant sous la
   barre statique (mode prof) / directement sous le haut du panneau (mode
   élève, barre statique display:none). */
.doc-page-pdf .doc-pdf-viewport {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}
/* Barre STATIQUE (mode prof uniquement — Recompiler + Global/Local) : jamais
   flottante, jamais masquée, contrairement à .doc-pdf-toolbar plus bas
   (comportement d'avant l'unification élève/prof, demande explicite de
   l'utilisateur). Reprend l'apparence de l'ancienne barre unique de doc.css
   (.doc-pdf-toolbar non scopée .doc-page-pdf) : fine bande en haut du
   panneau, dans le flux normal. */
/* Grille à 3 pistes (pas flex space-between) : avec seulement 2 groupes,
   space-between les poussait chacun à une extrémité — le groupe Global/
   Local n'était donc PAS centré par rapport à la zone PDF entière, juste
   collé à droite du groupe Recompiler. Une 3e piste (vide, ou occupée par
   le bouton métadonnées ci-dessous) équilibre les deux côtés du groupe
   central, qui se retrouve alors réellement centré. */
.doc-page-pdf .doc-pdf-static-toolbar {
    flex: 0 0 auto;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    padding: 6px 10px;
    border-bottom: 1px solid var(--border);
}
.doc-page-pdf .doc-pdf-static-toolbar > .doc-pdf-toolbar-group:first-child {
    justify-self: start;
}
.doc-page-pdf .doc-pdf-toolbar-group-center {
    justify-self: center;
}
/* Bouton métadonnées de la feuille (cf. template) : coin haut-droit de la
   zone PDF, déplacé ici depuis l'en-tête du sommaire (demande explicite). */
.doc-pdf-toolbar-group-meta {
    justify-self: end;
    display: flex;
    align-items: center;
    gap: 8px;
}
/* Pagination en lecture seule, entre la bascule Global/Local et le bouton
   d'infos — simple miroir texte de la barre flottante (data-pdf-current-
   input/data-pdf-count), pas un second champ éditable (cf. sheet_pdf_viewer.js,
   syncStaticPageIndicator). */
.doc-pdf-page-indicator-static {
    font-size: 12px;
    color: var(--text-muted);
    white-space: nowrap;
}
/* .exo-info-btn (exercise_card.css) est pensé pour flotter en position:
   absolute, invisible par défaut (opacity:0, révélé au survol de la carte
   parente) — inadapté ici, un simple item de la grille de la barre
   statique : même neutralisation que .doc-sidebar-toc-info-btn (doc.css,
   l'ancien emplacement de ce bouton dans le sommaire). Reste visible en
   permanence (pas de masquage au survol, contrairement aux ⓘ de cartes). */
.doc-pdf-meta-btn {
    position: static;
    width: 26px;
    height: 26px;
    min-width: 26px;
    top: auto;
    right: auto;
    /* .exo-info-btn (exercise_card.css) est opacity:0 par défaut, révélé
       seulement au survol de .exercise-card/.doc-block (aucun des deux ici)
       — sans cet override le bouton restait invisible ET inerte en
       permanence (régression : le commentaire ci-dessus annonçait déjà
       l'intention "visible en permanence" mais l'override manquait). */
    opacity: 1;
    pointer-events: auto;
}
.doc-pdf-meta-btn svg { width: 15px; height: 15px; }
/* Réservée au mode prof : la vraie 3e colonne fixe n'a pas de panneau
   "embedded", donc pas besoin d'override par mode ici — seul le mode élève
   (embedded) la masque, cf. plus bas .doc-page-pdf-embedded. */
.doc-page-pdf-embedded .doc-pdf-static-toolbar {
    display: none;
}
/* Plein écran : masque la barre statique (Recompiler + Global/Local, mode
   prof) même quand elle existe normalement (colonne fixe) — demande
   explicite : le plein écran doit être IDENTIQUE prof/élève, jamais de
   Recompiler dedans, et la géométrie (letterboxing, cf. .doc-pdf-viewport
   plus haut) doit être celle du mode élève dans les deux cas. Sans ce
   masquage, la barre statique restait un item flex au-dessus du viewport en
   mode prof, lui volant sa hauteur (48px de moins que la fenêtre entière) —
   contrairement au mode élève où elle est déjà display:none en permanence. */
.doc-page-pdf.pdf-fullscreen .doc-pdf-static-toolbar {
    display: none;
}
.doc-page-pdf .doc-pdf-toolbar {
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%) translateY(-6px);
    width: auto;
    z-index: 5;
    background: rgba(18, 22, 29, 0.92);
    border: 1px solid rgba(255,255,255,0.1);
    border-radius: 10px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.35);
    opacity: 0;
    pointer-events: none;
    transition: opacity .15s ease, transform .15s ease;
}
/* .pdf-toolbar-awake : posée/retirée en JS (cf. sheet_detail.html, plus
   scopé à `pdfPanel.classList.contains('doc-page-pdf-embedded')` — le même
   mécanisme d'éveil/survol s'applique désormais à n'importe quel
   .doc-page-pdf) selon l'activité (souris qui bouge/clic dans le haut de
   la zone PDF) — le menu doit pouvoir disparaître après le délai
   d'inactivité même si le curseur reste immobile QUELQUE PART dans la zone
   PDF (pas seulement à l'extérieur, demande explicite) : un :hover sur
   TOUT le panneau le maintiendrait visible en permanence tant que le
   curseur reste dans la zone, immobile ou pas, court-circuitant le timer
   JS. Le seul :hover gardé en secours est celui de la toolbar ELLE-MÊME
   (pas tout le panneau) — pour le cas où le curseur reste immobile PILE
   sur la toolbar : sans lui, .pdf-toolbar-awake expirerait sous le curseur
   au bout d'1s alors que la souris est dessus, ce qui la ferait disparaître
   alors qu'on est en train de s'en servir. Visible dès l'affichage du PDF,
   puis invisible après un délai d'inactivité (demande explicite). */
.doc-page-pdf .doc-pdf-toolbar:hover,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-toolbar,
.doc-page-pdf .doc-pdf-toolbar:focus-within {
    opacity: 1;
    pointer-events: auto;
    transform: translateX(-50%) translateY(0);
}
/* La page démarre donc tout en haut de la zone (plus de bandeau réservé) —
   0 en haut pour que le PDF commence pile au bord du panneau une fois
   scrollé tout en haut (le padding-top laissait voir quelques pixels du
   fond sombre du panneau au-dessus de la 1re page, demande explicite) ;
   la toolbar flotte PAR-DESSUS au survol donc n'a pas besoin de cet espace. */
.doc-page-pdf .doc-pdf-pages { padding-top: 0; }

/* ---- Barre flottante (télécharger/ouvrir + pagination + zoom) : IDENTIQUE
   pixel pour pixel en mode prof et élève, plus aucune distinction — depuis
   que Recompiler/Global-Local ont leur propre barre statique séparée (cf.
   .doc-pdf-static-toolbar plus haut), cette barre flottante ne contient plus
   que les 2 groupes déjà communs aux deux modes : plus de raison de garder
   deux mécanismes de mise en page différents (l'ancien flex justify-content:
   space-between du mode prof vs la grille centrée du mode élève). Un seul
   scopé sur .doc-page-pdf tout court (pas -embedded) pour les deux.
   3 zones franches — gauche (télécharger/ouvrir), centre (pagination),
   droite (zoom) — ÉGALES (1fr 1fr 1fr, pas 1fr auto 1fr) et chacune centrée
   dans sa zone (justify-self:center partout, pas start/end) : avec des
   colonnes de largeur inégale et un contenu collé à un bord, l'espace mort
   résiduel retombait tout entier d'UN SEUL côté du séparateur — les
   séparateurs n'étaient donc pas centrés par rapport à ce qu'ils séparent,
   malgré la grille "3 colonnes" déjà en place. Avec 3 zones égales + contenu
   centré dans chacune, les séparateurs (ci-dessous, posés sur la toolbar
   elle-même aux 2 frontières 33%/66%, pas sur un groupe précis) tombent
   naturellement à mi-chemin entre deux groupes quelle que soit leur largeur
   propre. */
.doc-page-pdf .doc-pdf-toolbar {
    /* PAS de position:relative ici : .doc-pdf-toolbar est déjà position:
       absolute (cf. plus haut, la vraie règle "menu flottant" — top:8px,
       left:50%, width:auto) — la réécrire en relative (même sélecteur,
       plus loin dans la feuille donc prioritaire à spécificité égale)
       la remettait dans le flux normal : elle perdait l'espace réservé
       au-dessus d'elle ET s'étirait sur toute la largeur du panneau PDF
       (grid 1fr 1fr 1fr sur une boîte désormais 100% large) au lieu de
       rester un petit menu centré. absolute reste un contexte de
       positionnement valide pour les ::before/::after ci-dessous, pas
       besoin de relative pour ça. */
    display: grid;
    /* 5 pistes, pas 3 : les 2 pistes intercalées (colonnes 2 et 4) sont
       réservées aux séparateurs eux-mêmes (::before/::after, maintenant de
       VRAIS items de grille — plus des pseudo-éléments position:absolute
       superposés, cf. plus bas). Les 3 pistes de contenu (1, 3, 5) sont en
       "auto" (pas 1fr) : le 1er groupe (2 boutons, ~56px) est bien plus
       étroit que la pagination/le zoom (~110-115px chacun) — avec des
       pistes 1fr forcées à la MÊME largeur, son contenu centré nageait
       dans un excédent de marge des deux côtés (visible surtout côté
       séparateur, l'espace y semblait énorme comparé aux autres). En
       "auto", chaque piste épouse son contenu — les marges autour de
       chaque séparateur (sa propre piste --dp-pdf-sep-w, centrée) restent
       IDENTIQUES partout, quelle que soit la largeur du groupe voisin. Le
       menu garde de toute façon une largeur "auto" globale (cf. la règle
       .doc-page-pdf .doc-pdf-toolbar plus haut) : pas de 1fr à répartir,
       juste la somme des 5 pistes. */
    grid-template-columns: auto var(--dp-pdf-sep-w, 31px) auto var(--dp-pdf-sep-w, 31px) auto;
    /* Une seule ligne, explicite (pas "auto" implicite) : sans ça, les 5
       items (2 groupes réels + display:contents + 2 pseudo-éléments
       ::before/::after) se répartissaient sur PLUSIEURS lignes générées
       implicitement (une par item avec une hauteur "auto" propre à chacun,
       16/28/16px constatés) au lieu d'une seule — la toolbar gonflait
       verticalement (padding fantôme au-dessus/en dessous des boutons) au
       lieu de rester une simple ligne horizontale. grid-row:1 ci-dessous
       sur chaque item force explicitement le même comportement, en secours
       si l'un d'eux devait un jour se retrouver replacé implicitement. */
    grid-template-rows: 1fr;
    align-items: center;
    justify-content: normal;
}
.doc-pdf-toolbar-group-right-wrap {
    display: contents;
}
.doc-page-pdf .doc-pdf-toolbar > .doc-pdf-toolbar-group:first-child {
    grid-column: 1;
    grid-row: 1;
    justify-self: center;
}
.doc-page-pdf .doc-pdf-toolbar-group-right {
    grid-column: 3;
    grid-row: 1;
    justify-self: center;
}
.doc-page-pdf .doc-pdf-toolbar-group-zoom {
    grid-column: 5;
    grid-row: 1;
    justify-self: center;
}
/* Les 2 séparateurs (gauche/centre et centre/droite) occupent chacun leur
   propre piste de grille (colonnes 2 et 4 ci-dessus) — pas posés en
   position:absolute sur la toolbar entière comme avant (un simple % de la
   largeur totale, sans rapport avec une vraie piste, ne laissait aucun
   espace réservé autour du trait : les groupes voisins pouvaient venir le
   toucher). centrés dans leur piste (justify-self/align-self:center), ils
   ont donc PAR CONSTRUCTION le même espace de chaque côté = la moitié de
   --dp-pdf-sep-w. L'ancien séparateur interne à .doc-pdf-toolbar-group-zoom
   (span .doc-pdf-toolbar-sep, cf. template) est donc redondant et masqué. */
.doc-pdf-toolbar-group-zoom .doc-pdf-toolbar-sep {
    display: none;
}
.doc-page-pdf .doc-pdf-toolbar::before,
.doc-page-pdf .doc-pdf-toolbar::after {
    content: "";
    display: block;
    grid-row: 1;
    align-self: center;
    justify-self: center;
    width: 1px;
    height: 16px;
    background: var(--border, rgba(255,255,255,0.12));
}
.doc-page-pdf .doc-pdf-toolbar::before { grid-column: 2; }
.doc-page-pdf .doc-pdf-toolbar::after { grid-column: 4; }
/* .doc-page-main/.doc-page-pdf ont toutes deux un `display:flex` INCONDITIONNEL
   dans doc.css (pas dans un `:not([hidden])`) : l'attribut `hidden` posé en JS
   pour basculer HTML <-> PDF (cf. script de sheet_detail.html) ne suffit donc
   pas seul — l'UA stylesheet (`[hidden]{display:none}`) est une feuille de
   PRIORITÉ INFÉRIEURE à toute règle auteur, quelle que soit sa spécificité :
   sans ce correctif, l'élément "caché" restait affiché (flex), juste écrasé à
   une largeur ridicule par le flex-grow du panneau/corps resté visible à côté. */
.doc-page-main[hidden],
.doc-page-pdf[hidden] {
    display: none;
}
/* .doc-pdf-recompile-group et .doc-pdf-toolbar-group-center vivent désormais
   dans .doc-pdf-static-toolbar (déjà display:none en embedded, cf. plus
   haut) — plus besoin de les cibler individuellement ici. */
.doc-page-pdf-embedded .doc-pdf-resize-handle {
    display: none;
}

/* ---- Titre en carte (bandeau, icône livre + titre + sous-titre) ----
   Fond sombre (pas la couleur du type) + bordure ET icône dans la couleur
   du type (--kind-cours/-td/-resume, cf. body.dp-kind-* plus haut) : c'est
   la bordure/l'icône qui portent la couleur, pas tout le bloc. Titre en
   blanc, sous-titre en gris discret — TOUS LES DEUX dans le même bloc que
   l'icône (dp-subtitle est un enfant de dp-title-text, lui-même dans
   dp-title-card : jamais en dehors). */
.dp-title-card {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 14px;
    margin: 12px 0 10px;
    padding: 18px 20px;
    border: 1px solid var(--kind-cours);
    border-radius: var(--radius, 10px);
    background: var(--surface-2);
}
.dp-kind-td .dp-title-card { border-color: var(--kind-td); }
.dp-kind-resume .dp-title-card { border-color: var(--kind-resume); }

.dp-title-icon-box {
    display: flex;
    /* flex-start (pas center) + margin-top sur .dp-title-icon ci-dessous :
       calé plus finement sur le HAUT DES CARACTÈRES du titre (pas sur le
       haut de la ligne, qui inclut le "leading" au-dessus des lettres,
       cf. .dp-title line-height ~1.55 * 30px) — un centrage simple sur
       toute la hauteur de ligne laissait l'icône visuellement trop haute. */
    align-items: flex-start;
    justify-content: center;
    flex: 0 0 auto;
    color: var(--kind-cours);
    height: calc(30px * 1.3);
}
.dp-kind-td .dp-title-icon-box { color: var(--kind-td); }
.dp-kind-resume .dp-title-icon-box { color: var(--kind-resume); }

.dp-title-icon {
    width: 44px;
    height: 44px;
    object-fit: contain;
    margin-top: 8px;
}

.dp-title-text {
    /* flex:1 (pas juste flex item par défaut, qui ne prend que la largeur de
       son contenu) : sans ça, .dp-title/.dp-sheet-title restent aussi
       étroits que leur texte, donc 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 dispo dans .dp-title-card. */
    flex: 1 1 0;
    min-width: 0;
    margin-left: 10px;
    /* flex column (pas juste block) : les nœuds de texte blancs entre {{ h1 }}
       et {{ subtitle }} dans le template (indentation Django) créaient une
       ligne fantôme après le sous-titre (~12px de "padding" bas fantôme,
       symptôme observé) — un conteneur flex ignore les enfants texte
       blancs comme items, ce qui l'élimine sans toucher au template. */
    display: flex;
    flex-direction: column;
}

.dp-title {
    font-family: var(--font-display, 'Spectral', serif);
    font-size: 22px;
    font-weight: 700;
    margin: 0;
    color: var(--text-primary, #e8ecf1);
    /* h1 est centré par défaut (base.css) : ce titre vit dans une carte à
       icône + texte alignés à gauche, pas en pleine page — override local. */
    text-align: left;
}

/* Titre de la feuille : sous le nom du chapitre (le "gros titre" ci-dessus,
   cf. .dp-title), délibérément plus discret — le chapitre porte l'identité
   visuelle principale de la page désormais. */
.dp-sheet-title {
    font-size: 13px;
    font-weight: 500;
    color: var(--text-muted);
    margin-top: 2px;
}

.dp-subtitle {
    /* Enfant direct de .dp-title-card (comme .dp-title-rule ci-dessous),
       pas de .dp-title-text : pleine largeur, sous l'icône ET le séparateur
       plutôt que confiné à la colonne titre/sous-titre. */
    flex: 1 0 100%;
    font-size: 13px;
    color: var(--text-primary, #e8ecf1);
    margin-top: 10px;
}

/* Séparateur entre le bloc titre (chapitre + feuille) et le sous-titre/le
   contenu — toujours affiché (cf. sheet_detail.html, plus conditionné à un
   sous-titre présent). */
.dp-title-rule {
    /* Enfant direct de .dp-title-card (flex-wrap:wrap ci-dessus), pas de
       .dp-title-text : flex-basis:100% le force sur sa propre ligne pleine
       largeur, sous l'icône ET le texte, plutôt que confiné à la largeur de
       la seule colonne titre/sous-titre. */
    flex: 1 0 100%;
    border: none;
    border-top: 1px solid var(--border-soft, rgba(255,255,255,0.1));
    /* Le gap:14px de .dp-title-card (partagé avec l'écart icône/texte)
       s'applique aussi devant cette ligne wrappée — margin négatif pour le
       ramener à 7px (moitié) rien que pour le séparateur, sans toucher au
       gap icône/texte. */
    margin: -7px 0 0;
}

.dp-content {
    /* Le contenu réel (_sheet_body_items.html) garde le style de
       latex_engine.css/doc.css — on ne touche pas à ce qui est à
       l'intérieur, seulement à ce qui l'entoure. */
}

/* =====================================================================
   Refonte visuelle (aération / hiérarchie / cohérence) — inspirée des
   principes de la maquette fournie (pas une reproduction à l'identique).
   Tout est scopé sous .dp-page (ou via les classes dp-* déjà uniques à
   cet espace de test) : les classes de production réutilisées ici
   (doc-toc-*, doc-env, doc-sidebar-card...) ne sont donc touchées que
   DANS cette page, jamais dans doc.css lui-même.
   ===================================================================== */

/* ---- Aération générale : plus d'air entre sommaire / corps / PDF, et
   entre le contenu et le bord de la fenêtre. ---- */
.dp-page .doc-page-scroll { gap: 36px; }
.dp-page .doc-chapters-scroll-sheet { padding-right: 20px; }

/* ---- Barre supérieure : poids visuel réduit — plus de fond plein/
   double bordure, juste un trait bas discret qui sépare du contenu. Le
   style des groupes de boutons (segmented control) est défini une seule
   fois plus haut (cf. .dp-switch/.dp-display) ; gap augmenté ici entre les
   groupes (breadcrumb / switch / affichage) pour aérer, cf. demande. ---- */
.dp-context-bar {
    background: transparent;
    border-top: none;
    border-bottom: 1px solid var(--border-soft, rgba(255,255,255,0.07));
    /* 0, pas quelques px : le sommaire/corps/PDF doivent toucher la barre
       (demande explicite) — leur position réelle est de toute façon
       mesurée en JS via getBoundingClientRect(), pas supposée depuis cette
       marge (cf. updateSidebarStickyTop/updateContentMaxHeight plus bas
       dans sheet_detail.html), donc aucun recalcul à faire côté JS. */
    margin-bottom: 0;
}
.dp-context-bar-inner {
    min-height: var(--ctx-bar-height, 44px);
    /* PAS de column-gap ici : depuis le passage à une grille "miroir" de
       .doc-page-scroll (cf. plus haut), la piste 2 (--dp-gap, 40px/20px)
       EST déjà l'espace entre sommaire et contenu — un column-gap
       supplémentaire ici décalerait le switch/l'affichage vers la droite
       sans plus correspondre au vrai bord du contenu en dessous (constaté :
       ~18-28px de décalage selon le mode). */
}

/* ---- Sommaire : largeur réduite, titres de section plus lisibles,
   indentation régulière, transitions discrètes sur l'état actif. ----
   flex-basis en --dp-sidebar-w (pas un 216px en dur ici) : cette variable
   est aussi celle que .dp-context-bar-inner utilise pour sa 1ère piste de
   grille (cf. plus haut) — le fil d'Ariane ne peut tomber pile sur le bord
   du sommaire que si les deux lisent la MÊME valeur, définie une seule
   fois. */
/* Les DEUX pages feuille lisent la même variable, sans nombre en dur ici :
   c'est ce qui garantit qu'elles ne peuvent pas diverger.
   `--fpdf-sidebar-width` n'existe que si la poignée de redimensionnement de
   la nouvelle page a servi (elle la pose sur `.spdf-page`, donc sans effet
   sur la page classique). */
.dp-page .doc-page-sidebar,
.spdf-page .doc-page-sidebar {
    flex: 0 0 var(--fpdf-sidebar-width, var(--dp-sidebar-w));
    padding-top: 10px;
}
/* body:has(.doc-page-pdf) .doc-page-sidebar (doc.css, mode prof) remet
   padding-top:0 avec une spécificité (0,2,1) qui bat la règle juste
   au-dessus (0,2,0) — override direct à spécificité au moins égale pour
   que les 10px s'appliquent aussi en mode prof. */
body:has(.doc-page-pdf) .dp-page .doc-page-sidebar { padding-top: 10px; }
/* Corps HTML aligné sur le même padding-top que le sommaire (cf. ci-dessus)
   pour que les deux contenus démarrent visuellement au même niveau. */
/* box-sizing:border-box — sans lui, ce padding-top s'ajoutait EN PLUS de
   la max-height calculée en JS (content-box, la valeur par défaut), rendant
   la boîte du corps HTML 10px plus haute que le panneau PDF juste à côté
   (même maxH visé pourtant) : les deux doivent occuper exactement le même
   espace vertical, seul le padding interne doit changer. */
.dp-page .doc-page-main { padding-top: 10px; box-sizing: border-box; }
.sheet-toc-scope .doc-sidebar-card.doc-sidebar-toc-card {
    border: 1px solid var(--border-soft, rgba(255,255,255,0.08));
    border-radius: var(--radius, 10px);
    background: transparent;
    padding: 10px 6px;
}
/* #doc-toc-wrap (doc.css) déborde de -16px pour coller sa scrollbar au bord
   DE PRODUCTION (carte sans bordure visible, ça ne se voit pas). Ici, la
   carte a maintenant un vrai contour (cf. ci-dessus) : ce même -16px faisait
   sortir la scrollbar par-dessus/au-delà de la bordure. On la ramène à
   l'intérieur (0, le padding 6px de la carte suffit comme marge). */
.sheet-toc-scope #doc-toc-wrap {
    /* Annule le padding-right de la carte (6px) pour coller la scrollbar
       contre l'intérieur de la bordure, pas à distance d'elle. */
    margin-right: -6px;
    padding-right: 4px;
}
.sheet-toc-scope .doc-sidebar-label { font-size: 0.72rem; letter-spacing: 0.08em; }
.sheet-toc-scope .doc-toc-l1 {
    font-size: 0.86rem;
    letter-spacing: 0.01em;
    padding-bottom: 10px;
    margin-bottom: 8px;
    border-bottom-color: var(--border-soft, rgba(255,255,255,0.07));
}
.sheet-toc-scope .doc-toc-l1:not(:first-child) {
    margin-top: 16px;
    padding-top: 16px;
    border-top-color: var(--border-soft, rgba(255,255,255,0.07));
}
.sheet-toc-scope .doc-toc-item {
    padding: 7px 10px;
    transition: background-color .15s ease, color .15s ease;
}
.sheet-toc-scope .doc-toc-icon { flex: 0 0 auto; width: 15px; height: 15px; }
/* Élément actif : uniquement le fond (couleur du type) change, jamais de
   bordure ni de padding — sinon le texte du sommaire se décale au clic. */

/* ---- Carte de titre : plus basse, sans fond ni bordure, titre plus
   affirmé et sous-titre plus discret, icône légèrement agrandie. ---- */
.dp-title-card {
    border: none;
    background: none;
    padding: 4px 0 16px 30px;
    margin: 0 0 20px;
}
/* .dp-title-card porte aussi .sheet-detail-info.sheet-card (cf. template —
   marqueur lu par sheets.js pour toutes les sauvegardes de cette page) :
   cette classe a sa propre règle dans doc.css qui neutralise (entre autres)
   margin-bottom à 0, pensée pour le bandeau .doc-header de production —
   plus spécifique (2 classes) que .dp-title-card seule (1 classe) ci-dessus,
   elle écrasait silencieusement le margin-bottom:20px voulu ici. border/
   background restent bien "none" (déjà le cas par design, cf. commentaire
   plus haut) : seul margin-bottom est restauré, avec une spécificité au
   moins égale (3 classes). */
.dp-title-card.sheet-detail-info.sheet-card {
    margin-bottom: 20px;
}
.dp-title { font-size: 30px; font-weight: 800; letter-spacing: -0.01em; }
.dp-sheet-title { font-size: 13px; font-weight: 500; color: var(--text-muted); margin-top: 2px; }
.dp-subtitle { font-size: 12.5px; color: var(--text-primary, #e8ecf1); margin-top: 10px; }

/* ---- Contenu principal : espacement vertical un peu plus généreux entre
   les blocs, contrastes de fond légèrement adoucis, marges internes
   uniformisées. ---- */
.dp-content.doc-body,
.dp-page .doc-section-group,
.dp-page .doc-subsection-group {
    gap: 9px;
}
.dp-page .doc-env {
    padding: 16px 20px;
    filter: saturate(0.88);
}
.dp-page .doc-env-head { gap: 10px; }

/* ---- Sections / sous-sections : plus de sticky (le titre épinglé au
   scroll, pensé pour la production, ajoutait ici trop de complexité pour
   peu de valeur — cf. demande) — retour à un flux normal. On annule tout ce
   qui ne servait qu'à cet épinglage (position, fond "raccord" au défilement,
   flou, bande ::after qui masquait le débordement sous le bandeau). */
.dp-page .doc-section,
.dp-page .doc-subsection {
    /* position:relative (pas static) — .doc-section/.doc-subsection sont
       aussi des .doc-block, dont la poignée de drag-and-drop
       (.doc-block-drag-handle, doc.css) est position:absolute et a besoin
       d'un ancêtre positionné comme référence. En position:static, elle
       perdait tout contexte de positionnement et partait se coller au bord
       de l'écran (symptôme observé : plus de poignée visible sur les
       sections). relative + top:auto conserve le flux normal voulu
       ci-dessus tout en réparant ce cas. */
    position: relative;
    top: auto;
    z-index: auto;
    margin-top: 0;
    background: none;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
}
.dp-page .doc-section::after { content: none; }
/* Espacement resserré : l'ancien padding vertical (8px) + marge (8px) +
   bande ::after (10px) cumulaient beaucoup d'air, surtout après les
   sections (trait + bande + marge du groupe suivant). */
.dp-page .doc-section,
.dp-page .doc-subsection {
    padding: 2px 0;
    margin-bottom: 2px;
}
/* Section : même traitement que la sous-section (padding-bottom nul, trait
   fin en bas, margin-bottom 4px) — l'ancien padding-bottom généreux (8px)
   pensé pour le sticky cumulait avec le gap du groupe (18px) et cassait la
   cohérence des écarts entre deux items de la feuille : maintenant l'écart
   après une section est le même que partout ailleurs (margin-bottom 4px +
   gap 18px du groupe, comme la sous-section). */
.dp-page .doc-subsection,
.dp-page .doc-section { padding-bottom: 0; }

/* =====================================================================
   Toolbar du panneau PDF (compilation / aperçu / pagination / zoom) :
   même famille de composants que la context-bar (boutons translucides,
   coins arrondis homogènes, hover discret avec léger "lift"), avec une
   hiérarchie assumée — Recompiler (action principale) mis en valeur,
   pagination/zoom (actions secondaires) plus discrets pour que l'œil
   aille d'abord vers les contrôles utiles. Scopé sous .dp-page : ces
   classes sont partagées avec sheet_detail.html en production (cf.
   doc.css), on ne touche donc rien en dehors de cet espace de test.
   ===================================================================== */
.dp-page .doc-pdf-toolbar,
.dp-page .doc-pdf-static-toolbar { padding: 8px 14px; }
.dp-page .doc-pdf-toolbar-group { gap: 6px; }
/* Menu flottant au survol (.doc-page-pdf .doc-pdf-toolbar, pas la barre
   statique) +10% de largeur — demande explicite : padding horizontal élargi
   d'autant (14px -> 15px), le --dp-pdf-sep-w des séparateurs déjà élargi
   pareil juste au-dessus. Spécificité (3 classes) volontairement plus
   élevée que .dp-page .doc-pdf-toolbar ci-dessus pour gagner quel que soit
   l'ordre dans la feuille. */
.dp-page .doc-page-pdf .doc-pdf-toolbar { padding: 8px 15px; }

/* Boutons de la barre d'outils PDF (recompiler, télécharger, zoom…).
   Le scope est `.pdf-tools`, posé sur la BARRE elle-même, et non `.dp-page`
   comme avant : la nouvelle page feuille remonte ces mêmes boutons dans la
   barre de contexte, hors de tout `.dp-page`, et ils y perdaient tout — le
   contour, la teinte du type de feuille, l'état de survol. Accroché à la
   barre, le style suit les boutons partout où on les met.
   Le `.dp-kind-*` des règles teintées reste porté par <body>, commun aux
   deux pages. */
.pdf-tools .doc-pdf-btn {
    border-radius: 7px;
    background: rgba(255,255,255,0.03);
    border-color: rgba(255,255,255,0.07);
    transition: background-color .15s ease, color .15s ease, transform .15s ease, box-shadow .15s ease;
}
.pdf-tools .doc-pdf-btn:hover {
    background: rgba(255,255,255,0.08);
    border-color: rgba(255,255,255,0.12);
    transform: translateY(-1px);
}
.pdf-tools .doc-pdf-btn:active { transform: translateY(0); }
/* Pas de translateY sur Recompiler : le survol y déclenche déjà le swap
   barre/Stop (cf. doc.css) — le décalage vertical en plus donnait
   l'impression que le bouton "sautait" au lieu de changer proprement
   d'état sur place. */
.pdf-tools .doc-pdf-btn-recompile:hover { transform: none; }
.pdf-tools .doc-pdf-btn:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--bg-elev, #12161d), 0 0 0 4px var(--accent, #6d7bff);
}

/* Action principale du panneau : mise en valeur par une teinte accent
   légère (pas un aplat plein, pour rester dans le même registre "glossy
   discret" que les segmented controls ci-dessus) — se distingue ainsi
   nettement des boutons secondaires/utilitaires à côté. */
.pdf-tools .doc-pdf-btn-recompile {
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d7bff) 20%, transparent), color-mix(in srgb, var(--accent, #6d7bff) 11%, transparent));
    border-color: color-mix(in srgb, var(--accent, #6d7bff) 38%, transparent);
    color: var(--text, #e8ecf1);
    font-weight: 600;
}
.pdf-tools .doc-pdf-btn-recompile:hover {
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d7bff) 28%, transparent), color-mix(in srgb, var(--accent, #6d7bff) 17%, transparent));
}
/* Teinté par le kind de la feuille (vert Cours/violet TD/bleu Résumé),
   cohérent avec le reste de la DA (segmented controls HTML/TeX, Global/
   Local, plein écran) — pas le bleu --accent générique. */
.dp-kind-cours .pdf-tools .doc-pdf-btn-recompile {
    background: linear-gradient(180deg, color-mix(in srgb, var(--kind-cours) 20%, transparent), color-mix(in srgb, var(--kind-cours) 11%, transparent));
    border-color: color-mix(in srgb, var(--kind-cours) 38%, transparent);
}
.dp-kind-cours .pdf-tools .doc-pdf-btn-recompile:hover {
    background: linear-gradient(180deg, color-mix(in srgb, var(--kind-cours) 28%, transparent), color-mix(in srgb, var(--kind-cours) 17%, transparent));
}
.dp-kind-td .pdf-tools .doc-pdf-btn-recompile {
    background: linear-gradient(180deg, color-mix(in srgb, #7A5CF5 20%, transparent), color-mix(in srgb, #7A5CF5 11%, transparent));
    border-color: color-mix(in srgb, #7A5CF5 38%, transparent);
}
.dp-kind-td .pdf-tools .doc-pdf-btn-recompile:hover {
    background: linear-gradient(180deg, color-mix(in srgb, #7A5CF5 28%, transparent), color-mix(in srgb, #7A5CF5 17%, transparent));
}
.dp-kind-resume .pdf-tools .doc-pdf-btn-recompile {
    background: linear-gradient(180deg, color-mix(in srgb, #4D8FE8 20%, transparent), color-mix(in srgb, #4D8FE8 11%, transparent));
    border-color: color-mix(in srgb, #4D8FE8 38%, transparent);
}
.dp-kind-resume .pdf-tools .doc-pdf-btn-recompile:hover {
    background: linear-gradient(180deg, color-mix(in srgb, #4D8FE8 28%, transparent), color-mix(in srgb, #4D8FE8 17%, transparent));
}
.dp-kind-banque .pdf-tools .doc-pdf-btn-recompile {
    background: linear-gradient(180deg, color-mix(in srgb, #DE3AA8 20%, transparent), color-mix(in srgb, #DE3AA8 11%, transparent));
    border-color: color-mix(in srgb, #DE3AA8 38%, transparent);
}
.dp-kind-banque .pdf-tools .doc-pdf-btn-recompile:hover {
    background: linear-gradient(180deg, color-mix(in srgb, #DE3AA8 28%, transparent), color-mix(in srgb, #DE3AA8 17%, transparent));
}

/* Pagination + zoom : actions secondaires, délibérément plus discrètes
   (icônes réduites, opacité abaissée au repos) — elles restent accessibles
   mais ne concurrencent plus visuellement Recompiler/le format d'affichage. */
/* Téléchargement/nouvel onglet (les deux <a> directement enfants du 1er
   groupe, PAS .doc-pdf-recompile-group qui reste inchangé — demande
   explicite) + pagination/zoom : entièrement masqués au repos, révélés
   ensemble au survol de la toolbar, dans les DEUX modes (élève ET prof —
   seuls Recompiler/Global-Local, propres au mode prof, ne bougent pas). */
.dp-page .doc-pdf-toolbar-group > .doc-pdf-btn,
.dp-page .doc-pdf-toolbar-group-right .doc-pdf-btn,
.dp-page .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.dp-page .doc-pdf-page-indicator,
.dp-page .doc-pdf-zoom-level {
    opacity: 0;
    transition: opacity .15s ease;
}
.dp-page .doc-pdf-toolbar:hover .doc-pdf-toolbar-group > .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:hover .doc-pdf-toolbar-group-right .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:hover .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:hover .doc-pdf-page-indicator,
.dp-page .doc-pdf-toolbar:hover .doc-pdf-zoom-level,
.dp-page .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group > .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group-right .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.dp-page .doc-pdf-toolbar:focus-within .doc-pdf-page-indicator,
.dp-page .doc-pdf-toolbar:focus-within .doc-pdf-zoom-level,
.dp-page .doc-pdf-toolbar-group > .doc-pdf-btn:focus-visible,
.dp-page .doc-pdf-toolbar-group-right .doc-pdf-btn:focus-visible,
.dp-page .doc-pdf-toolbar-group-zoom .doc-pdf-btn:focus-visible,
/* Mode élève (toolbar flottante, cf. .doc-page-pdf-embedded plus haut) : la
   toolbar elle-même n'est révélée qu'au survol du PANNEAU entier — pas UN
   survol de tout le panneau ici : pointer-events:none tant qu'elle est
   invisible empêche déjà tout :hover direct sur elle avant qu'elle ne soit
   awake (via mousemove/clic, cf. sheet_detail.html), donc pas de cercle
   vicieux à craindre en se limitant à .doc-pdf-toolbar:hover — ce qui,
   contrairement à :hover sur tout le panneau, permet aux icônes de
   redisparaître avec le reste du menu après le délai d'inactivité même si
   le curseur reste immobile ailleurs dans la zone PDF (demande explicite,
   cf. .doc-pdf-toolbar:hover plus haut pour le même correctif sur le menu
   entier). */
.doc-page-pdf .doc-pdf-toolbar:hover .doc-pdf-toolbar-group > .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:hover .doc-pdf-toolbar-group-right .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:hover .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:hover .doc-pdf-page-indicator,
.doc-page-pdf .doc-pdf-toolbar:hover .doc-pdf-zoom-level,
.doc-page-pdf .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group > .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group-right .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:focus-within .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.doc-page-pdf .doc-pdf-toolbar:focus-within .doc-pdf-page-indicator,
.doc-page-pdf .doc-pdf-toolbar:focus-within .doc-pdf-zoom-level,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-toolbar-group > .doc-pdf-btn,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-toolbar-group-right .doc-pdf-btn,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-toolbar-group-zoom .doc-pdf-btn,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-page-indicator,
.doc-page-pdf.pdf-toolbar-awake .doc-pdf-zoom-level {
    opacity: 1;
}
.dp-page .doc-pdf-toolbar-group-right .doc-pdf-btn,
.dp-page .doc-pdf-toolbar-group-zoom .doc-pdf-btn {
    width: 24px;
    min-width: 24px;
    height: 24px;
}
.dp-page .doc-pdf-toolbar-group-right .doc-pdf-btn svg,
.dp-page .doc-pdf-toolbar-group-zoom .doc-pdf-btn svg { width: 13px; height: 13px; }
.dp-page .doc-pdf-toolbar-sep { background: rgba(255,255,255,0.1); }

/* Aperçu Global/Local : même famille visuelle que le segmented control
   HTML/TeX (.dp-display, cf. plus haut) — fond translucide commun, actif en
   dégradé teinté par le kind de la feuille — mais on ne touche à AUCUNE
   dimension (padding/gap/taille d'icône restent celles de
   .doc-pdf-apercu-toggle-compact, doc.css) : seule l'habillage visuel change. */
.dp-page .doc-pdf-apercu-toggle {
    border: 1px solid rgba(255,255,255,0.08);
    background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015));
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.04), 0 1px 3px rgba(0,0,0,0.18);
    border-radius: 8px;
    padding: 2px;
}
.dp-page .doc-pdf-apercu-btn {
    border: 1px solid transparent;
    border-radius: 6px;
    color: var(--text-muted, #9aa4b2);
    transition: background-color .15s ease, color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.dp-page .doc-pdf-apercu-btn:not(.is-active):hover {
    color: var(--text-primary, #e8ecf1);
    background: rgba(255,255,255,0.07);
    transform: translateY(-1px);
}
.dp-page .doc-pdf-apercu-btn.is-active {
    background: linear-gradient(180deg, color-mix(in srgb, var(--accent, #6d7bff) 82%, white), var(--accent, #6d7bff));
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.3), 0 1px 3px rgba(0,0,0,0.25);
    color: #fff;
}
.dp-kind-cours .dp-page .doc-pdf-apercu-btn.is-active {
    background: linear-gradient(180deg, color-mix(in srgb, var(--kind-cours) 82%, white), var(--kind-cours));
}
.dp-kind-td .dp-page .doc-pdf-apercu-btn.is-active {
    background: linear-gradient(180deg, #7A5CF5 0%, #7353F0 20%, #6B4BE8 55%, #5B3BD1 100%);
}
.dp-kind-resume .dp-page .doc-pdf-apercu-btn.is-active {
    background: linear-gradient(180deg, #4D8FE8 0%, #4487E3 20%, #3C7FDC 55%, #316FC8 100%);
}
.dp-kind-banque .dp-page .doc-pdf-apercu-btn.is-active {
    background: linear-gradient(180deg, #F45FC0 0%, #EA4EB5 20%, #DE3AA8 55%, #C22090 100%);
}

/* Bandeau d'erreur LaTeX (doc.css : margin:6px sur les 4 côtés) : collé aux
   bords gauche/droit/bas du panneau PDF (comme la maquette), seul le bord
   du haut garde un peu d'air pour le séparer visuellement des pages
   au-dessus. Coins carrés en bas (flush avec le panneau) — arrondis
   conservés en haut.
   (padding-top:0 sur .doc-pdf-pages, qui vivait ici, supprimé : pensé pour
   l'ancienne barre statique du mode prof, qui réservait déjà sa hauteur —
   contredisait désormais le menu flottant unifié, cf. .doc-page-pdf
   .doc-pdf-pages plus haut, qui veut 8px partout.) */
.dp-page .doc-pdf-error {
    margin: 6px 0 0 0;
    border-radius: 4px 4px 0 0;
}
/* La console d'erreurs de compilation LaTeX est un outil de mise au point
   pour la rédaction (prof) — recompiler n'est même pas possible en mode
   élève, ce bandeau n'a donc rien à afficher qui lui soit utile. */
body.mode-eleve .doc-pdf-error {
    display: none !important;
}

/* .exo-meta-import-btn : déplacé dans exercise_card.css (chargé partout où
   le panneau info apparaît, pas seulement sur sheet_detail). */

/* ===== Palette de création rapide de bloc (Ctrl/Cmd+N) ===== */
.dp-block-palette[hidden] { display: none !important; }
.dp-block-palette {
    position: fixed;
    top: 14vh;
    left: 50%;
    transform: translateX(-50%);
    z-index: 9200;
    width: min(420px, 92vw);
}
.dp-block-palette-box {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    box-shadow: 0 16px 48px rgba(0,0,0,.5);
    overflow: hidden;
}
.dp-block-palette-input {
    width: 100%;
    background: var(--inset);
    border: none;
    border-bottom: 1px solid var(--border-soft);
    color: var(--text);
    padding: 10px 14px;
    font-size: 0.9rem;
    outline: none;
}
.dp-block-palette-list {
    list-style: none;
    margin: 0;
    padding: 6px;
    max-height: 320px;
    overflow-y: auto;
}
.dp-block-palette-item {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border-radius: 6px;
    cursor: pointer;
    color: var(--text);
    font-size: 0.86rem;
}
.dp-block-palette-item svg { flex-shrink: 0; color: var(--text-muted); }
.dp-block-palette-item.active,
.dp-block-palette-item:hover {
    background: var(--accent);
    color: var(--on-accent);
}
.dp-block-palette-item.active svg,
.dp-block-palette-item:hover svg { color: inherit; }
.dp-block-palette-empty {
    padding: 10px 14px;
    color: var(--text-muted);
    font-size: 0.85rem;
}

/* =====================================================================
   CONFINEMENT DES CARTES HORS ÉCRAN (content-visibility)

   Sur une feuille dense, chaque frame d'un glisser-déposer salit la mise en
   page (transform de l'élément flottant, déplacement du placeholder) puis la
   relit — le navigateur recalcule alors la mise en page de TOUTES les cartes,
   y compris les ~150 hors écran. Mesuré sur la feuille 147 (banque de 152
   exercices) : 8,5 ms par recalcul complet, pour un budget de 16,7 ms si on
   veut tenir 60 fps — d'où le lag ressenti, que le JS ne peut pas corriger
   (drag_reorder.js ne lit déjà qu'un ou deux rects par frame).

   `content-visibility: auto` fait sauter mise en page ET peinture des cartes
   hors écran. Mesures comparatives sur cette même feuille :
       sans confinement ......... 8,5 ms
       sur .exo-statement ....... 3,1 ms
       sur .exercise-card ....... 0,4 ms   <- retenu
       sur .sheet-block ......... 0,2 ms

   Posé sur .exercise-card et NON sur .sheet-block, malgré le gain marginal
   de ce dernier : content-visibility implique `contain: paint`, qui découpe
   au ras de la boîte, or .doc-block-drag-handle est volontairement en
   `right: -16px` (dans la gouttière, hors du bloc) — la poignée deviendrait
   invisible et le drag impossible. La poignée est un frère de .exercise-card,
   donc hors du sous-arbre confiné : rien à déplacer.

   `contain-intrinsic-size: auto 260px` : 260px sert d'estimation seulement
   tant que la carte n'a jamais été rendue ; `auto` fait ensuite mémoriser sa
   taille réelle, pour que la barre de défilement ne saute pas.
   ===================================================================== */
.dp-page .exercise-card {
    content-visibility: auto;
    contain-intrinsic-size: auto 260px;
}

/* Seule exception : .exo-tex-modal et .exo-notes-modal vivent DANS la carte
   et sont en `position: fixed; inset: 0` (plein écran). Sous un ancêtre
   confiné, elles seraient réduites et découpées aux dimensions de leur
   carte. On lève donc le confinement de la seule carte dont une modale est
   ouverte — elles sont `hidden` au repos et une seule s'ouvre à la fois, donc
   le surcoût est nul. Ce sont les deux SEULS descendants en position fixed ou
   sticky d'une carte (vérifié sur la page rendue) : si on en ajoute un
   autre, il faudra l'ajouter ici. */
.dp-page .exercise-card:has(.exo-tex-modal:not([hidden])),
.dp-page .exercise-card:has(.exo-notes-modal:not([hidden])) {
    content-visibility: visible;
}

/* Levée temporaire du confinement, le temps d'une frame au chargement (cf.
   `bootstrapCardSizes` dans sheet_detail.html). Sur une page neuve, aucune
   carte n'a encore de taille mémorisée : `contain-intrinsic-size` les estime
   TOUTES à 260px alors qu'elles font réellement 200 à 400px. Le navigateur
   restaure alors la position de défilement dans un document dont la hauteur
   est fausse, et on atterrit à côté du contenu attendu — symptôme observé :
   après un F5, la zone affichée est vide jusqu'à ce qu'on scrolle.
   Forcer un calcul complet une fois donne à chaque carte sa taille réelle,
   que `contain-intrinsic-size: auto` mémorise ensuite. */
body.cv-bootstrap .dp-page .exercise-card { content-visibility: visible; }

/* Sommaire déplaçable (mode prof) : chaque entrée est sa propre poignée
   (cf. sheet_detail.html, second initDragReorder) — le clic de navigation
   reste prioritaire, seul le curseur "grabbing" pendant l'appui signale
   qu'on peut aussi la glisser. */
body.mode-admin .doc-toc-item:active { cursor: grabbing; }
body.mode-admin .doc-toc-item.dragging { cursor: grabbing; }
