/* ============================================================================
   BarHunt — direction artistique « affiche sérigraphiée sur sous-bock ».

   Le site n'est pas un guide qui note des bars, c'est un carnet à cocher : la
   feuille imite une affiche imprimée en deux encres sur du carton de sous-bock.
     · deux encres seulement, et leur sémantique est fixe —
       le VERMILLON est la voix du site (progression, paliers, ce que
       l'utilisateur a fait), le VERT BOUTEILLE porte les données rapportées
       (les bars tels qu'OpenStreetMap les décrit, que le site ne juge pas) ;
     · aucun arrondi, des filets épais, des ombres portées dures et jamais
       floutées : une ombre floue est une lumière, une ombre dure est un
       décalage de passe d'impression ;
     · un grain fixe en surimpression sur toute la page ;
     · le titrage en Archivo Black capitales, le texte courant en Archivo.

   Deux thèmes : carton (clair) et encre (sombre). Le choix système fait foi,
   le bouton de la barre du haut le surcharge (data-theme sur <html>).

   Sommaire
     1.  Jetons                       7.  Composants du produit
     2.  Base                         8.  Cartons, panneaux, pages de texte
     3.  Barre du haut                9.  Comptes et authentification
     4.  Boutons et formulaires      10.  Pied de page
     5.  Bandeaux de message         11.  Mobile
     6.  Accueil                     12.  Mouvement réduit
   ============================================================================ */

/* --- Fontes auto-hébergées (la CSP interdit les CDN de fontes) ------------ */
@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/archivo-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Archivo";
    font-style: normal;
    font-weight: 400 700;
    font-display: swap;
    src: url("/fonts/archivo-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                   U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                   U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
    font-family: "Archivo Black";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/archivo-black-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                   U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                   U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
    font-family: "Archivo Black";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("/fonts/archivo-black-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                   U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                   U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ============================================================================
   1. Jetons
   ============================================================================ */
:root {
    /* --- Carton et encres --- */
    --paper:      #EDE3CE;
    --paper-sunk: #E0D4B9;   /* aplat en creux : panneaux, cartons de section */
    --paper-deep: #D2C2A0;   /* filets secondaires, séparateurs internes */
    --ink:        #17130D;
    --ink-soft:   #5C5340;
    --red:        #C6431C;   /* la voix du site : progression, paliers, actions */
    --red-wash:   rgba(198, 67, 28, .12);
    --blue:       #1F5E4A;   /* les données rapportées : ce qu'OSM décrit */
    --blue-wash:  rgba(31, 94, 74, .12);
    --on-red:     #F7F1E4;   /* texte posé sur un aplat vermillon */
    /* Les deux aplats appellent le même texte : en clair les deux encres sont
       sombres, en sombre les deux sont claires. Un seul jeton suffirait, mais
       deux noms disent l'intention à la lecture d'une règle isolée. */
    --on-blue:    #F7F1E4;

    /* Multiply en clair, screen en sombre : dans les deux cas le grain et les
       aplats se comportent comme une encre posée sur le carton. */
    --overprint: multiply;
    --grain: .4;
    color-scheme: light;

    /* --- Titrage et texte --- */
    --display: "Archivo Black", "Haettenschweiler", "Impact", "Arial Narrow Bold", sans-serif;
    --body:    "Archivo", ui-sans-serif, system-ui, "Segoe UI", Roboto, sans-serif;
    --mono:    "Consolas", ui-monospace, "SF Mono", Menlo, monospace;

    /* Approche du titrage : Archivo Black est un gras large, il ne demande pas
       l'ouverture d'un condensé — mais elle reste inversement proportionnelle
       au corps, les fûts se collent d'autant plus que la taille baisse. */
    --track-xl: .004em;   /* 3 rem et plus */
    --track-l:  .014em;   /* 2 à 3 rem */
    --track-m:  .024em;   /* 1,2 à 2 rem */
    --track-s:  .04em;    /* titrage en petit corps */
    --track-caps: .14em;  /* capitales d'étiquette, corps < 1 rem */

    /* --- Trait et ombre --- */
    --rail: 3px;                        /* épaisseur du filet courant */
    --rail-thick: 6px;                  /* bandeau de section, bord de tuile */
    --drop: 5px 5px 0 var(--ink);       /* ombre portée dure, jamais floutée */
    --drop-sm: 3px 3px 0 var(--ink);

    /* --- Alias littéraux ---
       Le style de carte (public/map/style.json, étape 7) et tout script qui
       colore un rendu lisent ces variables via
       getComputedStyle().getPropertyValue(), qui ne résout PAS var(). Elles
       doivent donc rester des valeurs littérales, redéfinies à l'identique dans
       chaque thème. Ce ne sont pas des doublons : ce sont les seules entrées
       lisibles depuis JavaScript. --line n'habille rien ici, c'est la grille
       des rendus, plus douce que le filet d'encre. */
    --bg: #EDE3CE; --panel: #E0D4B9; --line: #C2AF8C;
    --text: #17130D; --muted: #5C5340;
    --accent: #C6431C; --accent2: #1F5E4A;
}

/* Thème encre : préférence système… */
@media (prefers-color-scheme: dark) {
    :root {
        --paper: #16130D; --paper-sunk: #211B12; --paper-deep: #3A3122;
        --ink: #F0E8D6; --ink-soft: #9C917A;
        --red: #FF6A3D; --red-wash: rgba(255, 106, 61, .14);
        --blue: #6FC9A3; --blue-wash: rgba(111, 201, 163, .14);
        --on-red: #16130D; --on-blue: #16130D;
        --overprint: screen; --grain: .3; color-scheme: dark;

        --bg: #16130D; --panel: #211B12; --line: #3A3122;
        --text: #F0E8D6; --muted: #9C917A;
        --accent: #FF6A3D; --accent2: #6FC9A3;
    }
}
/* …puis choix explicite du lecteur, qui doit l'emporter dans les deux sens. */
:root[data-theme="dark"] {
    --paper: #16130D; --paper-sunk: #211B12; --paper-deep: #3A3122;
    --ink: #F0E8D6; --ink-soft: #9C917A;
    --red: #FF6A3D; --red-wash: rgba(255, 106, 61, .14);
    --blue: #6FC9A3; --blue-wash: rgba(111, 201, 163, .14);
    --on-red: #16130D; --on-blue: #16130D;
    --overprint: screen; --grain: .3; color-scheme: dark;

    --bg: #16130D; --panel: #211B12; --line: #3A3122;
    --text: #F0E8D6; --muted: #9C917A;
    --accent: #FF6A3D; --accent2: #6FC9A3;
}
:root[data-theme="light"] {
    --paper: #EDE3CE; --paper-sunk: #E0D4B9; --paper-deep: #D2C2A0;
    --ink: #17130D; --ink-soft: #5C5340;
    --red: #C6431C; --red-wash: rgba(198, 67, 28, .12);
    --blue: #1F5E4A; --blue-wash: rgba(31, 94, 74, .12);
    --on-red: #F7F1E4; --on-blue: #F7F1E4;
    --overprint: multiply; --grain: .4; color-scheme: light;

    --bg: #EDE3CE; --panel: #E0D4B9; --line: #C2AF8C;
    --text: #17130D; --muted: #5C5340;
    --accent: #C6431C; --accent2: #1F5E4A;
}

/* ============================================================================
   2. Base

   Contrastes (WCAG AA) : sur les deux thèmes, --ink et --ink-soft passent le
   4.5:1 du texte courant. Le vermillon sur carton clair plafonne à 3.9:1 : il
   est réservé au titrage, aux aplats et aux filets, jamais à du texte courant
   en petit corps — d'où les liens de contenu en encre soulignée de vermillon
   plutôt qu'en vermillon plein.
   ============================================================================ */
*, *::before, *::after { box-sizing: border-box; }

body {
    margin: 0;
    font-family: var(--body);
    font-size: 1rem;
    background: var(--paper);
    color: var(--ink);
    line-height: 1.55;
    overflow-x: hidden;
    -webkit-font-smoothing: antialiased;
}

/* Grain de sérigraphie : une seule couche fixe, en surimpression sur toute la
   page (tiroir et bandeaux compris : c'est la feuille entière qui est imprimée).
   z-index sous les 1000 du tiroir de carte, au-dessus de tout le reste. */
body::after {
    content: "";
    position: fixed; inset: 0;
    z-index: 900; pointer-events: none;
    opacity: var(--grain);
    mix-blend-mode: var(--overprint);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E");
}

a { color: inherit; text-decoration: none; }
::selection { background: var(--red); color: var(--on-red); }

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

h1, h2, h3 {
    font-family: var(--display);
    font-weight: 400;
    text-transform: uppercase;
    text-wrap: balance;
    line-height: 1.02;
}
h1 { font-size: 2.4rem; letter-spacing: var(--track-l); }
h2 { font-size: 1.5rem; letter-spacing: var(--track-m); }
h3 { font-size: 1.15rem; letter-spacing: var(--track-m); }

strong { font-weight: 700; }
em { font-style: italic; }

/* Les chiffres s'alignent partout où ils se comparent d'une ligne à l'autre. */
.stats strong, .progress-value, .tally-value, .bar-row-state, .rating {
    font-variant-numeric: tabular-nums;
}

.container { max-width: 900px; margin: 0 auto; padding: 2.5rem 1.5rem 3rem; }

/* Titre de section posé sous un filet épais : le bandeau est la structure de
   l'affiche, il découpe la page en placards successifs. */
.container > h1,
.container > h2 {
    margin: 2.6rem 0 1.1rem;
    padding-top: .9rem;
    border-top: var(--rail-thick) solid var(--ink);
}
.container > h1 { font-size: clamp(1.9rem, 5vw, 2.6rem); }
.container > h1:first-child,
.container > h2:first-child { margin-top: 0; }

/* Lien dans un bloc de texte : encre soulignée de vermillon. Le soulignement
   porte la couleur, le texte garde son contraste. Le :not(.btn) n'est pas une
   précaution de style mais une nécessité : ce sélecteur descendant l'emporte
   sur .btn, et un bouton posé dans un paragraphe y perdrait son ombre dure. */
.container p a:not(.btn), .page a:not(.btn), .notice a:not(.btn),
.info a:not(.btn), .drawer-body a:not(.btn), .account-row a:not(.btn) {
    font-weight: 700;
    box-shadow: inset 0 -.14em 0 var(--red);
}
.container p a:not(.btn):hover, .page a:not(.btn):hover, .notice a:not(.btn):hover,
.info a:not(.btn):hover, .drawer-body a:not(.btn):hover,
.account-row a:not(.btn):hover { background: var(--red-wash); }

/* Masquage visuel sans retrait du flux d'accessibilité (cases à cocher,
   étiquettes de notation) : le lecteur d'écran lit, l'œil ne voit rien. */
.sr-only {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}

/* ============================================================================
   3. Barre du haut
   ============================================================================ */
.topbar {
    display: flex; justify-content: space-between; align-items: center;
    gap: 1rem;
    padding: .85rem clamp(1rem, 4vw, 1.75rem);
    background: var(--paper);
    border-bottom: var(--rail) solid var(--ink);
    position: relative; z-index: 160;
}
.logo {
    font-family: var(--display); font-size: 1.35rem;
    text-transform: uppercase; letter-spacing: var(--track-m);
}
.logo span { color: var(--red); }

.topbar-brand { display: flex; align-items: center; gap: 1.75rem; }
.topbar-actions { display: flex; align-items: center; gap: .5rem; }

.topbar nav { display: flex; align-items: center; gap: 1.25rem; }
.topbar nav a {
    display: inline-block; padding: .3rem 0;
    font-size: .82rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps);
}
.nav-link { color: var(--ink); }
/* Soulignement d'encre au survol, tiré depuis la gauche. */
.topbar nav a.nav-link {
    background-image: linear-gradient(var(--red), var(--red));
    background-repeat: no-repeat; background-position: 0 100%;
    background-size: 0 var(--rail);
    transition: background-size .18s steps(4);
}
.topbar nav a.nav-link:hover { background-size: 100% var(--rail); }
.topbar nav .inline { display: inline-block; }

.topbar-actions .inline { display: inline-flex; }
/* Les liens de compte vivent hors du <nav> (ils tiennent la droite de la barre
   en desktop) : ils ne reçoivent donc rien de `.topbar nav a` et portent leur
   propre étiquetage. */
.topbar-actions .nav-link {
    font-size: .82rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps);
}
.topbar-actions .nav-link:hover { color: var(--red); }
.account-link { white-space: nowrap; }
/* Doublon du compte réservé au menu déroulant mobile. */
.topbar nav a.nav-account,
.topbar nav form.nav-account { display: none; }

/* Bascule carton / encre */
.theme-toggle {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; flex: none;
    background: var(--paper); color: var(--ink);
    border: 2px solid var(--ink); cursor: pointer; padding: 0;
    transition: transform .1s steps(2), box-shadow .1s steps(2);
}
.theme-toggle:hover { box-shadow: var(--drop-sm); transform: translate(-2px, -2px); }
.theme-toggle:active { box-shadow: none; transform: translate(0, 0); }
.theme-toggle svg { width: 17px; height: 17px; display: block; }
/* Une seule icône visible : celle du thème vers lequel le bouton bascule.
   Sur carton on propose l'encre (lune), sur encre on propose le carton (soleil). */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-sun { display: block; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

/* Hamburger : réservé au mobile. */
.nav-toggle {
    display: none; flex-direction: column; justify-content: center; gap: 5px;
    background: none; border: 0; cursor: pointer; padding: .55rem; min-height: 44px;
}
.nav-toggle-bar {
    width: 24px; height: var(--rail); background: var(--ink);
    transition: transform .2s, opacity .2s;
}
.topbar.nav-open .nav-toggle-bar:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.topbar.nav-open .nav-toggle-bar:nth-child(2) { opacity: 0; }
.topbar.nav-open .nav-toggle-bar:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

/* ============================================================================
   4. Boutons, liens d'action, formulaires
   ============================================================================ */
.btn {
    /* inline-flex plutôt qu'inline-block : la hauteur de frappe minimale de
       44 px ne doit pas laisser le libellé collé en haut du carton. */
    display: inline-flex; align-items: center; justify-content: center;
    text-align: center; cursor: pointer;
    padding: .7rem 1.2rem; min-height: 44px;
    background: var(--red); color: var(--on-red);
    border: var(--rail) solid var(--ink); border-radius: 0;
    font-family: var(--display); font-size: .84rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
    line-height: 1.55;
    box-shadow: var(--drop-sm);
    transition: transform .1s steps(2), box-shadow .1s steps(2);
}
.btn:hover { box-shadow: var(--drop); transform: translate(-2px, -2px); }
.btn:active { box-shadow: none; transform: translate(3px, 3px); }
.btn.big, .btn.block { display: flex; width: 100%; }
.btn.big { padding: .85rem; font-size: .95rem; }
.btn.ghost { background: transparent; color: var(--ink); }
.btn.ghost:hover { background: var(--red-wash); }
/* Bouton de données : tout ce qui manipule l'existant rapporté (filtrer un
   type de bar, ouvrir une fiche) parle en seconde encre, pas en voix du site. */
.btn.data { background: var(--blue); color: var(--on-blue); }
.btn.small, .small-btn {
    padding: .42rem .8rem; min-height: 36px; font-size: .74rem;
    border-width: 2px; box-shadow: 2px 2px 0 var(--ink);
}
.btn.small:hover, .small-btn:hover { box-shadow: var(--drop-sm); }
.btn.danger { background: transparent; color: var(--red); border-color: var(--red); }
.btn.danger:hover { background: var(--red); color: var(--on-red); }
.btn[disabled], .btn[aria-disabled="true"] {
    background: var(--paper-deep); color: var(--ink-soft);
    box-shadow: none; cursor: not-allowed; transform: none;
}

.link {
    background: none; border: 0; padding: 0; cursor: pointer;
    font: inherit; font-weight: 700; color: var(--ink);
    box-shadow: inset 0 -.14em 0 var(--red);
}
.link:hover { background: var(--red-wash); }
.inline { display: inline; }
.actions { display: flex; flex-wrap: wrap; gap: .6rem; align-items: center; }

input[type="search"], input[type="text"], input[type="email"], input[type="url"],
input[type="password"], input[type="number"], input[type="date"], select, textarea {
    font: inherit;
    padding: .6rem .7rem; min-height: 44px;
    background: var(--paper); color: var(--ink);
    border: var(--rail) solid var(--ink); border-radius: 0;
}
textarea { resize: vertical; min-height: 8rem; }
select { cursor: pointer; }
input::placeholder, textarea::placeholder { color: var(--ink-soft); opacity: .8; }

/* Le code à 6 chiffres est lu caractère par caractère : on l'espace et on le
   met au corps du titrage, c'est le seul champ qu'on saisit sans se relire. */
input.code {
    font-family: var(--display); font-size: 1.5rem;
    letter-spacing: .5em; text-align: center;
    padding: .6rem .2rem .6rem .7rem;
}

.field-hint { display: block; margin-top: .35rem; font-size: .78rem; color: var(--ink-soft); }

.back {
    display: inline-block; margin-bottom: 1.5rem;
    font-size: .78rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps);
    color: var(--ink-soft);
}
.back:hover { color: var(--red); }

/* ============================================================================
   5. Bandeaux de message

   La confirmation porte la voix du site (filet vermillon), l'information reste
   en seconde encre, l'erreur est un aplat plein qu'on ne peut pas rater.
   ============================================================================ */
.notice, .error, .info {
    margin: 0 0 1.75rem; padding: .85rem 1.1rem;
    border: var(--rail) solid var(--ink);
    font-weight: 600;
}
.notice { background: var(--paper-sunk); border-left: var(--rail-thick) solid var(--red); }
.info   { background: var(--paper-sunk); border-left: var(--rail-thick) solid var(--blue); }
.error  { background: var(--red); color: var(--on-red); }
.error a { color: inherit; text-decoration: underline; text-underline-offset: 3px; }

.muted { color: var(--ink-soft); }
.small { font-size: .82rem; }

/* ============================================================================
   6. Accueil — manchette et compteurs
   ============================================================================ */
.hero { padding: .5rem 0 2rem; }
.hero h1 {
    margin: 0 0 1rem;
    font-size: clamp(2.4rem, 8.5vw, 4.6rem);
    line-height: .95;
    letter-spacing: var(--track-xl);
    /* Hors-repérage : la passe vermillon est décalée sous la passe d'encre,
       comme une affiche imprimée trop vite. */
    --offx: -6px; --offy: 5px;
    text-shadow: var(--offx) var(--offy) 0 var(--red);
    animation: hors-reperage .5s cubic-bezier(.2, .8, .3, 1) both;
}
@keyframes hors-reperage {
    from { text-shadow: 0 0 0 var(--red); }
    to   { text-shadow: var(--offx) var(--offy) 0 var(--red); }
}
.hero p {
    max-width: 46ch; margin: 0 0 1.75rem;
    font-size: 1.05rem; color: var(--ink-soft);
}
.hero p strong { color: var(--ink); box-shadow: inset 0 -.45em 0 var(--red-wash); }
.hero .actions { margin-top: 1.5rem; }

/* Rangée de compteurs séparés par des filets, sans carton : ce sont des
   chiffres bruts, pas des tuiles. */
.stats { display: flex; flex-wrap: wrap; gap: 0; border-top: var(--rail) solid var(--ink); }
.stats div { display: flex; flex-direction: column; padding: .9rem 1.5rem .9rem 0; }
.stats div + div { padding-left: 1.5rem; border-left: var(--rail) solid var(--ink); }
.stats strong {
    font-family: var(--display); font-weight: 400;
    font-size: clamp(2rem, 5vw, 2.8rem); line-height: 1;
    letter-spacing: var(--track-l); color: var(--red);
}
.stats span {
    margin-top: .3rem; font-size: .72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}

/* ============================================================================
   7. Composants du produit

   Le vocabulaire des écrans à venir (liste, carte, progression, avis). Chaque
   composant est décrit avec le balisage qu'il attend : les étapes suivantes
   réutilisent ces classes au lieu d'en inventer.
   ============================================================================ */

/* --- 7.1 Barre de progression -------------------------------------------
   L'élément signature du produit : un aplat d'encre qui avance, à angles vifs,
   le pourcentage en Archivo Black par-dessus. La largeur de .progress-fill est
   posée en style inline par la vue (seule donnée réellement dynamique).

   <div class="progress" role="img" aria-label="12 bars sur 240, soit 5 %">
       <div class="progress-fill" style="width:5%"></div>
       <span class="progress-value">5 %</span>
   </div>

   Le pourcentage est en position absolue au-dessus de la barre, pas dans le
   remplissage : à 2 % il resterait illisible, coincé dans un trait d'encre. */
.progress {
    position: relative;
    height: 44px; margin: 1.25rem 0;
    background: var(--paper-sunk);
    border: var(--rail) solid var(--ink);
    box-shadow: var(--drop-sm);
    overflow: hidden;
}
.progress-fill {
    height: 100%;
    background: var(--red);
    /* Le remplissage est bordé à droite d'un filet d'encre : la limite de la
       passe reste nette même quand l'aplat touche le bord du carton. */
    border-right: var(--rail) solid var(--ink);
    transition: width .4s steps(12);
}
.progress-fill[style*="width:100%"], .progress-fill[style*="width: 100%"] { border-right: 0; }
.progress-value {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--display); font-size: 1.05rem;
    letter-spacing: var(--track-s); color: var(--ink);
    /* Halo de carton : le chiffre reste lisible qu'il soit sur l'aplat
       vermillon ou sur le carton nu, sans changer de couleur en cours de
       barre. Quatre décalages d'un pixel, jamais de flou. */
    text-shadow: 1px 1px 0 var(--paper-sunk), -1px 1px 0 var(--paper-sunk),
                 1px -1px 0 var(--paper-sunk), -1px -1px 0 var(--paper-sunk);
}
.progress.small { height: 28px; margin: .6rem 0; }
.progress.small .progress-value { font-size: .8rem; }

/* Le compteur qui accompagne la barre : « 12 / 240 ». */
.tally { display: flex; align-items: baseline; gap: .5rem; }
.tally-value {
    font-family: var(--display); font-weight: 400;
    font-size: clamp(2rem, 6vw, 3.2rem); line-height: 1;
    letter-spacing: var(--track-l); color: var(--red);
}
.tally-total { font-size: 1.1rem; font-weight: 700; color: var(--ink-soft); }
.tally-label {
    font-size: .72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}

/* --- 7.2 Case à cocher « fait / à faire » --------------------------------
   Format affiche : un carré évidé au filet vert (le bar existe, données
   rapportées) qui devient un aplat vermillon une fois coché (la voix du site :
   c'est l'utilisateur qui a agi). La case native est masquée mais reste la
   cible réelle du clavier et du lecteur d'écran.

   <label class="tick">
       <input type="checkbox" name="done" ...>
       <span class="tick-box" aria-hidden="true"></span>
       <span class="tick-text">Fait</span>
   </label>                                                                  */
.tick {
    display: inline-flex; align-items: center; gap: .7rem;
    cursor: pointer; min-height: 44px;
    /* Le libellé ne doit pas se sélectionner quand on coche vite plusieurs
       lignes de la liste à la suite. */
    -webkit-user-select: none; user-select: none;
}
.tick input {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
}
.tick-box {
    position: relative; flex: none;
    width: 30px; height: 30px;
    background: var(--paper);
    border: var(--rail) solid var(--blue);
    box-shadow: var(--drop-sm);
    transition: transform .1s steps(2), box-shadow .1s steps(2);
}
.tick:hover .tick-box { background: var(--blue-wash); }
.tick input:focus-visible + .tick-box { outline: var(--rail) solid var(--red); outline-offset: 2px; }
.tick input:checked + .tick-box,
.tick-box.is-on {
    background: var(--red); border-color: var(--ink);
}
/* La coche est tracée en deux barres, pas en glyphe : une police peut manquer,
   deux rectangles tournés ne manquent jamais. */
.tick input:checked + .tick-box::before,
.tick input:checked + .tick-box::after,
.tick-box.is-on::before,
.tick-box.is-on::after {
    content: ""; position: absolute; background: var(--on-red);
}
.tick input:checked + .tick-box::before,
.tick-box.is-on::before {
    width: 4px; height: 10px; left: 6px; top: 10px; transform: rotate(-45deg);
}
.tick input:checked + .tick-box::after,
.tick-box.is-on::after {
    width: 4px; height: 18px; left: 15px; top: 2px; transform: rotate(35deg);
}

/* Variante bouton d'envoi : dans la todo-list, la case EST le bouton qui poste
   le formulaire. Un <input type="checkbox"> n'envoie rien tant qu'on ne valide
   pas, et une liste où il faut cocher PUIS valider n'est plus une liste à
   cocher. L'état vient alors de .is-on (posé par le serveur, puis par le
   rehaussement fetch) et non de :checked, et il est annoncé par aria-pressed. */
button.tick {
    background: none; border: 0; padding: 0;
    font: inherit; color: inherit;
}
button.tick:focus-visible { outline: none; }
button.tick:focus-visible .tick-box { outline: var(--rail) solid var(--red); outline-offset: 2px; }
button.tick:active .tick-box { box-shadow: none; transform: translate(2px, 2px); }
button.tick[disabled] { cursor: wait; }
.tick input:disabled + .tick-box { border-color: var(--ink-soft); box-shadow: none; }
.tick input:disabled ~ .tick-text { color: var(--ink-soft); }
.tick-text {
    font-size: .74rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.tick input:checked ~ .tick-text { color: var(--ink); }
/* Case seule, sans libellé visible (une par ligne de liste). */
.tick.bare { gap: 0; }

/* --- 7.3 Ligne de liste --------------------------------------------------
   La todo-list : case, nom, quartier et type (données rapportées, seconde
   encre), état à droite. Une ligne faite se signale par son filet gauche
   vermillon, pas par un fond : la liste doit rester lisible d'un bloc.

   <li class="bar-row is-done">
       <label class="tick bare">…</label>
       <a class="bar-row-name" href="…">Le Saxo</a>
       <p class="bar-row-meta"><span>Saint-Marc</span><span>Bar</span></p>
       <span class="bar-row-state">Fait</span>
   </li>                                                                     */
.bar-list { list-style: none; margin: 1.5rem 0; padding: 0; border-top: var(--rail) solid var(--ink); }
.bar-row {
    display: flex; align-items: center; gap: 1rem;
    padding: .8rem 1rem;
    background: var(--paper-sunk);
    border: var(--rail) solid var(--ink); border-top: 0;
    border-left: var(--rail-thick) solid var(--paper-deep);
}
.bar-row.is-done { border-left-color: var(--red); }
.bar-row-body { flex: 1; min-width: 0; }
.bar-row-name {
    display: block;
    font-family: var(--display); font-size: 1.05rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
    overflow-wrap: anywhere;
}
a.bar-row-name:hover { color: var(--red); }
.bar-row-meta {
    display: flex; flex-wrap: wrap; gap: .15rem .5rem; margin: .15rem 0 0;
    font-size: .74rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
/* Séparateur « · » entre les infos, jamais en bout de ligne. */
.bar-row-meta span + span::before { content: "· "; }
.bar-row-state {
    flex: none;
    font-size: .7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.bar-row.is-done .bar-row-state { color: var(--red); }
.bar-row-aside { flex: none; display: flex; align-items: center; gap: .6rem; }

/* Indicateur d'avis : la liste dit qu'un avis existe, elle ne le montre pas.
   Quatre notes par ligne noieraient la seule information qu'on y cherche du
   pouce ; le détail est à un clic, sur la fiche — d'où le lien. Filet vermillon
   parce que c'est l'utilisateur qui parle, pas OpenStreetMap. */
.rated-flag {
    flex: none; padding: .12rem .4rem;
    font-size: .65rem; font-weight: 700; white-space: nowrap;
    text-transform: uppercase; letter-spacing: var(--track-caps);
    color: var(--red); border: 2px solid var(--red);
}
.rated-flag:hover { background: var(--red); color: var(--on-red); }

/* Bar disparu d'OSM ou fermé : il ne compte plus dans l'objectif et ne doit pas
   se lire comme les autres. Il reste cliquable et décochable pour autant —
   quelqu'un a pu le cocher avant sa disparition, et lui retirer sa ligne
   effacerait une soirée qui a bien eu lieu. */
.bar-row.is-gone { opacity: .75; }
.bar-row.is-gone .bar-row-name { text-decoration: line-through; text-decoration-thickness: 2px; }
.bar-row-meta .is-gone-flag { color: var(--red); }

/* Bouton « à faire » de la ligne. En seconde encre : mettre un bar de côté,
   c'est manipuler l'inventaire, pas déclarer ce qu'on a fait. */
.wish-btn {
    display: inline-flex; align-items: center; justify-content: center;
    flex: none; width: 36px; height: 36px; padding: 0;
    background: var(--paper); color: var(--ink-soft);
    border: 2px solid var(--blue); border-radius: 0;
    font-size: 1rem; line-height: 1; cursor: pointer;
    box-shadow: 2px 2px 0 var(--ink);
    transition: transform .1s steps(2), box-shadow .1s steps(2);
}
.wish-btn:hover { background: var(--blue-wash); color: var(--ink); box-shadow: var(--drop-sm); }
.wish-btn:active { box-shadow: none; transform: translate(2px, 2px); }
.wish-btn.is-on { background: var(--blue); color: var(--on-blue); border-color: var(--ink); }
.wish-btn[disabled] { cursor: wait; }

/* Les deux formulaires de bascule d'une ligne ne sont que des enveloppes : ils
   ne doivent pas peser dans la mise en page de la ligne. */
.row-form { display: flex; flex: none; margin: 0; }

/* Étiquette de donnée rapportée : type de bar, quartier, horaire. Toujours en
   seconde encre — c'est OSM qui parle, pas le site. */
.chip {
    display: inline-block;
    font-size: .68rem; font-weight: 700; white-space: nowrap;
    padding: .2rem .55rem;
    background: var(--paper); color: var(--ink-soft);
    border: 2px solid var(--blue);
    text-transform: uppercase; letter-spacing: var(--track-caps);
}
.chip.is-strong { background: var(--blue); color: var(--on-blue); border-color: var(--ink); }

/* --- 7.4 Panneau latéral (tiroir de la carte) ----------------------------
   Un tiroir plaqué au bord droit, pas une bulle flottante : une bulle ancrée
   au marqueur se replace à chaque déplacement de la carte et masque la zone
   qu'on vient de regarder. Le tiroir garde sa position, la carte reste
   entièrement lisible à sa gauche.

   <aside class="drawer" id="barDrawer" hidden aria-labelledby="drawerTitle">…

   Le repli est piloté par l'attribut `hidden` (état réel, lu par le lecteur
   d'écran) ; la classe .is-open n'ajoute que le glissement. */
.drawer {
    position: fixed; top: 0; right: 0; bottom: 0; z-index: 1000;
    width: min(400px, 100vw);
    display: flex; flex-direction: column;
    background: var(--paper);
    border-left: var(--rail-thick) solid var(--ink);
    box-shadow: -8px 0 0 var(--red);
    transform: translateX(0);
    transition: transform .2s steps(4);
    overflow: hidden;
}
.drawer[hidden] { display: none; }
.drawer.is-closing { transform: translateX(100%); }
.drawer-head {
    display: flex; align-items: flex-start; gap: 1rem;
    padding: 1.1rem 1.2rem;
    border-bottom: var(--rail) solid var(--ink);
    background: var(--paper-sunk);
}
.drawer-head h2 { margin: 0; flex: 1; min-width: 0; font-size: 1.2rem; overflow-wrap: anywhere; }
.drawer-close {
    flex: none; width: 34px; height: 34px;
    display: flex; align-items: center; justify-content: center;
    background: var(--paper); color: var(--ink);
    border: 2px solid var(--ink); cursor: pointer; font-size: 1rem;
}
.drawer-close:hover { background: var(--red); color: var(--on-red); }
.drawer-body { flex: 1; overflow-y: auto; padding: 1.2rem; }
.drawer-body > * + * { margin-top: 1rem; }
.drawer-foot {
    padding: 1rem 1.2rem;
    border-top: var(--rail) solid var(--ink);
    background: var(--paper-sunk);
}

/* --- 7.5 Notation sur 5 --------------------------------------------------
   Cinq blocs carrés remplis à l'encre. En lecture, la note est une image
   accessible ; en saisie, cinq boutons radio empilés sous les blocs.

   Lecture : <span class="rating" role="img" aria-label="Ambiance : 3 sur 5">
                 <b class="is-on"></b><b class="is-on"></b><b class="is-on"></b><b></b><b></b>
             </span>                                                          */
.rating { display: inline-flex; gap: 4px; vertical-align: middle; }
.rating b {
    width: 18px; height: 18px;
    background: var(--paper);
    border: 2px solid var(--ink);
}
.rating b.is-on { background: var(--red); }
.rating-line { display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.rating-label {
    font-size: .7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
/* L'axe et sa précision forment un bloc : « Prix » seul se comprend comme
   « cher / pas cher », alors qu'on note ici le rapport qualité-prix. */
.rating-head { display: flex; flex-direction: column; gap: .15rem; min-width: 0; }
.rating-hint { font-size: .78rem; color: var(--ink-soft); }

/* Saisie : les radios sont masquées, les blocs deviennent la cible. Pas de
   remplissage « jusqu'à » au survol comme des étoiles — cinq cases discrètes
   se lisent mieux qu'un dégradé, et se rectifient sans viser. */
.rating-input { display: inline-flex; gap: 6px; }
.rating-input label { cursor: pointer; }
.rating-input input {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; overflow: hidden; clip-path: inset(50%);
}
.rating-input span {
    display: block; width: 30px; height: 30px;
    background: var(--paper);
    border: var(--rail) solid var(--ink);
    box-shadow: 2px 2px 0 var(--ink);
}
.rating-input label:hover span { background: var(--red-wash); }
.rating-input input:checked + span { background: var(--red); }
.rating-input input:focus-visible + span { outline: var(--rail) solid var(--red); outline-offset: 2px; }

/* Case « pas noté », en tête de groupe. Elle existe parce qu'un groupe de
   boutons radio ne se vide plus une fois coché : sans elle, une note posée par
   erreur serait définitive. Elle se lit comme un cran d'arrêt et non comme une
   sixième note — creusée dans le carton, sans ombre, barrée d'un filet. */
.rating-none { margin-right: .5rem; }
.rating-none span {
    display: flex; align-items: center; justify-content: center;
    background: var(--paper-deep); box-shadow: none;
}
.rating-none span::before { content: ""; width: 12px; height: 3px; background: var(--ink-soft); }
.rating-none:hover span { background: var(--paper-deep); }
/* La sélection ne la remplit PAS de vermillon : « pas noté » n'est pas une
   note, et l'encre du site ne dit que ce que l'utilisateur a fait. */
.rating-none input:checked + span { background: var(--paper-deep); box-shadow: 2px 2px 0 var(--ink); }
.rating-none input:checked + span::before { background: var(--ink); }

/* --- 7.6 Bandeau de palier ----------------------------------------------
   Un franchissement s'annonce sur toute la largeur, en aplat plein : c'est le
   seul moment où le site hausse la voix.

   <p class="milestone"><strong>Palier franchi</strong> 25 % des bars de Rouen</p> */
.milestone {
    /* Débord hors du conteneur de 900 px : le bandeau touche les deux bords de
       l'écran, comme une bande d'affiche collée en travers. */
    margin-left: calc(-50vw + 50%); margin-right: calc(-50vw + 50%);
    margin-top: 2rem; margin-bottom: 2rem;
    padding: 1.1rem clamp(1.5rem, 5vw, 4rem);
    background: var(--red); color: var(--on-red);
    border-top: var(--rail-thick) solid var(--ink);
    border-bottom: var(--rail-thick) solid var(--ink);
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .3rem 1rem;
    font-family: var(--display); font-size: 1rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
}
.milestone strong {
    font-weight: 400; font-size: 1.5rem; letter-spacing: var(--track-m);
    /* La mention du palier est détourée en négatif de carton pour se détacher
       de l'aplat sur lequel elle est posée. */
    background: var(--on-red); color: var(--red); padding: .1rem .5rem;
}
.milestone.is-quiet { background: var(--paper-sunk); color: var(--ink); }
.milestone.is-quiet strong { background: var(--red); color: var(--on-red); }

/* --- 7.7 Marqueur de carte -----------------------------------------------
   Carré à angles vifs, ombre dure. L'état de base est le restant (carré évidé
   au filet vert, données rapportées) ; .is-done le remplit de vermillon, parce
   que c'est l'utilisateur qui l'a rempli. Posé par MapLibre en marqueur HTML.

   <button class="pin" type="button" aria-label="Le Saxo, à faire"></button>
   <button class="pin is-done" type="button" aria-label="Le Saxo, fait"></button> */
.pin {
    width: 20px; height: 20px; padding: 0;
    background: var(--paper);
    border: var(--rail) solid var(--blue);
    box-shadow: 3px 3px 0 var(--ink);
    cursor: pointer;
    transition: transform .1s steps(2), box-shadow .1s steps(2);
}
.pin.is-done { background: var(--red); border-color: var(--ink); }
.pin:hover, .pin.is-active {
    transform: translate(-2px, -2px);
    box-shadow: 5px 5px 0 var(--ink);
}
/* Le marqueur sélectionné garde le carton visible sous l'aplat : un carré
   plein cerné d'un filet de carton se repère dans une grappe de marqueurs. */
.pin.is-active { outline: 2px solid var(--paper); outline-offset: 0; }
.pin.is-cluster {
    width: auto; height: auto; min-width: 30px;
    padding: .15rem .3rem;
    background: var(--blue); color: var(--on-blue); border-color: var(--ink);
    font-family: var(--display); font-size: .74rem; line-height: 1.2;
}

/* Légende de carte : les deux états du marqueur, nommés. */
.pin-legend { display: flex; flex-wrap: wrap; gap: 1rem; align-items: center; }
.pin-legend span {
    display: inline-flex; align-items: center; gap: .5rem;
    font-size: .7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}

/* --- 7.8 Barre de filtres et décompte ------------------------------------
   Les filtres manipulent l'inventaire rapporté : le carton est bordé de la
   seconde encre, pas de la voix du site. Chaque champ porte son étiquette au-
   dessus plutôt qu'un placeholder — l'étiquette disparue à la saisie est
   exactement ce qu'on cherche quand on revient sur une liste déjà filtrée.   */
.filters {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: .9rem 1rem;
    margin: 1.6rem 0 1.1rem; padding: 1rem 1.1rem;
    background: var(--paper-sunk);
    border: var(--rail) solid var(--ink);
    border-left: var(--rail-thick) solid var(--blue);
}
.filter-field { display: flex; flex-direction: column; gap: .3rem; flex: 1 1 11rem; }
.filter-field > span {
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.filter-field select { width: 100%; }
.filter-tick { flex: 1 1 100%; }
.filter-actions { display: flex; flex-wrap: wrap; gap: .6rem; flex: none; }

/* Le décompte de ce qui est affiché. Quand un filtre est en place, il porte un
   filet vermillon et le dit en toutes lettres : un « 3 / 6 » sans contexte se
   lit comme une progression qui aurait reculé. */
.list-summary {
    display: flex; flex-wrap: wrap; align-items: baseline; gap: .2rem .9rem;
    margin: 0; font-size: .82rem; color: var(--ink-soft);
}
.list-summary strong { font-size: .95rem; color: var(--ink); }
.list-summary.is-filtered { padding-left: .8rem; border-left: var(--rail-thick) solid var(--red); }
.list-summary-flag {
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--red);
}

/* En-tête d'avancement de la liste : le compteur et la barre dans un même
   carton, sans marge basse résiduelle sous la barre. */
.progress-block .tally { margin: 0; }
.progress-block .progress { margin-bottom: 0; }
.progress-block .muted { margin: .9rem 0 0; }

/* --- 7.9 Fiche de bar ----------------------------------------------------
   Les faits rapportés en liste de définitions : chaque ligne est une donnée
   OSM, séparée de la suivante par un filet fin. Un champ absent n'a pas de
   ligne — « Horaires : non renseignés » occupe la place sans rien apprendre. */
.venue-meta { margin: 0 0 1.5rem; }
.venue-action { margin-bottom: 1.5rem; }
.venue-state {
    margin: 0 0 1rem;
    font-family: var(--display); font-size: 1.05rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
}
.venue-state.is-done { color: var(--red); }

.facts {
    display: grid; grid-template-columns: minmax(7rem, auto) 1fr;
    gap: 0 1.4rem; margin: 1.2rem 0 2rem;
}
.facts dt, .facts dd { padding: .65rem 0; border-top: 2px solid var(--paper-deep); }
.facts dt {
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.facts dd { margin: 0; overflow-wrap: anywhere; }
.facts a { font-weight: 700; box-shadow: inset 0 -.14em 0 var(--red); }
.facts a:hover { background: var(--red-wash); }
/* Les horaires gardent la syntaxe OSM : ils se lisent comme une valeur
   recopiée telle quelle, pas comme une phrase du site. */
.facts code {
    font-family: var(--mono); font-size: .9em;
    padding: .1em .35em; background: var(--paper-sunk);
    border: 2px solid var(--paper-deep);
}

/* --- 7.10 Écran de carte -------------------------------------------------
   La carte est l'écran signature : elle doit faire partie de l'affiche, pas
   ressembler à un widget posé dessus. D'où le débord sur toute la largeur (même
   procédé que .milestone), les deux filets épais qui l'encadrent comme une bande
   collée en travers, et le repeint intégral des contrôles MapLibre plus bas —
   sans quoi la carte arriverait avec ses boutons arrondis et ses ombres floues. */
.map-shell {
    position: relative;
    margin-left: calc(-50vw + 50%); margin-right: calc(-50vw + 50%);
    /* Une hauteur en vh pure donne un ruban de 200 px sur un portable en
       paysage ; un pixel fixe gâche un grand écran. D'où le clamp. */
    height: clamp(24rem, calc(100vh - 17rem), 46rem);
    background: var(--paper-sunk);
    border-top: var(--rail-thick) solid var(--ink);
    border-bottom: var(--rail-thick) solid var(--ink);
}
/* Hauteur seule, jamais `position: absolute` : MapLibre pose sa propre classe
   .maplibregl-map sur ce conteneur, et sa feuille — chargée après la nôtre —
   y remet `position: relative`. Le conteneur repassait alors en flux normal,
   sans hauteur, et la carte se réduisait à une bande vide. */
.map-canvas { height: 100%; }

/* Panneau d'échec : la carte ne doit JAMAIS rester blanche en silence. Le fond
   de carte manquant (artefact non généré) est le cas courant sur un dépôt
   fraîchement cloné, et il s'annonce ici en toutes lettres. */
.map-fallback {
    position: absolute; inset: 0; z-index: 2;
    display: flex; flex-direction: column; justify-content: center; gap: .6rem;
    margin: 0; padding: clamp(1.5rem, 5vw, 4rem);
    background: var(--paper-sunk); color: var(--ink);
}
.map-fallback[hidden] { display: none; }
.map-fallback strong {
    font-family: var(--display); font-size: 1.1rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
}
.map-fallback code {
    font-family: var(--mono); font-size: .85em;
    padding: .1em .35em; background: var(--paper-deep); border: 2px solid var(--paper-deep);
}

/* Étiquette de survol d'un marqueur : le nom et le type, sans avoir à cliquer.
   Une seule étiquette pour toute la carte, déplacée d'un marqueur à l'autre —
   en poser une par bar chargerait le DOM de cent cinquante éléments dont un
   seul sert à la fois.

   `pointer-events: none` n'est pas décoratif : sans lui, l'étiquette se glisse
   sous le curseur, déclenche le mouseleave du marqueur, disparaît, et le
   survol se met à clignoter. */
.map-tip {
    position: absolute; z-index: 3;
    pointer-events: none;
    transform: translate(-50%, -100%);
    max-width: min(18rem, 60vw);
    padding: .35rem .6rem;
    background: var(--paper);
    border: 2px solid var(--ink);
    box-shadow: var(--drop-sm);
}
.map-tip[hidden] { display: none; }
/* Posée sous le marqueur quand il n'y a pas la place au-dessus. */
.map-tip.is-below { transform: translate(-50%, 0); }
.map-tip b {
    display: block;
    font-family: var(--display); font-size: .8rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
    /* Un nom à rallonge tronque plutôt qu'il ne passe à la ligne : l'étiquette
       doit garder la même hauteur d'un bar à l'autre pour ne pas sautiller. */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.map-tip span {
    display: block;
    font-size: .62rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
/* Sur un écran tactile il n'y a pas de survol : le premier contact vaut clic et
   ouvre le panneau. L'étiquette ne s'y affiche jamais (le script ne la câble
   même pas) ; cette règle est la ceinture qui va avec les bretelles. */
@media (hover: none) {
    .map-tip { display: none; }
}

/* Titre et légende au-dessus du cadre. */
.map-head {
    display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
    gap: .6rem 1.5rem; margin: 0 0 1.1rem;
}

/* --- Contrôles MapLibre repeints à la charte -----------------------------
   MapLibre livre ses boutons en arrondis, ombre floue, fond blanc : trois
   choses que la direction artistique refuse. On ne surcharge que l'apparence,
   jamais la géométrie ni le positionnement, qui sont calculés par la
   bibliothèque.

   ATTENTION à la spécificité : maplibre-gl.css est chargée APRÈS cette feuille
   (cf. layout.php), donc à égalité de spécificité c'est ELLE qui l'emporte. De
   là le préfixe `.map-shell` sur chaque sélecteur, et le `:not(:empty)` repris
   tel quel de la bibliothèque : sans eux, les contrôles restent blancs et
   arrondis, ce qui ne se voit que sur le thème sombre. */
.map-shell .maplibregl-ctrl-group {
    background: var(--paper);
    border: var(--rail) solid var(--ink);
    border-radius: 0;
}
.map-shell .maplibregl-ctrl-group:not(:empty) { box-shadow: var(--drop-sm); }
.map-shell .maplibregl-ctrl-group button {
    width: 32px; height: 32px;
    background: var(--paper);
    border-radius: 0;
}
.map-shell .maplibregl-ctrl-group button + button { border-top: 2px solid var(--ink); }
.map-shell .maplibregl-ctrl-group button:not(:disabled):hover { background: var(--red-wash); }
.map-shell .maplibregl-ctrl-group button:focus { box-shadow: none; outline: var(--rail) solid var(--red); }
/* Les icônes des contrôles sont des SVG sombres encapsulés en data-uri : sur le
   fond d'encre du thème sombre elles disparaîtraient. L'inversion est le seul
   moyen de les retourner sans réécrire les images. */
[data-theme="dark"] .map-shell .maplibregl-ctrl-icon { filter: invert(1); }

/* Attribution ODbL : PERMANENTE, jamais repliée derrière un « i ». C'est la
   condition de réutilisation des données OpenStreetMap, pas une mention de
   politesse — d'où le contrôle non compact et ce style qui l'assume. */
.map-shell .maplibregl-ctrl.maplibregl-ctrl-attrib {
    padding: .25rem .6rem;
    background: var(--paper);
    border: 2px solid var(--ink);
    border-radius: 0;
    font-family: var(--body); font-size: .66rem; line-height: 1.5;
    color: var(--ink-soft);
}
.map-shell .maplibregl-ctrl-attrib a { color: var(--ink); text-decoration: underline; }
.map-shell .maplibregl-ctrl-attrib-button { display: none; }

/* --- 7.11 Bloc d'avis personnel ------------------------------------------
   Le carnet : quatre axes notés sur 5 et une note libre. Il porte la voix du
   site (carton .voice) parce que c'est l'utilisateur qui parle — les données
   rapportées d'OSM, au-dessus, restent en seconde encre.

   Le rappel de confidentialité est DANS le bloc, pas dans les conditions
   d'utilisation : c'est ce qui permet d'être honnête dans ses notes, et
   personne ne note franchement en doutant de qui lit.

   <section class="panel voice review">
       <h2>Mon avis</h2>
       <p class="review-privacy">Ton carnet, visible de toi seul.</p>
       <form class="review-form">…</form>
   </section>                                                                */
.review { margin: 2rem 0; }
.review h2 { margin: 0 0 .4rem; font-size: 1.2rem; }
.review-privacy {
    margin: 0 0 1.2rem; padding-left: .7rem;
    border-left: var(--rail-thick) solid var(--red);
    font-size: .82rem; color: var(--ink-soft);
}
.review-form { display: flex; flex-direction: column; gap: 1rem; }
/* Filet fin entre les axes : quatre lignes de même graisse collées les unes aux
   autres se lisent comme un seul pavé et font perdre de vue laquelle on note. */
.review-form .rating-line + .rating-line,
.review .review-note { padding-top: 1rem; border-top: 2px solid var(--paper-deep); }
.review-note { display: flex; flex-direction: column; gap: .4rem; }
.review-note textarea { width: 100%; min-height: 6.5rem; }
.review .actions { margin-top: .3rem; }
.review .actions .muted { flex: 1 1 14rem; }
/* Confirmation d'enregistrement du panneau de la carte, à côté du bouton : sur
   la fiche, c'est le rechargement de la page qui porte le bandeau. */
.review-saved {
    font-size: .7rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--red);
}
.review-saved[hidden] { display: none; }

/* Version compacte du tiroir de la carte : même formulaire, sans carton propre
   (il vit déjà dans un panneau) et sans en-tête — le titre du tiroir dit de
   quel bar on parle. Les axes s'empilent : 400 px de large ne tiennent pas une
   étiquette, sa précision et six cases sur la même ligne. */
.review-compact {
    display: flex; flex-direction: column; gap: .8rem;
    margin-top: 1.2rem; padding-top: 1rem;
    border-top: var(--rail) solid var(--ink);
}
.review-compact[hidden] { display: none; }
.review-compact .rating-line { flex-direction: column; align-items: flex-start; gap: .35rem; }
.review-compact .rating-hint { font-size: .74rem; }
.review-compact .rating-input span { width: 26px; height: 26px; }
.review-compact .review-note { border-top: 0; padding-top: 0; }
.review-compact .review-note textarea { min-height: 5rem; }

/* --- 7.12 Écran de progression -------------------------------------------
   Trois listes de même facture : les paliers de la ville, le tableau de chasse
   par quartier, la répartition par type. Aucune tuile, aucun carton par ligne —
   des rangées séparées par des filets, comme un palmarès imprimé au dos d'une
   affiche. Les cartons se réservent aux blocs qui portent une action.          */

/* La phrase d'objectif sous la barre globale : « Encore 4 bars avant Or. »
   C'est la seule information actionnable de l'écran, elle est au titrage. */
.next-goal {
    margin: 1rem 0 0;
    font-family: var(--display); font-size: 1rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
    color: var(--red);
}

/* Paliers. Le numéro d'ordre du <ol> est masqué : quatre paliers nommés se
   comptent d'eux-mêmes, et « 3. Or » ferait lire un rang plutôt qu'un seuil.
   Un palier franchi passe en encre pleine et prend le filet vermillon ; les
   autres restent en creux — l'affiche ne célèbre que ce qui est acquis. */
.tier-list {
    list-style: none; margin: 1.5rem 0; padding: 0;
    border-top: var(--rail) solid var(--ink);
    border-bottom: var(--rail) solid var(--ink);
}
.tier {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: .3rem 1rem;
    padding: .85rem 1rem;
    color: var(--ink-soft);
}
.tier + .tier { border-top: 2px solid var(--paper-deep); }
.tier.is-won { color: var(--ink); background: var(--red-wash); border-left: var(--rail-thick) solid var(--red); }
.tier-name {
    flex: none; min-width: 7rem;
    font-family: var(--display); font-size: 1.05rem;
    text-transform: uppercase; letter-spacing: var(--track-s);
}
/* L'objectif chiffré en bars tient la même colonne d'un palier à l'autre. */
.tier-goal { flex: none; min-width: 8rem; font-size: .85rem; font-variant-numeric: tabular-nums; }
.tier-state {
    margin-left: auto;
    font-size: .72rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps);
}
.tier.is-won .tier-state { color: var(--red); }

/* Quartiers et types. Chaque rangée porte sa propre barre : c'est le tableau de
   chasse, il doit se parcourir en diagonale sans lire un seul chiffre. */
.scope-list { list-style: none; margin: 1.5rem 0; padding: 0; border-top: var(--rail) solid var(--ink); }
.scope { padding: 1rem 0; border-bottom: 2px solid var(--paper-deep); }
.scope-head { display: flex; align-items: baseline; justify-content: space-between; gap: .5rem 1rem; }
.scope-name {
    font-family: var(--display); font-size: 1rem;
    text-transform: uppercase; letter-spacing: var(--track-s); color: var(--ink);
}
a.scope-name:hover { color: var(--red); }
/* Le palier atteint, en aplat : c'est un acquis, pas une étiquette de donnée —
   d'où l'encre du site et non la seconde encre des faits rapportés. */
.scope-tier {
    flex: none; padding: .15rem .55rem;
    background: var(--red); color: var(--on-red);
    font-family: var(--display); font-size: .68rem;
    text-transform: uppercase; letter-spacing: var(--track-caps);
}
.scope-note {
    display: flex; flex-wrap: wrap; gap: .2rem .9rem;
    margin: .5rem 0 0; font-size: .8rem; color: var(--ink-soft);
}
.scope-count { font-variant-numeric: tabular-nums; }
.scope-done { color: var(--red); font-weight: 700; }
/* Variante sans objectif (la répartition par type) : pas de palier à annoncer,
   la rangée se resserre au lieu de garder la place d'un badge absent. */
.scope-list.is-compact .scope { padding: .7rem 0; }
.scope-list.is-compact .scope-count { font-size: .8rem; color: var(--ink-soft); }

/* Synthèse des notes : la moyenne réelle à côté des cinq cases, qui elles
   arrondissent — cinq cases ne savent pas montrer un 3,4. */
.rating-average { display: inline-flex; align-items: center; gap: .6rem; }
.rating-average strong {
    font-family: var(--display); font-weight: 400; font-size: 1rem;
    letter-spacing: var(--track-s); color: var(--red);
    font-variant-numeric: tabular-nums;
}

/* Le meilleur et le pire du carnet. Deux lignes, même graisse : le pire n'est
   pas un blâme, c'est l'autre bout de la même liste. */
.pick-list { list-style: none; margin: 1.2rem 0 0; padding: 0; }
.pick {
    display: flex; align-items: baseline; flex-wrap: wrap; gap: .3rem .9rem;
    padding: .7rem 0;
}
.pick + .pick { border-top: 2px solid var(--paper-deep); }
.pick-label {
    flex: none; min-width: 8rem;
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.pick a { font-weight: 700; box-shadow: inset 0 -.14em 0 var(--red); }
.pick a:hover { background: var(--red-wash); }
.pick-score { margin-left: auto; font-size: .85rem; color: var(--ink-soft); font-variant-numeric: tabular-nums; }

/* ============================================================================
   8. Cartons, panneaux, pages de texte
   ============================================================================ */
.panel, .card {
    background: var(--paper-sunk);
    border: var(--rail) solid var(--ink);
    padding: 1.2rem 1.3rem;
    box-shadow: var(--drop-sm);
}
.panel > :first-child, .card > :first-child { margin-top: 0; }
.panel > :last-child, .card > :last-child { margin-bottom: 0; }
/* Un carton titré par sa passe : vermillon quand il parle de l'avancement,
   vert quand il présente des données rapportées. */
.panel.voice { border-top: var(--rail-thick) solid var(--red); }
.panel.data  { border-top: var(--rail-thick) solid var(--blue); }

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 1.1rem; }
.card { display: flex; flex-direction: column; transition: transform .12s steps(2), box-shadow .12s steps(2); }
a.card:hover, .card:hover { transform: translate(-3px, -3px); box-shadow: 7px 7px 0 var(--red); }
.card h3 { margin: .3rem 0 .5rem; font-size: 1.2rem; }

.page { max-width: 720px; }
.page h2 { margin-top: 2.2rem; padding-top: .8rem; border-top: var(--rail) solid var(--ink); }
.page ul { padding-left: 1.2rem; }
.page li { margin: .45rem 0; }
.page li::marker { color: var(--red); }
/* Étiquette OpenStreetMap citée dans le texte (amenity=bar) : elle doit se lire
   comme une valeur à recopier telle quelle, pas comme de la prose. */
.page code {
    font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
    font-size: .88em;
    padding: .1em .35em;
    background: var(--paper-deep);
    border: 2px solid var(--paper-deep);
    white-space: nowrap;
}

/* ============================================================================
   9. Comptes et authentification
   ============================================================================ */
.auth {
    max-width: 30rem;
    background: var(--paper-sunk);
    border: var(--rail) solid var(--ink);
    border-top: var(--rail-thick) solid var(--red);
    padding: 1.6rem;
    box-shadow: var(--drop);
}
.auth h1 { margin: 0 0 1rem; font-size: clamp(1.6rem, 5vw, 2.1rem); }
.auth label {
    display: block; margin-bottom: 1.2rem;
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.auth input, .auth textarea, .auth select { display: block; width: 100%; margin-top: .4rem; }
.auth .field-hint, .auth .small { text-transform: none; letter-spacing: 0; font-weight: 400; }
/* Rangée d'actions secondaires sous le bouton principal (renvoyer un code,
   annuler un changement) : elles s'alignent au lieu de s'empiler en colonne. */
.auth-actions { display: flex; flex-wrap: wrap; gap: .6rem; margin-top: 1.2rem; }
.auth-alt {
    margin: 1.4rem 0 0; padding-top: 1rem;
    border-top: 2px solid var(--paper-deep);
    font-size: .9rem; color: var(--ink-soft);
}
.auth-alt + .auth-alt { margin-top: .4rem; padding-top: 0; border-top: 0; }

/* --- Mon compte : une ligne de réglage par action --- */
.account h1 { margin-bottom: 1.4rem; }
.account-rows { border: var(--rail) solid var(--ink); background: var(--paper-sunk); }
.account-row {
    display: flex; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: .4rem 1rem; padding: 1rem 1.1rem;
}
.account-row + .account-row,
.account-form + .account-row,
.account-rows > .error { border-top: 2px solid var(--paper-deep); }
.account-row-label {
    flex: none; min-width: 9rem;
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.account-row-value { font-size: .95rem; overflow-wrap: anywhere; }
.account-row .muted.small { flex-basis: 100%; margin: 0; }
.account-rows > .error { margin: 0; border-left: 0; border-right: 0; border-bottom: 0; }

/* Le formulaire vit dans un <details> : le triangle natif est remplacé par un
   carton d'action, sans quoi la ligne repliée ne se distingue pas d'un texte. */
.account-form { border-top: 2px solid var(--paper-deep); }
.account-form > summary {
    list-style: none; cursor: pointer;
    padding: .7rem 1.1rem; min-height: 44px;
    display: flex; align-items: center; gap: .5rem;
    font-family: var(--display); font-size: .74rem;
    text-transform: uppercase; letter-spacing: var(--track-s); color: var(--ink-soft);
}
.account-form > summary::-webkit-details-marker { display: none; }
/* Chevron dessiné au filet : il pivote à l'ouverture. */
.account-form > summary::before {
    content: ""; flex: none;
    width: 9px; height: 9px;
    border-right: var(--rail) solid var(--red);
    border-bottom: var(--rail) solid var(--red);
    transform: rotate(-45deg); margin-right: .2rem;
    transition: transform .15s steps(2);
}
.account-form[open] > summary { color: var(--ink); background: var(--red-wash); }
.account-form[open] > summary::before { transform: rotate(45deg); }
.account-form > summary:hover { color: var(--ink); }
.account-form form {
    display: flex; flex-direction: column; gap: .2rem;
    max-width: 26rem; padding: 1.1rem;
    background: var(--paper); border-top: 2px solid var(--paper-deep);
}
.account-form label {
    display: block; margin-bottom: 1rem;
    font-size: .68rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: var(--track-caps); color: var(--ink-soft);
}
.account-form input { display: block; width: 100%; margin-top: .4rem; }
.account-form .muted.small { margin: 0 0 1rem; text-transform: none; letter-spacing: 0; }
.account-form .btn { align-self: flex-start; }

.account-zone { margin-top: 3rem; padding-top: 1.5rem; border-top: var(--rail-thick) solid var(--ink); }

/* Tableau de bord */
.dashboard h1 { margin-bottom: .6rem; }

/* ============================================================================
   10. Pied de page
   ============================================================================ */
.site-footer {
    margin: 4rem 0 0; padding: 1.75rem clamp(1.5rem, 5vw, 4rem) 2.5rem;
    background: var(--paper-sunk);
    border-top: var(--rail-thick) solid var(--ink);
    text-align: center;
}
.footer-note { margin: 0 auto; max-width: 80ch; color: var(--ink-soft); font-size: .82rem; line-height: 1.6; }
.footer-note strong { color: var(--ink); }
.footer-links { display: flex; flex-wrap: wrap; gap: 1.4rem; align-items: center; justify-content: center; margin-top: 1rem; }
.footer-links a, .footer-legal-btn {
    font-size: .7rem; font-weight: 700; color: var(--ink-soft);
    text-transform: uppercase; letter-spacing: var(--track-caps);
    border-bottom: 2px solid var(--paper-deep); padding-bottom: 2px;
}
.footer-legal-btn { background: none; border-top: 0; border-left: 0; border-right: 0; cursor: pointer; font-family: inherit; }
.footer-links a:hover, .footer-legal-btn:hover { color: var(--red); border-bottom-color: var(--red); }

/* Mentions légales : modale plein écran, même carton bordé que le reste du site. */
#legal-modal { position: fixed; inset: 0; z-index: 200; display: flex; align-items: center; justify-content: center; }
#legal-modal[hidden] { display: none; }
.legal-backdrop { position: absolute; inset: 0; background: var(--ink); opacity: .82; }
.legal-box {
    position: relative; z-index: 1;
    background: var(--paper); border: var(--rail-thick) solid var(--ink);
    box-shadow: 10px 10px 0 var(--red);
    width: min(620px, calc(100vw - 2rem)); max-height: calc(100vh - 4rem);
    overflow-y: auto; padding: 2rem;
    text-align: left;
}
.legal-box h2 { font-size: 1.1rem; margin: 1.8rem 0 .5rem; color: var(--red); }
.legal-box h2:first-child { margin-top: 0; }
.legal-box p, .legal-box li { font-size: .88rem; color: var(--ink-soft); line-height: 1.6; }
.legal-box p strong, .legal-box li strong { color: var(--ink); }
.legal-box ul { padding-left: 1.2rem; margin: .5rem 0; }
.legal-box li::marker { color: var(--red); }
.legal-box a { color: var(--blue); text-decoration: underline; text-underline-offset: 2px; }
.legal-close {
    position: absolute; top: 1rem; right: 1rem;
    background: var(--paper); border: 2px solid var(--ink);
    color: var(--ink); cursor: pointer; font-size: 1rem;
    width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
}
.legal-close:hover { background: var(--red); color: var(--on-red); }

/* ============================================================================
   11. Mobile (≤ 700px)
   ============================================================================ */
@media (max-width: 700px) {
    /* Barre du haut : les liens repassent dans le panneau déroulant. */
    .topbar { padding: .7rem 1rem; }
    .nav-toggle { display: inline-flex; }
    .topbar-actions .account-link,
    .topbar-actions .inline { display: none; }
    .topbar.nav-open nav a.nav-account,
    .topbar.nav-open nav form.nav-account { display: block; }
    .topbar nav { display: none; }
    .topbar.nav-open nav {
        display: flex; flex-direction: column; align-items: stretch; gap: 0;
        position: absolute; top: 100%; left: 0; right: 0; z-index: 150;
        background: var(--paper); border-bottom: var(--rail) solid var(--ink);
        padding: .5rem 1rem 1.1rem;
        box-shadow: 0 10px 0 -4px var(--red);
    }
    .topbar.nav-open nav a {
        display: block; padding: .85rem .25rem; text-align: left;
        border-bottom: 2px solid var(--paper-deep);
        background-image: none;
    }
    .topbar.nav-open nav .inline { display: block; }
    .topbar.nav-open nav .btn {
        display: flex; width: 100%; border-bottom-width: var(--rail);
        margin-top: 1rem; padding: .8rem 1.1rem;
    }

    .container { padding: 1.5rem 1rem 2rem; }
    .container > h1, .container > h2 { margin: 2rem 0 .9rem; }

    .hero { padding: .25rem 0 1.5rem; }
    .hero h1 { --offx: -4px; --offy: 3px; }
    .stats div { padding: .75rem 1rem .75rem 0; }
    .stats div + div { padding-left: 1rem; }

    .auth, .panel, .card { padding: 1.1rem; }
    .account-row { flex-direction: column; align-items: stretch; }
    .account-row-label { min-width: 0; }

    .milestone { padding: 1rem; font-size: .9rem; }

    /* Progression : palier et coup de cœur s'empilent. L'état et la note
       perdent leur `margin-left: auto` — poussés à droite d'une ligne à eux,
       ils flotteraient loin de ce qu'ils qualifient. */
    .tier, .pick { flex-direction: column; gap: .15rem; }
    .tier-state, .pick-score { margin-left: 0; }

    /* Ligne de liste : la case reste à gauche, l'état passe sous le nom. Il
       s'aligne sur le nom (retrait de la case) et le bouton « à faire » tient
       le bord droit : le pouce a les deux cibles aux deux extrémités. */
    .bar-row { flex-wrap: wrap; padding: .7rem .8rem; }
    .bar-row-aside {
        width: 100%; padding-left: calc(30px + 1rem);
        justify-content: space-between;
    }

    /* Filtres : une ligne par champ, le bouton en pleine largeur. Empiler est
       ici la bonne réponse — trois menus côte à côte à cette largeur donnent
       trois champs trop courts pour lire le quartier sélectionné. */
    .filters { padding: .9rem; gap: .8rem; }
    .filter-field { flex-basis: 100%; }
    .filter-actions { width: 100%; }
    .filter-actions .btn { flex: 1; }

    /* Avis : l'axe passe au-dessus de ses cases. Six cases de 30 px et une
       étiquette côte à côte ne tiennent pas dans cette largeur — la ligne se
       casserait de travers plutôt que de s'empiler proprement. */
    .review-form .rating-line { flex-direction: column; align-items: flex-start; gap: .4rem; }
    .review-form .rating-input span { width: 34px; height: 34px; }
    .review .actions .btn { flex: 1; }

    /* Fiche : l'étiquette passe au-dessus de sa valeur, sinon la colonne de
       gauche mange la moitié de la largeur pour un mot. */
    .facts { grid-template-columns: 1fr; gap: 0; }
    .facts dt { padding-bottom: 0; }
    .facts dd { padding-top: .2rem; border-top: 0; }

    /* Titre et légende s'empilent, et la carte récupère la place laissée : sur
       un téléphone, l'en-tête tient moins de hauteur que sur un grand écran,
       la réserve de 17 rem du cadrage large y couperait la carte pour rien. */
    .map-head { display: block; }
    .map-head h1 { margin-bottom: .8rem; }
    .map-shell { height: clamp(20rem, calc(100vh - 12rem), 34rem); }

    /* Le tiroir devient une feuille tirée du bas : à cette largeur, un panneau
       latéral couvrirait la carte entière et n'aurait plus rien de latéral. */
    .drawer {
        top: auto; left: 0; width: 100%;
        max-height: 72vh;
        border-left: 0; border-top: var(--rail-thick) solid var(--ink);
        box-shadow: 0 -8px 0 var(--red);
    }
    .drawer.is-closing { transform: translateY(100%); }

    .legal-box { padding: 1.5rem 1.25rem; }
}

/* ============================================================================
   12. Mouvement réduit
   ============================================================================ */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: .001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: .001ms !important;
    }
    .card:hover, .btn:hover, .theme-toggle:hover, .pin:hover, .tick-box { transform: none; }
}
