/*
 * GED — feuille de style unique.
 *
 * Servie en statique, sans compilation. Aucune dépendance.
 * La CSP interdisant le style « inline », tout ce qui concerne l'apparence
 * doit vivre ici : pas de <style> ni d'attribut style= dans les vues Blade.
 */

:root {
    --ground: #f2f4f5;
    --surface: #ffffff;
    --surface-2: #e8eded;
    --ink: #14201f;
    --ink-2: #4a5a58;
    --ink-3: #7a8a88;
    --rule: #d4dbda;
    /* Un filet plus marqué : bords en pointillé (zone de dépôt, aperçu vide),
       pastille inactive. Employée depuis longtemps sans être définie (§69) :
       la déclaration était alors ignorée, et l'aperçu vide n'avait aucun
       bord. */
    --rule-strong: #aab6b4;
    --accent: #0b6b5f;
    --accent-ink: #08544a;
    --accent-soft: #dfeeeb;
    --danger: #9e2b20;
    --danger-soft: #f8e5e2;
    --ok: #2c6a31;
    --ok-soft: #e3efe3;

    /* Favori. Un ambre soutenu, distinct de l'accent et du danger : marquer un
       dossier n'est ni l'état courant, ni une action destructrice. */
    --pin: #b5822a;

    --radius: 4px;
    --gap: 16px;

    /*
     * Les contrôles NATIFS suivent le thème : liste ouverte d'un <select>,
     * calendrier d'un champ de date, barres de défilement. Sans cette
     * déclaration, ils restaient clairs en thème sombre — la seule partie de
     * l'écran que les variables ne pouvaient pas atteindre.
     */
    color-scheme: light dark;

    /*
     * Palette des notes.
     *
     * Deux valeurs par couleur : le fond et son filet. Elles vivent ici, et
     * non en base : la colonne « notes.color » ne stocke qu'une clé, si bien
     * que chaque couleur a une version claire et une version sombre sans que
     * la donnée ne connaisse le thème. Changer la palette est une
     * modification de feuille de style, jamais une migration.
     *
     * Choisies assez pâles pour que l'encre par défaut reste lisible dessus :
     * une note n'a pas de couleur de texte propre.
     */
    --note-jaune: #fdf3c8;
    --note-jaune-edge: #e3cf8b;
    --note-bleu: #dbe9f8;
    --note-bleu-edge: #a5c6e5;
    --note-vert: #dbf0de;
    --note-vert-edge: #a3d3ad;
    --note-rose: #fbdfe7;
    --note-rose-edge: #ecadc0;
    --note-orange: #fde4cd;
    --note-orange-edge: #efbb90;
    --note-gris: #e6ebeb;
    --note-gris-edge: #c1c9c9;
}

/*
 * Barres de défilement fines, aux couleurs du thème, comme celle du sommaire
 * de la documentation : listes déroulantes, zones de texte, tableaux et
 * panneaux défilants. La liste ouverte d'un <select> est dessinée par le
 * navigateur ; Chrome et Edge y appliquent ces couleurs, d'autres gardent
 * leur barre système.
 */
select,
textarea,
.table-scroll,
.menu-panel,
.preview-modal-body,
dialog {
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
}
select:hover,
textarea:hover,
.table-scroll:hover,
.menu-panel:hover,
.preview-modal-body:hover,
dialog:hover {
    scrollbar-color: var(--ink-3) transparent;
}

@media (prefers-color-scheme: dark) {
    :root {
        --ground: #0d1413;
        --surface: #151e1d;
        --surface-2: #1d2726;
        --ink: #e3eae9;
        --ink-2: #a2b1af;
        --ink-3: #6f7e7c;
        --rule: #273331;
        --rule-strong: #3f4d4b;
        --accent: #4fc7b2;
        --accent-ink: #7fdccb;
        --accent-soft: #12302c;
        --danger: #e87c70;
        --danger-soft: #381916;
        --ok: #77c07c;
        --ok-soft: #16281a;
        --pin: #dcac57;

        /*
         * En thème sombre, les mêmes couleurs deviennent des teintes
         * profondes : un pastel clair y serait un rectangle éblouissant, et
         * l'encre claire du thème y deviendrait illisible. La CLÉ stockée en
         * base ne change pas — c'est tout l'intérêt de ne pas y avoir écrit
         * un code hexadécimal.
         */
        --note-jaune: #3a3116;
        --note-jaune-edge: #5e4e22;
        --note-bleu: #16283a;
        --note-bleu-edge: #23425a;
        --note-vert: #17321f;
        --note-vert-edge: #245130;
        --note-rose: #381c25;
        --note-rose-edge: #58303d;
        --note-orange: #3a2a16;
        --note-orange-edge: #5c4324;
        --note-gris: #232b2b;
        --note-gris-edge: #39413f;
    }
}

* { box-sizing: border-box; }

/*
 * L'attribut « hidden » doit l'emporter sur toute règle de mise en page.
 *
 * Le navigateur ne lui applique qu'un [hidden]{display:none} de spécificité
 * nulle : la moindre classe posant un display — .ctx-item{display:block},
 * .upload-list{display:flex} — le bat, et l'élément reste affiché alors que
 * JavaScript le croit masqué. Aucune erreur, aucun symptôme côté script : le
 * menu contextuel proposait « Aperçu » sur un dossier, et le champ de
 * renommage s'affichait en permanence.
 *
 * C'est le seul !important de la feuille, et il est là pour cette raison.
 */
[hidden] { display: none !important; }

body {
    margin: 0;
    background: var(--ground);
    color: var(--ink);
    font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
          "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
}

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

.sr-only {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
}

.skip {
    position: absolute;
    left: -9999px;
}
.skip:focus {
    left: 8px;
    top: 8px;
    z-index: 10;
    background: var(--surface);
    padding: 8px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ---------------------------------------------------------------- barre haute */

.topbar {
    display: flex;
    align-items: center;
    gap: var(--gap);
    padding: 10px 16px;
    background: var(--surface);
    border-bottom: 1px solid var(--rule);
    position: sticky;
    top: 0;
    z-index: 5;
}

.brand {
    font-weight: 600;
    font-size: 16px;
    color: var(--ink);
    text-decoration: none;
    white-space: nowrap;
}

.search {
    flex: 1;
    min-width: 0;
    max-width: 520px;
}

.search input {
    width: 100%;
    padding: 7px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--ink);
    font: inherit;
}

/* Occupe la place de la barre de recherche quand celle-ci est masquée, pour
   que le nom de compte reste à droite sans saut de mise en page. */
.topbar-spacer { flex: 1; }

.account {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-left: auto;
}

.account-name {
    color: var(--ink-2);
    font-size: 13.5px;
    white-space: nowrap;
}

.nav-toggle { display: none; }

/*
 * Compte et API, dans le bandeau — sur écran large seulement.
 *
 * Sous 860 px, les deux sont masqués (voir la requête média) et la barre
 * latérale repliée les propose à leur place, par « .sidebar-narrow-only ».
 * Le seuil est le même des deux côtés : aucune largeur ne montre les deux.
 */
.api-widget,
.account-widget { display: inline-flex; }

.api-btn { display: inline-flex; align-items: center; gap: 6px; }

/* Un SVG sans dimensions vaut 300 × 150 (§41.5). */
.api-btn svg { width: 16px; height: 16px; flex: none; }

.account-link {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 8px;
    border: 0;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-2);
    font: inherit;
    cursor: pointer;
    text-decoration: none;
}
.account-link:hover { background: var(--surface-2); color: var(--ink); }
/* Un bouton de menu n'est pas une page : « is-current » dit seulement que la
   page affichée est l'une de ses entrées. */
.account-link.is-current,
.account-link.is-current .account-name { color: var(--accent-ink); }

/* Un SVG sans dimensions vaut 300 × 150 (§41.5). */
.account-link svg { width: 16px; height: 16px; flex: none; }
.account-link .account-name { color: inherit; }

.api-panel[popover] { width: 280px; gap: 2px; }
.api-panel .menu-title { margin-bottom: 6px; }

.api-menu-item {
    display: flex;
    flex-direction: column;
    gap: 1px;
    padding: 7px 8px;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
}
.api-menu-item:hover { background: var(--surface-2); }
.api-menu-item[aria-current="page"] { background: var(--accent-soft); color: var(--accent-ink); }

.api-menu-name { font-size: 13.5px; font-weight: 600; }
.api-menu-note { font-size: 12.5px; color: var(--ink-2); }

/* Masqué par défaut, révélé sous 860 px — jamais l'inverse (§45.3). */
.sidebar-narrow-only { display: none; }

/* ------------------------------------------------------------------- coquille */

.shell {
    display: grid;
    grid-template-columns: 240px minmax(0, 1fr);
    align-items: start;
    max-width: 1400px;
    margin: 0 auto;
}

.sidebar {
    padding: 20px 12px;
    border-right: 1px solid var(--rule);
    min-height: calc(100vh - 53px);
    position: sticky;
    top: 53px;
}

.sidebar-empty {
    color: var(--ink-3);
    font-size: 13.5px;
    padding: 0 8px;
    margin: 0;
}

.content {
    padding: 20px 24px 60px;
    min-width: 0;
}

/* ---------------------------------------------------------------- composants */

.breadcrumb {
    font-size: 14px;
    color: var(--ink-2);
    margin-bottom: 18px;
}

.flash {
    padding: 10px 14px;
    border-radius: var(--radius);
    border: 1px solid;
    margin: 0 0 18px;
    font-size: 14px;
}
.flash p { margin: 0 0 4px; }
.flash p:last-child { margin: 0; }

.flash-ok {
    background: var(--ok-soft);
    border-color: var(--ok);
    color: var(--ok);
}

.flash-error {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}

.empty {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 28px;
}
.empty h2 { margin: 0 0 8px; font-size: 17px; }
.empty p { margin: 0; color: var(--ink-2); max-width: 60ch; }

.btn {
    font: inherit;
    font-size: 14px;
    padding: 7px 14px;
    border-radius: var(--radius);
    border: 1px solid var(--rule);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}
.btn:hover { background: var(--surface-2); }

.btn-primary {
    background: var(--accent);
    border-color: var(--accent);
    color: #ffffff;
}
.btn-primary:hover { background: var(--accent-ink); border-color: var(--accent-ink); }

.btn-quiet { background: transparent; }

.btn-block { width: 100%; padding: 10px; }

/* ------------------------------------------------------- arbre des dossiers */

.sidebar-root {
    display: block;
    padding: 6px 10px;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
    font-size: 14px;
    font-weight: 600;
}
.sidebar-root:hover { background: var(--surface-2); }

.sidebar-root.is-current,
.tree-link.is-current {
    background: var(--accent-soft);
    color: var(--accent-ink);
    font-weight: 600;
}

/*
 * Séparation entre l'arborescence et les vues transverses.
 *
 * Parcourir ses documents et administrer son compte ne relèvent pas du même
 * geste : le trait et l'intitulé marquent le changement de registre, que la
 * seule marge ne suffisait pas à rendre lisible.
 */
.sidebar-divider {
    border: 0;
    border-top: 1px solid var(--rule);
    margin: 20px 10px 14px;
}

.sidebar-heading {
    margin: 0 0 6px;
    padding: 0 10px;
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.tree {
    list-style: none;
    margin: 2px 0 0;
    padding-left: 4px;
}
.tree .tree { border-left: 1px solid var(--rule); padding-left: 6px; }

/*
 * Une ligne = le bouton de dépliage + le lien.
 *
 * La surbrillance porte sur la ligne entière et non sur le seul lien : avec
 * l'icône sortie du lien, un fond posé sur le lien seul aurait laissé le
 * pictogramme en dehors du bandeau. Le lien garde néanmoins sa propre règle,
 * qui suffit si :has() n'est pas appliqué.
 */
.tree-row {
    display: flex;
    align-items: center;
    gap: 2px;
    border-radius: var(--radius);
}
.tree-row:hover { background: var(--surface-2); }
.tree-row.is-current { background: var(--accent-soft); }

.tree-toggle {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 24px;
    height: 24px;
    padding: 0;
    border: 0;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-3);
    cursor: pointer;
}
.tree-toggle svg { width: 15px; height: 15px; display: block; }
.tree-toggle:hover { color: var(--ink); }

/* Un dossier sans enfant n'est pas un bouton : rien ne doit laisser croire
   qu'il s'ouvre. */
.tree-toggle.is-leaf { cursor: default; opacity: 0.75; }

/* Les deux tracés sont rendus, un seul est affiché : l'état est porté par
   aria-expanded, qui sert donc à la fois l'accessibilité et l'affichage —
   impossible qu'ils divergent. */
.tree-toggle .tree-icon-open { display: none; }
.tree-toggle[aria-expanded="true"] .tree-icon-open { display: block; }
.tree-toggle[aria-expanded="true"] .tree-icon-shut { display: none; }

.tree-row.is-current .tree-toggle { color: var(--accent-ink); }

.tree-link {
    flex: 1;
    min-width: 0;
    display: block;
    padding: 5px 8px 5px 2px;
    border-radius: var(--radius);
    color: var(--ink-2);
    text-decoration: none;
    font-size: 14px;
    overflow-wrap: anywhere;
}
.tree-link:hover { color: var(--ink); }

/* La branche en cours de chargement s'annonce, sans faire sauter la mise en
   page : le contenu arrive au même endroit. */
[data-tree-branch][aria-busy="true"] {
    min-height: 18px;
    background: linear-gradient(90deg, transparent, var(--surface-2), transparent);
    border-radius: var(--radius);
}

/* ------------------------------------------------------------------ contenu */

.breadcrumb a { text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }
.breadcrumb-sep { color: var(--ink-3); margin: 0 6px; }

.hint {
    color: var(--ink-2);
    font-size: 14px;
    margin: 0 0 18px;
    max-width: 70ch;
}

.toolbar { margin-bottom: 18px; }

.inline-form {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}
.inline-form input[type="text"] {
    flex: 1;
    min-width: 180px;
    max-width: 340px;
    padding: 7px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
}

.table-scroll {
    overflow-x: auto;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.listing {
    width: 100%;
    border-collapse: collapse;
    font-size: 14px;
    min-width: 520px;
}
.listing thead th {
    text-align: left;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-3);
    padding: 10px 14px;
    border-bottom: 1px solid var(--rule);
    background: var(--surface-2);
}
.listing td {
    padding: 9px 14px;
    border-bottom: 1px solid var(--rule);
    vertical-align: middle;
}
.listing tbody tr:last-child td { border-bottom: none; }
.listing tbody tr:hover { background: var(--surface-2); }

.row-name {
    color: var(--ink);
    text-decoration: none;
    font-weight: 500;
    overflow-wrap: anywhere;
}
.row-name:hover { text-decoration: underline; }

/*
 * Nom découpé en tête et queue, pour une troncature par le milieu.
 *
 * Sur écran large : AUCUNE règle. Les deux fragments sont deux spans en ligne
 * qui se suivent, le nom s'affiche entier et se replie comme avant. La
 * mécanique de troncature vit entièrement dans la requête média « téléphone »
 * plus bas — c'est ce qui garantit que le bureau n'est pas touché.
 */
.fname { overflow-wrap: anywhere; }

/*
 * Un nom de document cliquable qui n'est pas un lien.
 *
 * Sur une collecte, le nom ouvre l'aperçu : c'est une action, pas une
 * navigation, donc un <button>. Il lui faut seulement perdre l'apparence que
 * le navigateur donne aux boutons, sans rien perdre du reste — hérité de
 * .row-name, y compris le soulignement au survol.
 */
.row-name-btn {
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-weight: 500;
    text-align: left;
    cursor: pointer;
}

/*
 * Icône devant un nom, dans une liste.
 *
 * Un SVG sans width ni height vaut 300x150 pixels : l'oublier ne dégrade pas
 * la mise en page, il la détruit (§41.5). La règle reste en « inline » plutôt
 * que de passer .row-name en flex — cette classe sert sur cinq écrans, dont
 * quatre affichent une puce textuelle et non un tracé.
 */
.row-name svg {
    width: 15px;
    height: 15px;
    margin-right: 7px;
    vertical-align: -2px;
    color: var(--ink-3);
}

/*
 * Champ court dans une barre de formulaire.
 *
 * Sans cela, un nombre de mégaoctets hérite de la largeur prévue pour un nom
 * de fichier, et laisse un vide absurde entre le chiffre et son unité.
 */
.filter-narrow { flex: 0 0 auto; min-width: 0; }
.filter-narrow .input-affix { width: 130px; flex: none; }

.muted { color: var(--ink-2); }
.nowrap { white-space: nowrap; }

/*
 * Emplacement d'un résultat de recherche : « 📁 Dans : Clients › Particuliers ».
 *
 * Le chemin peut être long, et la colonne ne doit pas pousser le tableau à
 * défiler pour autant : largeur bornée, et repli sur plusieurs lignes plutôt
 * qu'une troncature — un chemin coupé ne distingue plus rien, ce qui est
 * précisément ce que cette colonne sert à faire.
 *
 * L'icône du type doit recevoir sa taille : un SVG sans width ni height vaut
 * 300x150 pixels (§41.5).
 */
.loc {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0 5px;
    max-width: 340px;
    line-height: 1.5;
    overflow-wrap: anywhere;
}
.loc svg {
    width: 14px;
    height: 14px;
    flex: none;
    color: var(--ink-3);
    align-self: center;
}
.loc-in,
.loc-sep { color: var(--ink-3); }
.row-actions { text-align: right; white-space: nowrap; }

/*
 * Menu d'actions — élément « popover » natif.
 *
 * Le panneau est rendu dans la couche supérieure du navigateur : il n'est donc
 * ni découpé par le conteneur défilant du tableau, ni compté dans la hauteur de
 * ses parents. C'est ce que ne permettait pas un simple position:absolute.
 *
 * position:fixed + inset:auto annulent le centrage par défaut des popovers ;
 * les coordonnées sont posées par app.js, faute de position d'ancrage CSS
 * disponible partout.
 */
.menu-panel[popover] {
    position: fixed;
    inset: auto;
    margin: 0;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 12px;
    width: 280px;
    max-width: calc(100vw - 16px);
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.4);

    /*
     * Un popover se PEINT dans la couche supérieure, mais il HÉRITE du CSS de
     * ses ancêtres dans le DOM — et ces panneaux vivent dans une cellule
     * « .row-actions », qui porte « text-align: right » et « white-space:
     * nowrap » pour aligner les icônes.
     *
     * Sans ces deux remises à zéro, tout texte un peu long refuse de revenir à
     * la ligne : il déborde et le panneau se dote d'une barre de défilement
     * horizontale. Le symptôme n'apparaît que sur les panneaux contenant une
     * phrase, ce qui l'a rendu invisible jusqu'au partage de dossier.
     */
    text-align: left;
    white-space: normal;

    /* Un nom de dossier sans espace ne doit pas élargir le panneau. */
    overflow-wrap: anywhere;
}

.menu-panel[popover]:popover-open {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/*
 * Panneau portant un VRAI formulaire, à plusieurs champs empilés.
 *
 * La règle générale « .menu-panel form » est un flex HORIZONTAL, taillée pour
 * « un champ + un bouton » — le renommage, le déplacement. Le formulaire de
 * partage a trois blocs : sans ce correctif, ils se rangeaient côte à côte
 * dans 280 px, les libellés se chevauchaient et le panneau prenait une barre
 * de défilement horizontale.
 */
.menu-panel-form[popover] { width: 420px; }

.menu-panel-form form {
    flex-direction: column;
    align-items: stretch;
    gap: 12px;
}

.menu-panel-form .hint {
    margin: 0;
    font-size: 13px;
    max-width: none;
}

/* Neutralise « .filter:first-child { flex: 1; min-width: 200px } », qui vient
   de la barre de filtres de la recherche et n'a pas de sens empilé. */
.menu-panel-form .filter,
.menu-panel-form .filter:first-child {
    flex: none;
    min-width: 0;
    width: 100%;
}

.menu-panel-form .filter input {
    width: 100%;
    min-width: 0;
    font-size: 13.5px;
}

.menu-panel-form .btn { width: 100%; }

/*
 * Champ de fichier d'un panneau.
 *
 * Un « input[type=file] » a une largeur intrinsèque proche de 300 px : sans
 * « min-width: 0 », il impose sa mesure au conteneur flex et le panneau prend
 * une barre de défilement horizontale. Il n'hérite pas non plus de la police
 * du document, d'où le « font: inherit ».
 */
.menu-panel input[type="file"] {
    width: 100%;
    min-width: 0;
    font: inherit;
    font-size: 13px;
    color: var(--ink-2);
}

/*
 * Le bouton natif du champ de fichier, habillé comme un « .btn ».
 *
 * « ::file-selector-button » est la SEULE façon de le styler sans JavaScript.
 * L'alternative répandue — masquer le champ et déguiser son <label> en
 * bouton — coûterait le nom du fichier choisi, que le navigateur affiche
 * gratuitement à côté et qu'il faudrait alors réécrire en script. Or ce
 * panneau doit fonctionner sans JavaScript (§18), et c'est le seul retour
 * disant QUEL document on s'apprête à mettre en ligne.
 */
.menu-panel input[type="file"]::file-selector-button {
    margin-right: 10px;
    padding: 6px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

.menu-panel input[type="file"]:hover::file-selector-button { background: var(--surface-2); }

.menu-panel input[type="file"]:focus-visible::file-selector-button {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
    outline: none;
}

/*
 * Panneau d'une action unique — renommage, déplacement.
 *
 * Il valait 250 px, et son formulaire restant en flex HORIZONTAL, le champ
 * n'en gardait qu'environ 150 : un nom de document en fait couramment
 * quarante caractères, dont on ne voyait qu'une dizaine. Renommer obligeait à
 * défiler dans le champ pour relire ce qu'on était en train de modifier.
 *
 * D'où la largeur portée à 420 px ET l'empilement : le bouton sous le champ
 * rend au nom toute la largeur du panneau, au lieu de la partager. Le
 * « max-width » de « .menu-panel » le borne sur téléphone, et app.js ramène
 * de toute façon le panneau dans la fenêtre.
 *
 * Même raisonnement pour la liste des destinations du déplacement, dont les
 * libellés sont des chemins indentés.
 */
.menu-panel-sm[popover] { width: 420px; gap: 10px; }

.menu-panel-sm form {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
}

.menu-panel-sm .btn { width: 100%; }

.menu-title {
    margin: 0;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-3);
}

/* ------------------------------------------------------ sélection multiple */

.col-pick {
    width: 34px;
    padding-right: 0;
    text-align: center;
}

/*
 * Case à cocher dessinée entièrement en CSS.
 *
 * « appearance: none » retire le rendu du système, dont l'apparence varie d'un
 * navigateur et d'un thème à l'autre — et qui ignore la palette du projet. La
 * coche est un simple angle de bordure pivoté : pas d'image, pas de data-URI,
 * donc rien à autoriser dans la CSP.
 */
.col-pick input[type="checkbox"] {
    appearance: none;
    -webkit-appearance: none;
    position: relative;
    width: 17px;
    height: 17px;
    margin: 0;
    vertical-align: middle;
    border: 1.5px solid var(--ink-3);
    border-radius: 3px;
    background: var(--surface);
    cursor: pointer;
    transition: background-color 110ms ease-out, border-color 110ms ease-out;
}

.col-pick input[type="checkbox"]:hover { border-color: var(--accent); }

.col-pick input[type="checkbox"]:checked,
.col-pick input[type="checkbox"]:indeterminate {
    background: var(--accent);
    border-color: var(--accent);
}

/* La coche : deux bordures d'un carré vide, pivotées. */
.col-pick input[type="checkbox"]:checked::after {
    content: "";
    position: absolute;
    left: 4.5px;
    top: 1px;
    width: 4px;
    height: 8px;
    border: solid #ffffff;
    border-width: 0 2px 2px 0;
    transform: rotate(43deg);
}

/*
 * État indéterminé de la case « tout sélectionner » : un tiret, jamais une
 * coche. Une sélection partielle ne doit pas se lire comme une sélection
 * complète.
 */
.col-pick input[type="checkbox"]:indeterminate::after {
    content: "";
    position: absolute;
    left: 3px;
    top: 6.5px;
    width: 9px;
    height: 2px;
    background: #ffffff;
    border-radius: 1px;
}

/*
 * La ligne sélectionnée se teinte : sur une page de cinquante fichiers, une
 * case cochée de 17 pixels se repère mal. Le fond survit au défilement, la
 * case non.
 */
.listing tbody tr:has(input[data-pick]:checked) { background: var(--accent-soft); }

@media (prefers-reduced-motion: reduce) {
    .col-pick input[type="checkbox"] { transition: none; }
}

/*
 * Barre d'actions groupées. Collée en haut de la zone de contenu : la
 * sélection se fait en défilant, et les actions doivent rester atteignables
 * sans remonter.
 */
.batch-bar {
    position: sticky;
    top: 0;
    z-index: 20;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    margin: 0 0 var(--gap);
    padding: 10px 14px;
    background: var(--accent-soft);
    border: 1px solid var(--accent);
    border-radius: var(--radius);
}

.batch-count {
    margin: 0;
    font-weight: 600;
    color: var(--accent-ink);
}

.batch-bar form { display: flex; align-items: center; gap: 8px; margin: 0; }

/* --------------------------------------------------------- glisser-déposer */

/*
 * L'élément en cours de déplacement s'efface, sans disparaître : il doit rester
 * repérable pour qu'on sache ce qu'on tient.
 */
.is-dragging { opacity: 0.45; }

/*
 * Cible acceptant le dépôt. Le contour est posé en « outline » et non en
 * « border » : une bordure décalerait le contenu d'un pixel au survol, et cette
 * secousse rend la visée difficile au moment précis où elle compte.
 */
.is-drop-target {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
    background: var(--accent-soft);
    border-radius: var(--radius);
}

/* Une ligne de tableau ignore border-radius : le fond suffit à la désigner. */
tr.is-drop-target { outline-offset: -2px; }

[draggable="true"] { -webkit-user-drag: element; }

/* --------------------------------------------------------- menu contextuel */

/*
 * Un seul menu pour toute la page, placé au curseur par app.js.
 *
 * Il n'est pas un « popover » natif, contrairement aux panneaux d'action :
 * ceux-ci se ferment mutuellement en couche supérieure, et le menu doit
 * justement pouvoir en ouvrir un — celui du déplacement — au moment où il
 * disparaît.
 */
.ctx-menu {
    position: fixed;
    z-index: 60;
    min-width: 190px;
    max-width: calc(100vw - 16px);
    padding: 6px;
    background: var(--surface);
    color: var(--ink);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 10px 30px -12px rgba(0, 0, 0, 0.4);
    text-align: left;
}

.ctx-label {
    margin: 2px 8px 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--ink-3);
    /* Un nom long ne doit pas étirer le menu au-delà de l'écran. */
    max-width: 260px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.ctx-items { display: flex; flex-direction: column; }

.ctx-item {
    display: block;
    width: 100%;
    padding: 7px 10px;
    border: 0;
    border-radius: 3px;
    background: none;
    color: var(--ink);
    font: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.ctx-item:hover,
.ctx-item:focus-visible {
    background: var(--surface-2);
}

.ctx-item.is-danger { color: var(--danger); }
.ctx-item.is-danger:hover,
.ctx-item.is-danger:focus-visible { background: var(--danger-soft); }

/* Le renommage remplace la liste dans le même cadre, sans second panneau. */
.ctx-rename {
    display: flex;
    /* Empilé pour la même raison que « .menu-panel-sm » : le menu contextuel
       ne fait que 190 px, et un nom long y était illisible. */
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    width: 380px;
    max-width: calc(100vw - 24px);
    padding: 4px;
}

.ctx-rename input { min-width: 0; flex: 1; }
.ctx-rename .btn { width: 100%; }

/*
 * Formulaire de partage porté par le menu contextuel.
 *
 * Plus large que le menu d'actions, qui suffit à des libellés : deux champs et
 * un avertissement y tiendraient à l'étroit. Le texte doit pouvoir revenir à
 * la ligne — le menu, lui, ne le permet pas pour ses entrées.
 */
.ctx-share {
    width: 380px;
    max-width: calc(100vw - 24px);
    padding: 4px 6px 6px;
    white-space: normal;
    overflow-wrap: anywhere;
}
.ctx-share .hint { margin: 0 0 10px; font-size: 12.5px; }
.ctx-share .filter { margin-bottom: 10px; }
.ctx-share .btn { width: 100%; }

/* ------------------------------------------------------- actions en ligne */

/*
 * Une icône par action plutôt qu'un menu générique : chaque fonction est
 * atteignable en un clic, et celles qui attendent une saisie ouvrent leur
 * propre popover.
 */
.row-tools {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 2px;
}

/* Les formulaires ne doivent pas rompre l'alignement de la rangée. */
.row-tools form { display: flex; margin: 0; }

.icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: transparent;
    color: var(--ink-3);
    cursor: pointer;
    text-decoration: none;
}
.icon-btn:hover {
    background: var(--surface-2);
    border-color: var(--rule);
    color: var(--ink);
}
.icon-btn.is-danger:hover {
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger);
}
.icon-btn svg { width: 16px; height: 16px; display: block; }

/*
 * Favori — l'étoile se REMPLIT, elle ne change pas de tracé.
 *
 * Un seul « case » dans le composant d'icônes, donc aucun risque que les deux
 * états divergent : c'est le même chemin, peint ou non. Même raisonnement que
 * les deux dossiers de l'arborescence, dont l'affichage suit aria-expanded.
 */
.icon-btn.is-pinned { color: var(--pin); }
.icon-btn.is-pinned svg { fill: currentColor; }
.icon-btn.is-pinned:hover { color: var(--pin); }

/*
 * Marqueur de favori dans une liste : une étoile inerte, à côté du nom.
 *
 * Hors du lien, et donc hors de la boîte flexible que la troncature par le
 * milieu installe sous 620 px (§45.4) — un troisième enfant y aurait disputé
 * la place aux deux fragments du nom.
 */
.row-pin {
    display: inline-flex;
    margin-left: 6px;
    vertical-align: -2px;
    color: var(--pin);
}
.row-pin svg { width: 13px; height: 13px; display: block; fill: currentColor; }

/*
 * Le bouton « ⋮ » est masqué PAR DÉFAUT, ici et non dans la requête média.
 *
 * Écrite à l'intérieur de « @media (max-width: 620px) », cette règle ne
 * s'appliquait pas au-dessus du seuil : le bouton héritait alors du
 * « display » de .icon-btn et s'affichait en SIXIÈME icône sur le bureau,
 * doublant en silence le clic droit.
 *
 * Le défaut ne se voyait pas en rétrécissant la fenêtre pour vérifier le
 * mobile — c'est en la RÉÉLARGISSANT qu'il apparaissait.
 *
 * Règle générale : ce qui ne doit exister que sur téléphone se masque par
 * défaut et se révèle dans la requête média. Jamais l'inverse.
 */
.row-more { display: none; }

/* ------------------------------------------------------------- infobulles */

/*
 * Infobulle en CSS pur, via ::after et attr().
 *
 * Alignée sur le bord DROIT du bouton, et non centrée : le tableau est un
 * conteneur défilant (overflow-x: auto), qui découpe donc aussi
 * verticalement. Une infobulle centrée déborderait à droite du dernier
 * bouton et serait tronquée — le même écueil que l'ancien menu d'actions.
 * En s'étendant vers la gauche, elle reste toujours dans le cadre.
 *
 * aria-label reste le nom accessible du bouton : cette infobulle est un
 * confort visuel, jamais le seul porteur de l'information.
 */
.icon-btn { position: relative; }

.icon-btn[data-tip]::after,
.icon-btn[data-tip]::before {
    opacity: 0;
    pointer-events: none;
    position: absolute;
    z-index: 6;
    transition: opacity 110ms ease-out, transform 110ms ease-out;
}

.icon-btn[data-tip]::after {
    content: attr(data-tip);
    right: 0;
    bottom: calc(100% + 7px);
    transform: translateY(3px);
    padding: 5px 9px;
    border-radius: 4px;
    background: var(--ink);
    color: var(--ground);
    font-size: 12px;
    line-height: 1.35;
    font-weight: 500;
    white-space: nowrap;
    box-shadow: 0 4px 14px -6px rgba(0, 0, 0, 0.5);
}

/* Petite pointe, alignée sur le centre du bouton. */
.icon-btn[data-tip]::before {
    content: "";
    right: 11px;
    bottom: calc(100% + 2px);
    transform: translateY(3px);
    border: 5px solid transparent;
    border-top-color: var(--ink);
}

.icon-btn[data-tip]:hover::after,
.icon-btn[data-tip]:hover::before,
.icon-btn[data-tip]:focus-visible::after,
.icon-btn[data-tip]:focus-visible::before {
    opacity: 1;
    transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
    .icon-btn[data-tip]::after,
    .icon-btn[data-tip]::before { transition: none; transform: none; }
}

/*
 * Dans le bandeau, l'infobulle bascule SOUS le bouton.
 *
 * Le bandeau est collé en haut de la fenêtre (« position: sticky; top: 0 ») :
 * une infobulle posée au-dessus de son bouton sort de l'écran et se fait
 * couper net. Rien ne la découpe — ni conteneur défilant, ni overflow — c'est
 * le bord de la fenêtre lui-même.
 *
 * Portée sur « .topbar » et non sur le seul bouton des notes épinglées : ce
 * n'est pas une particularité de ce bouton, c'est une propriété de
 * l'emplacement. Tout bouton icône ajouté au bandeau rencontrerait exactement
 * le même défaut.
 *
 * SEULES les propriétés de position sont redéfinies ici. Toucher au
 * « transform » aurait battu la règle de survol — même spécificité, mais
 * écrite plus bas dans la feuille : l'infobulle serait restée décalée en
 * permanence, sans jamais se poser.
 */
.topbar .icon-btn[data-tip]::after {
    top: calc(100% + 7px);
    bottom: auto;
}

.topbar .icon-btn[data-tip]::before {
    top: calc(100% + 2px);
    bottom: auto;
    border-top-color: transparent;
    border-bottom-color: var(--ink);
}

/*
 * Dans l'en-tête de la modale d'aperçu, l'infobulle bascule SOUS le bouton.
 *
 * Même remède que pour le bandeau, mais pas la même cause : ici, c'est bien
 * un découpage — la modale est en « overflow: hidden », et une bulle sortant
 * par le haut de son en-tête serait rognée net (§43.13). Vers le bas, elle
 * reste dans le corps de la modale, donc entière.
 *
 * Portée sur l'en-tête, pas sur les flèches : c'est une propriété de
 * l'emplacement, et tout bouton icône ajouté là rencontrerait le même défaut.
 *
 * SEULES LES PROPRIÉTÉS DE POSITION SONT REDÉFINIES. Toucher au « transform »
 * battrait la règle de survol — même spécificité, écrite plus haut dans la
 * feuille — et l'infobulle resterait décalée en permanence, sans jamais se
 * poser (§48.10).
 */
.preview-modal-head .icon-btn[data-tip]::after {
    top: calc(100% + 7px);
    bottom: auto;
}

.preview-modal-head .icon-btn[data-tip]::before {
    top: calc(100% + 2px);
    bottom: auto;
    border-top-color: transparent;
    border-bottom-color: var(--ink);
}

/* Sur écran tactile, il n'y a pas de survol : l'infobulle resterait collée
   après un appui. */
@media (pointer: coarse) {
    .icon-btn[data-tip]::after,
    .icon-btn[data-tip]::before { display: none; }
}

/* La cible tactile recommandée est plus large que l'icône elle-même. */
@media (pointer: coarse) {
    .icon-btn { width: 40px; height: 40px; }
    .row-tools { gap: 0; }
}
.menu-panel form { display: flex; gap: 6px; }

/*
 * Champs des panneaux d'action et du menu contextuel.
 *
 * Une seule règle pour les deux : ce sont les mêmes formulaires, atteints par
 * deux chemins. Le menu contextuel n'avait aucun style et affichait un champ
 * brut du navigateur, sans rapport avec le reste de l'interface.
 */
.menu-panel input[type="text"],
.menu-panel select,
.ctx-rename input[type="text"] {
    flex: 1;
    min-width: 0;
    padding: 6px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--ink);
    font: inherit;
    font-size: 13.5px;
}

.menu-panel input[type="text"]:focus-visible,
.menu-panel select:focus-visible,
.ctx-rename input[type="text"]:focus-visible {
    border-color: var(--accent);
    /* Halo plutôt qu'un contour : dans un panneau étroit, un outline décalé
       de 2 px déborde du cadre et se fait rogner. */
    box-shadow: 0 0 0 3px var(--accent-soft);
    outline: none;
}

.btn-danger {
    color: var(--danger);
    border-color: var(--danger);
    background: transparent;
}
.btn-danger:hover { background: var(--danger-soft); }

.menu-panel .btn-danger { width: 100%; }

/* --------------------------------------------------------- dépôt de fichiers */

.uploader { margin-bottom: 20px; }

.drop-zone {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 22px 16px;
    border: 1.5px dashed var(--rule-strong, var(--rule));
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink-2);
    text-align: center;
    cursor: pointer;
    font-size: 14px;
}
.drop-zone strong { color: var(--ink); font-size: 15px; }
.drop-zone:hover { border-color: var(--accent); }
.drop-zone.is-over {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-ink);
}
.drop-zone:focus-within { outline: 2px solid var(--accent); outline-offset: 2px; }

/*
 * La zone du Drive (§73) : un cadre qui porte le libellé principal ET les deux
 * boutons de sélection. Le libellé est étiré sur tout le cadre par son ::after,
 * si bien qu'un clic n'importe où hors des boutons ouvre le sélecteur de
 * fichiers — comme lorsque le cadre entier était un <label>.
 */
.drop-zone-full {
    position: relative;
    gap: 12px;
    padding: 24px 16px;
}
/* Le cadre ne s'allume que pour SON champ : un bouton focalisé porte déjà
   son propre anneau, deux contours imbriqués ne diraient rien de plus. */
.drop-zone-full:focus-within { outline: none; }
.drop-zone-full:has(> [data-file-input]:focus-visible) {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}
.drop-zone-hit {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    cursor: pointer;
}
.drop-zone-hit::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
}
.drop-zone-hit svg { width: 26px; height: 26px; margin-bottom: 4px; color: var(--ink-3); }
.drop-zone-hit strong { font-size: 16px; }
.drop-zone-hit span { font-size: 13px; }
.drop-zone-full:hover .drop-zone-hit svg,
.drop-zone-full.is-over .drop-zone-hit svg { color: var(--accent); }
.drop-zone-full.is-over strong { color: var(--accent-ink); }

/* Au-dessus du libellé étiré. Les vides entre les boutons laissent passer le
   clic vers la zone, qui reste ainsi cliquable partout. */
.drop-zone-actions {
    position: relative;
    z-index: 1;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 8px;
    pointer-events: none;
}
.drop-zone-actions .btn {
    pointer-events: auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    white-space: nowrap;
}
.drop-zone-actions svg { flex: none; width: 15px; height: 15px; }

.upload-list {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.upload-item {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 120px 90px;
    gap: 12px;
    align-items: center;
    padding: 8px 12px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    font-size: 13.5px;
}
.upload-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.upload-state { text-align: right; color: var(--ink-2); font-size: 12.5px; }

.upload-item progress {
    width: 100%;
    height: 6px;
    accent-color: var(--accent);
}
.upload-item.is-done .upload-state { color: var(--ok); }

/*
 * Même contenu déjà présent ailleurs (§63) : la ligne pose sa question en
 * place, sans modale. Ambre, comme un avertissement — ce n'est pas un échec.
 */
.upload-item.is-question { border-color: var(--pin); }
.upload-item.is-question .upload-state {
    color: var(--ink);
    text-align: left;
    grid-column: 2 / -1;
    white-space: normal;
    line-height: 1.4;
    overflow-wrap: anywhere;
}
.upload-actions {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
}

/* Envoi d'un dossier (§64) : la liste des fichiers qui ne partiront pas,
   bornée pour rester lisible. */
.import-skipped {
    margin: 0;
    padding-left: 18px;
    max-height: 200px;
    overflow-y: auto;
    font-size: 13.5px;
    color: var(--ink-2);
    overflow-wrap: anywhere;
}
.upload-notice .upload-state { grid-column: 2 / -1; text-align: left; white-space: normal; }

/* Trois choix : ils reviennent à la ligne plutôt que de déborder. */
.conflict-actions { flex-wrap: wrap; }
.conflict-dialog strong { overflow-wrap: anywhere; }
.upload-item.is-failed { border-color: var(--danger); background: var(--danger-soft); }
.upload-item.is-failed .upload-state {
    color: var(--danger);
    text-align: left;
    grid-column: 2 / -1;
    white-space: normal;
    line-height: 1.4;
}

/*
 * Effacement des messages transitoires.
 *
 * Seules les confirmations portent cette classe ; les erreurs ne l'obtiennent
 * jamais. La durée doit rester alignée sur FADE_DURATION dans app.js.
 */
.is-leaving {
    opacity: 0;
    transition: opacity 260ms ease-out;
}

@media (prefers-reduced-motion: reduce) {
    .is-leaving { transition: none; }
}

/* Pendant le rafraîchissement asynchrone de la liste. */
[data-listing][aria-busy="true"] { opacity: 0.55; }

@media (max-width: 620px) {
    .upload-item { grid-template-columns: minmax(0, 1fr) 70px; }
    .upload-item progress { grid-column: 1 / -1; }

    /* Les deux boutons se partagent la ligne, puis s'empilent à pleine
       largeur quand ils n'y tiennent plus : cibles tactiles larges. */
    .drop-zone-actions { align-self: stretch; }
    .drop-zone-actions .btn { flex: 1 1 200px; padding: 9px 14px; }
}

/* --------------------------------------------------------- fiche fichier */

.row-icon { color: var(--ink-3); margin-right: 8px; }
.num { font-variant-numeric: tabular-nums; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }

/*
 * Champ de renommage et son extension, réunis en UN seul contrôle.
 *
 * L'extension n'est pas modifiable (§37.17) : elle est recomposée côté serveur.
 * Affichée à côté du champ, elle avait l'air d'un libellé oublié ; soudée au
 * champ sur un fond distinct, elle se lit pour ce qu'elle est — une partie du
 * nom que l'on ne peut pas toucher.
 */
.input-affix {
    display: flex;
    align-items: stretch;
    flex: 1;
    min-width: 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    overflow: hidden;
}

.input-affix:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/* Le champ perd sa propre bordure : c'est l'enveloppe qui la porte. */
.input-affix input[type="text"],
.input-affix input[type="number"] {
    flex: 1;
    min-width: 0;
    padding: 6px 10px;
    border: 0;
    border-radius: 0;
    background: transparent;
    color: var(--ink);
    font: inherit;
    font-size: 13.5px;
}

.input-affix input[type="text"]:focus-visible,
.input-affix input[type="number"]:focus-visible {
    box-shadow: none;
    outline: none;
}

.ext-suffix {
    display: flex;
    align-items: center;
    padding: 0 9px;
    color: var(--ink-3);
    background: var(--surface-2);
    border-left: 1px solid var(--rule);
    font-size: 13px;
    white-space: nowrap;
    /* Le nom se sélectionne, l'extension non : elle n'est pas éditable. */
    user-select: none;
}

/* Porte une icône sur la page publique : il lui faut donc un axe, et non
   un simple centrage de texte. */
.btn-block-link {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    text-decoration: none;
}

.detail {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 24px;
    max-width: 720px;
}
.detail-title { margin: 0 0 20px; font-size: 19px; overflow-wrap: anywhere; }

.detail-meta {
    display: grid;
    grid-template-columns: 140px minmax(0, 1fr);
    gap: 8px 16px;
    margin: 0 0 24px;
    font-size: 14px;
}
.detail-meta dt { color: var(--ink-3); }
.detail-meta dd { margin: 0; overflow-wrap: anywhere; }
.detail-meta .hash { font-size: 12px; color: var(--ink-2); }

@media (max-width: 620px) {
    .detail-meta { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .detail-meta dd { margin-bottom: 10px; }
}

.detail-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.hint-inline { margin: 0; font-size: 13.5px; }

/*
 * Conséquence à connaître avant d'agir — pas une erreur, pas un danger.
 *
 * Un filet ambré et l'encre normale : le texte en rouge se lirait comme un
 * échec, alors qu'il s'agit d'informer d'un effet de bord voulu (un lien de
 * partage qui servira la nouvelle version).
 *
 * AUCUN fond teinté, délibérément : les panneaux qui portent cet encart
 * contiennent des boutons dont l'état de survol se marque par une teinte, et
 * teinter le conteneur rendrait ce retour invisible (§50.5).
 */
.hint-warn {
    border-left: 2px solid var(--pin);
    padding-left: 10px;
    margin-bottom: 12px;
}

/* ------------------------------------------------------------- recherche */

.filters {
    display: flex;
    flex-direction: column;
    gap: 14px;
    padding: 16px;
    margin-bottom: 18px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.filter { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.filter:first-child { flex: 1; min-width: 200px; }

.filters-row { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 12px; }

/* Sur la seconde ligne, le premier filtre (« Modifié entre ») n'a pas à
   s'étirer comme le champ du nom : les boutons sont repoussés à droite. */
.filters-row + .filters-row .filter:first-child { flex: 0 1 auto; min-width: 0; }
.filters-actions { display: flex; gap: 8px; margin-left: auto; }

.filter label,
.filter-label {
    font-size: 12.5px;
    color: var(--ink-3);
}

/* Deux bornes pour un même critère — dates, tailles — sur une seule ligne. */
.filter-pair { display: flex; align-items: center; gap: 6px; }
.filter-pair > span { font-size: 12.5px; color: var(--ink-3); }
.filter-pair input[inputmode="decimal"] { width: 5.5em; }

/*
 * Raccourcis de recherche : des liens en pastilles. Actif (aria-current), le
 * raccourci se remplit ; le recliquer le retire.
 */
.search-presets {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 22px;
    margin: -6px 0 18px;
}
.search-presets-group { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
.search-presets-caption { font-size: 12.5px; color: var(--ink-3); margin-right: 2px; }
.preset-chip {
    padding: 3px 10px;
    border: 1px solid var(--rule);
    border-radius: 999px;
    font-size: 13px;
    color: var(--ink-2);
    text-decoration: none;
    background: var(--surface);
}
.preset-chip:hover { border-color: var(--accent); color: var(--accent-ink); }
.preset-chip[aria-current="true"] {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent-ink);
    font-weight: 500;
}

.filter input,
.filter select {
    padding: 7px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--ink);
    font: inherit;
    font-size: 14px;
    max-width: 100%;
}

@media (max-width: 620px) {
    .filters-row { flex-direction: column; align-items: stretch; }
    .filter { width: 100%; }
    .filters-actions { margin-left: 0; }
    .filters-actions .btn { flex: 1; }
}

.pagination { margin-top: 16px; font-size: 14px; }
.pagination svg { width: 16px; height: 16px; vertical-align: middle; }

/* -------------------------------------------------------------- partages */

.sidebar-link {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 6px 10px;
    color: var(--ink-2);
    text-decoration: none;
    font-size: 14px;
    border-radius: var(--radius);
}
.sidebar-link:hover { background: var(--surface-2); color: var(--ink); }
.sidebar-link.is-current {
    background: var(--accent-soft);
    color: var(--accent-ink);
    font-weight: 600;
}

/* L'icône situe la rubrique d'un coup d'œil ; le libellé reste le nom
   accessible, l'icône n'étant qu'un repère visuel. */
.sidebar-link svg {
    width: 15px;
    height: 15px;
    flex-shrink: 0;
    color: var(--ink-3);
}
.sidebar-link:hover svg { color: var(--ink-2); }
.sidebar-link.is-current svg { color: var(--accent-ink); }

.sidebar-sublink {
    display: block;
    padding: 4px 10px 4px 24px !important;
    font-size: 13px;
    color: var(--ink-3);
}
.sidebar-sublink:hover { color: var(--ink); }
.sidebar-sublink.is-current {
    background: transparent;
    color: var(--accent-ink);
    font-weight: 500;
}

/* ---------------------------------------------------------------- aperçu */

/* Détails à gauche, aperçu à droite. En dessous de 980 px, la lecture d'un
   document dans une colonne étroite n'a plus d'intérêt : on empile. */
/*
 * Fiche d'un fichier : l'APERÇU À GAUCHE, les informations à droite.
 *
 * C'est le document qu'on vient regarder ; la taille et l'empreinte se
 * consultent une fois. L'ordre du balisage suit celui de l'écran, donc aucune
 * règle « order » n'est employée : sous 980 px la grille passe à une colonne
 * et l'empilement — aperçu, informations, partage — est celui du HTML.
 */
.detail-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 380px);
    gap: 18px;
    align-items: start;
}
.detail-split > .detail { max-width: none; }

/* Colonne de droite : deux cadres empilés, séparés comme les colonnes. */
.detail-side {
    display: grid;
    gap: 18px;
    min-width: 0;
}
.detail-side > .detail { max-width: none; }

/*
 * Colonne de gauche : l'aperçu, puis l'historique des versions.
 *
 * LA RÈGLE DE LARGEUR CI-DESSOUS N'EST PAS DÉCORATIVE.
 *
 * La largeur est libérée par « .detail-split > .detail », un sélecteur
 * d'ENFANT DIRECT. Tant que la colonne de gauche était elle-même la carte de
 * l'aperçu, la règle s'appliquait. En enveloppant deux cartes dans ce
 * conteneur, elles en sortent : sans la redéclaration, elles retomberaient sur
 * « .detail { max-width: 720px } » et l'aperçu se mettrait à plafonner à 720 px
 * sur grand écran — sans erreur, la page restant correcte et simplement plus
 * étroite. Même famille que le piège de spécificité de la corbeille (§45.7).
 */
.detail-stack {
    display: grid;
    gap: 18px;
    min-width: 0;
}
.detail-stack > .detail { max-width: none; }

@media (max-width: 980px) {
    .detail-split { grid-template-columns: minmax(0, 1fr); }
}

.preview-pane { display: flex; flex-direction: column; }

/*
 * En-tête de l'aperçu : le titre à gauche, la navigation à droite — la même
 * disposition que l'en-tête de la modale d'aperçu, dont c'est le pendant sur
 * la fiche.
 */
.preview-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 6px;
}
.preview-head .detail-subtitle { margin: 0; }

.file-nav {
    display: flex;
    align-items: center;
    gap: 2px;
    flex: none;
}

/*
 * Aux extrémités du dossier, la flèche RESTE EN PLACE, désactivée.
 *
 * La retirer déplacerait l'autre sous le curseur : on cliquerait « suivant »
 * en croyant cliquer « précédent ». C'est le même raisonnement que la colonne
 * de sélection, qui garde sa largeur même vide.
 */
.icon-btn:disabled {
    opacity: 0.35;
    cursor: default;
}
.icon-btn:disabled:hover {
    background: transparent;
    border-color: transparent;
    color: var(--ink-3);
}

.preview-frame {
    width: 100%;
    height: 560px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface-2);
}

.preview-image {
    max-width: 100%;
    height: auto;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface-2);
    /* Damier discret : distingue une zone transparente d'un fond blanc. */
    background-image:
        linear-gradient(45deg, var(--rule) 25%, transparent 25%),
        linear-gradient(-45deg, var(--rule) 25%, transparent 25%),
        linear-gradient(45deg, transparent 75%, var(--rule) 75%),
        linear-gradient(-45deg, transparent 75%, var(--rule) 75%);
    background-size: 16px 16px;
    background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

.preview-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    min-height: 180px;
    padding: 24px;
    border: 1px dashed var(--rule-strong);
    border-radius: var(--radius);
    color: var(--ink-2);
    text-align: center;
    font-size: 14px;
}
.preview-empty p { margin: 0; }

/* ------------------------------------------------------- aperçu en modale */

.preview-modal {
    width: min(1000px, calc(100vw - 32px));
    max-width: none;
    max-height: calc(100vh - 48px);
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink);
    overflow: hidden;
}
.preview-modal::backdrop { background: rgba(8, 16, 15, 0.62); }

.preview-modal-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 16px;
    border-bottom: 1px solid var(--rule);
}
.preview-modal-head h2 {
    margin: 0;
    font-size: 15px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.preview-modal-tools { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }

/*
 * Flèches de la modale : collées l'une à l'autre, comme celles de la fiche
 * d'un fichier, et légèrement détachées des boutons qui suivent — ce sont
 * deux groupes de commandes distincts, l'un navigue, l'autre agit.
 */
.preview-nav {
    display: flex;
    align-items: center;
    gap: 2px;
    margin-right: 4px;
}

.preview-modal-body {
    padding: 16px;
    display: flex;
    justify-content: center;
    max-height: calc(100vh - 130px);
    overflow: auto;
}
.preview-modal-body .preview-frame { height: calc(100vh - 170px); min-height: 320px; }
.preview-modal-body .preview-image { max-height: calc(100vh - 170px); object-fit: contain; }

@media (prefers-reduced-motion: no-preference) {
    .preview-modal[open] { animation: confirm-in 140ms ease-out; }
}

@media (max-width: 620px) {
    .preview-modal-head { flex-direction: column; align-items: flex-start; gap: 10px; }
    .preview-modal-body { padding: 10px; }
}

/* Plus de marge propre : le cadre est désormais dans la colonne de droite,
   où l'écart de la grille l'espace du cadre d'informations. */
.detail-share { margin-top: 0; }
.detail-subtitle { margin: 0 0 6px; font-size: 16px; }

.share-form {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: 12px;
    padding: 14px;
    margin: 16px 0 20px;
    background: var(--ground);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}
.share-form .filter { flex: 1; min-width: 180px; }

.share-none { font-size: 14px; margin: 0; }

.stack-form { max-width: 420px; }
.stack-form .field { margin-bottom: 14px; }

/* ------------------------------------- visionneuse PDF de secours (§46) */

/*
 * N'apparaît que là où le navigateur n'a pas de visionneuse — Chrome sur
 * Android. Le poste de travail garde son <iframe> native, et aucune de ces
 * règles ne s'y applique : elles ne visent que des éléments que le script
 * crée dans ce seul cas.
 */

.pdfview {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    height: 560px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface-2);
    overflow: hidden;
}

.pdfview-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--rule);
    background: var(--surface);
}

/*
 * 38 px de côté : la cible tactile minimale. Ces commandes sont, sur
 * téléphone, le seul moyen de changer de page — les réduire à la taille
 * d'une icône de bureau les rendrait inutilisables là où elles servent.
 */
.pdfview-btn {
    min-width: 38px;
    height: 38px;
    padding: 0 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font-size: 17px;
    line-height: 1;
    cursor: pointer;
}
.pdfview-btn:hover { background: var(--surface-2); }

.pdfview-count {
    min-width: 68px;
    text-align: center;
    font-size: 13px;
    color: var(--ink-2);
    font-variant-numeric: tabular-nums;
}

.pdfview-link {
    height: 38px;
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font-size: 13px;
    text-decoration: none;
}
.pdfview-link:hover { background: var(--surface-2); }

/* Repousse les liens à droite, les commandes de lecture restant groupées. */
.pdfview-btn + .pdfview-link { margin-left: auto; }

.pdfview-pages {
    flex: 1;
    min-height: 0;
    padding: 8px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    overflow-y: auto;
    overflow-x: hidden;
    /* Le défilement s'arrête au document : il n'entraîne pas la page
       derrière la modale une fois la dernière page atteinte. */
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

/*
 * Le défilement horizontal n'apparaît qu'agrandi. En permanence, il
 * découperait aussi la verticale (§43.13) sans jamais servir : à l'échelle 1
 * une page fait exactement la largeur disponible.
 */
.pdfview.is-zoomed .pdfview-pages { overflow-x: auto; }

.pdfview-page {
    flex: 0 0 auto;
    width: 100%;
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 2px;
}

/* Le canvas suit la largeur que le script a donnée au cadre ; sa hauteur
   découle du rapport de la page. */
.pdfview-page canvas {
    display: block;
    width: 100%;
    height: auto;
}

.pdfview-notice {
    margin: auto;
    padding: 24px;
    color: var(--ink-2);
    font-size: 14px;
    text-align: center;
}

/* Dans la modale, la visionneuse occupe la hauteur laissée par l'en-tête. */
.preview-modal-body .pdfview {
    height: calc(100vh - 190px);
    min-height: 320px;
}

@media (max-width: 620px) {
    /*
     * Sur téléphone, la hauteur est celle de l'écran, pas une valeur fixe :
     * 560 px dépasseraient la fenêtre et feraient défiler la page ENTIÈRE
     * pour lire le document.
     */
    .pdfview { height: 72vh; }
    .preview-modal-body .pdfview { height: calc(100vh - 210px); }
    .pdfview-bar { padding: 6px; }
    .pdfview-link { padding: 0 10px; }

    /*
     * Les liens ne sont plus repoussés à droite : sur une largeur de
     * téléphone la barre passe de toute façon sur deux lignes, et l'espace
     * inséré laissait « Télécharger » seul sur la seconde, loin de tout.
     * Groupés, les deux liens s'y retrouvent ensemble.
     */
    .pdfview-btn + .pdfview-link { margin-left: 0; }
}

/* ------------------------------------------------------------- doublons */

.dup-group {
    margin-bottom: 22px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    overflow: hidden;
}

.dup-head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 14px;
    padding: 10px 14px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--rule);
    font-size: 13.5px;
}

.dup-count { font-weight: 600; }

.dup-count-info {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 14px;
    padding: 10px 14px;
    font-size: 13.5px;
    background: var(--surface-2);
    border-bottom: 1px solid var(--rule);
}

.dup-status {
    font-weight: 600;
    color: var(--accent);
}

.dup-actions {
    display: flex;
    gap: 10px;
    justify-content: center;
    padding: 14px;
    background: var(--surface-2);
    border-top: 1px solid var(--rule);
}

.btn-secondary {
    background: var(--surface);
    border-color: var(--rule);
    color: var(--ink);
}
.btn-secondary:hover { background: var(--surface-2); }

/* Le tableau d'un groupe vit déjà dans un cadre : il n'a pas besoin du sien. */
.dup-group .listing { border: 0; }

/* ----------------------------------------------- partage, pages publiques */

/*
 * Pages publiques de partage.
 *
 * Classes distinctes de « share-* », qui habillent la liste des liens côté
 * PROPRIÉTAIRE en cartes bordées. Réutiliser les mêmes noms aurait donné à
 * chaque fichier l'allure d'une fiche de partage.
 *
 * Aucune de ces règles ne suppose de JavaScript : le layout public n'en
 * charge aucun, et c'est autant de surface en moins sur la seule partie
 * exposée (§37.24).
 */

.pub-shell {
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 100%;
}

/*
 * Marque, au-dessus de la carte.
 *
 * Discrète, et volontairement pas un lien : la partie publique n'offre aucune
 * navigation vers l'application. Elle sert à ancrer une page qui, sans elle,
 * n'indique pas d'où vient le document.
 */
.pub-brand {
    display: flex;
    align-items: center;
    gap: 7px;
    margin: 0 0 14px;
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: var(--ink-3);
}
.pub-brand svg { width: 16px; height: 16px; }

.pub-hero { text-align: center; margin-bottom: 24px; }
.pub-hero .share-name { margin-bottom: 6px; }

/*
 * Pastille de type.
 *
 * Ce n'est PAS un aperçu du document : la partie publique ne rend jamais de
 * contenu tiers (§37.29). L'icône est déduite de l'extension et ne demande
 * aucune lecture du fichier — donc aucun accès disque sur une page publique.
 */
.pub-badge {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 3px;
    width: 74px;
    height: 74px;
    margin-bottom: 16px;
    border-radius: 16px;
    background: var(--accent-soft);
    color: var(--accent-ink);
}
.pub-badge svg { width: 26px; height: 26px; }

.pub-badge-ext {
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: 0.08em;
    /* Une extension longue ne doit ni élargir la pastille ni déborder de son
       arrondi : « jpeg » et « xlsx » sont des cas courants. */
    max-width: 60px;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Lien protégé : cadenas neutre. La couleur d'accent est réservée à ce qui
   est effectivement accessible. */
.pub-badge-lock { background: var(--surface-2); color: var(--ink-2); }

/* Version réduite, alignée au titre dans l'en-tête d'un dossier. */
.pub-badge-sm {
    flex: none;
    width: 44px;
    height: 44px;
    margin-bottom: 0;
    border-radius: 12px;
}
.pub-badge-sm svg { width: 20px; height: 20px; }

.pub-facts { margin: 0; font-size: 13.5px; color: var(--ink-2); }
.pub-dot { color: var(--ink-3); margin: 0 5px; }

/*
 * En-tête d'un dossier partagé : pastille, titre, puis l'archive en action
 * SECONDAIRE. En bloc pleine largeur sous la liste, elle pesait visuellement
 * plus lourd que le contenu qu'elle empaquette.
 */
.pub-head {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    margin-bottom: 20px;
}
.pub-head-text { flex: 1; min-width: 0; }
.pub-head .share-name { margin-bottom: 4px; }
/*
 * Les boutons publics portent une icône, ce qui impose deux choses : un axe
 * pour aligner le tracé sur le texte, et des dimensions explicites — un SVG
 * sans width ni height vaut 300x150px par défaut, ce qui n'est pas un détail
 * cosmétique mais une mise en page détruite.
 */
.pub-zip {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    flex: none;
    white-space: nowrap;
    text-decoration: none;
}
.pub-zip svg,
.btn-block-link svg { flex: none; width: 16px; height: 16px; }

.pub-crumbs {
    font-size: 13.5px;
    color: var(--ink-3);
    margin-bottom: 10px;
    overflow-wrap: anywhere;
}
.pub-crumbs a { color: var(--ink-2); }

.pub-list {
    list-style: none;
    margin: 18px 0;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    overflow: hidden;
}

.pub-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 10px 14px;
    border-bottom: 1px solid var(--rule);
}
.pub-row:last-child { border-bottom: 0; }
.pub-row:hover { background: var(--surface-2); }

.pub-row-name {
    display: flex;
    align-items: center;
    gap: 9px;
    min-width: 0;
    color: var(--ink);
    text-decoration: none;
    overflow-wrap: anywhere;
}
.pub-row-name:hover { text-decoration: underline; }

/* L'icône ne porte pas de classe : le composant « x-icon » ne relaie pas les
   attributs, et le sélecteur descendant évite d'avoir à le modifier. */
.pub-row-name svg {
    flex: none;
    width: 15px;
    height: 15px;
    color: var(--ink-3);
}

.pub-row-meta {
    color: var(--ink-3);
    font-size: 13px;
    white-space: nowrap;
}

.pub-empty {
    margin: 18px 0;
    padding: 22px 14px;
    border: 1px dashed var(--rule);
    border-radius: var(--radius);
    text-align: center;
    font-size: 14px;
    color: var(--ink-3);
}

/* Pied de carte : rassemble ce qui flottait sous le bouton. */
.pub-foot {
    margin: 22px 0 0;
    padding-top: 14px;
    border-top: 1px solid var(--rule);
    font-size: 12.5px;
    color: var(--ink-3);
    text-align: center;
}

@media (max-width: 520px) {
    /* L'archive passe sous le titre plutôt que de le comprimer. */
    .pub-head { flex-wrap: wrap; }
    .pub-zip { width: 100%; justify-content: center; }
}

/* ------------------------------------------------- versions d'un document */

/*
 * Historique, dans la colonne de gauche sous l'apercu.
 *
 * Une liste, et non un tableau : quatre informations courtes par ligne, dont
 * aucune n'a besoin d'etre alignee sur celle du dessus. Un tableau aurait de
 * surcroit ramene le conteneur a defilement dont §43.13 rappelle qu'il rogne
 * AUSSI la verticale, donc les infobulles.
 */
.versions-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 12px;
}
.versions-head .detail-subtitle { margin: 0; }
.versions-weight { margin: 0; font-size: 13px; white-space: nowrap; }

.versions { list-style: none; margin: 0; padding: 0; }

.version-row {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 9px 10px;
    border-radius: var(--radius);
    border-bottom: 1px solid var(--rule);
}
.version-row:last-of-type { border-bottom: none; }

/* La courante est teintee, non encadree : un cadre de plus dans une carte qui
   en est deja un ferait un troisieme niveau de boite pour rien. */
.version-row.is-current { background: var(--accent-soft); }

.version-n {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    font-weight: 600;
    flex: none;
    min-width: 28px;
}

/* min-width: 0 — sans lui, un commentaire long pousserait la ligne et la carte
   au lieu de se replier (meme cause que §47.9). */
.version-meta { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.version-date { font-size: 14px; }
.version-note {
    font-size: 12.5px;
    color: var(--ink-3);
    font-style: italic;
    overflow-wrap: anywhere;
}

.version-tag {
    font-size: 11px;
    color: var(--accent-ink);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-left: 6px;
}

.version-size { font-size: 12.5px; color: var(--ink-3); flex: none; }

.version-tools { display: flex; align-items: center; gap: 2px; flex: none; }

.versions-policy { margin-top: 12px; color: var(--ink-3); }

/*
 * Sous 620 px, la ligne passe en deux rangees : le nom et la date d'abord, les
 * commandes en dessous. Meme principe que les listes passees en cartes (§45.1),
 * sans avoir a toucher au balisage.
 */
@media (max-width: 620px) {
    .version-row { flex-wrap: wrap; }
    .version-meta { flex-basis: 100%; order: 2; }
    .version-size { order: 3; }
    .version-tools { order: 4; margin-left: auto; }
}

.share-list { display: flex; flex-direction: column; gap: 12px; }

.share-row {
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 14px;
    background: var(--surface);
}
.share-row.is-inactive { opacity: 0.65; }

.share-row-head {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    margin-bottom: 10px;
}
.share-row-file { font-weight: 600; overflow-wrap: anywhere; }
.share-row-file a { color: var(--ink); text-decoration: none; }
.share-row-file a:hover { text-decoration: underline; }

.chip {
    font-size: 11.5px;
    padding: 2px 8px;
    border-radius: 999px;
    border: 1px solid;
    white-space: nowrap;
}
.chip-ok { color: var(--ok); border-color: var(--ok); background: var(--ok-soft); }
.chip-off { color: var(--ink-3); border-color: var(--rule-strong); background: var(--surface-2); }
/* Décompte neutre — « 3 v. ». Ni un état, ni une alerte : une information de
   plus sur la ligne, qui ne doit pas rivaliser avec « Partagé ». */
.chip-count { color: var(--ink-3); border-color: var(--rule); background: transparent; }
.chip-info { color: var(--accent-ink); border-color: var(--accent); background: var(--accent-soft); }
.chip-danger { color: var(--danger); border-color: var(--danger); background: var(--danger-soft); }

.share-link { display: flex; gap: 8px; margin-bottom: 10px; }
.share-link input {
    flex: 1;
    min-width: 0;
    padding: 7px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--ink-2);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
}

.share-row-meta {
    margin: 0 0 10px;
    font-size: 13px;
    color: var(--ink-3);
}

.share-row-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.share-row-actions .btn-danger { width: auto; }

/*
 * Lien de la collecte et actions, sur une seule ligne.
 *
 * Le retour à la ligne est INTRINSÈQUE : aucun point de rupture à tenir à
 * jour. Le lien réclame une largeur minimale (« flex-basis »), et dès que la
 * place manque — écran étroit, barre latérale ouverte, fenêtre réduite — les
 * boutons passent d'eux-mêmes en dessous. Une media query aurait figé une
 * largeur d'écran, alors que ce qui compte est la place réellement disponible.
 */
.space-bar {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-bottom: 10px;
}

.space-bar .share-link {
    flex: 1 1 320px;
    min-width: 0;
    margin-bottom: 0;
}

.space-bar .share-none { flex: 1 1 320px; }

/* Les actions ne se compriment pas : un bouton à moitié lisible ne sert à
   rien, mieux vaut qu'il descende. */
.space-bar .share-row-actions { flex: 0 0 auto; }

@media (max-width: 620px) {
    .share-form { flex-direction: column; align-items: stretch; }
    .share-link { flex-direction: column; }
}

/* ------------------------------------------------- page publique de partage */

.share-eyebrow {
    margin: 0 0 6px;
    font-size: 11.5px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.share-name { margin: 0 0 20px; font-size: 19px; overflow-wrap: anywhere; }



/* ------------------------------------------------- boîte de confirmation */

dialog.confirm {
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink);
    width: 420px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 24px 60px -20px rgba(0, 0, 0, 0.5);
}

/* ::backdrop n'hérite pas des variables de :root de façon fiable selon les
   navigateurs : la couleur y est donc littérale. */
dialog.confirm::backdrop {
    background: rgba(8, 16, 15, 0.5);
}

.confirm-inner {
    padding: 22px 24px 18px;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
.confirm-inner h2 {
    margin: 0;
    font-size: 16.5px;
    overflow-wrap: anywhere;
}
.confirm-inner p {
    margin: 0;
    color: var(--ink-2);
    font-size: 14.5px;
    line-height: 1.5;
}

.confirm-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 8px;
}

@media (prefers-reduced-motion: no-preference) {
    dialog.confirm[open] {
        animation: confirm-in 120ms ease-out;
    }
    @keyframes confirm-in {
        from { opacity: 0; transform: translateY(-6px); }
        to { opacity: 1; transform: translateY(0); }
    }
}

/* ------------------------------------------------------------------ connexion */

body.centered {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    padding: 24px;
}

.card {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 6px;
    padding: 32px;
    width: 100%;
    max-width: 380px;
}
.card h1 { margin: 0 0 4px; font-size: 20px; }
.card-sub { margin: 0 0 24px; color: var(--ink-2); font-size: 14px; }

/*
 * Largeur de la carte selon ce qu'elle porte.
 *
 * 380px est la mesure d'un formulaire de connexion. La liste d'un dossier
 * partagé y était comprimée au point que chaque nom se repliait sur deux
 * lignes ; la fiche d'un fichier, elle, y flottait.
 */
.card-public { border-top: 3px solid var(--accent); }
.card-share { max-width: 460px; }
.card-browse { max-width: 680px; }

/*
 * Carte des pages d'erreur : entre le formulaire de connexion et la fiche
 * d'un fichier partagé. Une phrase de deux lignes flotte dans 680 px et se
 * replie en quatre dans 380.
 */
.card-error { max-width: 440px; }

/* ------------------------------------------------------------- pages d'erreur */

/*
 * Un seul bloc pour les deux décors.
 *
 * Dans l'application il occupe la zone de contenu ; sur la partie publique il
 * est posé dans la carte, qui apporte déjà son fond, sa bordure et ses
 * marges. D'où les marges verticales reprises plus bas pour ce seul cas :
 * dupliquer le bloc aurait laissé les deux versions diverger, exactement ce
 * que le partiel commun cherche à empêcher.
 */
.err {
    text-align: center;
    max-width: 44ch;
    margin: 0 auto;
    padding: 64px 8px 72px;
}

/*
 * Pastille de l'icône. Discrète — l'information est dans le titre, pas ici :
 * c'est un repère, pas une illustration.
 */
.err-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 52px;
    height: 52px;
    margin: 0;
    border: 1px solid var(--rule);
    border-radius: 50%;
    background: var(--surface-2);
    color: var(--ink-3);
}

/* Sans dimensions, un SVG vaut 300 × 150 px (§41.5). */
.err-badge svg { width: 22px; height: 22px; }

/*
 * Le code, en gros mais en encre tertiaire : il se voit, il ne domine pas.
 * C'est le titre qui dit ce qui s'est passé — le chiffre ne parle qu'à ceux
 * qui le connaissent déjà.
 */
.err-code {
    margin: 20px 0 2px;
    font-size: 40px;
    font-weight: 700;
    line-height: 1.1;
    letter-spacing: 0.01em;
    color: var(--ink-3);
    font-variant-numeric: tabular-nums;
}

.err h1 {
    margin: 0 0 8px;
    font-size: 20px;
}

.err-text {
    margin: 0;
    color: var(--ink-2);
}

.err-actions { margin: 26px 0 0; }

/*
 * Dans la carte publique, celle-ci porte déjà 32 px de rembourrage : les
 * 64 px du décor applicatif y feraient une carte deux fois trop haute pour
 * trois lignes de texte.
 */
.card .err { padding: 6px 0 0; }

@media (max-width: 620px) {
    .err { padding: 40px 4px 48px; }
    .err-code { font-size: 34px; }
}

.field { margin-bottom: 16px; }
.field label {
    display: block;
    font-size: 13.5px;
    color: var(--ink-2);
    margin-bottom: 5px;
}
.field input {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--ground);
    color: var(--ink);
    font: inherit;
}

/* Case à cocher et son libellé sur une ligne, le libellé cliquable en entier.
   Hors de « .field » : sa règle « input { width: 100% } » l'étirerait. */
.check-field {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: -4px 0 18px;
    font-size: 13.5px;
    color: var(--ink-2);
    cursor: pointer;
}
.check-field input {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--accent);
}


/* --------------------------------------------------- collectes et dépôts */

/*
 * Bloc d'un envoi reçu.
 *
 * Reprend l'allure de .dup-group, mais SANS découpage : les infobulles des
 * icônes sortent vers le haut, et un conteneur qui clippe les coupe net.
 *
 * Le piège vaut d'être connu : « overflow-x: auto » ne borne pas que
 * l'horizontale. La spécification impose au navigateur de calculer l'autre axe
 * en « auto » dès que l'un des deux n'est plus « visible » — un conteneur à
 * défilement horizontal découpe donc aussi ce qui dépasse en hauteur. Ici, le
 * tableau tient dans la largeur (quatre colonnes, un nom qui se replie) : il
 * n'a pas besoin de défiler, donc pas de raison de découper.
 */
.deposit-group {
    margin-bottom: 22px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.deposit-group .listing { border: 0; background: transparent; }

/*
 * Réglages repliés.
 *
 * Un <details> natif : ouverture, fermeture et accessibilité au clavier sont
 * l'affaire du navigateur, et rien n'est à écrire en JavaScript. L'écran garde
 * l'essentiel visible — le nom, le lien, les actions — et range le reste.
 */
.settings {
    margin-top: 16px;
    border-top: 1px solid var(--rule);
    padding-top: 12px;
}

.settings > summary {
    cursor: pointer;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--ink-2);
    list-style: none;
    display: flex;
    align-items: center;
    gap: 7px;
    width: fit-content;
    padding: 3px 6px;
    margin-left: -6px;
    border-radius: var(--radius);
}
.settings > summary::-webkit-details-marker { display: none; }
.settings > summary:hover { background: var(--surface-2); color: var(--ink); }

/* Le chevron dit l'état, et il tourne — pas de seconde icône à tenir à jour. */
.settings > summary::before {
    content: "›";
    display: inline-block;
    font-size: 15px;
    line-height: 1;
    color: var(--ink-3);
    transition: transform 120ms ease-out;
}
.settings[open] > summary::before { transform: rotate(90deg); }

.settings .share-form { margin-top: 12px; margin-bottom: 0; }

/*
 * Vidage définitif de la corbeille.
 *
 * Reprend la mécanique de « .settings » — même <details>, même chevron — mais
 * en rouge, et détaché du tableau. Ce n'est pas un réglage : c'est la seule
 * action de l'application dont on ne revient pas.
 *
 * Le repli fait partie de la protection. Un bouton rouge posé à côté des
 * boutons « Restaurer » serait à une erreur de visée d'une perte définitive.
 */
.danger-zone {
    margin-top: 22px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    padding: 10px 14px;
}

.danger-zone > summary {
    cursor: pointer;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--danger);
    list-style: none;
    display: flex;
    align-items: center;
    gap: 7px;
    width: fit-content;
    padding: 3px 6px;
    margin-left: -6px;
    border-radius: var(--radius);
}
.danger-zone > summary::-webkit-details-marker { display: none; }
.danger-zone > summary:hover { background: var(--danger-soft); }

.danger-zone > summary::before {
    content: "›";
    display: inline-block;
    font-size: 15px;
    line-height: 1;
    transition: transform 120ms ease-out;
}
.danger-zone[open] > summary::before { transform: rotate(90deg); }

/*
 * Ouvert, seul le CADRE prend la couleur de ce qu'il propose.
 *
 * Pas de fond teinté : « .btn-danger » est un bouton au trait sur fond
 * transparent, dont le survol se marque justement par « --danger-soft ». Un
 * encadré de cette teinte aurait rendu ce survol strictement invisible — le
 * seul retour visuel du bouton le plus dangereux de l'application.
 */
.danger-zone[open] { border-color: var(--danger); }
.danger-zone .hint { margin: 10px 0 12px; }
.danger-zone form { margin: 0; }

/*
 * Message du déposant, cité tel quel.
 *
 * Texte écrit par un tiers non authentifié : il est échappé par Blade, et
 * présenté comme une citation pour qu'on ne le confonde jamais avec un
 * libellé de l'application.
 */
.deposit-message {
    margin: 0 14px 12px;
    padding: 10px 12px;
    border-left: 2px solid var(--accent);
    background: var(--ground);
    border-radius: 0 var(--radius) var(--radius) 0;
    font-size: 14px;
    color: var(--ink-2);
    overflow-wrap: anywhere;
}

/* Liste de ce qui va partir, sur la page publique de dépôt. */
.deposit-picks {
    list-style: none;
    margin: 14px 0;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    overflow: hidden;
}

.deposit-pick {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-bottom: 1px solid var(--rule);
    font-size: 14px;
}
.deposit-pick:last-child { border-bottom: 0; }

.deposit-pick-name {
    flex: 1;
    min-width: 0;
    overflow-wrap: anywhere;
}

.deposit-pick-size,
.deposit-pick-state {
    flex: none;
    font-size: 13px;
    color: var(--ink-3);
    white-space: nowrap;
}

.deposit-pick-remove { flex: none; font-size: 13px; padding: 3px 8px; }

/* Un fichier parti ne se retire plus : l'état remplace le bouton. */
.deposit-pick.is-done .deposit-pick-state { color: var(--ok); }
.deposit-pick.is-failed .deposit-pick-state { color: var(--danger); }
.deposit-pick.is-failed { background: var(--danger-soft); }

/* La zone de dépôt s'allume au survol d'un glisser venu du bureau. */
.drop-zone.is-active {
    border-color: var(--accent);
    background: var(--accent-soft);
}

/* Le champ de message, sur la page publique. */
.field textarea {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    resize: vertical;
}

/* ------------------------------------------------------------------ notes */

/*
 * Le tableau de notes.
 *
 * Conçu pour le téléphone d'abord, et sans une seule requête média : la
 * grille compte elle-même ses colonnes selon la place disponible.
 *
 * « minmax(min(100%, 250px), 1fr) » et non « minmax(250px, 1fr) » : sur un
 * écran de 320 px, la seconde forme imposerait une colonne de 250 px plus les
 * marges, et la page déborderait horizontalement. Le « min(100%, …) » laisse
 * la carte se réduire à la largeur réelle. C'est le même piège que celui de
 * « .listing { min-width: 520px } » relevé en §45.6, et il se serait présenté
 * exactement de la même façon.
 *
 * « align-items: start » : sans lui, toutes les cartes d'une rangée prendraient
 * la hauteur de la plus longue. Un tableau de post-it n'est pas un tableau de
 * données — deux lignes de texte n'ont pas à occuper la hauteur d'une page.
 */
.note-board {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 250px), 1fr));
    align-items: start;
    gap: 12px;
}

.note {
    display: flex;
    flex-direction: column;
    gap: 8px;
    /* Indispensable dans une grille : sans lui, un mot long — une URL —
       refuse de descendre sous sa propre largeur et élargit la colonne. */
    min-width: 0;
    padding: 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
}

/* Épinglée : un filet à gauche, pas une couleur imposée. La couleur
   appartient à l'utilisateur ; le repère d'épinglage ne doit pas la lui
   reprendre. */
.note.is-pinned { box-shadow: inset 3px 0 0 var(--accent); }

.note-c-jaune { background: var(--note-jaune); border-color: var(--note-jaune-edge); }
.note-c-bleu { background: var(--note-bleu); border-color: var(--note-bleu-edge); }
.note-c-vert { background: var(--note-vert); border-color: var(--note-vert-edge); }
.note-c-rose { background: var(--note-rose); border-color: var(--note-rose-edge); }
.note-c-orange { background: var(--note-orange); border-color: var(--note-orange-edge); }
.note-c-gris { background: var(--note-gris); border-color: var(--note-gris-edge); }

.note-top { display: flex; align-items: flex-start; gap: 8px; }

.note-title {
    flex: 1;
    min-width: 0;
    margin: 0;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.35;
    /* Un titre collé sans espace — un identifiant, une référence — ne doit
       pas élargir la carte. */
    overflow-wrap: anywhere;
}

/* « margin-left: auto » plutôt que « justify-content: space-between » sur le
   parent : une note peut n'avoir aucun titre, et l'épingle se retrouverait
   alors à gauche. */
.note-pin { margin-left: auto; flex: none; }
.note-pin-btn[aria-pressed="true"] { color: var(--accent-ink); }

/*
 * Le corps de la note.
 *
 * Écrêté à une dizaine de lignes : une carte n'est pas une page, et une note
 * de trois cents mots écraserait toutes ses voisines. Le texte entier reste
 * accessible dans le panneau de modification — il n'est jamais perdu, juste
 * replié.
 */
.note-body {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 10;
    line-clamp: 10;
    overflow: hidden;
    font-size: 14px;
    line-height: 1.5;
    /*
     * LA règle qui tient la mise en page sur téléphone : une URL de cent
     * caractères sans espace est un seul « mot » pour le navigateur. Sans
     * cela, elle pousse la carte, donc la colonne, donc la page — et le
     * défilement horizontal réapparaît là où l'on venait de le supprimer.
     */
    overflow-wrap: anywhere;
}

.note-body a { color: var(--accent-ink); overflow-wrap: anywhere; }

/* Le panneau se colle au bas de la carte, quelle que soit la longueur du
   texte : sur une rangée, toutes les commandes se lisent à la même place. */
.note-menu { margin-top: auto; }

.note-foot {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 30px;
    cursor: pointer;
    font-size: 12px;
    color: var(--ink-2);
    /* Le chevron par défaut de <summary>, retiré dans les deux syntaxes en
       usage. Le repère est le « ⋮ », qui dit ce qui va s'ouvrir. */
    list-style: none;
}
.note-foot::-webkit-details-marker { display: none; }

.note-date { flex: 1; min-width: 0; }

.note-more { display: inline-flex; flex: none; color: var(--ink-3); }
.note-more svg { width: 16px; height: 16px; display: block; }

.note-menu[open] .note-more { color: var(--ink); }

.note-panel {
    display: flex;
    flex-direction: column;
    gap: 12px;
    padding-top: 10px;
    margin-top: 6px;
    border-top: 1px solid var(--rule);
}

.note-panel .flash { margin: 0; }
.note-panel form { margin: 0; }

.note-edit { display: flex; flex-direction: column; gap: 8px; }

/*
 * Les champs sont posés sur le fond de l'application, pas sur celui de la
 * note : sur un pastel, un champ de la même teinte ne se voit plus comme un
 * champ.
 */
.note input[type="text"],
.note textarea,
.note-new input[type="text"],
.note-new textarea {
    width: 100%;
    min-width: 0;
    padding: 7px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: 14px;
}

.note textarea,
.note-new textarea { resize: vertical; min-height: 64px; }

/* ------------------------------------------- notes epinglees, bandeau */

/*
 * L'encart des notes épinglées.
 *
 * Il vit dans « .account », le groupe de droite du bandeau. Rien n'est rendu
 * quand aucune note n'est épinglée : le bandeau retrouve alors exactement la
 * mise en page d'avant, et le risque sur la ligne mobile — déjà tendue par
 * §45.6 — ne se matérialise que si l'on se sert de la fonctionnalité.
 *
 * Le déclencheur est un bouton ICÔNE avec un compteur, jamais un libellé : un
 * cinquième élément textuel ne tiendrait pas dans la largeur d'un téléphone.
 */
.pin-widget { display: inline-flex; }

/*
 * « .icon-btn » impose 30 px de large, ce qui conviendrait à l'icône seule.
 * Le compteur demande de la place : la largeur redevient automatique, et le
 * remplissage horizontal garde une cible confortable.
 */
.pin-btn {
    width: auto;
    min-width: 30px;
    padding: 0 8px;
    gap: 5px;
    color: var(--ink-2);
}
.pin-btn:hover { color: var(--ink); background: var(--surface-2); }

.pin-count { font-size: 12px; font-weight: 600; line-height: 1; }

.pinned-panel[popover] { width: 320px; gap: 10px; }

.pinned-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pinned-item {
    display: block;
    padding: 7px 8px;
    border-radius: var(--radius);
    color: var(--ink);
    text-decoration: none;
}
.pinned-item:hover { background: var(--surface-2); }

.pinned-head { display: flex; align-items: center; gap: 7px; min-width: 0; }

/* La pastille reprend la couleur de la note : c'est ce qui relie l'entrée de
   l'encart à sa carte sur l'écran des notes. Les classes « swatch-* » ne
   posent qu'un fond et un filet, la taille vient d'ici. */
.pin-dot {
    width: 9px;
    height: 9px;
    flex: none;
    border-radius: 50%;
    border: 1px solid var(--rule);
}

/*
 * Le titre tient sur UNE ligne, quoi qu'il arrive : l'encart est un rappel,
 * pas une lecture. « min-width: 0 » est indispensable — sans lui, un élément
 * flexible refuse de descendre sous la largeur de son contenu et déborderait
 * au lieu de s'ellipser.
 */
.pinned-title {
    min-width: 0;
    font-size: 13.5px;
    font-weight: 600;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Deux lignes d'extrait, décalées sous la pastille pour aligner le texte. */
.pinned-excerpt {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
    margin-top: 2px;
    padding-left: 16px;
    font-size: 12.5px;
    line-height: 1.4;
    color: var(--ink-2);
    /* Une URL collée dans une note ne doit pas élargir le panneau. */
    overflow-wrap: anywhere;
}

.pinned-more { margin: 0; font-size: 12.5px; max-width: none; }

.pinned-all {
    font-size: 13px;
    padding-top: 8px;
    border-top: 1px solid var(--rule);
    text-decoration: none;
}
.pinned-all:hover { text-decoration: underline; }

/* ------------------------------------------------------------ pastilles */

/*
 * Une pastille par couleur, jamais un sélecteur libre.
 *
 * Six choix se lisent d'un coup d'œil et se visent au doigt ; un nuancier
 * demanderait une bibliothèque, du JavaScript, et rendrait deux notes
 * « presque de la même couleur » — ce qui ne veut plus rien dire.
 *
 * Le fieldset est remis à zéro : son cadre et sa marge par défaut n'ont pas
 * de sens ici, seule sa légende compte, et elle est en « sr-only ».
 */
.swatches {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
    padding: 0;
    border: 0;
    min-width: 0;
}

.swatch {
    width: 30px;
    height: 30px;
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 50%;
    cursor: pointer;
    display: block;
}

.swatch-jaune { background: var(--note-jaune); border-color: var(--note-jaune-edge); }
.swatch-bleu { background: var(--note-bleu); border-color: var(--note-bleu-edge); }
.swatch-vert { background: var(--note-vert); border-color: var(--note-vert-edge); }
.swatch-rose { background: var(--note-rose); border-color: var(--note-rose-edge); }
.swatch-orange { background: var(--note-orange); border-color: var(--note-orange-edge); }
.swatch-gris { background: var(--note-gris); border-color: var(--note-gris-edge); }

/* Couleur courante, et couleur choisie dans le formulaire de création : le
   même repère pour les deux, parce que c'est la même information. */
.swatch.is-current,
.swatch-pick input:checked + .swatch {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

.swatch-pick { display: inline-flex; position: relative; cursor: pointer; }

/*
 * Le bouton radio est masqué visuellement mais reste dans le flux et reste
 * focusable : « display: none » l'aurait retiré du parcours au clavier, et la
 * couleur ne serait plus choisissable sans souris.
 */
.swatch-pick input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}

.swatch-pick input:focus-visible + .swatch {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
}

/* ------------------------------------------------- portée d'un jeton d'API */

/*
 * Deux cartes à cocher, plutôt qu'une liste déroulante de deux options.
 *
 * C'est ici que se décide ce qu'un script pourra détruire : les deux portées
 * doivent se lire côte à côte, avec ce que chacune autorise. Un « select »
 * cache la moitié de l'information derrière un clic et ne laisse la place
 * qu'à une ligne de libellé.
 *
 * La grille se compte elle-même, sans requête média — même dispositif que la
 * grille des notes (§47.9). « min(100%, 190px) » et non « 190px » seul : sur
 * un écran étroit, une colonne minimale figée déborderait la page au lieu de
 * passer à une colonne.
 *
 * Le fieldset est remis à zéro : son cadre et sa marge par défaut n'ont pas de
 * sens ici.
 */
.scopes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 190px), 1fr));
    gap: 8px;
    margin: 0 0 16px;
    padding: 0;
    border: 0;
    min-width: 0;
}

/* La légende reprend l'allure d'un « .field label », qu'elle remplace. */
.scopes legend {
    padding: 0;
    margin-bottom: 5px;
    font-size: 13.5px;
    color: var(--ink-2);
}

.scope-pick {
    position: relative;
    display: flex;
    cursor: pointer;
    min-width: 0;
}

/*
 * Le bouton radio est masqué VISUELLEMENT mais reste dans le flux et reste
 * focusable : « display: none » l'aurait retiré du parcours au clavier, et la
 * portée ne serait plus choisissable sans souris. Même dispositif que les
 * pastilles de couleur des notes.
 */
.scope-pick input {
    position: absolute;
    opacity: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    cursor: pointer;
}

.scope-card {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
    padding: 11px 13px 11px 34px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    position: relative;
}

/*
 * La pastille est dessinée en CSS, pas importée : c'est un rond et un point,
 * et le composant d'icônes ne rendrait pas l'état coché de lui-même.
 */
.scope-card::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 13px;
    width: 14px;
    height: 14px;
    border: 1px solid var(--ink-3);
    border-radius: 50%;
    background: var(--ground);
}

.scope-card::after {
    content: "";
    position: absolute;
    left: 16px;
    top: 17px;
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: var(--accent);
    /* Présent mais invisible : l'animer ou le créer à la volée ferait
       sautiller la carte au moment du clic. */
    opacity: 0;
}

.scope-card-name {
    font-size: 14px;
    font-weight: 600;
    color: var(--ink);
}

.scope-card-note {
    font-size: 12.5px;
    line-height: 1.45;
    color: var(--ink-3);
    /* Une description un peu longue ne doit pas élargir sa colonne. */
    overflow-wrap: anywhere;
}

.scope-pick:hover .scope-card { border-color: var(--accent); }

.scope-pick input:checked + .scope-card {
    border-color: var(--accent);
    background: var(--accent-soft);
}

.scope-pick input:checked + .scope-card::before { border-color: var(--accent); }
.scope-pick input:checked + .scope-card::after { opacity: 1; }

.scope-pick input:checked + .scope-card .scope-card-name { color: var(--accent-ink); }

/* Halo plutôt qu'un contour : un outline décalé déborderait la carte voisine
   dans une grille à gouttière de 8 px. */
.scope-pick input:focus-visible + .scope-card {
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

/*
 * Le tableau des jetons ne colle pas au bouton « Créer un jeton » : créer et
 * gérer sont deux choses, et l'écart le dit mieux qu'un titre de plus.
 */
.token-list { margin-top: 28px; }

/* Le geste groupé est détaché du tableau : il ne vise aucune ligne. */
.devices-others { margin-top: 16px; }

/* ---------------------------------------------------------- saisie rapide */

/*
 * Toujours ouvert, toujours en tête.
 *
 * Pas de bouton « + Nouvelle note » à cliquer d'abord : ce clic-là est
 * exactement celui qui fait qu'on ne note pas.
 */
.note-new {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 12px;
    margin-bottom: 18px;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.note-new-foot {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}

/* Le bouton part à droite, et passe seul à la ligne quand les pastilles
   occupent toute la largeur — sur un écran étroit, il y gagne en surface. */
.note-new-foot .btn { margin-left: auto; }

/* -------------------------------------------------------------- responsive */

@media (max-width: 860px) {
    .shell { grid-template-columns: minmax(0, 1fr); }

    .sidebar {
        display: none;
        position: static;
        min-height: 0;
        border-right: none;
        border-bottom: 1px solid var(--rule);
    }
    .sidebar.is-open { display: block; }

    .nav-toggle { display: inline-block; }

    .account-name { display: none; }

    /* Le bandeau n'a plus la place : la barre latérale prend le relais. */
    .api-widget,
    .account-widget { display: none; }
    .sidebar-narrow-only { display: block; }

    .content { padding: 16px; }
}

/*
 * Collectes du menu.
 *
 * Même filet de branche que l'arborescence des dossiers : les deux listes
 * sont des enfants d'une entrée de menu, et doivent se décaler de la même
 * façon — sans quoi l'œil lit deux mécaniques différentes là où il n'y en a
 * qu'une.
 */
.tree-flat {
    margin-left: 10px;
    padding-left: 10px;
    border-left: 1px solid var(--rule);
}

/*
 * L'étoile des favoris de la barre latérale.
 *
 * Peinte, à la différence des icônes de dossier : c'est ce qui distingue d'un
 * coup d'œil la liste des favoris de l'arborescence, juste en dessous, dont
 * elle reprend par ailleurs exactement le décalage et le filet.
 */
.sidebar-section[aria-label="Favoris"] .tree-toggle { color: var(--pin); }
.sidebar-section[aria-label="Favoris"] .tree-toggle svg { fill: currentColor; }

/* ======================================================================
   TÉLÉPHONE — les tableaux deviennent des cartes
   ======================================================================

   Le tableau de bureau est bon et reste intact : tout ce qui suit vit sous
   620 px, et rien au-dessus n'est modifié.

   Le problème n'était pas la densité mais la LARGEUR MINIMALE : « .listing »
   imposait 520 px, et « .table-scroll » en tirait un défilement horizontal.
   Un défilement horizontal n'est pas une mise en page, c'est l'aveu qu'il n'y
   en a pas — il cache les actions hors de l'écran, au doigt, sans que rien
   n'indique qu'elles existent.

   Le balisage, lui, ne change pas : chaque <tr> conserve ses attributs
   data-ctx-*, son « draggable » et sa case à cocher. Le glisser-déposer, la
   sélection multiple, le menu contextuel et le rechargement asynchrone de la
   liste continuent donc de fonctionner à l'identique — ils ne connaissent que
   le balisage, jamais la présentation.

   Hiérarchie retenue pour chaque carte :

     1. le nom, seul sur la première ligne, sur toute la largeur ;
     2. les informations secondaires, réunies sur une ligne, séparées par « · » ;
     3. les actions, sur cette même ligne, poussées à droite.
*/

@media (max-width: 620px) {

    /*
     * Le conteneur défilant redevient transparent : plus rien à faire
     * défiler, et le cadre est désormais porté par chaque carte.
     *
     * « overflow-x: visible » remplace « auto », et non l'inverse : il faut
     * que l'absence de débordement soit vraie, pas masquée. Bénéfice annexe —
     * un conteneur en « auto » découpe AUSSI la verticale (§43.13), donc les
     * infobulles.
     */
    .table-scroll {
        overflow-x: visible;
        background: transparent;
        border: 0;
        border-radius: 0;
    }

    .listing {
        min-width: 0;
        display: block;
        font-size: 14px;
    }

    /* Les en-têtes n'ont plus de colonnes à nommer : chaque valeur se lit à
       côté du nom, dans son contexte. La légende reste, pour les lecteurs
       d'écran. */
    .listing thead { display: none; }
    .listing tbody { display: block; }

    .listing tr {
        position: relative;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        gap: 3px 10px;
        padding: 11px 12px;
        margin-bottom: 8px;
        background: var(--surface);
        border: 1px solid var(--rule);
        border-radius: var(--radius);
    }
    .listing tbody tr:last-child { margin-bottom: 0; }
    .listing tbody tr:hover { background: var(--surface); }

    /*
     * « :not([hidden]) » double la règle « !important » posée en tête de
     * feuille : la colonne de sélection reste masquée tant que le script ne
     * l'a pas révélée. Le piège est connu — un display d'auteur l'emporte sur
     * celui du navigateur, et l'élément réapparaît sans la moindre erreur —
     * et il justifie de l'écrire ici plutôt que de compter sur le garde-fou.
     */
    .listing td:not([hidden]) {
        display: block;
        padding: 0;
        border: 0;
    }

    /* Le nom prend la ligne entière : c'est l'information à lire en premier,
       et la plus susceptible d'être longue. */
    .listing td.cell-name {
        flex: 0 0 100%;
        min-width: 0;
        font-size: 14.5px;
    }

    .listing td.cell-meta {
        flex: 0 1 auto;
        min-width: 0;
        font-size: 12.5px;
        color: var(--ink-2);
    }

    /* Séparateur entre deux informations voisines. Posé en CSS et non dans le
       balisage : le nombre de colonnes varie d'un écran à l'autre, et un
       séparateur écrit à la main finirait en tête de ligne le jour où l'on en
       retire une. */
    .listing td.cell-meta + td.cell-meta::before {
        content: "·";
        margin-right: 9px;
        color: var(--ink-3);
    }

    /* Une valeur qui ne se comprend pas seule — un simple nombre — dit ce
       qu'elle compte. Sur le tableau de bureau, l'en-tête s'en chargeait. */
    .listing td.cell-meta[data-label]::after {
        content: " " attr(data-label);
        color: var(--ink-3);
    }

    /*
     * Les actions rejoignent la ligne des informations, poussées à droite.
     *
     * Aucune largeur ne leur est réservée : selon l'écran, une ligne en porte
     * une ou cinq, et une gouttière fixe serait fausse partout sauf à un
     * endroit. Faute de place, elles passent d'elles-mêmes à la ligne
     * suivante — jamais hors de l'écran.
     */
    .listing td.row-actions {
        flex: 0 0 auto;
        margin-left: auto;
        text-align: right;
        white-space: normal;
    }
    .listing td.row-actions form { display: inline-flex; margin: 0; }

    /*
     * La case à cocher sort du flux : c'est le seul élément de largeur fixe
     * et de position constante. Lui réserver une marge évite que le nom ne se
     * décale au moment où la sélection s'active. La gouttière n'est prise que
     * lorsque la colonne est révélée.
     */
    .listing td.col-pick:not([hidden]) {
        position: absolute;
        left: 12px;
        top: 13px;
        width: auto;
        padding: 0;
    }
    .listing tr:has(td.col-pick:not([hidden])) { padding-left: 42px; }

    /*
     * Repli des actions vers le menu.
     *
     * Cinq icônes ne tiennent pas au doigt dans la largeur d'un téléphone. Là
     * où la ligne porte un menu contextuel — Drive et recherche — elles
     * cèdent la place au seul bouton « ⋮ », qui ouvre CE menu : mêmes
     * entrées, mêmes routes, rien de dupliqué.
     *
     * Le repli est conditionné à la classe « js » : sans script le menu est
     * inerte, et ce sont alors les icônes qui restent, quitte à se replier sur
     * deux lignes. Aucune action n'est jamais retirée, seulement regroupée.
     *
     * Le sélecteur porte sur « .row-tools:has(.row-more) » et non sur la
     * page : un écran dont les lignes n'ont pas de menu — corbeille,
     * doublons, documents reçus — garde ses icônes, qui y sont trois au plus.
     */
    .js .listing .row-tools:has(.row-more) > .icon-btn:not(.row-more),
    .js .listing .row-tools:has(.row-more) > form { display: none; }

    .js .listing .row-more { display: inline-flex; }

    /*
     * Troncature par le milieu — voir .fname plus haut.
     *
     * Le nom devient une boîte flexible : la tête absorbe la place restante
     * et s'ellipse, la queue garde la sienne. « min-width: 0 » sur la tête est
     * indispensable : sans lui, un élément flexible refuse de descendre sous
     * la largeur de son contenu, et déborderait au lieu de s'ellipser.
     */
    /*
     * Conditionné à « :has(.fname) », et pour deux raisons.
     *
     * La ligne « lot » de la corbeille — « 12 fichiers — a, b, c » — n'a pas
     * de nom unique et n'est donc pas découpée. En boîte flexible, cette
     * énumération deviendrait un élément insécable qui déborderait ; en bloc,
     * elle se replie normalement.
     *
     * Le second sélecteur n'est pas une redondance : sur la corbeille,
     * « row-name » est porté par la CELLULE et non par un lien à l'intérieur.
     * La règle « .listing td:not([hidden]) » ci-dessus la remettrait alors en
     * « block » — elle est plus spécifique — et la chaîne de boîtes flexibles
     * dont dépend l'ellipse serait rompue sur ce seul écran, sans erreur
     * visible ailleurs.
     */
    .row-name:has(.fname),
    .listing td.row-name:has(.fname) {
        display: flex;
        /*
         * « center » et non « baseline » : un élément flexible en
         * « overflow: hidden » — c'est le cas de .fname-head — n'a pas de
         * ligne de base propre, le navigateur la synthétise depuis son bord
         * inférieur. Aligné sur la puce, le nom serait descendu de la hauteur
         * d'un jambage.
         */
        align-items: center;
        min-width: 0;
    }
    .row-name .row-icon { flex: none; }
    .row-name svg { flex: none; }

    .fname {
        display: flex;
        min-width: 0;
        overflow-wrap: normal;
    }
    .fname-head {
        min-width: 0;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .fname-tail { flex: none; white-space: nowrap; }

    /* Un chemin de recherche n'a plus à être borné à une largeur fixe : il
       dispose de la ligne des informations. */
    .loc { max-width: 100%; }

    /* Doublons et documents reçus : les cartes portent leur propre cadre,
       celui du groupe ferait un double filet autour de chacune. */
    .dup-group .listing tr,
    .deposit-group .listing tr { border-color: var(--rule); }

    /*
     * La barre groupée : un <select> se dimensionne sur son option la plus
     * longue, et la liste des destinations contient des chemins indentés.
     * Sans « min-width: 0 », il élargit la barre, donc la page.
     */
    .batch-bar { gap: 8px; }
    .batch-bar form,
    .batch-bar form select { flex: 1; min-width: 0; }

    .empty { padding: 20px; }

    /*
     * Bandeau : la recherche passe sur sa propre ligne.
     *
     * Marque, compte et bouton « Dossiers » tiennent ensemble ; y ajouter un
     * champ de saisie les écrasait tous les quatre. « order » suffit à la
     * faire descendre, sans rien changer à l'ordre du balisage — donc ni au
     * parcours au clavier, ni à la lecture d'un lecteur d'écran.
     */
    .topbar { flex-wrap: wrap; row-gap: 9px; padding: 10px 12px; }
    .search,
    .topbar-spacer { order: 3; flex: 1 0 100%; max-width: none; }
}

/*
 * Fil d'Ariane — repli plutôt que débordement.
 *
 * Un chemin profond dépassait la largeur du téléphone et poussait la page
 * entière à défiler. Il se replie désormais ligne à ligne : le chemin complet
 * reste lisible, ce qu'une troncature à « … » lui aurait retiré — et c'est
 * justement l'information qui dit où l'on se trouve.
 *
 * Hors requête média : un chemin long déborde aussi une fenêtre de bureau
 * étroite, et le repli reste sans effet visible tant qu'il y a la place.
 */
.breadcrumb {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    overflow-wrap: anywhere;
}
.breadcrumb-sep { flex: none; }

/*
 * Cibles tactiles.
 *
 * La case à cocher dessinée en CSS mesure 17 px : c'est juste sur un écran,
 * intenable au doigt. Elle grandit sans que sa coche ne se décentre, celle-ci
 * étant positionnée en absolu.
 */
@media (pointer: coarse) {
    .col-pick input[type="checkbox"] { width: 22px; height: 22px; }
    .col-pick input[type="checkbox"]:checked::after { left: 6.5px; top: 3px; }

    /* Les entrées du menu contextuel sont le chemin d'action principal sur
       téléphone : elles doivent se viser sans effort. */
    .ctx-item { padding-top: 10px; padding-bottom: 10px; }

    /* Les numéros de page sont rendus collés les uns aux autres : au doigt,
       on en manque un sur deux. */
    .pagination a,
    .pagination span[aria-current] { display: inline-block; padding: 5px 4px; }

    /* Les pastilles de couleur sont le geste le plus fréquent du panneau
       d'une note : 30 px se visent mal, 36 px se visent bien. */
    .swatch { width: 36px; height: 36px; }

    /* Le pied d'une carte est la zone qui ouvre son panneau : elle doit
       s'atteindre sans viser. */
    .note-foot { min-height: 38px; }

    /* Les entrées de l'encart des épinglées sont des liens serrés les uns
       contre les autres : au doigt, on en manque un sur deux. */
    .pinned-item { padding-top: 10px; padding-bottom: 10px; }
}

/* ------------------------------------------------- documentation de l'API */

/*
 * Page AUTONOME : bandeau léger, sommaire à gauche, contenu au centre — la
 * disposition des pages de référence d'API, que tout le monde connaît.
 *
 * Le sommaire est collant et défile de son côté : sur une page de trente
 * endpoints, on doit pouvoir sauter de l'un à l'autre sans remonter en haut.
 * Sous 900 px, il redevient un bloc repliable en tête de page.
 */
.doc-body { --doc-topbar: 52px; }

.doc-topbar {
    position: sticky;
    top: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: 12px;
    height: var(--doc-topbar);
    padding: 0 20px;
    background: var(--surface);
    border-bottom: 1px solid var(--rule);
}

.doc-topbar-title {
    padding-left: 12px;
    border-left: 1px solid var(--rule);
    color: var(--ink-2);
    font-weight: 600;
}

.doc-topbar-links {
    display: flex;
    gap: 18px;
    margin-left: auto;
    font-size: 14px;
}
.doc-topbar-links a { color: var(--ink-2); text-decoration: none; white-space: nowrap; }
.doc-topbar-links a:hover { color: var(--ink); text-decoration: underline; }

.doc-shell {
    display: grid;
    grid-template-columns: 270px minmax(0, 1fr);
    align-items: start;
    max-width: 1320px;
    margin: 0 auto;
}

/* ---------------------------------------------------------------- sommaire */

.doc-nav {
    position: sticky;
    top: var(--doc-topbar);
    border-right: 1px solid var(--rule);
}

/* Sur écran large, le sommaire est toujours ouvert : son titre ne sert à rien. */
.doc-nav > summary { display: none; }

.doc-nav > nav {
    /* Positionnée : c'est dans cette boîte que le script mesure offsetTop. */
    position: relative;
    max-height: calc(100vh - var(--doc-topbar));
    overflow-y: auto;
    padding: 20px 14px 40px 20px;
    font-size: 13.5px;
    /* Une barre native pleine hauteur, gouttière claire comprise, doublait le
       filet de droite et jurait en thème sombre. Fine, sur fond transparent,
       aux couleurs du thème : elle se fait oublier comme celle de la fenêtre. */
    scrollbar-width: thin;
    scrollbar-color: var(--rule) transparent;
}

.doc-nav > nav:hover { scrollbar-color: var(--ink-3) transparent; }

.doc-nav-heading {
    margin: 18px 0 4px;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.doc-nav-heading:first-child { margin-top: 0; }
.doc-nav-heading a { color: inherit; text-decoration: none; }
.doc-nav-heading a:hover { color: var(--ink); }

.doc-nav-list { margin: 0; padding: 0; list-style: none; }

.doc-nav-list a {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 4px 8px;
    border-radius: var(--radius);
    color: var(--ink-2);
    text-decoration: none;
    line-height: 1.35;
}
.doc-nav-list a:hover { background: var(--surface-2); color: var(--ink); }

/* Section en cours, posée par api-doc.js. */
.doc-nav a.is-active {
    background: var(--accent-soft);
    color: var(--accent-ink);
    font-weight: 600;
}
.doc-nav-heading a.is-active { background: none; }

/* Le badge garde sa largeur : les libellés s'alignent en colonne. */
.doc-nav-endpoint .http-method { flex: none; min-width: 50px; font-size: 10px; }

/* ----------------------------------------------------------------- contenu */

.doc-main {
    min-width: 0;
    padding: 28px 40px 80px;
    max-width: 900px;
}

/* L'ancre ne doit pas se cacher sous le bandeau collant. */
.doc-section,
.doc-endpoint { scroll-margin-top: calc(var(--doc-topbar) + 16px); }

.doc-section + .doc-section { margin-top: 44px; }

.doc-title { margin: 0 0 10px; font-size: 26px; }
.doc-lead { margin: 0 0 22px; font-size: 16px; color: var(--ink-2); }

.doc-h2 {
    margin: 0 0 14px;
    padding-bottom: 8px;
    font-size: 20px;
    border-bottom: 1px solid var(--rule);
}
.doc-h3 { margin: 12px 0 6px; font-size: 16px; }
.doc-h4 {
    margin: 20px 0 8px;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--ink-3);
}

.doc-main p { margin: 0 0 10px; line-height: 1.6; }
.doc-hint { font-size: 13.5px; color: var(--ink-2); }

.doc-main code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12.5px;
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--surface-2);
    overflow-wrap: anywhere;
}

.doc-facts {
    display: grid;
    grid-template-columns: 160px minmax(0, 1fr);
    gap: 8px 16px;
    margin: 0;
    font-size: 14px;
}
.doc-facts dt { color: var(--ink-3); }
.doc-facts dd { margin: 0; }

.doc-steps { margin: 0 0 14px; padding-left: 22px; line-height: 1.6; }
.doc-steps li + li { margin-top: 4px; }

/*
 * Blocs de code : le SEUL contenu autorisé à défiler horizontalement. Une
 * ligne de commande ne se replie pas sans devenir fausse, et un JSON replié
 * perd son indentation. Le défilement est porté par le bloc, jamais par la
 * page (§45.6).
 */
.doc-code {
    margin: 0 0 12px;
    padding: 12px 14px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    overflow-x: auto;
}
.doc-code code {
    padding: 0;
    background: none;
    font-size: 12.5px;
    line-height: 1.6;
    white-space: pre;
    overflow-wrap: normal;
}

.doc-table-wrap { margin: 0 0 12px; overflow-x: auto; }

.doc-table {
    width: 100%;
    border-collapse: collapse;
    font-size: 13.5px;
}
.doc-table th,
.doc-table td {
    padding: 7px 10px;
    border-bottom: 1px solid var(--rule);
    text-align: left;
    vertical-align: top;
}
.doc-table th {
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--ink-3);
}
.doc-table td:first-child { white-space: nowrap; }

.doc-required {
    margin-left: 4px;
    font-size: 11px;
    font-weight: 600;
    color: var(--danger);
}

/* ------------------------------------------------------------- endpoints */

.doc-group { margin-top: 56px; }

.doc-endpoint {
    padding: 18px 20px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
}
.doc-endpoint + .doc-endpoint { margin-top: 18px; }

.doc-endpoint-head {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
}
.doc-endpoint-head .chip { margin-left: auto; }

/*
 * Portée exigée. Colorée : c'est l'information qui décide si un jeton de
 * sauvegarde, en lecture seule, pourra appeler l'endpoint. Vert pour la
 * lecture, ambre pour l'écriture — la couleur de la modification dans les
 * badges de méthode. Fond teinté par color-mix(), pour suivre les deux
 * thèmes sans jeu de couleurs propre.
 */
.scope-read,
.scope-write {
    font-weight: 600;
    border-color: currentColor;
}
.scope-read {
    color: var(--ok);
    background: var(--ok-soft);
}
.scope-write {
    color: var(--pin);
    background: color-mix(in srgb, var(--pin) 14%, transparent);
}

/*
 * Bouton « Copier », posé par api-doc.js sur chaque bloc de code. Il vit
 * dans une enveloppe positionnée, et non dans le <pre> : le <pre> défile
 * horizontalement, et le bouton défilerait avec lui.
 */
.doc-code-wrap { position: relative; }
.doc-code-wrap > .doc-code { padding-right: 76px; }

.doc-copy {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 3px 9px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface-2);
    color: var(--ink-2);
    font: inherit;
    font-size: 12px;
    cursor: pointer;
}
.doc-copy:hover { color: var(--ink); border-color: var(--ink-3); }
.doc-copy.is-done { color: var(--ok); border-color: var(--ok); }

/*
 * Bloc d'exemples : une barre (langage, copie) posée par le script au-dessus
 * des variantes, dont une seule est visible. Sans script, pas de barre : le
 * cadre entoure la seule variante HTTP.
 */
.doc-example {
    margin: 0 0 12px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background: var(--surface);
    overflow: hidden;
}
.doc-example > .doc-code {
    margin: 0;
    border: none;
    border-radius: 0;
}

.doc-example-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 8px;
    border-bottom: 1px solid var(--rule);
    background: var(--surface-2);
}
.doc-example-bar .doc-copy { position: static; background: var(--surface); }

/*
 * Sélecteur de langage. Natif — clavier, lecteur d'écran et liste du système
 * sans une ligne de script — mais habillé : « appearance: none » retire le
 * rendu du système, et le chevron est dessiné par deux dégradés plutôt que
 * par une image, que la CSP n'autoriserait qu'en l'élargissant.
 */
.doc-lang {
    appearance: none;
    -webkit-appearance: none;
    padding: 4px 30px 4px 10px;
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    background-color: var(--surface);
    background-image:
        linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
        linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
    background-position: calc(100% - 15px) 55%, calc(100% - 10px) 55%;
    background-size: 5px 5px;
    background-repeat: no-repeat;
    color: var(--ink);
    font: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}
.doc-lang:hover { border-color: var(--ink-3); }
.doc-lang:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

.doc-response .doc-code-wrap { margin: 0 12px 12px; }
.doc-response .doc-code-wrap > .doc-code { margin: 0; }

.doc-path { font-size: 14px !important; background: none !important; padding: 0 !important; color: var(--ink); }

/*
 * Méthodes : une couleur par verbe, au trait sur fond transparent — lisible
 * dans les deux thèmes sans jeu de couleurs propre. Lecture en vert, création
 * dans la couleur de l'application, modification en ambre, suppression en
 * rouge : le danger se voit avant d'avoir lu.
 */
.http-method {
    display: inline-block;
    min-width: 58px;
    padding: 2px 6px;
    border: 1px solid currentColor;
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 11.5px;
    font-weight: 700;
    text-align: center;
}
.m-get { color: var(--ok); }
.m-post { color: var(--accent); }
.m-patch { color: var(--pin); }
.m-delete { color: var(--danger); }

.http-status {
    display: inline-block;
    min-width: 40px;
    padding: 1px 6px;
    border-radius: 4px;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 12px;
    font-weight: 700;
    text-align: center;
}
.s-ok { background: var(--ok-soft); color: var(--ok); }
.s-warn { background: var(--danger-soft); color: var(--danger); }
.s-err { background: var(--danger); color: var(--surface); }

.doc-responses { display: flex; flex-direction: column; gap: 6px; }

.doc-response {
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}
.doc-response > summary {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 8px 12px;
    cursor: pointer;
    font-size: 13.5px;
    list-style: none;
}
.doc-response > summary::-webkit-details-marker { display: none; }
.doc-response > summary::after {
    content: "▸";
    margin-left: auto;
    color: var(--ink-3);
}
.doc-response[open] > summary::after { content: "▾"; }
.doc-response > summary:hover { background: var(--surface-2); }
.doc-response .doc-code { margin: 0 12px 12px; }
.doc-response-binary { margin: 0 12px 10px !important; }

/* ------------------------------------------------------------- téléphone */

@media (max-width: 899px) {
    .doc-shell { grid-template-columns: minmax(0, 1fr); }

    .doc-topbar { padding: 0 14px; }
    .doc-topbar-links { gap: 12px; font-size: 13px; }

    /*
     * Le sommaire devient un bloc repliable en tête de contenu. Le script le
     * referme au chargement ; sans script, il reste ouvert — la page est
     * longue à parcourir, mais rien n'est perdu.
     */
    .doc-nav {
        position: sticky;
        top: var(--doc-topbar);
        z-index: 4;
        border-right: none;
        border-bottom: 1px solid var(--rule);
        background: var(--surface);
    }
    .doc-nav > summary {
        display: block;
        padding: 10px 16px;
        font-weight: 600;
        cursor: pointer;
    }
    .doc-nav > nav {
        max-height: 60vh;
        padding: 4px 16px 16px;
    }

    .doc-main { padding: 20px 16px 60px; }

    .doc-facts { grid-template-columns: minmax(0, 1fr); gap: 2px; }
    .doc-facts dd + dt { margin-top: 10px; }

    .doc-endpoint { padding: 14px; }
    .doc-endpoint-head .chip { margin-left: 0; }
}

/* ------------------------------------------------ double authentification */

/*
 * Le QR code est TOUJOURS noir sur blanc, thème sombre compris : beaucoup
 * d'applications ne lisent pas un code aux couleurs inversées. La marge
 * blanche (la « zone de silence » de la norme) fait partie du code.
 */
.qr-code {
    display: inline-block;
    background: #ffffff;
    padding: 12px;
    border: 1px solid var(--rule);
    border-radius: 6px;
    margin: 4px 0 14px;
    line-height: 0;
}
.qr-code:empty { display: none; }
.qr-code svg { display: block; width: 192px; height: 192px; }

/*
 * Les deux actions de la double authentification sont deux blocs : régénérer
 * est anodin, désactiver ne l'est pas — et vit replié dans .danger-zone.
 */
.two-factor-status { margin: 0 0 18px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.two-factor-block {
    padding: 16px 0 4px;
    border-top: 1px solid var(--rule);
}
.two-factor-block-title { margin: 0 0 6px; font-size: 14.5px; }

/* ------------------------------------------------ sécurité et activité (§70) */

.security-block {
    padding: 16px 0 4px;
    border-top: 1px solid var(--rule);
}
.security-block + .security-block { margin-top: 8px; }
.security-block-title { margin: 0 0 8px; font-size: 14.5px; }

.security-state { margin-top: 8px; }

/* Deux colonnes qui passent à la ligne quand la place manque : la gauche
   porte les tableaux d'accès, d'où sa part plus grande. */
.security-layout {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-items: flex-start;
}
.security-main { flex: 3 1 480px; min-width: 0; }
.security-side { flex: 2 1 320px; min-width: 0; }
.security-layout .detail { max-width: none; }

/* Activité : deux lignes par événement. Un refus se repère par sa pastille. */
.activity-list { list-style: none; margin: 0; padding: 0; }
.activity-item { padding: 10px 0; border-top: 1px solid var(--rule); }
.activity-item:first-child { border-top: 0; padding-top: 0; }
.activity-what { margin: 0; overflow-wrap: anywhere; }
.activity-label { font-weight: 600; }
.activity-when { margin: 2px 0 0; font-size: 13px; color: var(--ink-2); }
.activity-when time { white-space: nowrap; }

/* Hors écran, et non masqué : la sélection de repli du bouton « Copier »
   ne fonctionne pas sur un élément invisible. */
.offscreen { position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }

/* ------------------------------------------------ modale des codes de secours */

dialog.recovery-dialog {
    padding: 0;
    border: 1px solid var(--rule);
    border-radius: 12px;
    background: var(--surface);
    color: var(--ink);
    width: 460px;
    max-width: calc(100vw - 24px);
    max-height: calc(100vh - 24px);
    overflow: auto;
    box-shadow: 0 30px 80px -24px rgba(0, 0, 0, 0.55);
}
/* Sans script, rendue « open » dans le flux : centrée, pas collée au bord. */
dialog.recovery-dialog:not(:modal) { position: relative; margin: 24px auto; }
dialog.recovery-dialog::backdrop {
    background: rgba(8, 16, 15, 0.55);
    backdrop-filter: blur(3px);
}

.recovery-hero {
    text-align: center;
    padding: 30px 28px 20px;
    background:
        radial-gradient(120% 90% at 50% 0%, var(--accent-soft) 0%, transparent 70%);
    border-bottom: 1px solid var(--rule);
}
.recovery-badge {
    display: inline-grid;
    place-items: center;
    width: 64px;
    height: 64px;
    margin-bottom: 14px;
    border-radius: 50%;
    color: var(--ok);
    background: var(--ok-soft);
    box-shadow: 0 0 0 6px color-mix(in srgb, var(--ok-soft) 55%, transparent);
}
.recovery-badge svg { width: 32px; height: 32px; stroke-width: 1.4; }
.recovery-hero h2 { margin: 0 0 6px; font-size: 19px; }
.recovery-hero p { margin: 0 auto; max-width: 46ch; color: var(--ink-2); font-size: 14px; line-height: 1.5; }

.recovery-body { padding: 18px 24px 4px; }

.recovery-warn {
    display: flex;
    gap: 10px;
    align-items: flex-start;
    margin: 0 0 16px;
    padding: 10px 12px;
    border-left: 3px solid var(--pin);
    border-radius: 4px;
    background: var(--surface-2);
    font-size: 13.5px;
    line-height: 1.45;
}
.recovery-warn svg { flex: none; width: 17px; height: 17px; margin-top: 1px; color: var(--pin); }

/* Numérotés : on coche plus facilement « le 3 est utilisé » que « celui
   qui commence par K ». */
.recovery-grid {
    list-style: none;
    counter-reset: code;
    margin: 0 0 14px;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 190px), 1fr));
    gap: 8px;
}
.recovery-grid li {
    counter-increment: code;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 10px;
    border: 1px solid var(--rule);
    border-radius: 6px;
    background: var(--surface-2);
}
.recovery-grid li::before {
    content: counter(code);
    min-width: 1.2em;
    font-size: 11.5px;
    font-weight: 600;
    color: var(--ink-3);
    text-align: right;
}
.recovery-grid code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 13.5px;
    letter-spacing: 0.04em;
    user-select: all;
}

.recovery-tools { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.recovery-tools .btn { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; }
.recovery-tools svg { width: 15px; height: 15px; }

.recovery-ack { margin: 0; }

.recovery-actions {
    display: flex;
    justify-content: flex-end;
    padding: 14px 24px 20px;
}
.recovery-actions .btn:disabled { opacity: 0.45; cursor: not-allowed; }

@media (prefers-reduced-motion: no-preference) {
    dialog.recovery-dialog:modal { animation: recovery-in 260ms cubic-bezier(0.2, 0.9, 0.3, 1.2); }
    dialog.recovery-dialog:modal .recovery-badge { animation: recovery-badge 520ms 120ms cubic-bezier(0.2, 0.9, 0.3, 1.4) backwards; }
}
@keyframes recovery-in {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
}
@keyframes recovery-badge {
    from { opacity: 0; transform: scale(0.4) rotate(-12deg); }
}

.card-foot { margin: 20px 0 0; font-size: 13px; color: var(--ink-2); }
.card-foot a { display: block; margin-top: 6px; }

/* ------------------------------------------------------- tableau de bord */

/*
 * §65. Des cartes sur deux colonnes, qui repassent à une seule quand la place
 * manque — sans requête média, comme la grille des notes (§47.9).
 *
 * DEUX au plus, et c'était ce qui manquait (§71) : avec un simple
 * minmax(340px, 1fr), un écran large en plaçait trois, et la quatrième carte
 * restait seule sur sa ligne. Le minimum d'une colonne vaut désormais au moins
 * la MOITIÉ de la largeur (moins l'espacement) : une troisième ne tient
 * jamais. Sous 2 × 340 px, le minimum de 340 px l'emporte et la grille passe
 * à une colonne.
 *
 * « stretch » : les deux cartes d'une ligne ont la même hauteur, sans quoi le
 * 2 × 2 paraîtrait décalé.
 */
.dash-title { margin: 4px 0 18px; font-size: 20px; }

.dash-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, max(340px, calc((100% - 16px) / 2))), 1fr));
    gap: 16px;
    align-items: stretch;
}
.dash-wide { grid-column: 1 / -1; }

.dash-card {
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 6px;
    padding: 18px 20px;
    min-width: 0;
}
.dash-card-title { margin: 0 0 12px; font-size: 14.5px; }

.dash-figure { margin: 0 0 12px; font-size: 14px; }
.dash-figure strong { font-size: 24px; font-weight: 600; margin-right: 6px; }

/* Barres en SVG : la largeur est un attribut, que la CSP autorise. */
.dash-stack { display: block; width: 100%; height: 14px; border-radius: 7px; overflow: hidden; }
.dash-track { fill: var(--surface-2); }
.dash-fill { fill: var(--accent); }

.dash-part-current { fill: var(--accent); background: var(--accent); }
.dash-part-versions { fill: #6f9bd1; background: #6f9bd1; }
.dash-part-trash { fill: var(--pin); background: var(--pin); }
.dash-part-deposits { fill: #9a7ac7; background: #9a7ac7; }

.dash-legend {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 8px 22px;
    font-size: 13.5px;
}
.dash-legend li { display: flex; align-items: center; gap: 7px; }
.dash-legend strong { font-weight: 600; }
.dash-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.dash-list { list-style: none; margin: 0; padding: 0; font-size: 13.5px; }
.dash-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 4px 10px;
    padding: 9px 0;
    border-top: 1px solid var(--rule);
}
.dash-list li:first-child { border-top: 0; padding-top: 0; }
.dash-list li > span:first-child { flex: 1; min-width: 0; }
.dash-list a { font-size: 13px; }
.dash-note { flex-basis: 100%; font-size: 12.5px; }

.dash-status { display: flex; gap: 10px; align-items: flex-start; margin: 0 0 10px; font-size: 14px; line-height: 1.45; }
.dash-status svg { flex: none; width: 20px; height: 20px; margin-top: 1px; }
.dash-status-ok svg { color: var(--ok); }
.dash-status-warn svg { color: var(--pin); }
.dash-status-bad svg { color: var(--danger); }
.dash-status-bad { color: var(--danger); }

.dash-bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; font-size: 13.5px; }
.dash-bars li {
    display: grid;
    grid-template-columns: minmax(0, 9em) minmax(40px, 1fr) auto;
    align-items: center;
    gap: 10px;
}
.dash-bar-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dash-bar { display: block; width: 100%; height: 8px; border-radius: 4px; overflow: hidden; }
.dash-bar-value { white-space: nowrap; text-align: right; font-variant-numeric: tabular-nums; }

.dash-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.dash-table th {
    text-align: left;
    font-size: 12px;
    font-weight: 500;
    color: var(--ink-3);
    padding: 0 10px 6px 0;
    border-bottom: 1px solid var(--rule);
}
.dash-table td { padding: 7px 10px 7px 0; border-bottom: 1px solid var(--rule); vertical-align: baseline; }
.dash-table tr:last-child td { border-bottom: 0; }
.dash-table td:nth-child(2) { overflow-wrap: anywhere; }
.dash-table .num { text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; padding-right: 0; }
.dash-table td:first-child { width: 2em; padding-right: 12px; }

@media (max-width: 620px) {
    .dash-where { display: none; }
}
