/* Story 2.2 — carte web publique read-only.
 * Ces styles ne sont chargés QUE par map.php : les sélecteurs « globaux »
 * (#auth-nav, .footer-distributed) sont donc scoping-safe (page carte uniquement).
 * Aucun hex d'état codé ici : les couleurs de swatch viennent de legend.json via
 * la variable inline --swatch posée par map.js (source = packages/verticals/parking.json). */

/* --- Carte plein cadre : la carte EST le produit (DESIGN.md §Space/Layout) --- */
html, body { margin: 0; padding: 0; height: 100%; }
body { overflow: hidden; background: #F2EFE9; }
.map-root { position: fixed; inset: 0; }
.map-canvas { position: absolute; inset: 0; width: 100%; height: 100%; }

/* --- Read-only (AC5) : retirer le pied de page hérité du template. Aucune
 * surface de CONTRIBUTION ni d'écriture sur /map. --- */
.footer-distributed { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════════
 * 🚪 STORY 9.16 — `#auth-nav` N'EST PLUS MASQUÉE, ET C'ÉTAIT LA MOITIÉ DU BUG.
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * 🐞 CE QUI ÉTAIT MESURÉ EN PRODUCTION le 19/08/2026 : la barre de compte était
 *    bien SERVIE dans le HTML de `/map` (elle vient de `templates/head.php`, et
 *    cette page ne pose pas `$PAGE_NO_SCRIPTS`), mais la ligne
 *    `#auth-nav { display: none !important; }` qui vivait ici depuis la story
 *    2.3 la retirait de l'écran. Sur l'accueil elle n'était même pas rendue.
 *    Résultat : le seul endroit du site où l'on pouvait se connecter était une
 *    landing SEO, c'est-à-dire une page où personne ne va délibérément, et les
 *    surfaces livrées par la story 9.11 sur `/settings` (abonnement, export
 *    RGPD, suppression de compte) étaient inatteignables.
 *
 * 🔒 POURQUOI RÉVÉLER L'EXISTANT ICI PLUTÔT QU'ÉCRIRE UNE PORTE NEUVE, ALORS
 *    QUE L'ACCUEIL, LUI, EN REÇOIT UNE NEUVE. Les deux pages n'ont pas la même
 *    contrainte, et c'est la contrainte qui décide, pas le goût de
 *    l'uniformité :
 *      · l'accueil est du SSR intégral, la barre y coûterait jQuery ET
 *        Bootstrap sur la seule page du site qui s'en est débarrassée. Elle y
 *        reçoit donc un simple lien, rendu par le serveur (voir `index.php`) ;
 *      · la carte, elle, charge DÉJÀ les deux bibliothèques, et le fond de
 *        carte est lui-même du JavaScript : une barre révélée par un script n'y
 *        ajoute aucune dépendance et ne peut pas échouer sans que la page
 *        entière ait déjà échoué. Le correctif y est donc le RETRAIT d'une
 *        ligne, ce qui est la plus petite chose vraie qu'on puisse faire.
 *
 * 🔒 CE QUE ÇA N'OUVRE PAS. La barre porte deux liens et une déconnexion :
 *    `/{locale}/login` et `/{locale}/settings`. Aucun formulaire, aucun champ
 *    de identifiant, aucune requête. La carte reste incapable de contribuer, et
 *    les cinq verrous qui l'imposent n'ont pas été touchés. Le mot
 *    « lecture seule » de l'en-tête de ce fichier parlait de l'ÉCRITURE, il le
 *    dit maintenant.
 *
 * ⚠️ GÉOMÉTRIE, ET MA PREMIÈRE VERSION ÉTAIT FAUSSE, MESURÉE AU NAVIGATEUR.
 *    La barre est posée par le gabarit en `top: 10px; right: 10px` avec un
 *    `z-index: 1000` EN LIGNE, donc elle passe AU-DESSUS de la recherche
 *    (`z-index: 8`), qui est centrée en haut et large de
 *    `min(440px, 100vw - 24px)`.
 *
 *    Au-delà de 767 px les deux ne se touchent pas, et c'est vérifié plutôt que
 *    supposé : à 768 px la recherche occupe 164 → 604 et la barre 673 → 758.
 *
 *    En dessous, la recherche prend toute la largeur. J'avais d'abord descendu
 *    la barre « juste sous » la recherche, en supposant à cette dernière une
 *    hauteur d'environ 40 px. **Mesuré à 390 px de large, elle en fait 73** :
 *    le crédit « Recherche via OpenStreetMap » que la politique d'usage de
 *    Nominatim impose fait partie de la pilule. La barre chevauchait donc ce
 *    crédit sur 23 px. Et descendre encore ne marche pas non plus : la liste de
 *    résultats est DANS la grille du formulaire (`grid-column: 1 / -1`), donc
 *    elle fait grandir la pilule vers le bas jusqu'à 40 % de la hauteur d'écran
 *    dès qu'on cherche un lieu. Aucune position sous la recherche n'est sûre.
 *
 *    La barre rejoint donc la COLONNE DU BAS À DROITE, au-dessus du bouton des
 *    filtres, qui est déjà l'endroit où ce fichier empile son chrome et dont
 *    les décalages sont déjà calibrés (sélecteur de langue, encart d'écran, et
 *    remontée quand le panneau de détail est ouvert). Les valeurs reprennent
 *    celles de `.map-filters-toggle` plus sa hauteur mesurée (37 px) et une
 *    gouttière de 8 px.
 *
 * 🚨 `env(safe-area-inset-bottom)` EST REPRIS SOUS 480 px, pour la raison
 *    écrite plus bas à propos du bouton des filtres : le gabarit pose le
 *    sélecteur de langue à `calc(70px + env(...))` et `templates/head.php` sert
 *    un `viewport-fit=cover`. Sur un téléphone à encoche l'encart est réel,
 *    donc toute la pile MONTE d'autant. Un décalage fixe aurait tenu au banc,
 *    où l'encart vaut zéro, et serait retombé sur le sélecteur sur l'appareil
 *    de tout le monde. */
@media (max-width: 767px) {
    #auth-nav {
        top: auto !important;
        bottom: 127px !important;
        right: 10px !important;
    }
    /* Le panneau de détail occupe le bas de l'écran sous md : on remonte la
       barre comme on remonte déjà les boutons de la légende et des filtres. */
    body.map-detail-open #auth-nav { bottom: calc(38vh + 93px) !important; }
}
@media (max-width: 480px) {
    #auth-nav { bottom: calc(181px + env(safe-area-inset-bottom)) !important; }
    body.map-detail-open #auth-nav { bottom: calc(38vh + 93px) !important; }
}

/* --- Attribution MapLibre : TOUJOURS visible, jamais couverte (AC4 — exigence
 * légale). Positionnée en bas-gauche pour ne pas entrer en conflit avec le
 * sélecteur de langue (bas-droite, hérité du template). Le lien ODbL y est
 * cliquable. --- */
.maplibregl-ctrl-bottom-left { z-index: 5; }
.maplibregl-ctrl-attrib {
    background: rgba(255, 255, 255, 0.9);
    font-size: 12px;
    line-height: 1.5;
    padding: 2px 8px;
    max-width: min(92vw, 680px);
}
.maplibregl-ctrl-attrib a { color: #1B5E9B; text-decoration: underline; }

/* --- Affordance « ? » d'ouverture de légende, groupée près de l'attribution --- */
.map-legend-toggle {
    position: absolute;
    left: 10px;
    bottom: 42px;
    z-index: 6;
    width: 34px;
    height: 34px;
    border: none;
    border-radius: 9999px;              /* rounded.full (DESIGN tokens) */
    cursor: pointer;
    background: rgba(41, 44, 47, 0.92);
    color: #fff;
    font-size: 18px;
    font-weight: 700;
    line-height: 34px;
    text-align: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.map-legend-toggle:hover { background: rgba(41, 44, 47, 1); }
.map-legend-toggle:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* --- Panneau légende flottant : encodage redondant couleur + motif + glyphe,
 * plus, depuis la story 11.7, une ligne d'explication par état qui en porte une.
 * 🐞 Ce commentaire disait « 4 états » et il était FAUX depuis le cinquième
 *    (17/08). C'est le même défaut que le titre « Légende, 4 états » corrigé ce
 *    jour-là : un compte écrit à la main à côté d'une liste injectée devient
 *    faux tout seul. On ne le remplace pas par « 5 », on retire le compte. --- */
.map-legend {
    position: absolute;
    left: 10px;
    bottom: 84px;
    z-index: 7;
    width: min(280px, calc(100vw - 20px));
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
    padding: 12px 14px;
    color: #222;
    font-size: 14px;
    /* MAJEUR 1 de la revue du 25/09/2026 — LE PANNEAU NE GRANDIT PLUS SANS
     * BORNE VERS LE HAUT. La story 21.6 a ajouté une section (« Fond de
     * carte »), et le panneau, ancré par le BAS et sans hauteur bornée,
     * pouvait grandir jusque sous la recherche (`.map-search`, z-index 8,
     * supérieur au sien) sur un petit écran : sa croix de fermeture y
     * devenait alors INATTEIGNABLE, mesuré à 375×667 et 360×640
     * (`elementFromPoint` y rendait `map-search`, pas la croix). La
     * réserve de 148px couvre `bottom: 84px` PLUS l'espace de la
     * recherche (`top: 12px` + sa hauteur + une marge) : un panneau plus
     * long que l'écran défile désormais EN INTERNE au lieu de pousser sa
     * tête hors champ. */
    max-height: calc(100vh - 148px);
    overflow-y: auto;
    overscroll-behavior: contain;
}
.map-legend[hidden] { display: none; }
.map-legend__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
}
.map-legend__title { font-weight: 700; }
.map-legend__close {
    border: none;
    background: none;
    font-size: 22px;
    line-height: 1;
    cursor: pointer;
    color: #555;
    padding: 0 4px;
}
.map-legend__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 10px;
}
/* Story 11.7 : la rangée porte désormais DEUX lignes possibles (libellé, puis
 * explication). L'alignement passe donc en haut, et la pastille comme le glyphe
 * reçoivent le décalage optique qui les remet en face de la PREMIÈRE ligne.
 * Une rangée SANS explication garde exactement sa hauteur d'avant. */
.map-legend__row { display: flex; align-items: flex-start; gap: 10px; }
.map-legend__text { flex: 1; display: flex; flex-direction: column; gap: 2px; }
/* Interlignes en PIXELS ENTIERS, et c'est mesuré, pas décoratif : un multiple
 * fractionnaire (14 px × 1,35 = 18,9) fait démarrer les rangées suivantes sur
 * un demi-pixel, et les pastilles à motif se retrouvent anticrénelées
 * différemment. Le rendu témoin de la story compare les cinq pastilles AU
 * PIXEL avant et après : sans ces deux valeurs entières, la comparaison
 * n'aurait pas pu conclure. */
.map-legend__label { flex: 1; line-height: 19px; }
.map-legend__meaning {
    font-size: 11px;
    line-height: 15px;
    color: #555;
}
/* Story 3.5 (D4a) — note overview : sémantique des pastilles monde documentée. */
.map-legend__note {
    margin: 10px 0 0;
    padding-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, 0.12);
    font-size: 11px;
    font-style: italic;
    color: #555;
}
/* Story 21.6 : le fond de carte, sobre ou en couleur. Servi `hidden` par
 * défaut (map.php), révélé par map.js seulement si la config publie un fond
 * couleur : discret, gratuit, jamais un mur Premium (contrairement à la
 * section verrouillée de 9.10). Même patron visuel que `.map-filters__toggle`
 * (paire de boutons à bascule, `aria-pressed` porte l'état actif). */
.map-legend__basemap {
    margin: 10px 0 0;
    padding-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, 0.12);
}
.map-legend__subtitle {
    display: block;
    font-size: 11px;
    font-weight: 700;
    color: #555;
    margin-bottom: 6px;
}
.map-legend__basemap-buttons { display: flex; gap: 6px; }
.map-legend__basemap-btn {
    flex: 1 1 0;
    border: 1px solid #D3D7DA;
    border-radius: 8px;
    background: #fff;
    color: #222;
    font: inherit;
    font-size: 12px;
    padding: 6px 8px;
    cursor: pointer;
}
.map-legend__basemap-btn:hover { background: #F2F4F5; }
.map-legend__basemap-btn:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-legend__basemap-btn[aria-pressed="true"] {
    border-color: #1B5E9B;
    border-width: 2px;
    background: #EAF1F8;
    font-weight: 600;
}
/* `line-height` figé sur celui du libellé : sous `align-items: flex-start`
 * (story 11.7) le glyphe doit rester sur la PREMIÈRE ligne, pas se recentrer
 * sur une rangée devenue plus haute. */
.map-legend__glyph {
    font-weight: 700;
    min-width: 1.4em;
    text-align: center;
    line-height: 19px;
}

/* Swatch = couleur (var --swatch, depuis legend.json) + PATTERN (classe dérivée du
 * token `pattern`). L'accessibilité ne dépend JAMAIS de la teinte seule (AC1/AC5). */
.map-legend__swatch {
    width: 28px;
    height: 18px;
    border-radius: 3px;
    flex: 0 0 auto;
    border: 1px solid rgba(0, 0, 0, 0.25);
    background-color: var(--swatch, #757575);
}
.map-legend__swatch--hatch_diagonal {
    background-image: repeating-linear-gradient(
        45deg,
        rgba(255, 255, 255, 0) 0 2px,
        rgba(255, 255, 255, 0.9) 2px 4px
    );
}
.map-legend__swatch--dashed {
    background-color: transparent;
    border-style: dashed;
    border-width: 2px;
    border-color: var(--swatch, #757575);
}
.map-legend__swatch--dotted {
    background-color: transparent;
    border-style: dotted;
    border-width: 2px;
    border-color: var(--swatch, #757575);
}
/* `dash_dot` — motif du CINQUIÈME état (story 10.10, spec de dérivation 1.5.0).
 *
 * 🚨 TROUVÉ EN ÉCRIVANT LA STORY, ET ABSENT DE LA LISTE DES CHANTIERS : le
 *    token `dash_dot` n'avait AUCUNE règle ici. Sans elle la pastille tombait
 *    sur le fond plein de `.map-legend__swatch`, donc `free_cond` se
 *    distinguait de `free` PAR LA SEULE TEINTE, dans la légende comme dans la
 *    pill du panneau de détail. C'est exactement ce que FR-3 interdit, et ça
 *    n'aurait levé aucune erreur.
 *
 * CSS n'a pas de `border-style: dash-dot` : le tiret-point est donc DESSINÉ, en
 * bande centrale (trait long, blanc, point, blanc), ce qui le rend distinct des
 * quatre autres motifs sans emprunter ni le fond plein, ni la diagonale, ni un
 * style de bordure déjà pris. */
.map-legend__swatch--dash_dot {
    background-color: transparent;
    background-image: repeating-linear-gradient(
        90deg,
        var(--swatch, #757575) 0 7px,
        transparent 7px 10px,
        var(--swatch, #757575) 10px 12px,
        transparent 12px 15px
    );
    background-repeat: repeat-x;
    background-position: center;
    background-size: 100% 6px;
}

/* ==========================================================================
   Story 2.3 — Recherche de lieu (pill haut) + panneau de détail (sheet/360px).
   Breakpoint `md` = 768px (convention template, consignée dans la story). Aucun
   hex d'état codé ici : la pill réutilise les swatches légende (var --swatch).
   ========================================================================== */

/* --- Pill de recherche flottante (haut-centre, sous une éventuelle nav) --- */
.map-search {
    position: absolute;
    top: 12px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 8;
    width: min(440px, calc(100vw - 24px));
    display: grid;
    grid-template-columns: 1fr auto auto;
    align-items: center;
    gap: 4px;
    background: #fff;
    border-radius: 9999px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.22);
    padding: 4px 6px 4px 14px;
}
.map-search__label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0);
    white-space: nowrap;
}
.map-search__input {
    border: none;
    outline: none;
    font-size: 15px;
    padding: 8px 4px;
    background: transparent;
    color: #222;
    min-width: 0;
}
.map-search__submit, .map-search__clear {
    border: none;
    background: none;
    cursor: pointer;
    font-size: 18px;
    line-height: 1;
    width: 36px; height: 36px;
    border-radius: 9999px;
    color: #555;
}
.map-search__submit:hover, .map-search__clear:hover { background: rgba(0, 0, 0, 0.06); }
.map-search__submit:focus-visible, .map-search__clear:focus-visible,
.map-search__input:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* Liste de résultats sous la pill */
.map-search__results {
    grid-column: 1 / -1;
    list-style: none;
    margin: 4px 0 2px;
    padding: 0;
    max-height: 40vh;
    overflow-y: auto;
    border-top: 1px solid rgba(0, 0, 0, 0.08);
}
.map-search__results[hidden] { display: none; }
.map-search__result-btn {
    display: block;
    width: 100%;
    text-align: left;
    border: none;
    background: none;
    padding: 9px 12px;
    font-size: 14px;
    cursor: pointer;
    color: #222;
    border-radius: 8px;
}
.map-search__result-btn:hover { background: rgba(0, 0, 0, 0.06); }
.map-search__result-btn:focus-visible { outline: 2px solid #1B5E9B; outline-offset: -2px; }
.map-search__status {
    grid-column: 1 / -1;
    margin: 0;
    padding: 0 12px 4px;
    font-size: 13px;
    color: #666;
    min-height: 0;
}
.map-search__status:empty { display: none; }

/* Crédit géocodeur (politique d'usage Nominatim/OSM : les résultats sont crédités). */
.map-search__credit {
    grid-column: 1 / -1;
    margin: 0;
    padding: 0 12px 6px;
    font-size: 11px;
    color: #8a8f94;
}

/* --- Panneau de détail : ≥ md = panneau droit STATIQUE 360px --- */
.map-detail {
    position: absolute;
    z-index: 9;
    background: #fff;
    color: #1c1f22;
    /* Géométrie « ordinateur » posée ICI, dans la règle de base, et non dans
       une requête `min-width: 768px` : entre 767 et 768 px (zoom du
       navigateur), aucune des deux requêtes ne s'appliquerait et le panneau
       pleine hauteur reviendrait. Sous 768 px la requête téléphone plus bas
       redéfinit tout (haut, côtés, bas, rayon, ombre). Motif et mesures dans
       le bloc du 24/09/2026 juste après. */
    box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25);
    display: flex;
    flex-direction: column;
    top: 52px;
    right: 10px;
    bottom: 130px;
    width: 360px;
    max-width: 90vw;
    border-radius: 12px;
    padding: 16px 18px;
    overflow-y: auto;
}
.map-detail[hidden] { display: none; }
.map-detail__grabber { display: none; }   /* grabber = mobile uniquement */
.map-detail__close {
    position: absolute;
    top: 10px; right: 12px;
    border: none; background: none;
    font-size: 26px; line-height: 1;
    cursor: pointer; color: #555;
    width: 40px; height: 40px; border-radius: 9999px;
}
.map-detail__close:hover { background: rgba(0, 0, 0, 0.06); }
.map-detail__close:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* ═══════════════════════════════════════════════════════════════════════════
 * 🐞 24/09/2026 : SUR ORDINATEUR, UN PARKING OUVERT RENDAIT LA CARTE IMPOSSIBLE
 *    À FILTRER, ET ON NE POUVAIT PLUS LE FERMER.
 * ═══════════════════════════════════════════════════════════════════════════
 *
 * Retour de Mickaël, en usage réel : « le bouton [des filtres] n'apparaît
 * plus » et « le bouton de fermeture de la petite fenêtre est sous le bouton de
 * connexion ». Mesuré au navigateur sur `/fr/map` servi, de 768 à 1440 px de
 * large, par test de survol (`elementFromPoint`) :
 *   · le panneau pleine hauteur, posé en `top: 0, right: 0, bottom: 0`,
 *     couvrait le bouton « Filtres » (le point central rendait `map-detail`),
 *   · sa croix (`top: 10px, right: 12px`) tombait sous `#auth-nav`, que le
 *     gabarit pose en `top: 10px, right: 10px` avec un `z-index: 1000` EN LIGNE
 *     (le point central rendait `login-btn`).
 * Les deux défauts s'additionnaient : le panneau cachait les filtres et ne se
 * fermait plus, donc les filtres restaient hors d'atteinte.
 *
 * 🔒 LE REMÈDE NE DÉPLACE AUCUN BOUTON, IL RÉDUIT LE PANNEAU À LA PLACE LIBRE.
 *    Pousser `#auth-nav` à gauche du panneau le faisait retomber sur la
 *    recherche centrée en haut dès que la fenêtre fait moins d'environ 1 370 px
 *    (mesuré avant d'écrire). Le panneau devient donc une carte posée dans la
 *    colonne de droite, ENTRE la barre de compte et la pile du bas (règle de
 *    base de `.map-detail`, plus haut) :
 *      · haut : 10 (barre) + 31 (sa hauteur mesurée) + 11 de garde = 52 px,
 *      · bas : 82 (bouton des filtres) + 37 (sa hauteur) + 11 de garde = 130 px,
 *        ce qui dégage aussi le sélecteur de langue, plus bas encore.
 *
 * 🔒 LA CROIX NE DÉFILE PLUS AVEC LE CONTENU. Elle est en position absolue dans
 *    le panneau, donc tant que le PANNEAU défilait, elle partait avec son
 *    contenu, et la carte plus courte défile plus souvent (trouvé par la revue,
 *    mesuré à 1024 × 768). Sur ordinateur c'est désormais le CORPS qui défile,
 *    le panneau ne bouge plus, et la croix reste sur un fond opaque (au survol
 *    aussi) au-dessus du texte qui passe dessous. Le corps garde 6 px de marge
 *    intérieure, rendus par une marge négative, pour que l'anneau de focus
 *    clavier (3 de trait, 2 de décalage) ne soit pas rogné à son bord.
 *
 * 🔒 SEUL LE PANNEAU DES FILTRES OUVERT PASSE AU-DESSUS DU DÉTAIL (10 contre 9),
 *    parce que c'est le dernier geste de la personne. La NOTE flottante des
 *    filtres, elle, reste dessous (7) : elle s'affiche d'elle-même dès qu'un
 *    filtre atténue les inconnus, et la première version de ce correctif, qui
 *    la montait aussi, recouvrait les boutons « Itinéraire » du panneau
 *    (trouvé par la revue, mesuré à 768, 1024 et 1440 px).
 *
 * ⚠️ ET LA RECHERCHE ÉTAIT DÉJÀ COUVERTE, sous 1 160 px avec l'ancien panneau
 *    (collé au bord) et sous 1 180 px avec la carte (décalée de 10 px). Centrée
 *    sur toute la largeur, sa moitié droite tombait sous le panneau, loupe et
 *    croix comprises (mesuré à 768 et 1024 px). Tant que le panneau est ouvert,
 *    son centre recule JUSTE ASSEZ pour que son bord droit s'arrête 10 px avant
 *    le panneau (largeur moins 360 de panneau, 10 de marge et 10 de garde), et
 *    ne bouge pas du tout quand la place suffit : aucune borne de largeur à
 *    maintenir, aucun saut sur un grand écran.
 *
 * 🔒 UNE SEULE REQUÊTE, LE COMPLÉMENT EXACT DE CELLE DU TÉLÉPHONE.
 *    `not all and (max-width: 767px)` s'applique dès que `(max-width: 767px)` ne
 *    s'applique pas, et jamais en même temps. Une borne voisine (767,01 px) ne
 *    le faisait pas : les moteurs arrondissent les longueurs au 1/64 de pixel,
 *    et à 767 px ENTIERS les deux requêtes étaient vraies ensemble (mesuré par
 *    la revue dans Chromium et WebKit). Sous 768 px RIEN de ce bloc ne
 *    s'applique, et c'est vérifié : la première version posait le z-index du
 *    panneau des filtres hors requête, et sur téléphone il recouvrait alors la
 *    croix et la poignée de la feuille du bas (mesuré à 390 et 700 px).
 * Verrou : `tests/unit/map-detail-desktop-overlap.test.js`. */
@media not all and (max-width: 767px) {
    .map-detail { overflow: hidden; }
    .map-detail__body {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
        padding: 6px;
        margin: -6px;
    }
    .map-detail__close { z-index: 1; background: #fff; }
    .map-detail__close:hover { background: #f0f0f0; }
    body.map-detail-open .map-filters { z-index: 10; }
    body.map-detail-open .map-search {
        left: min(50vw, calc(100vw - 380px - min(220px, 50vw - 202px)));
        width: min(440px, calc(100vw - 404px));
    }
}
.map-detail__live {
    position: absolute; width: 1px; height: 1px;
    overflow: hidden; clip: rect(0 0 0 0);
}

.map-detail__header { display: flex; flex-direction: column; gap: 8px; padding-right: 36px; }
.map-detail__title { margin: 0; font-size: 20px; font-weight: 700; line-height: 1.2; }

/* Pill d'état : rendue à l'IDENTIQUE de la ligne de légende (swatch + glyph + label). */
.map-detail__pill {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    align-self: flex-start;
    padding: 4px 10px 4px 4px;
    background: rgba(0, 0, 0, 0.04);
    border-radius: 9999px;
}
.map-detail__pill-glyph { font-weight: 700; min-width: 1.2em; text-align: center; }
.map-detail__pill-label { font-weight: 600; font-size: 14px; }

.map-detail__walk { margin: 10px 0 0; font-size: 14px; color: #444; }

/* Attributs : liste de définition compacte */
.map-detail__attrs {
    margin: 14px 0 0;
    display: grid;
    grid-template-columns: minmax(96px, auto) 1fr;
    gap: 6px 14px;
}
.map-detail__attr-label { margin: 0; font-size: 13px; color: #666; }
.map-detail__attr-value { margin: 0; font-size: 14px; font-weight: 500; }

/* Story 10.3 (NFR-5) — mention de provenance d'une valeur CONTRIBUÉE. Même
   registre typographique que la fraîcheur et le conflit : c'est un fait, pas
   une alerte. Aucune couleur d'avertissement, aucun badge (UX-DR22). Le poids
   normal la distingue de la valeur qu'elle suit, sans la concurrencer. */
.map-detail__attr-note {
    display: block;
    margin: 2px 0 0;
    font-size: 12px;
    font-weight: 400;
    color: #6b7075;
}

.map-detail__freshness {
    margin: 14px 0 0;
    font-size: 13px;
    color: #6b7075;
}

/* Story 6.5 (AC5.3) — ligne de conflit. Même registre TYPOGRAPHIQUE que la
   fraîcheur : c'est un FAIT, pas une alerte. Aucun badge, aucune pastille,
   aucune couleur de danger (UX-DR22 bannit les badges ; 6.4/D3 : le conflit se
   dit comme un dénombrement, jamais comme un jugement de méfiance). */
.map-detail__conflict {
    margin: 6px 0 0;
    font-size: 13px;
    color: #6b7075;
}

/* Story 9.13 : deux destinations nommées côte à côte, qui repassent à la ligne
   sur un sheet étroit. Aucun logo, aucun pictogramme : une icône de marque
   serait une ressource tierce chargée, ce que le verrou d'origines refuse. */
.map-detail__actions { margin: 16px 0 0; display: flex; flex-wrap: wrap; gap: 8px; }
.map-detail__navigate {
    display: inline-block;
    padding: 9px 16px;
    border-radius: 9999px;
    background: #1B5E9B;
    color: #fff;
    text-decoration: none;
    font-weight: 600;
    font-size: 14px;
}
.map-detail__navigate:hover { background: #164f83; }
.map-detail__navigate:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* Variante grise : entonnoir « confirmez dans l'app » + badges stores (AUCUNE écriture). */
.map-detail__grey {
    margin: 14px 0 0;
    padding: 14px;
    background: rgba(117, 117, 117, 0.10);
    border-radius: 12px;
}
.map-detail__grey-title { margin: 0 0 6px; font-weight: 700; font-size: 15px; }
.map-detail__grey-body { margin: 0 0 12px; font-size: 14px; color: #444; line-height: 1.4; }
/* Story 9.16 (AC4) : l'invitation à contribuer sur un état CONNU. Elle est
   DISCRÈTE là où le bloc gris est ENCADRÉ, et l'écart est voulu : sur un
   parking gris l'entonnoir est le propos de la fiche, sur un parking dont
   l'état est connu il n'est qu'une note de bas de fiche. Un second encadré
   grisé y ferait concurrence à la donnée qu'on est venu lire. */
.map-detail__contribute {
    margin: 14px 0 0;
    padding: 12px 0 0;
    border-top: 1px solid rgba(0, 0, 0, 0.10);
}
.map-detail__contribute-note {
    margin: 0 0 10px;
    font-size: 13px;
    color: #555;
    line-height: 1.4;
}
.map-detail__contribute .map-detail__stores:empty { display: none; }

.map-detail__stores { display: flex; flex-direction: column; gap: 8px; }
.map-detail__store {
    display: block;
    text-align: center;
    padding: 10px 14px;
    border-radius: 8px;
    text-decoration: none;
    font-weight: 600;
    font-size: 14px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    color: #fff;
    background: #292c2f;
}
.map-detail__store--google { background: #1B5E9B; border-color: #1B5E9B; }
.map-detail__store:hover { opacity: 0.92; }
.map-detail__store:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* --- < md : bottom sheet à 2 ancrages (peek ~38% + full), carte pannable au peek --- */
@media (max-width: 767px) {
    .map-detail {
        top: auto;
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        max-width: 100%;
        border-radius: 16px 16px 0 0;
        box-shadow: 0 -4px 24px rgba(0, 0, 0, 0.25);
        transition: height 0.22s ease;
        padding-top: 22px;
    }
    .map-detail.is-peek { height: 38vh; }
    .map-detail.is-full { height: 88vh; }
    /* Grabber tactile (drag/tap pour agrandir/réduire) */
    .map-detail__grabber {
        display: block;
        position: absolute;
        top: 6px; left: 50%;
        transform: translateX(-50%);
        width: 44px; height: 5px;
        padding: 0;
        border: none;
        border-radius: 9999px;
        background: rgba(0, 0, 0, 0.25);
        cursor: grab;
    }
    /* Zone de tap élargie sur le grabber (≥ 48dp) sans grossir le trait visible. */
    .map-detail__grabber::before {
        content: "";
        position: absolute;
        top: -12px; left: -18px; right: -18px; bottom: -12px;
    }

    /* Relever l'attribution ODbL + l'affordance légende au-dessus du sheet (jamais
       couvertes — AC4/AC6). Au peek comme au full. */
    body.map-detail-open .maplibregl-ctrl-bottom-left { bottom: calc(38vh + 8px); transition: bottom 0.22s ease; }
    body.map-detail-open .map-legend-toggle { bottom: calc(38vh + 48px); }
    body.map-detail-open.map-detail-full .maplibregl-ctrl-bottom-left { bottom: calc(88vh + 8px); }
    body.map-detail-open.map-detail-full .map-legend-toggle { bottom: calc(88vh + 48px); }
}

/* Reduce Motion : neutralise les transitions du sheet (UX-DR / a11y). */
@media (prefers-reduced-motion: reduce) {
    .map-detail, body.map-detail-open .maplibregl-ctrl-bottom-left { transition: none; }
}

/* --- Mobile étroit : chrome utilisable au doigt (AC5/AC6) --- */
@media (max-width: 480px) {
    /* L'attribution ODbL peut passer sur 2 lignes en étroit : on relève l'affordance
       « ? » et la légende pour ne jamais recouvrir la mention légale (AC4). */
    .map-legend-toggle { width: 40px; height: 40px; line-height: 40px; font-size: 20px; bottom: 78px; }
    /* La réserve suit le `bottom` relevé de ce contexte (128px au lieu de
     * 84px), même raisonnement que la règle de base. */
    .map-legend { bottom: 128px; max-height: calc(100vh - 192px); }
    .map-search { top: 8px; }
}

/* --- Story 1.6 (AC4.2) : erreur de configuration VISIBLE (jamais d'écran
   blanc muet). Bandeau centré au-dessus de la carte, révélé par map.js quand
   app-config.json est injoignable/invalide ou que le boot échoue. --- */
.map-config-error {
    position: absolute;
    top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    z-index: 30;
    max-width: min(420px, calc(100vw - 32px));
    margin: 0;
    padding: 14px 18px;
    border-radius: 10px;
    background: rgba(33, 33, 33, 0.92);
    color: #fff;
    font-size: 15px;
    line-height: 1.45;
    text-align: center;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.35);
}

/* ═══════════════════════════════════════════════════════════════════════════
   Story 9.9 : FILTRES GRATUITS (lecture filtrée, aucune surface d'écriture)
   ═══════════════════════════════════════════════════════════════════════════
   L'état actif d'un contrôle ne repose JAMAIS sur la seule couleur (AC6) : il
   porte aussi `aria-pressed` pour la technologie d'assistance et une coche pour
   l'oeil. Le fond et la bordure ne font que renforcer ce que ces deux canaux
   disent déjà. */
/* ── Story 9.12 (correctif A) : LE COIN BAS-DROIT APPARTIENT AU SÉLECTEUR DE
   LANGUE, LES FILTRES S'EMPILENT AU-DESSUS DE LUI ───────────────────────────
   Mesuré au navigateur le 18/08/2026 sur `/fr/map` : le bouton était posé à
   `bottom: 42px` et le sélecteur de langue de `templates/footer.php`, qui est
   `position: fixed` en bas à droite avec un `z-index: 9999`, le recouvrait sur
   75 × 30 pixels en 1200 × 919. Conséquence mesurée par test de survol
   (`elementFromPoint`) : 24,7 % seulement de la surface du bouton répondait, et
   son CENTRE rendait un drapeau. Tout ce que la story 9.9 a livré était donc
   inatteignable.

   ⚠️ LE REMÈDE N'EST PAS DE MONTER LE `z-index` DU BOUTON. Cela aurait échangé
      un contrôle inatteignable contre un autre, le sélecteur de langue passant
      alors dessous. Les deux doivent répondre, donc ils ne doivent plus se
      chevaucher du tout.

   ⚠️ ET LE SÉLECTEUR N'EST PAS DÉPLACÉ, MÊME S'IL EST LE PLUS HAUT DES DEUX.
      Il est posé par `templates/footer.php`, donc il vit sur les NEUF pages du
      site : le bouger ou toucher son `z-index` ferait porter à huit pages
      étrangères le prix d'un défaut qui n'existe que sur la carte. `map.css`
      n'étant chargé que par `map.php`, la correction locale à la carte est la
      seule dont le rayon d'action égale celui du défaut (même raisonnement que
      `#auth-nav` et `.footer-distributed` en tête de ce fichier).

   LES NOMBRES SONT MESURÉS, PAS DEVINÉS, et c'est nécessaire : la HAUTEUR du
   sélecteur n'est déclarée nulle part, elle est dérivée de sa police et de ses
   marges intérieures. Bandes réservées relevées au navigateur (bord haut du
   sélecteur, en pixels depuis le bas de la fenêtre) :
     · au-delà de 768 px de large : 20 + 52 = 72
     · 481 à 768 px               : 15 + 42 = 57
     · 480 px et moins            : 70 + 56 = 126 (pastille repliée, et les
       70 px du gabarit ne sont pas rabaissés : ils protègent aussi de la barre
       d'outils basse des navigateurs mobiles).
   D'où 82 px hors téléphone (72 + 10 de garde) et 136 px sur téléphone
   (126 + 10). Le panneau suit le bouton, 42 px plus haut, sinon il retomberait
   lui aussi sur le sélecteur : mesuré, la pastille de langue couvrait 56 × 42
   pixels du panneau ouvert, et donc une de ses lignes.
   Preuve : `docs/web-9-12-coin-bas-droit-*.png`, verrou
   `tests/unit/map-corner-overlap.test.js`. */
.map-filters-toggle {
    position: absolute;
    right: 10px;
    bottom: 82px;
    z-index: 6;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    border: none;
    border-radius: 9999px;
    cursor: pointer;
    background: rgba(41, 44, 47, 0.92);
    color: #fff;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    padding: 8px 14px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
.map-filters-toggle[hidden] { display: none; }
.map-filters-toggle:hover { background: rgba(41, 44, 47, 1); }
.map-filters-toggle:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters-toggle__glyph { font-size: 16px; line-height: 1; }
/* Le compteur de filtres actifs est un canal VISUEL de plus, doublé par
   l'`aria-label` du bouton (posé par filter-panel.js). */
.map-filters-toggle[data-active-count]:not([data-active-count="0"])::after {
    content: attr(data-active-count);
    min-width: 18px;
    height: 18px;
    border-radius: 9999px;
    background: #fff;
    color: #292C2F;
    font-size: 12px;
    font-weight: 700;
    line-height: 18px;
    text-align: center;
}

/* Story 11.5, correction de revue : la note « les inconnus sont atténués » a un
   second réceptacle, montré seulement PANNEAU FERMÉ (le premier vit dans
   l'`<aside>`, qui est `hidden` tant qu'on ne l'ouvre pas).

   🔒 SA GÉOMÉTRIE N'EST PAS RECOPIÉE, ELLE EST PARTAGÉE. Il est nommé dans les
      MÊMES règles de position que le panneau, en base comme dans la requête
      téléphone, précisément parce que ce coin a déjà produit deux défauts de
      chevauchement (9.12 : le sélecteur de langue couvrait le bouton, puis le
      correctif faisait passer la tête du panneau sous la recherche). Trois jeux
      d'offsets à maintenir en auraient produit un troisième. Ici il n'y en a
      qu'un, et les deux éléments ne sont jamais visibles en même temps, donc
      occuper le même emplacement est sans conséquence. */
.map-filters,
.map-filters-note-floating {
    position: absolute;
    right: 10px;
    /* 82 (bouton) + 37 (sa hauteur) + 5 de garde, voir le bloc du bouton. */
    bottom: 124px;
    z-index: 7;
    width: min(300px, calc(100vw - 20px));
    /* 🐞 TROISIÈME TERME AJOUTÉ PAR LA STORY 9.12, ET IL RÉPARE UN DÉFAUT QUE LE
       CORRECTIF A LUI-MÊME CRÉÉ. En montant le panneau de 84 à 124 px, sa tête
       (titre et bouton de fermeture) passait sous la pill de recherche dès que
       la fenêtre devenait courte : mesuré à 320 × 568, le haut du panneau
       tombait à 49 px du haut alors que la recherche descend à 85, et la
       recherche porte un `z-index` de 8 contre 7. La croix de fermeture serait
       devenue inatteignable, c'est-à-dire EXACTEMENT le défaut que cette story
       corrige, déplacé d'un coin à l'autre. Le panneau se borne donc à la place
       réellement libre : 100 px réservés en haut (la pill mesure 73 px et
       descend à 85) plus les 124 px de son propre décalage. */
    max-height: min(60vh, 520px, calc(100vh - 224px));
    overflow-y: auto;
    background: #fff;
    border-radius: 12px;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
    padding: 12px 14px;
    color: #222;
    font-size: 14px;
}
.map-filters[hidden] { display: none; }
/* La note flottante hérite de la carte du panneau (fond, rayon, ombre, place),
   et ne diverge que sur ce qui doit diverger : c'est une note, pas un titre. */
.map-filters-note-floating {
    margin: 0;
    font-size: 12px;
    line-height: 1.4;
    color: #5A5F63;
}
.map-filters-note-floating[hidden] { display: none; }
/* Passage de l'agent utilisateur-reel, 25/09/2026 : la croix de fermeture
   defile AVEC le contenu (le panneau entier est `overflow-y: auto`), donc en
   bas d'une longue liste elle n'est plus visible : aucun moyen de fermer sans
   remonter tout le defilement. L'en-tete est rendu COLLANT (`sticky`), avec un
   fond OPAQUE pour que le contenu qui passe dessous ne se voie pas au travers.
   Les marges negatives annulent le padding du panneau (12px haut, 14px cotes)
   sur ce seul bloc, pour que le fond colle vraiment au bord et ne laisse pas
   un liseret transparent de chaque cote. */
.map-filters__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: -12px -14px 8px;
    padding: 12px 14px 8px;
    position: sticky;
    top: -12px;
    background: #fff;
    z-index: 1;
}
.map-filters__title { font-weight: 700; }
.map-filters__subtitle {
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #5A5F63;
    margin: 12px 0 6px;
}
/* Story 21.6, passage de l'agent utilisateur-réel du 25/09/2026 : jumeau du
 * choix de fond de carte de la légende (`.map-legend__basemap*`), même
 * apparence, dans la rubrique AFFICHAGE du panneau des filtres. Pas de
 * `border-top` ici : c'est déjà le premier élément sous le `<h2>` de la
 * section, un filet de séparation y serait redondant. */
.map-filters__basemap { margin: 0 0 12px; }
.map-filters__basemap-subtitle {
    display: block;
    font-size: 11px;
    font-weight: 700;
    color: #5A5F63;
    margin-bottom: 6px;
}
.map-filters__basemap-buttons { display: flex; gap: 6px; }
.map-filters__basemap-btn {
    flex: 1 1 0;
    border: 1px solid #D3D7DA;
    border-radius: 8px;
    background: #fff;
    color: #222;
    font: inherit;
    font-size: 12px;
    padding: 6px 8px;
    cursor: pointer;
}
.map-filters__basemap-btn:hover { background: #F2F4F5; }
.map-filters__basemap-btn:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters__basemap-btn[aria-pressed="true"] {
    border-color: #1B5E9B;
    border-width: 2px;
    background: #EAF1F8;
    font-weight: 600;
}
.map-filters__close {
    border: none;
    background: none;
    cursor: pointer;
    font-size: 20px;
    line-height: 1;
    color: #5A5F63;
    padding: 2px 6px;
}
.map-filters__close:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters__list { list-style: none; margin: 0; padding: 0; }
.map-filters__row { margin: 0 0 6px; }
.map-filters__toggle {
    display: flex;
    align-items: center;
    gap: 8px;
    width: 100%;
    text-align: left;
    border: 1px solid #D3D7DA;
    border-radius: 8px;
    background: #fff;
    color: #222;
    font: inherit;
    font-size: 14px;
    padding: 8px 10px;
    cursor: pointer;
}
.map-filters__toggle:hover { background: #F2F4F5; }
.map-filters__toggle:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters__toggle[aria-pressed="true"] {
    border-color: #1B5E9B;
    border-width: 2px;
    background: #EAF1F8;
    font-weight: 600;
}
/* La coche occupe sa place même vide : l'alignement des libellés ne bouge pas
   quand un filtre s'allume, sinon la liste entière tressaute au clic. */
.map-filters__mark {
    display: inline-block;
    min-width: 1em;
    font-weight: 700;
    color: #1B5E9B;
}
.map-filters__label { flex: 1; }
.map-filters__help {
    margin: 4px 0 0 calc(1em + 8px);
    font-size: 12px;
    line-height: 1.4;
    color: #5A5F63;
}
.map-filters__note {
    margin: 10px 0 0;
    font-size: 12px;
    line-height: 1.4;
    color: #5A5F63;
}
/* Story 12.2 : la note des filtres avances porte enfin son geste, et c'est un LIEN.
   Aucun ecouteur n'est pose, il fonctionne avec les scripts coupes, et il est
   masque par `filter-panel.js` quand le droit est constate : inviter a s'abonner
   quelqu'un dont l'abonnement est actif serait faux. Cible de 44 points de haut,
   comme les autres controles du panneau. La teinte est celle de `__clear`, deja
   servie sur ce fond. */
.map-filters__account { margin-top: 8px; }
.map-filters__account a {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    color: #1B5E9B;
    font-weight: 600;
    text-decoration: underline;
}
/* Annonce du nombre de filtres actifs : REGION VIVANTE, jamais du texte visible.
   La cle `map.filters.active_count_a11y` est nommee `_a11y` et l'est vraiment : elle
   n'a pas de forme plurielle, donc la rendre a l'ecran afficherait « 1 filtres
   actifs » en francais. Le canal VISUEL du compte est la pastille chiffree du bouton
   d'ouverture, et l'etat de chaque controle est porte par sa coche et son
   `aria-pressed`. */
.map-filters__count {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}
.map-filters__clear {
    margin-top: 8px;
    border: 1px solid #D3D7DA;
    border-radius: 8px;
    background: #fff;
    color: #1B5E9B;
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    padding: 6px 12px;
    cursor: pointer;
}
.map-filters__clear[hidden] { display: none; }
.map-filters__clear:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* --- Story 9.10 : la section PREMIUM, dessinée et verrouillée ---------------
   Elle est SÉPARÉE des gratuits par un filet, jamais par la seule couleur : le
   verrouillage se lit dans le cadenas, dans le nom accessible du bouton
   (« Premium, verrouillé ») et dans le titre de section, donc jamais dans la
   teinte seule. Le curseur reste `default` : rien ici ne s'actionne. */
.map-filters__advanced { border-top: 1px solid #E3E6E8; margin-top: 12px; }
.map-filters__advanced[hidden] { display: none; }
.map-filters__locked {
    cursor: default;
    background: #F7F8F9;
    border-style: dashed;
    color: #5A5F63;
}
.map-filters__locked:hover { background: #F7F8F9; }
.map-filters__locked .map-filters__mark { color: #5A5F63; }
.map-filters__locked:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters__value { font-variant-numeric: tabular-nums; color: #5A5F63; }

/* --- Story 11.6 : la section PREMIUM, DÉVERROUILLÉE ------------------------
   Un contrôle premium actif est dessiné EXACTEMENT comme un contrôle gratuit,
   parce qu'il en est un une fois le droit accordé : il n'y a donc aucune règle
   de teinte à écrire pour lui, seulement l'absence de la classe `--locked`.
   Le curseur, lui, est un `input[type=range]` NATIF : on ne lui retire ni son
   apparence de plateforme, ni son anneau de focus, ni son incrémentation au
   clavier, qui sont exactement ce qu'un contrôle réimplémenté perd en premier. */
.map-filters__row--advanced { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.map-filters__row--advanced .map-filters__toggle { flex: 1 1 100%; }
.map-filters__slider { flex: 1 1 auto; min-width: 120px; max-width: 100%; }
.map-filters__slider:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }
.map-filters__row--advanced .map-filters__help { flex: 1 1 100%; }
.map-filters__stores { display: flex; flex-direction: column; gap: 8px; margin-top: 10px; }
.map-filters__store {
    display: block;
    text-align: center;
    padding: 8px 12px;
    border-radius: 8px;
    text-decoration: none;
    font-weight: 600;
    font-size: 14px;
    border: 1px solid rgba(0, 0, 0, 0.2);
    color: #fff;
    background: #292c2f;
}
.map-filters__store--google { background: #1B5E9B; border-color: #1B5E9B; }
.map-filters__store:hover { opacity: 0.92; }
.map-filters__store:focus-visible { outline: 3px solid #1B5E9B; outline-offset: 2px; }

/* Passage de l'agent utilisateur-reel, 25/09/2026 : sur telephone, la barre de
   compte (#auth-nav, story 9.16) recouvrait le bas du panneau des filtres, et
   coupait la derniere ligne visible (« ... avec l'abonnement Premium »). Les
   deux n'avaient jamais ete calibres l'un contre l'autre : #auth-nav vient
   d'une story POSTERIEURE a celle qui a pose le decalage de 124 px de ce
   panneau (9.12, contre le selecteur de langue), et n'a jamais ete pris en
   compte ici.

   MESURE (Playwright, elementFromPoint, panneau ouvert) : a 390x844 comme a
   767px et moins, #auth-nav occupe la bande [127, 158] px depuis le bas de
   l'ecran (hauteur 31 px posee a bottom:127px, story 9.16). Le panneau
   s'arretait a bottom:124px : ses 34 derniers pixels rendus tombaient donc
   TOUJOURS sous la barre de compte, quel que soit le defilement interne. Le
   panneau est remonte de 42 px (127 + 31 + 8 px de garde = 166), et sa
   reserve de hauteur croit d'autant pour que son HAUT ne bouge pas (100 px
   sous la recherche, meme raisonnement que le commentaire ci-dessus). */
@media (max-width: 767px) {
    .map-filters,
    .map-filters-note-floating {
        bottom: 166px;
        max-height: min(60vh, 520px, calc(100vh - 266px));
    }
}

/* Story 9.12 (correctif A) : sur téléphone le sélecteur de langue se replie en
   pastille de 56 px posée 70 px au-dessus du bas, donc sa bande réservée est
   PLUS HAUTE que sur grand écran, pas moins. Les deux valeurs montent ensemble
   pour garder le panneau accroché à son bouton. */
@media (max-width: 480px) {
    /* 🚨 `env(safe-area-inset-bottom)` EST REPRIS ICI, ET CE N'EST PAS
       DÉCORATIF. Le gabarit pose le sélecteur à
       `calc(70px + env(safe-area-inset-bottom))`, et `templates/head.php` sert
       un `viewport-fit=cover` : sur un iPhone à encoche, l'encart est réel,
       donc la bande réservée MONTE d'autant. Un décalage fixe de 136 px aurait
       tenu au banc, où l'encart vaut zéro, et serait retombé sous le sélecteur
       sur l'appareil de tout le monde. Les deux valeurs héritent donc du même
       encart, et le chevauchement est nul quelle qu'en soit la taille. */
    .map-filters-toggle { bottom: calc(136px + env(safe-area-inset-bottom)); }
    /* La note flottante SUIT le panneau, elle ne se recalcule pas (11.5). */
    .map-filters,
    .map-filters-note-floating {
        /* Passage de l'agent utilisateur-reel, 25/09/2026 : meme correction
           que la regle >480px ci-dessus (#auth-nav recouvrait le bas du
           panneau), +42px ici aussi. A cette largeur #auth-nav est a
           calc(181px + env(...)), hauteur 31px : 181+31+8 de garde = 220. */
        bottom: calc(220px + env(safe-area-inset-bottom));
        /* Même réserve de 100 px sous la recherche, appliquée au décalage
           téléphone (220 + 100). */
        max-height: min(60vh, 520px, calc(100vh - 320px));
    }
}

/* Le panneau de détail occupe le bas de l'écran sous md : on remonte le bouton
   des filtres comme on remonte déjà celui de la légende. */
@media (max-width: 767px) {
    body.map-detail-open .map-filters-toggle { bottom: calc(38vh + 48px); }
}

/* Passage de l'agent utilisateur-réel, 25/09/2026 (point 5) : cibles tactiles
   mesurées sous la barre WCAG 2.5.5 (44 CSS px) au téléphone. Croix de la
   légende 21x22, croix des filtres 24x24, boutons « Sobre (gris) » / « En
   couleur » hauts de 34 : les quatre sont élargis en zone de TAP sans changer
   le rendu visuel de leur glyphe/texte (padding recalculé plutôt que taille de
   police, pour ne pas grossir la croix elle-même au-delà du nécessaire). */
@media (max-width: 767px) {
    .map-legend__close,
    .map-filters__close {
        display: inline-flex;
        align-items: center;
        justify-content: center;
        min-width: 44px;
        min-height: 44px;
        padding: 0;
    }
    .map-legend__basemap-btn,
    .map-filters__basemap-btn {
        min-height: 44px;
        display: flex;
        align-items: center;
        justify-content: center;
    }
}
