/* Page « Recettes » — cartes de données, aux couleurs du site.
   app.css donne aux sections une typographie d'article (paragraphes à 22px) :
   une liste de 800 recettes demande l'inverse, d'où les remises à l'échelle. */

.donnees-intro {
    h1, h2 {
        font-size: clamp(28px, 6vw, 42px);
    }

    .chiffres {
        list-style: none;
        display: flex;
        flex-wrap: wrap;
        gap: 12px;
        margin-bottom: 25px;

        /* Les tuiles sont des <li> : c'est eux, pas le lien, que la grille place. */
        li {
            display: flex;
        }

        a {
            display: flex;
            flex-direction: column;
            align-items: center;
            min-width: 120px;
            padding: 12px 20px;
            border: 2px solid var(--sombre);
            border-radius: 8px;
            text-decoration: none;
            color: var(--sombre);
            transition: background-color .2s, color .2s;

            b {
                font-size: 28px;
                line-height: 1.1;
            }

            span {
                font-size: 14px;
                text-transform: uppercase;
                letter-spacing: .08em;
            }

            &:hover {
                background-color: var(--sombre);
                color: var(--clair);
            }
        }
    }

}

.donnees {
    .barre {
        position: sticky;
        top: 0;
        z-index: 5;
        display: flex;
        flex-wrap: wrap;
        gap: 16px;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 24px;
        padding: 12px 0;
        background-color: var(--sombre);
    }

    .onglets {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;

        button {
            font: inherit;
            font-size: 16px;
            font-weight: bold;
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 8px 16px;
            border: 2px solid var(--clair);
            border-radius: 8px;
            background: none;
            color: var(--clair);
            cursor: pointer;

            span {
                font-weight: normal;
                font-size: 13px;
                opacity: .7;
            }

            &:hover {
                background-color: rgba(233, 219, 203, .15);
            }

            &[aria-selected="true"] {
                background-color: var(--clair);
                color: var(--sombre);
            }
        }
    }

    .recherche {
        display: flex;
        align-items: center;
        gap: 10px;

        input {
            font: inherit;
            font-size: 16px;
            padding: 9px 14px;
            min-width: min(320px, 60vw);
            border: 2px solid var(--clair);
            border-radius: 8px;
            background-color: var(--clair);
            color: var(--sombre);
        }

        .compteur {
            font-size: 14px;
            opacity: .75;
            white-space: nowrap;
        }
    }


    .rien {
        font-size: 18px;
        margin: 30px 0 0;
        text-align: center;
        opacity: .7;
    }

    /* Un onglet est découpé en groupes — l'atelier où l'on fabrique, la famille
       à laquelle un bâtiment appartient. Le titre ne colle pas en haut : la
       barre de recherche y est déjà, et sa hauteur change avec la largeur de
       l'écran — un titre collant finirait par passer dessous. */
    .groupe {
        margin-top: 40px;

        &:first-of-type {
            margin-top: 0;
        }

        > h2 {
            display: flex;
            gap: 10px;
            align-items: baseline;
            margin: 0 0 14px;
            padding-bottom: 6px;
            border-bottom: 1px solid rgba(233, 219, 203, .25);
            font-size: 22px;

            span {
                font-size: 14px;
                font-weight: normal;
                opacity: .6;
            }

            /* L'enseigne du groupe : l'icône de l'atelier où l'on fabrique.
               Elle se centre sur la ligne plutôt que de s'asseoir sur la
               ligne de base, où elle pendrait sous le titre. Une recette
               peut valoir dans deux ateliers : les deux icônes se serrent
               pour rester un seul bloc devant le titre. */
            > .ico {
                flex: none;
                align-self: center;
                object-fit: contain;
            }

            > .ico + .ico {
                margin-left: -4px;
            }
        }

        .apropos {
            margin: -8px 0 14px;
            font-size: 14px;
            opacity: .7;
        }
    }

    .cartes {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
        gap: 16px;
    }
}

.carte {
    display: flex;
    flex-direction: column;
    gap: 10px;
    padding: 14px 16px 12px;
    border-radius: 8px;
    background-color: var(--clair);
    color: var(--sombre);

    .tete {
        display: flex;
        align-items: center;
        gap: 10px;
    }

    .titre {
        flex: 1;
        min-width: 0;
    }

    h3 {
        font-size: 18px;
        line-height: 1.25;
        overflow-wrap: anywhere;

        .palier {
            font-size: 12px;
            font-weight: bold;
            vertical-align: middle;
            margin-left: 6px;
            padding: 1px 6px;
            border-radius: 4px;
            background-color: var(--sombre);
            color: var(--clair);
        }
    }

    .ident {
        font-family: ui-monospace, Consolas, monospace;
        font-size: 11px;
        line-height: 1.3;
        margin: 2px 0 0;
        opacity: .55;
        overflow-wrap: anywhere;
    }

    .lot {
        flex: none;
        font-weight: bold;
        font-size: 15px;
        padding: 3px 8px;
        border-radius: 6px;
        background-color: var(--sombre);
        color: var(--clair);
    }

    .note {
        font-size: 13px;
        line-height: 1.45;
        margin: 0;
        opacity: .7;
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }

    .couts {
        list-style: none;
        display: flex;
        flex-direction: column;
        gap: 5px;

        li {
            display: flex;
            align-items: center;
            gap: 8px;
            font-size: 14px;
        }

        .quoi {
            flex: 1;
            overflow-wrap: anywhere;
        }

        .combien {
            font-weight: bold;
            white-space: nowrap;
        }
    }

    .pied {
        display: flex;
        flex-wrap: wrap;
        gap: 4px 12px;
        align-items: baseline;
        margin-top: auto;
        padding-top: 9px;
        border-top: 1px solid rgba(27, 21, 22, .2);
        font-size: 13px;

        .lieu,
        .outil {
            margin-left: auto;
            text-align: right;
            opacity: .75;
        }
    }

    .ico {
        flex: none;
        object-fit: contain;
        image-rendering: auto;
    }

    .ico.creuse {
        display: inline-block;
        width: var(--d);
        height: var(--d);
        border: 1px dashed rgba(27, 21, 22, .4);
        border-radius: 4px;
    }
}

/* Tableaux de la page « Équipements ».

   Ces chiffres-là ne se lisent pas un par un, ils se comparent : d'où des
   colonnes plutôt que des cartes, des nombres alignés à droite en chasse fixe,
   et un en-tête qui trie d'un clic. Le tableau défile horizontalement dans son
   cadre plutôt que d'imposer une barre de défilement à toute la page. */
.donnees .defile {
    overflow-x: auto;
    border-radius: 8px;
    background-color: var(--clair);
    color: var(--sombre);
}

.donnees table.grille {
    border-collapse: collapse;
    width: 100%;
    font-size: 14px;

    th,
    td {
        padding: 8px 12px;
        text-align: left;
        border-bottom: 1px solid rgba(27, 21, 22, .14);
    }

    .num {
        text-align: right;
        white-space: nowrap;
        font-variant-numeric: tabular-nums;
    }

    thead th {
        padding: 0;
        vertical-align: bottom;
        border-bottom: 2px solid var(--sombre);

        button {
            font: inherit;
            font-size: 12px;
            font-weight: bold;
            text-transform: uppercase;
            letter-spacing: .04em;
            width: 100%;
            padding: 10px 12px;
            border: 0;
            background: none;
            color: inherit;
            text-align: inherit;
            cursor: pointer;

            &:hover {
                text-decoration: underline;
            }
        }

        &.num button {
            text-align: right;
        }

        /* La flèche dit dans quel sens on a trié — et tient sa place même quand
           la colonne ne l'est pas, pour que rien ne saute au clic. */
        button::after {
            content: ' ↕';
            opacity: .25;
        }

        &[aria-sort="ascending"] button::after {
            content: ' ↑';
            opacity: 1;
        }

        &[aria-sort="descending"] button::after {
            content: ' ↓';
            opacity: 1;
        }
    }

    tbody th {
        font-weight: normal;
        /* La colonne des noms porte deux lignes — le nom et l'identifiant — et
           n'a pas à se replier tant que le tableau peut défiler. */
        min-width: 190px;
    }

    tbody tr:hover {
        background-color: rgba(27, 21, 22, .05);
    }

    tbody tr:last-child th,
    tbody tr:last-child td {
        border-bottom: 0;
    }

    .nul {
        opacity: .28;
    }
}

.sujet {
    display: flex;
    align-items: center;
    gap: 9px;

    .titre {
        display: flex;
        flex-direction: column;
        line-height: 1.2;
    }

    .nom {
        font-weight: bold;
        white-space: nowrap;
    }

    .ident {
        font-family: ui-monospace, Consolas, monospace;
        font-size: 11px;
        opacity: .5;
    }

    .palier {
        font-size: 11px;
        font-weight: bold;
        margin-left: 6px;
        padding: 1px 5px;
        border-radius: 4px;
        background-color: var(--sombre);
        color: var(--clair);
    }
}

/* Visionneuse de la page « Carte ».

   Les fonds sont tous dans le document et se remplacent l'un l'autre ; c'est le
   fond visible qui règle la taille de la pile, et le calque s'y ajuste en
   absolu — seul moyen de les garder alignés sans figer de dimensions dans le
   script. Tout est porté sous .donnees : app.css donne aux <p> de section une
   taille d'article qui écraserait le cartouche. */
.donnees {
    .visionneuse {
        display: flex;
        flex-direction: column;
        gap: 14px;
    }

    /* Le cadre est une fenêtre : la pile s'y déplace et s'y agrandit, rien n'en
       sort. Son rapport est celui de la carte, pour qu'à l'échelle 1 elle le
       remplisse exactement — c'est ce qui rend le bornage trivial. */
    .cadre {
        position: relative;
        overflow: hidden;
        /* Le cadre garde le rapport de la carte quoi qu'il arrive : c'est la
           largeur qu'on plafonne, pas la hauteur. Plafonner la hauteur
           l'écraserait, et la pile étant tendue sur ses bords, la carte
           sortirait déformée. */
        width: 100%;
        max-width: calc(82vh * var(--ratio, 1));
        aspect-ratio: var(--ratio, 1);
        margin-inline: auto;
        border-radius: 8px;
        background-color: rgba(233, 219, 203, .08);
        touch-action: none;
        cursor: grab;
        /* Sans ça, tirer la carte sélectionne le texte autour au lieu de la
           déplacer, et le curseur reste en « je sélectionne ». */
        user-select: none;
        -webkit-user-select: none;

        &.tire {
            cursor: grabbing;

            /* Pendant le geste seulement : le navigateur garde la pile en
               texture et la fait glisser sans repeindre. En dehors, on relache.
               Laisser ce will-change en permanence coutait cher : la texture
               est rasterisee a une echelle donnee puis simplement etiree par le
               zoom, si bien que la carte restait floue quel que soit le niveau
               de carreaux charge — la pyramide ne servait plus a rien. */
            .pile {
                will-change: transform;
            }
        }
    }

    .pile {
        position: absolute;
        inset: 0;
        transform-origin: 0 0;

        img {
            position: absolute;
            inset: 0;
            display: block;
            width: 100%;
            height: 100%;
            /* Une image est glissable par défaut : le navigateur emportait le
               carreau au lieu de laisser la carte se déplacer. */
            -webkit-user-drag: none;
        }

        /* Rien de tout ça ne capte le pointeur, et il le faut.

           Le calque des régions est un SVG, il est SOUS les autres — z-index 7
           comme eux, mais en tête du document — et c'est lui qui doit recevoir
           le survol. Or un carreau de calque, posé plus loin dans le document au
           même rang, passe devant et avalait l'événement : aucune zone n'était
           jamais survolée. Les carreaux comme les calques image sont
           décoratifs, et le geste de déplacement est écouté sur #vue, qui les
           englobe tous. */
        .tuiles,
        img.calque {
            pointer-events: none;
        }

        /* Les carreaux sont empilés par z-index — jusqu'à PYRAMIDE.niveaux.length,
           soit 6. Un calque sans z-index vaut « auto » et passerait donc dessous,
           où les carreaux, opaques, le masqueraient entièrement. Il lui faut un
           rang au-dessus de la pyramide ; entre eux, l'ordre du document suffit. */
        .calque {
            z-index: 7;
        }
    }

    /* Le calque des régions, en SVG vectoriel.

       Il a remplacé une pyramide de carreaux de 1,6 Mo par 137 Ko de tracé, et
       ce n'est pas qu'une affaire de poids : un PNG servi d'un bloc est
       redimensionné à 1600 px par le CDN, et c'est pour ça que les autres
       calques sont tuilés. Un tracé vectoriel, lui, est net à tous les zooms.

       Les épaisseurs et les opacités sont celles du raster, au chiffre près :
       remplissage à 64/255, trame à 130/255, liseré crème à 235/255. Ce qui
       change, c'est qu'elles sont ici et non plus dans le script Python. */
    .zones {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;
        /* Même règle que pour les repères : la racine est transparente au
           pointeur, l'aire d'une zone ne l'est pas. Voir `.reperes`. */
        pointer-events: none;

        .zone {
            pointer-events: auto;
        }

        .aire {
            fill-opacity: .25;
            /* Le survol doit réagir tout de suite : plus long, le passage d'une
               zone à l'autre traîne derrière le curseur. */
            transition: fill-opacity .12s ease-out;
        }

        .trame {
            fill-opacity: .51;
            /* La trame est posée par-dessus l'aire : sans ça elle capterait le
               pointeur pour rien. La zone reste survolée par son aire. */
            pointer-events: none;
        }

        /* Le liseré. Il est **écrêté par sa propre région** — c'est l'écrêtage
           qui fait la règle : tracé à 12 m vers le large il ne mord que de 6 m
           vers l'intérieur et rien vers la mer, tracé à 6 m sur une frontière il
           en laisse 3 de chaque côté, donc 6 en tout. Ce sont exactement les
           deux largeurs du raster, obtenues sans les calculer.

           Les jointures sont rondes parce que le raster dilatait par transformée
           de distance : une ligne de niveau est ronde à toute orientation, là où
           un angle vif sortirait pointu sur un virage serré. */
        .bord {
            fill: none;
            stroke: rgb(250, 240, 205);
            stroke-opacity: .92;
            stroke-linejoin: round;
            stroke-linecap: round;
            pointer-events: none;

            &.large {
                stroke-width: 12;
            }

            &.interne {
                stroke-width: 6;
            }
        }

        /* Survol. L'atténuation s'accroche au groupe et non à la racine <svg> :
           la boîte d'un <svg> est survolée d'un bord à l'autre, mer comprise, si
           bien que tout se serait terni dès que le curseur entre dans la carte.
           Un <g> n'est survolé que par la surface peinte de ses enfants. */
        .jeu:hover .aire {
            fill-opacity: .12;
        }

        .zone:hover .aire,
        .zone.verrouillee .aire {
            fill-opacity: .5;
        }

        /* Un verrou est posé : les autres zones s'atténuent comme au survol,
           même si le pointeur est ailleurs — ou s'il n'y a pas de pointeur du
           tout, ce qui est le cas d'un doigt. */
        &:has(.zone.verrouillee) .aire {
            fill-opacity: .12;
        }

        /* Et la zone verrouillée se distingue d'une zone simplement survolée :
           son liseré s'épaissit. Sans ça, on ne sait pas si l'on a touché ou
           seulement effleuré. */
        .zone.verrouillee .bord {
            stroke-width: calc(18px * var(--inv, 1));
        }
    }

    /* Les courbes de niveau, tirées de la grille d'altitudes.

       **Elles ne reçoivent pas le pointeur.** Quatre cent cinquante lignes
       survolables sur toute la carte captureraient le survol des régions et
       gêneraient la mesure, pour dire une altitude que la réglette donne déjà
       sous le curseur. Les courbes montrent la forme, le relevé donne la valeur.

       **L'épaisseur annule le zoom, la géométrie non.** Une courbe est une ligne
       de lecture, pas un objet du terrain : elle doit garder sa finesse d'écran
       d'un bout à l'autre du zoom, comme le marqueur d'un repère. Son tracé, en
       revanche, est au sol et suit la carte.

       Les valeurs sont celles du repli posé en attribut par `bin/carte.js` : à
       l'ajustement, où --inv vaut 1, les deux donnent le même trait. */
    .niveaux {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;
        pointer-events: none;

        .courbe {
            fill: none;
            stroke: rgb(120, 96, 66);
            stroke-opacity: .55;
            stroke-width: calc(6px * var(--inv, 1));
            /* Le tracé vient d'une grille au pas de huit mètres : ses sommets
               font des angles que des jointures rondes rendent à la forme
               qu'avait le terrain. */
            stroke-linejoin: round;
            stroke-linecap: round;
        }

        /* La maîtresse tous les 20 m : quatre courbes fines entre deux fortes,
           de quoi compter le dénivelé sans lire un chiffre. */
        .maitresse {
            stroke-opacity: .8;
            stroke-width: calc(11px * var(--inv, 1));
        }

        /* Sous le niveau de la mer, l'encre change : le fond marin est relevé
           comme le reste, et le dire en brun laisserait croire à de la terre. */
        .sous {
            stroke: rgb(130, 180, 205);
        }
    }

    /* Les aires déclarées par le jeu : 120 m autour d'une ville, 38 autour
       d'une forteresse, 24 autour d'un camp.

       **Deux tracés, parce que les deux aires ne veulent pas dire la même
       chose.** Le disque d'une ville est vide — rien n'est bâti à moins de
       116 m de son centre, et l'anneau bâti démarre à 120 : c'est une limite,
       dont le bord porte l'enceinte. Le disque d'un camp est plein — 91 % de ses
       structures tiennent dans ses 24 m : c'est une emprise. Une limite se trace
       en anneau, une emprise en aplat ; les confondre désinformerait.

       **Le rayon est en unités de carte, l'épaisseur non.** Le rayon est une
       distance au sol : il doit grossir avec le terrain. Un liseré en unités de
       carte, lui, serait un cheveu à l'échelle 1 et un boudin à 1600 % — il
       annule donc le facteur de zoom par --inv, comme les repères. */
    .aires {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;

        /* Même règle que les repères : la racine d'un <svg> est survolée d'un
           bord à l'autre, peinte ou non. Transparente, elle laisse les régions
           dessous et les marqueurs dessus recevoir le pointeur ; seuls les
           cercles le prennent, là où ils ont peint. */
        pointer-events: none;

        circle {
            pointer-events: auto;
            transition: fill-opacity .12s ease-out;
        }

        /* La limite d'une ville : un anneau, et un intérieur qui ne reçoit pas
           le pointeur — c'est le bord qui porte le sens, et un disque de 120 m
           masquerait la région dessous sur quatre hectares. */
        .limite {
            fill: none;
            stroke: rgb(250, 214, 92);
            stroke-opacity: .85;
            stroke-width: calc(10px * var(--inv, 1));
        }

        .limite:hover {
            stroke-opacity: 1;
            stroke-width: calc(20px * var(--inv, 1));
        }

        /* L'emprise d'un camp ou d'une forteresse : un aplat, plus son bord,
           qui reste lisible quand l'aplat se perd sur un terrain sombre. */
        .emprise {
            fill: rgb(232, 88, 64);
            fill-opacity: .22;
            stroke: rgb(232, 88, 64);
            stroke-opacity: .8;
            stroke-width: calc(6px * var(--inv, 1));
        }

        .emprise:hover {
            fill-opacity: .42;
        }
    }

    /* Les repères d'entités — villes, repaires de bandits, chantiers navals —
       en vectoriel eux aussi.

       **La taille du marqueur ne dépend pas du zoom.** C'est le vrai gain sur le
       raster : un disque de deux pixels sur une toile de 4314 était invisible à
       l'échelle 1 et large comme une tache à 1600 %. Le marqueur annule donc le
       facteur de zoom, comme les noms de régions, par la variable --inv que le
       script pose sur la pile. Son rayon est en unités de carte, et l'échelle
       inverse le ramène à sa taille d'écran : un `transform` de SVG a son origine
       en (0, 0), et le `<g>` étant déjà translaté à la position du repère, la
       mise à l'échelle se fait bien autour de son centre.

       L'encre vient du calque lui-même, posée en variable sur le <svg> : c'est la
       même que la pastille de son bouton. */
    .reperes {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;

        /* **La racine ne capte pas le pointeur, les formes seules le font.**
           La boîte d'un <svg> posé dans du HTML est survolée d'un bord à l'autre,
           peinte ou non : le dernier calque de repères masquait ainsi les deux
           autres et les régions par-dessus le marché — 0 repère sur 51 trouvé au
           pointeur. En rendant la racine transparente et les formes opaques,
           l'ordre d'empilement n'a plus d'importance : chacun reçoit le pointeur
           là où il a peint, et nulle part ailleurs. */
        pointer-events: none;

        .repere {
            pointer-events: auto;
        }

        /* La contre-échelle passe par le rayon, et le trait par le même
           chemin — une épaisseur en unités de carte grossirait avec elle.

           **Aucune transition ici, et c'est délibéré.** Une transition de 120 ms
           sur `r` paraît anodine et coûte deux choses : le marqueur enfle puis se
           rétracte à chaque cran de zoom, et toute mesure prise juste après lit la
           valeur d'avant — de quoi croire pendant une heure que la compensation ne
           marche pas. Compenser un zoom doit être instantané. Le survol y perd
           son adoucissement : c'est le bon côté du marché. */
        circle {
            r: calc(18px * var(--inv, 1));
            fill: var(--encre, rgb(200, 200, 200));
            stroke: rgba(20, 18, 16, .75);
            stroke-width: calc(6px * var(--inv, 1));
        }

        .forteresse circle {
            r: calc(26px * var(--inv, 1));
        }

        /* L'empreinte des deux seules entités qui en ont une — le chantier naval
           et le marché. Elle est une vraie forme au sol : elle suit la carte au
           zoom, et ne se contre-met donc PAS à l'échelle. */
        .empreinte {
            fill: var(--encre, rgb(200, 200, 200));
            fill-opacity: .55;
            stroke: rgba(20, 18, 16, .5);
            stroke-width: 1;
        }

        .repere:hover circle {
            r: calc(30px * var(--inv, 1));
        }

        .repere.forteresse:hover circle {
            r: calc(40px * var(--inv, 1));
        }
    }

    /* Les noms de régions. Ils vivent dans la pile pour la suivre au
       déplacement sans un seul calcul — mais la pile est mise à l'échelle, et un
       nom qui grossirait avec le terrain deviendrait vite illisible. Chacun
       annule donc le facteur de zoom, que le script pose en --inv. */
    .noms {
        position: absolute;
        inset: 0;
        z-index: 9;   /* au-dessus des carreaux comme des calques */
        pointer-events: none;

        /* Une zone est survolée : son nom reste en pleine valeur, les autres
           s'effacent de moitié. C'est la seule part du survol qui demande du
           script — les étiquettes vivent dans une autre boîte que le SVG, et
           aucun sélecteur ne permet d'aller de l'une à l'autre. */
        &.survole b {
            opacity: .45;
        }

        b.survol {
            opacity: 1;
            font-size: 15px;
        }

        b {
            position: absolute;
            transform: translate(-50%, -50%) scale(var(--inv, 1));
            transition: opacity .12s ease-out, font-size .12s ease-out;
            font-size: 13px;
            font-weight: 600;
            letter-spacing: .02em;
            white-space: nowrap;
            color: rgba(255, 249, 234, .97);
            /* Le fond va du sable clair au vert sombre : une ombre portée reste
               le seul liseré qui tienne sur les deux. */
            text-shadow: 0 0 4px rgba(0, 0, 0, .95), 0 1px 2px rgba(0, 0, 0, .9);
        }
    }

    /* La pyramide : un calque par niveau, empilés par z-index, et dans chacun
       des carreaux placés en pourcentage — ce qui les rend indépendants de la
       taille réelle d'affichage. Le léger débord évite le liseré d'un pixel que
       l'arrondi laisse entre deux carreaux voisins. */
    .tuiles {
        position: absolute;
        inset: 0;

        .niveau {
            position: absolute;
            inset: 0;
        }

        img {
            position: absolute;
            display: block;
            /* Pas de débord ici : les carreaux sont posés sur des pixels entiers
               dans le repère de leur niveau, donc ils se touchent exactement.
               Un `scale` de débord les décalerait au lieu de les raccorder. */
        }
    }

    /* La réglette : l'échelle et les coordonnées, en bas à gauche du cadre.

       Une carte sans échelle ne dit pas ses distances, et celle-ci en avait été
       privée depuis le début. Les coordonnées, elles, sont celles du monde du
       jeu, en ASCII signe moins compris : elles sont faites pour être recopiées
       dans un salon Discord.

       Même langage visuel que la barre de zoom, en miroir : même fond, même
       rayon, même taille de texte. */
    .reglette {
        position: absolute;
        bottom: 10px;
        left: 10px;
        display: flex;
        align-items: center;
        gap: 10px;
        padding: 5px 8px;
        border-radius: 8px;
        background-color: rgba(27, 21, 22, .78);
        cursor: default;
        pointer-events: none;   /* elle n'intercepte ni le glisser ni le survol */

        /* La barre elle-même. Sa LARGEUR est posée par le script, en pixels : il
           choisit une longueur ronde de la suite 1-2-5 qui tienne dans 140 px, si
           bien que la barre mesure toujours entre 55 et 140 px, quel que soit le
           zoom. Les deux pattes verticales disent où elle commence et finit —
           sans elles on ne sait pas si on mesure le trait ou le texte. */
        .echelle {
            position: relative;
            height: 14px;
            border-left: 1px solid var(--clair);
            border-right: 1px solid var(--clair);
            border-bottom: 1px solid var(--clair);
            transition: width .12s ease-out;

            span {
                position: absolute;
                left: 50%;
                bottom: 2px;
                transform: translateX(-50%);
                padding: 0 4px;
                font-size: 11px;
                font-weight: 600;
                white-space: nowrap;
                color: var(--clair);
            }
        }

        output {
            min-width: 92px;
            font-size: 12px;
            font-variant-numeric: tabular-nums;
            color: var(--clair);
            opacity: .85;
        }

        /* La réglette laisse passer le pointeur, mais le bouton doit le
           reprendre — sinon il n'est pas cliquable. Et le geste de déplacement
           écarte `.reglette` comme il écarte déjà `.zoom`, sans quoi armer
           l'outil attraperait la carte. */
        button {
            pointer-events: auto;
            font: inherit;
            font-size: 12px;
            padding: 3px 9px;
            border: 1px solid rgba(233, 219, 203, .4);
            border-radius: 5px;
            background: none;
            color: var(--clair);
            cursor: pointer;

            &:hover {
                background-color: rgba(233, 219, 203, .18);
            }

            /* Armé : le bouton s'allume, pour qu'on sache pourquoi les clics ne
               font plus ce qu'ils faisaient. */
            &[aria-pressed="true"] {
                border-color: rgb(250, 240, 205);
                background-color: rgba(250, 240, 205, .22);
                font-weight: 600;
            }
        }
    }

    /* La mesure de distance.

       Le tracé vit dans un <svg> au même `viewBox` que les autres calques : la
       ligne est donc en unités de carte et suit le déplacement et le zoom sans un
       seul recalcul — c'est la mécanique que les zones ont prouvée.

       Épaisseurs et rayons sont contre-mis à l'échelle par `--inv`, comme les
       marqueurs d'entités, et **sans transition** : une transition sur ce qui
       compense le zoom fait enfler le trait à chaque cran. */
    .mesure {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        display: block;
        z-index: 8;            /* au-dessus des calques, sous les noms */
        pointer-events: none;  /* on mesure en cliquant la carte, pas le tracé */

        polyline {
            fill: none;
            stroke: rgb(250, 240, 205);
            stroke-width: calc(10px * var(--inv, 1));
            stroke-linecap: round;
            stroke-linejoin: round;
            paint-order: stroke;
        }

        /* Le segment en cours, tant que le clic n'est pas tombé : pointillé, pour
           qu'on voie tout de suite ce qui est acquis et ce qui ne l'est pas. */
        .apercu {
            stroke-dasharray: calc(26px * var(--inv, 1)) calc(20px * var(--inv, 1));
            opacity: .75;
        }

        circle {
            r: calc(14px * var(--inv, 1));
            fill: rgb(250, 240, 205);
            stroke: rgba(20, 18, 16, .75);
            stroke-width: calc(4px * var(--inv, 1));
        }
    }

    /* Le mode est armé : le curseur le dit sur toute la carte. */
    .cadre.mesure-active {
        cursor: crosshair;

        &.tire {
            cursor: crosshair;
        }
    }

    /* La tour de guet.

       Trois cercles concentriques dans un <svg> au même `viewBox` que les autres
       calques : ils sont donc en unités de carte — un pixel vaut un mètre — et
       un rayon écrit 197 se lit 197 m à tous les zooms, sans un recalcul.

       Épaisseurs et rayon de la borne sont contre-mis à l'échelle par `--inv`,
       comme la mesure, et sans transition.

       La visibilité, elle, ne se joue pas ici : le calque porte `display` en
       style en ligne, que le script bascule. Une règle de feuille ne pourrait
       pas l'éteindre. */
    .guet {
        position: absolute;
        inset: 0;
        width: 100%;
        height: 100%;
        z-index: 8;            /* au-dessus des calques, sous les noms */
        pointer-events: none;  /* on pose en cliquant la carte, pas le tracé */

        /* La détection au réglage courant : le seul disque plein de la carte,
           parce que c'est le seul de ces tracés qui décrit une surface couverte
           et non une limite. */
        .portee {
            fill: rgb(122, 196, 224);
            fill-opacity: .18;
            stroke: rgb(122, 196, 224);
            stroke-opacity: .9;
            stroke-width: calc(8px * var(--inv, 1));
        }

        /* Les deux bornes : ce que la tour donne au ras du sol, et ce qu'elle
           donnerait sur un site qui domine de dix mètres. Ténus et pointillés,
           ils encadrent le disque plein — la portée est une BANDE, et c'est ce
           qu'il faut voir d'un coup d'œil. */
        .enveloppe,
        .plancher {
            fill: none;
            stroke: rgb(122, 196, 224);
            stroke-opacity: .4;
            stroke-width: calc(5px * var(--inv, 1));
            stroke-dasharray: calc(4px * var(--inv, 1)) calc(14px * var(--inv, 1));
        }

        /* La liaison aux autres tours et au donjon. Elle reprend le jaune des
           aires : comme elles, c'est une portée de règle, pas un relevé. */
        .lien {
            fill: none;
            stroke: rgb(250, 214, 92);
            stroke-opacity: .7;
            stroke-width: calc(6px * var(--inv, 1));
            stroke-dasharray: calc(30px * var(--inv, 1)) calc(22px * var(--inv, 1));
        }

        .socle {
            r: calc(14px * var(--inv, 1));
            fill: rgb(250, 240, 205);
            stroke: rgba(20, 18, 16, .75);
            stroke-width: calc(4px * var(--inv, 1));
        }
    }

    .cadre.guet-actif {
        cursor: crosshair;

        &.tire {
            cursor: crosshair;
        }
    }

    /* Le réglage de la hauteur du site, sous la carte.

       Il n'apparaît qu'une fois l'outil armé : un curseur qui ne commande rien
       de visible n'a rien à faire sous la carte. */
    .choix.tour {
        gap: 12px;

        input[type="range"] {
            flex: 1 1 12rem;
            min-width: 0;
            max-width: 22rem;
            accent-color: rgb(122, 196, 224);
        }

        output {
            font-size: 13px;
            font-variant-numeric: tabular-nums;
            opacity: .85;
        }
    }

    .zoom {
        position: absolute;
        top: 10px;
        right: 10px;
        display: flex;
        align-items: stretch;
        gap: 4px;
        padding: 4px;
        border-radius: 8px;
        background-color: rgba(27, 21, 22, .78);
        cursor: default;

        button {
            font: inherit;
            font-size: 15px;
            font-weight: bold;
            min-width: 30px;
            padding: 3px 9px;
            border: 1px solid rgba(233, 219, 203, .4);
            border-radius: 5px;
            background: none;
            color: var(--clair);
            cursor: pointer;

            &:hover {
                background-color: rgba(233, 219, 203, .18);
            }
        }

        output {
            display: flex;
            align-items: center;
            min-width: 52px;
            justify-content: center;
            font-size: 13px;
            font-variant-numeric: tabular-nums;
            opacity: .8;
        }
    }

    .rangs {
        display: flex;
        flex-direction: column;
        gap: 10px;
    }

    /* « Aller à » : un champ de recherche des vingt-sept régions.

       Un `<input list>` et un `<datalist>` plutôt qu'une liste maison : le
       navigateur filtre, complète, gère le clavier et marche au doigt — et ça
       tient en dix lignes de script au lieu de cent. */
    .choix.aller {
        display: flex;
        align-items: center;
        gap: 8px;
        flex-wrap: wrap;

        input {
            font: inherit;
            font-size: 13px;
            flex: 1 1 15rem;
            min-width: 0;
            max-width: 26rem;
            padding: 5px 10px;
            border: 1px solid rgba(233, 219, 203, .28);
            border-radius: 6px;
            background-color: rgba(233, 219, 203, .06);
            color: var(--clair);

            &::placeholder {
                opacity: .5;
            }

            &:focus-visible {
                outline: 2px solid rgba(250, 240, 205, .55);
                outline-offset: 1px;
            }
        }
    }

    .choix {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        align-items: center;

        &.calques {
            padding-top: 10px;
            border-top: 1px solid rgba(233, 219, 203, .2);
        }

        .etiquette {
            font-size: 12px;
            text-transform: uppercase;
            letter-spacing: .08em;
            opacity: .6;
        }

        button {
            font: inherit;
            font-size: 13px;
            display: flex;
            align-items: center;
            gap: 8px;
            max-width: 15rem;
            padding: 4px 10px 4px 4px;
            border: 2px solid rgba(233, 219, 203, .35);
            border-radius: 8px;
            background: none;
            color: var(--clair);
            text-align: left;
            cursor: pointer;

            img {
                flex: none;
                width: 38px;
                height: 38px;
                object-fit: cover;
                border-radius: 4px;
                background-color: rgba(0, 0, 0, .3);
            }

            /* Un calque n'a pas de vignette : elle serait transparente à
               quatre-vingt-dix-neuf pour cent. Sa pastille porte l'encre qu'on
               cherche sur la carte, et c'est ça, la légende. */
            .pastille {
                flex: none;
                width: 14px;
                height: 14px;
                margin-left: 6px;
                border-radius: 50%;
                box-shadow: 0 0 0 1px rgba(0, 0, 0, .45);
            }

            span {
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
            }

            &:hover:not(:disabled) {
                border-color: var(--clair);
            }

            &[aria-pressed="true"] {
                background-color: var(--clair);
                border-color: var(--clair);
                color: var(--sombre);
            }

            /* Un calque n'a de sens que sur un fond de même cadrage ; ailleurs
               il reste proposé, mais éteint, plutôt que de disparaître. */
            &:disabled {
                opacity: .35;
                cursor: not-allowed;
            }
        }
    }

    .cartouche {
        display: flex;
        flex-wrap: wrap;
        gap: 4px 16px;
        align-items: baseline;
        margin: 0;
        padding-top: 10px;
        border-top: 1px solid rgba(233, 219, 203, .2);
        font-size: 14px;
        line-height: 1.5;

        b {
            font-size: 16px;
        }

        span {
            opacity: .75;
        }

        .mono {
            margin-left: auto;
            font-family: ui-monospace, Consolas, monospace;
            font-size: 12px;
            white-space: nowrap;
        }
    }

    .titre-legende {
        font-size: 20px;
        margin: 34px 0 14px;
    }

    .marqueurs {
        list-style: none;
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
        gap: 10px 18px;

        li {
            display: flex;
            align-items: center;
            gap: 10px;
            font-size: 14px;
        }

        img {
            flex: none;
            width: 28px;
            height: 28px;
            object-fit: contain;
        }
    }
}

[hidden] {
    display: none !important;
}

/* Sur téléphone, la barre collante ne doit pas manger l'écran : les onglets
   passent en grille de deux, et le champ prend la largeur restante. */
@media (max-width: 768px) {
    /* Deux tuiles par ligne ne tiennent pas : « TRANSFORMATION » est plus large
       que la moitié d'un écran de téléphone. On les couche plutôt à plat, le
       nombre puis l'intitulé, ce qui divise par deux la hauteur du bloc. */
    .donnees-intro .chiffres {
        gap: 8px;

        li {
            flex: 1 1 100%;
        }

        a {
            flex-direction: row;
            justify-content: flex-start;
            gap: 12px;
            width: 100%;
            min-width: 0;
            padding: 8px 14px;

            b {
                font-size: 22px;
            }
        }
    }

    .donnees .barre {
        justify-content: flex-start;
        gap: 10px;
        padding: 10px 0;
    }

    /* Cinq onglets en grille feraient trois rangées, soit un quart de l'écran
       occupé en permanence par une barre collante. Ils tiennent donc sur une
       seule ligne qui défile ; le dernier onglet coupé par le bord dit de
       lui-même qu'il y a la suite. */
    .donnees .onglets {
        flex-wrap: nowrap;
        width: 100%;
        gap: 6px;
        overflow-x: auto;
        scrollbar-width: none;

        &::-webkit-scrollbar {
            display: none;
        }

        button {
            flex: none;
            font-size: 14px;
            padding: 7px 12px;
        }
    }

    .donnees .recherche {
        width: 100%;
    }

    .donnees .recherche input {
        min-width: 0;
        width: 100%;
    }
}
