:root {
  --paper: #f5f4f1;      /* rail + header background */
  --paper-2: #eceae5;    /* status bar, hover */
  --panel: #ffffff;      /* map panels, detail column */
  --ink: #1a1c1e;        /* text, dark bars, active chip */
  --ink-2: #3d403c;
  --muted: #7a7e78;      /* labels */
  --faint: #9a9d98;      /* counts */
  --line: #d5d7d3;       /* section rules */
  --line-2: #c9ccc7;     /* control borders */
  --accent: #d95f18;     /* safety orange: primary action, badges, pin */
  --accent-ink: #b34d0e; /* accent text on light (contrast-safe) */

  /* Mono carries every figure (CA, effectif, distances, SIREN, years) so columns align,
     and every uppercase micro-label. Archivo carries prose. */
  --mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --sans: Archivo, system-ui, sans-serif;

  /* The footer sits inside #map-col; the fiche overlays that same box below 1100px and
     offsets its bottom by this so it stops at the footer instead of covering it.
     Keep in sync with the footer rule's padding + line-height. */
  --footer-h: 23px;

  /* The fiche's width. In table view #table-wrap insets by this so the columns it covers
     stay reachable, so the two must not drift apart. */
  --detail-w: 320px;
}

html, body { margin: 0; height: 100%; font-family: var(--sans); background: var(--paper-2); color: var(--ink); }
body { display: flex; flex-direction: column; }

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

/* Radius 0 and no shadows are the whole visual thesis -- a technical document, not app
   chrome. Every control below inherits that; none reintroduces a corner. */
input, select, button, textarea { border-radius: 0; font-family: inherit; }
button { cursor: pointer; }

.header-spacer { flex: 1; }

/* Visible to screen readers and crawlers, not to the eye. The clip-rect pattern rather
   than display:none or visibility:hidden -- those remove the element from the
   accessibility tree too, which is the opposite of the point. Carries the page's only
   h1 (see index.html). */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Equal-width flanks put the location + radius cluster on the header's true centre.
   flex-basis:0 so the two sides split the leftover space evenly regardless of their own
   content widths; min-width:0 lets them shrink before the middle group does. */
.header-side { flex: 1 1 0; min-width: 0; display: flex; align-items: center; gap: 14px; }
.header-side-end { justify-content: flex-end; }

/* ---- header ------------------------------------------------------------ */

header {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 14px;
  height: 56px; padding: 0 14px;
  background: var(--paper);
  border-bottom: 2px solid var(--ink);
  /* The header no longer overhangs anything (the filter menus moved into the rail),
     but it still stacks above Leaflet's controls. */
  position: relative; z-index: 2000;
}

.brand { display: flex; align-items: center; gap: 9px; }
/* Was a dark tile with the "FU" monogram. No border -- it framed the white band and drew
   the eye to it. 3:2 is the official ratio, so 27x18 rather than the old 28x28 square. */
.brand-mark { width: 27px; height: 18px; display: block; }
.brand-name { font: 700 14px/1 var(--sans); letter-spacing: .02em; white-space: nowrap; }

.field-label {
  font: 500 9.5px var(--mono); color: var(--muted);
  letter-spacing: .06em; white-space: nowrap;
}

/* One bordered box with no gaps between its parts: label, input and the two actions
   read as a single control rather than four adjacent ones. */
.field-group {
  display: flex; align-items: center; height: 36px;
  border: 1px solid var(--ink); background: var(--panel);
}
.field-group .field-label { padding: 0 9px; border-right: 1px solid var(--line); }
.field-group input {
  width: 212px; border: 0; background: transparent; padding: 0 10px;
  font: 400 13px var(--sans); color: var(--ink); outline-offset: -2px;
}
.field-group button {
  border: 0; border-left: 1px solid var(--line);
  background: var(--panel); color: var(--ink-2);
  font: 600 10.5px var(--mono); padding: 0 11px; height: 100%;
}
.field-group button:hover { background: var(--paper-2); }
.btn-accent { background: var(--accent); color: #fff; }
/* .field-group button (0,1,1) otherwise beats bare .btn-accent (0,1,0) on specificity and
   silently wins both background and text color -- Y ALLER read grey-on-white once located,
   correct only in the empty CENTRER SUR state, which has its own higher-specificity
   override. Repeating both properties here, at matching specificity, makes .btn-accent's
   orange fill + white text the one true answer everywhere it is used, field-group or not. */
.field-group .btn-accent { padding: 0 13px; background: var(--accent); color: #fff; }
.btn-accent:hover { background: var(--accent-ink); color: #fff; }
.field-group .btn-accent:hover { background: var(--accent-ink); }

/* Orange on white, in BOTH states: locating a place is the app's main benefit, so the
   control is always accented. A solid orange fill was tried here and shouted too loud --
   orange text on white catches the eye without turning the header into a banner. */
#locate-group {
  border-color: var(--accent);
}
#locate-group .field-label,
#locate-group input { color: var(--accent-ink); }

/* The global :focus-visible ring, pulled inward by the -2px offset on .field-group input,
   painted a 2px box in open space -- #locate-q is a borderless, transparent cell, so the
   ring traced nothing and read as a stray rectangle inside the box. The group below takes
   over the cue. Scoped to this input: every other field keeps the global ring. */
#locate-q:focus, #locate-q:focus-visible { outline: none; }

/* Focus accents the whole control, which is how it already reads. The resting border is
   var(--accent) (above), so the cue has to be a step DARKER to register at all; the
   box-shadow thickens it without border-width's 1px reflow of the header row. */
#locate-group:focus-within {
  border-color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px var(--accent-ink);
}

/* The only difference once a centre is set (app.js drops .needs-location, see
   syncLocateChrome): Y ALLER inverts to white-on-orange-text, so the primary action
   recedes to match the rest of the box instead of staying a filled call to action. */
#locate-group:not(.needs-location) .btn-accent {
  background: var(--panel); color: var(--accent-ink);
}
#locate-group:not(.needs-location) .btn-accent:hover { background: var(--paper-2); }

/* Own square button, not a third item in the field-group: GPS is a different mechanism
   from typing a place and deserves a different shape. Same 36px height as the group, and
   the same two-state accent as Y ALLER -- filled while there is no centre, outlined once
   there is (syncLocateChrome toggles .needs-location on it directly, since it sits
   outside #locate-group and cannot inherit the group's state). */
#geoloc {
  width: 36px; height: 36px; flex: none;
  border: 1px solid var(--accent); background: var(--panel);
  color: var(--accent-ink); font: 400 16px/1 var(--sans);
}
#geoloc.needs-location { background: var(--accent); color: #fff; }
#geoloc.needs-location:hover { background: var(--accent-ink); }
#geoloc:not(.needs-location):hover { background: var(--paper-2); }
/* Dim the icon while locating, without losing contrast on whichever ground is behind it. */
#geoloc.busy { opacity: .6; cursor: wait; }

.radius-group { display: flex; align-items: center; gap: 6px; }
/* Presets and the manual km input share one box: the manual radius is a peer of the
   presets, not a fallback tucked away somewhere else. */
.segmented { display: flex; height: 30px; border: 1px solid var(--line-2); background: var(--panel); }
.segmented button {
  border: 0; background: transparent; color: var(--ink);
  font: 400 11px var(--mono); padding: 0 7px;
}
.segmented button:hover { background: var(--paper-2); }
/* Orange, not ink: the radius is a filter like any other, and every other active filter
   in the rail already renders accent. */
.segmented button.active { background: var(--accent); color: #fff; }
.segmented input {
  /* 3 digits is the ceiling (a radius past 999 km covers the country), and the spinner
     that used to need room is gone. */
  width: 26px; border: 0;
  background: transparent; padding: 0; color: var(--ink);
  font: 400 11px var(--mono); text-align: right; outline-offset: -2px;
  /* Default browser spinner eats into the width meant for digits -- three characters
     barely fit next to it. Removed so "100" has room without widening the cell. */
  -moz-appearance: textfield;
}
.segmented input::-webkit-outer-spin-button,
.segmented input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.segmented input::placeholder { color: var(--faint); }
/* The custom cell needs its own ground and a visible unit so it reads as a field you can
   type into, not a fourth (unlabelled) preset tile. Ground is --panel (white), same as
   the preset buttons -- --paper-2 (the header's own grey) made an enabled-and-typeable
   field look identical to the whole control's genuinely disabled state (see
   .segmented.disabled below), which is the bug this replaced. */
.radius-custom-cell {
  display: flex; align-items: center; gap: 3px;
  height: 100%; padding: 0 8px 0 7px;
  border-left: 1px solid var(--line-2); background: var(--panel);
}
.radius-custom-cell .unit { font: 400 9.5px var(--mono); color: var(--muted); }
/* The focus cue belongs to the whole control, exactly as #locate-group:focus-within
   treats the search box. The old rule outlined the km cell alone, inset inside the
   segmented border, which read as a stray orange box floating in the field. */
.segmented:focus-within { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
/* Same exemption as #locate-q: the global :focus-visible ring, pulled inward by the
   -2px offset, would paint a second rectangle inside the cue above. */
.segmented input:focus, .segmented input:focus-visible { outline: none; }
/* Lit the same as an active preset: the custom radius is the one actually filtering. */
.radius-custom-cell.active { background: var(--accent); }
.radius-custom-cell.active input,
.radius-custom-cell.active .unit { color: #fff; }
.radius-custom-cell.active input::placeholder { color: rgba(255,255,255,.75); }
/* Only now, while the whole control is genuinely disabled, does the cell fall back to
   the header's own grey -- so "disabled" and "enabled but empty" no longer look the same. */
.segmented.disabled .radius-custom-cell { background: var(--paper-2); }

/* Disabled with no centre set: a radius filters nothing without one, so the cells must
   not be clickable at all -- not just visually muted (see syncLocateChrome). */
.segmented.disabled { opacity: .45; }
.segmented.disabled button, .segmented.disabled input { cursor: not-allowed; }
#radius-group.disabled .field-label { color: var(--faint); }

#view-toggle { display: flex; border: 1px solid var(--ink); }
#view-toggle button {
  border: 0; background: var(--panel); color: #5c605a;
  font: 600 10.5px var(--mono); padding: 8px 15px;
}
#view-toggle button.active { background: var(--ink); color: #fff; }

#export-csv {
  height: 30px; padding: 0 12px; border: 0;
  font: 600 10.5px var(--mono);
}
#export-csv[hidden] { display: none; }

/* ---- status bar -------------------------------------------------------- */

/* Replaces the bare count: it states the scope (radius, place) as well as the number,
   and absorbs the geocoder's status/error strings. */
#statusbar {
  flex: 0 0 auto;
  display: flex; align-items: center; gap: 9px;
  padding: 7px 14px;
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
}
#count { font: 600 12px var(--mono); white-space: nowrap; }
#count-scope { font: 400 11.5px var(--sans); color: #5c605a; }
#locate-status { font: 400 11.5px var(--sans); color: var(--muted); }
#locate-status.error { color: var(--accent-ink); }
#active-count {
  font: 500 10px var(--mono); color: var(--muted); letter-spacing: .05em; white-space: nowrap;
}
#clear-all {
  border: 0; background: none; padding: 0;
  font: 500 10px var(--mono); color: var(--accent-ink); letter-spacing: .05em; white-space: nowrap;
}
#clear-all:hover { color: var(--ink); }

/* ---- rail -------------------------------------------------------------- */

/* Flex column so #rail-credit's margin-top:auto pins it to the bottom when the menus are
   collapsed; it turns sticky once they overflow. */
#rail {
  width: 254px; flex: none;
  display: flex; flex-direction: column;
  border-right: 1px solid var(--line);
  background: var(--paper);
  overflow-y: auto;
  scrollbar-width: thin;
}
#rail > details { flex: none; }

.filter-menu { border-bottom: 1px solid var(--line); }
.filter-menu > summary {
  list-style: none;
  display: flex; align-items: center; gap: 8px;
  padding: 11px 12px; cursor: pointer; user-select: none;
}
.filter-menu > summary::-webkit-details-marker { display: none; }
.filter-menu > summary:hover { background: var(--paper-2); }
.sum-label { font: 600 10px var(--mono); letter-spacing: .06em; color: var(--ink-2); }
/* Pushed right; when the section is folded it shows what is applied, so a collapsed
   rail still says what it is filtering. */
.sum-note {
  margin-left: auto; text-align: right;
  font: 400 10px var(--mono); color: var(--faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.filter-menu > summary::after {
  content: "\25BC"; font: 400 9px/1 var(--sans); color: var(--muted); flex: none;
}
.filter-menu[open] > summary::after { content: "\25B2"; }

.badge {
  display: none; flex: none;
  padding: 2px 5px;
  background: var(--accent); color: #fff;
  font: 600 9.5px var(--mono);
}
.badge.visible { display: inline-block; }

.menu-body { padding: 0 12px 12px; }
.menu-list {
  max-height: 300px; overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  /* The overlay scrollbar sits on top of the content, so reserve a gutter -- otherwise
     it covers the right-aligned counts. */
  padding-right: 8px;
  scrollbar-width: thin;
}
.menu-list::-webkit-scrollbar { width: 8px; }
.menu-list::-webkit-scrollbar-track { background: transparent; }
.menu-list::-webkit-scrollbar-thumb { background: var(--line-2); }
.menu-list::-webkit-scrollbar-thumb:hover { background: var(--muted); }

.check, .naf-row {
  display: flex; align-items: center; gap: 8px;
  font: 400 12px var(--sans); padding: 3px 4px; cursor: pointer;
}
.check:hover, .naf-row:hover { background: var(--paper-2); }
.check input, .naf-row input { margin: 0; cursor: pointer; accent-color: var(--accent); width: 13px; height: 13px; }
.naf-row .naf-name { flex: 1; }
.naf-row .naf-codes { font: 400 10px var(--mono); color: var(--faint); white-space: nowrap; }
/* A group can carry four NAF codes ("24.51Z / 24.52Z / 24.53Z / 24.54Z"), which no 254px
   row fits beside a label. Side by side they either scrolled the rail sideways or got
   ellipsised mid-code ("24.53..."), which is worse than useless -- a truncated code
   identifies nothing. So the activity rows stack: name on top, codes under it. The
   effectif rows keep the inline layout, where the value is a short count. */
/* row-gap 0: the 8px gap is meant for the checkbox-to-label spacing, but once the row
   wraps it also pushes the codes line away from its name. Column gap stays 8px. */
#cap-chips .naf-row { flex-wrap: wrap; row-gap: 0; }
#cap-chips .naf-name { flex: 1 0 calc(100% - 21px); }
#cap-chips .naf-codes { flex: 0 0 calc(100% - 21px); margin-left: 21px; white-space: normal; }
/* No horizontal scrollbar: the lists only ever scroll vertically. */
.menu-list { overflow-x: hidden; }
/* Departements read as children of the region row above them. */
.dept-row { margin-left: 16px; }
.dept-row > span { font-family: var(--mono); font-size: 11.5px; }
.check[hidden] { display: none; }

/* "Colorer par activite" is a display mode, not a filter -- the dashed rule says so. */
.check-sep { margin-top: 3px; padding-top: 8px; border-top: 1px dashed var(--line-2); }
.menu-section { border-top: 1px solid var(--line); margin-top: 9px; padding-top: 7px; }
/* "Effectif non publie" closes the bracket list, set apart from the real brackets. */
.eff-unknown-row { border-top: 1px solid var(--line); margin-top: 4px; padding-top: 6px; }

.hint { color: var(--faint); font: 400 10.5px/1.4 var(--mono); margin: 7px 0 0; }

/* Chips: the input stays a real checkbox for keyboard and screen readers, and the
   span is what gets painted. */
.chip-grid { display: flex; flex-wrap: wrap; gap: 5px; }
.chip { position: relative; }
.chip input { position: absolute; opacity: 0; width: 0; height: 0; }
.chip span {
  display: block; padding: 4px 8px;
  border: 1px solid var(--line-2); background: var(--panel);
  font: 400 11px var(--mono); cursor: pointer;
}
.chip span:hover { background: var(--paper-2); }
.chip input:checked + span { background: var(--ink); color: #fff; border-color: var(--ink); }
.chip input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 1px; }

.range-sep {
  font: 500 9.5px var(--mono); text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); margin: 10px 0 5px;
}
.range-row { display: flex; gap: 6px; align-items: center; }
.range-row input {
  width: 68px; height: 28px; padding: 0 8px;
  background: var(--panel); border: 1px solid var(--line-2);
  font: 400 11px var(--mono); color: var(--ink); outline-offset: -2px;
}
.range-row > span { color: var(--faint); font: 400 11px var(--mono); }
.range-unit { white-space: nowrap; color: var(--muted); }

.search-row { display: flex; height: 30px; border: 1px solid var(--line-2); background: var(--panel); }
.search-row input {
  flex: 1; min-width: 0; border: 0; background: transparent; padding: 0 8px;
  font: 400 12px var(--sans); color: var(--ink); outline-offset: -2px;
}
.search-row button {
  border: 0; border-left: 1px solid var(--line); background: var(--panel);
  color: var(--ink-2); font: 600 10.5px var(--mono); padding: 0 10px;
}
.search-row button:hover { background: var(--paper-2); }

#loc-filter {
  width: 100%; box-sizing: border-box; height: 28px; padding: 0 8px; margin-bottom: 8px;
  background: var(--panel); border: 1px solid var(--line-2);
  font: 400 11.5px var(--sans); color: var(--ink); outline-offset: -2px;
}

.clear-one {
  width: 100%; margin-top: 12px; padding: 6px;
  border: 1px solid var(--line-2); background: var(--panel);
  color: var(--muted); font: 600 10px var(--mono); letter-spacing: .05em;
  text-transform: uppercase;
}
.clear-one:hover { color: var(--ink); background: var(--paper-2); }

/* The whole-rail fold. Above 720px it does nothing at all: the summary is hidden and the
   element ships `open`, so the six sections lay out exactly as they did before it existed.
   Everything that makes it a control lives in the narrow block at the bottom of this file. */
#rail-fold > summary { display: none; }

/* Attribution, not a filter. Discretion comes from opacity rather than a paler ink, so
   the label still passes contrast once hovered and the muted state is uniform.
   Sticky at the rail's bottom edge: as the last child of a scrolling column it otherwise
   sits ~85px below the fold with ACTIVITE open, and a credit nobody scrolls to is not a
   credit. margin-top:auto pins it down when the menus are all collapsed.
   The opacity sits on the contents, never on #rail-credit itself: on the element it also
   faded the background, and the filters scrolling underneath showed through it. */
#rail-credit {
  position: sticky; bottom: 0; margin-top: auto;
  display: block; padding: 14px 12px 16px; text-decoration: none;
  background: var(--paper); border-top: 1px solid var(--line);
}
#rail-credit span, #rail-credit img { opacity: .5; transition: opacity .15s; }
#rail-credit:hover span, #rail-credit:hover img,
#rail-credit:focus-visible span, #rail-credit:focus-visible img { opacity: 1; }
#rail-credit span {
  display: block; margin-bottom: 7px;
  font: 600 10px var(--mono); letter-spacing: .08em; color: var(--ink-2);
}
#rail-credit img { display: block; width: 110px; height: auto; }

/* ---- layout: rail | detail | map --------------------------------------- */

#layout { display: flex; flex: 1 1 auto; min-height: 0; }

/* Map area on top, footer bar underneath: the footer belongs to this column so it never
   runs under the rail. position:relative is what the fiche overlay anchors to, and what
   keeps it off the footer (via --footer-h). */
#map-col { flex: 1; min-width: 0; display: flex; flex-direction: column; position: relative; }
/* The absolute #map/#table-wrap measure against this, not #map-col -- so the footer can
   take its height without the map collapsing to 0 (see the #map note below). */
#map-area { flex: 1; min-height: 0; position: relative; }

/* The table lists whatever is inside map.getBounds(), so the map must keep a real size
   even in table view -- display:none would measure 0x0 and getBounds() would come back
   empty. It stays laid out and is covered by the table instead. Both fill #map-area,
   which is the flex row above the footer. */
#map { position: absolute; inset: 0; }
/* Leaflet sets tabindex="0" on the map so arrow keys can pan it, which made the global
   :focus-visible rule paint a 2px accent ring around the ENTIRE map on any click. The map
   is not a control and has no focus state worth announcing. Scoped to #map on purpose:
   the zoom buttons and attribution links inside .leaflet-container are real controls and
   keep their ring. */
#map:focus, #map:focus-visible { outline: none; }

#table-wrap {
  position: absolute; inset: 0;
  overflow: auto; display: none;
  background: var(--panel);
  /* Above Leaflet's own controls (zoom 1000, attribution 700) so they don't show
     through the table -- the map below stays laid out purely to keep its bounds. */
  z-index: 1100;
}
#table-wrap.visible { display: block; }
/* The fiche overlays the table rather than displacing it, so without this the right-hand
   columns lay out underneath it and cannot be scrolled into view. Insetting by its width
   gives the table the space actually visible; --detail-w keeps the two in sync. */
#detail-panel.visible:not(.floating) ~ #map-area #table-wrap { right: var(--detail-w); }
/* Explains why the table is shorter than the map count: it is viewport-scoped.
   Sticky at top:0 with th pushed below it so the two stack instead of overlapping. */
.table-notice {
  position: sticky; top: 0; z-index: 2;
  margin: 0; padding: 8px 10px;
  background: var(--paper);
  border-bottom: 1px solid var(--line);
  color: var(--muted); font: 400 11px var(--sans);
}

/* table-layout:fixed so the name column actually yields: with auto layout the widest
   shop name sets the column width and overflows the viewport no matter what max-width
   says. Fixed also means every column needs an explicit width (below).
   min-width is the sum of those widths: below it the wrap scrolls sideways instead of
   crushing all seven columns to unreadable slivers. */
table {
  border-collapse: collapse; width: 100%; min-width: 860px;
  font: 400 12px var(--sans); table-layout: fixed;
}
th, td { text-align: left; padding: 8px 11px; white-space: nowrap; }
td { overflow: hidden; text-overflow: ellipsis; border-bottom: 1px solid var(--line); }
th {
  position: sticky; top: 31px; z-index: 1;
  background: var(--ink); color: #fff;
  font: 600 9.5px var(--mono); letter-spacing: .05em;
  cursor: pointer; user-select: none;
}
th:hover { color: #f0a06a; }
/* Nom took every pixel the other six left over (532px at 1400, where the median name
   needs 129 and 90% fit in 248) -- a handful of very long names made it look necessary
   while it was really just the only column without a width. Capped here so the figures
   stay on screen; the ~10% that overflow ellipsize and the fiche shows the full name. */
th:first-child, td:first-child { width: 30ch; }    /* Nom */
th:nth-child(2), td:nth-child(2) { width: 14ch; }  /* Ville */
th:nth-child(3), td:nth-child(3) { width: 6ch; }   /* Dept */
th:nth-child(4), td:nth-child(4) { width: 22ch; }  /* Activite */
th:nth-child(5), td:nth-child(5) { width: 15ch; }  /* Effectif */
th:nth-child(6), td:nth-child(6) { width: 12ch; }  /* CA */
th:nth-child(7), td:nth-child(7) { width: 10ch; }  /* Creee en */
/* Figures right-aligned in mono so the columns compare down the page. */
td:nth-child(3), td:nth-child(5) { font-family: var(--mono); font-size: 11px; }
th:nth-child(6), td:nth-child(6), th:nth-child(7), td:nth-child(7) { text-align: right; }
td:nth-child(6), td:nth-child(7) { font-family: var(--mono); font-size: 11px; }
td:first-child { font-weight: 500; }
/* Every column is sortable, so every header shows an arrow: dimmed neutral until it is the
   active sort key, then flipped to match the direction. */
th::after { content: " \2195"; opacity: .4; }
th[data-sort="asc"]::after { content: " \2191"; opacity: 1; }
th[data-sort="desc"]::after { content: " \2193"; opacity: 1; }
tbody tr:hover { background: var(--paper); cursor: pointer; }

.leaflet-container { background: var(--paper-2); font-family: var(--sans); }
.leaflet-tooltip { background: var(--ink); color: #fff; border: none; font: 400 11px var(--sans); border-radius: 0; }
.leaflet-control-attribution { font-size: 9px; }

/* NAF colour legend. Above Leaflet's own controls (zoom 1000, attribution 700) but below
   #table-wrap (1100), so table view covers it along with the map. pointer-events:none so
   it never swallows a click meant for a dot. */
/* bottom clears #hint-open below it (26px + 10px inset + 8px gap), so the legend and the
   i button stack in one column and cannot overlap at any legend height. */
#naf-legend {
  position: absolute; left: 10px; bottom: 44px; z-index: 1050;
  background: var(--panel); border: 1px solid var(--line-2);
  padding: 6px 8px; font: 400 11px/1.6 var(--sans);
  max-height: 45%; overflow: auto; pointer-events: none;
}
#naf-legend b { display: block; margin-bottom: 2px; color: var(--muted); font: 500 9.5px var(--mono); letter-spacing: .05em; }
#naf-legend i { display: inline-block; width: 8px; height: 8px; border-radius: 50%;
                margin-right: 6px; vertical-align: middle; }

/* Bottom-left corner, beneath the legend. Same z-index band (1050): above the map and
   Leaflet's controls, below the table (1100) and the fiche (1200). Attribution keeps the
   opposite corner, so the two never meet. */
#hint-open {
  position: absolute; left: 10px; bottom: 10px; z-index: 1050;
  width: 26px; height: 26px;
  border: 1px solid var(--line-2); background: var(--panel);
  color: var(--ink-2); font: 600 12px/1 var(--mono);
}
#hint-open:hover { border-color: var(--accent); color: var(--accent-ink); }
/* Pressed look while the panel it opens is showing, so the toggle states its own state. */
#hint-open.active { background: var(--ink); border-color: var(--ink); color: #fff; }
#hint-open[hidden] { display: none; }

/* Located-point pin: pure CSS, so there is no icon image that can fail to load. Accent
   orange, never a dot colour, so the located point cannot be mistaken for a shop. */
.center-pin span {
  display: block; width: 14px; height: 14px; margin: 4px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid #fff;
  box-shadow: 0 0 0 2px rgba(217, 95, 24, .35);
}

/* ---- detail column ------------------------------------------------------ */

/* An overlay, not an in-flow column: opening it no longer narrows the map, so the dot
   under the cursor stays where it was clicked. Anchored to #map-col (position:relative)
   and above #table-wrap's z-index 1100 so it works from both views. */
#detail-panel {
  position: absolute; z-index: 1200;
  width: var(--detail-w); display: none;
  flex-direction: column;
  background: var(--panel);
  border: 1px solid var(--line-2);
  overflow-y: auto;
}
#detail-panel.visible { display: flex; }

/* Map view: a card inset from the map's top-right, only as tall as its content
   (bottom:auto + no height = shrink to fit), capped so a tall fiche scrolls instead of
   running past the map. */
#detail-panel.floating {
  top: 12px; right: 12px; bottom: auto;
  max-height: calc(100% - 24px - var(--footer-h));
  /* The house style is radius 0 and no shadows, but a card floating on map tiles needs
     some elevation or it reads as a rendering artefact. A 1px hard offset, not a blurred
     drop shadow -- enough to lift it, still not app chrome. */
  box-shadow: 0 1px 0 rgba(26, 28, 30, .18), 0 0 0 1px rgba(26, 28, 30, .04);
}

/* Table view: full height against the right edge. There is no map underneath to keep
   visible, and a row list reads better beside a full-height panel. */
#detail-panel:not(.floating) {
  top: 0; right: 0; bottom: var(--footer-h);
  border-top: 0; border-right: 0; border-bottom: 0;
}

#detail-head { padding: 13px; background: var(--ink); color: #fff; position: relative; }
#detail-head h2 { font: 600 16px/1.2 var(--sans); margin: 0; padding-right: 22px; }
#detail-close {
  position: absolute; top: 10px; right: 11px;
  background: none; border: none; color: #a8aba6;
  font: 400 16px/1 var(--sans); cursor: pointer; padding: 2px 4px;
}
#detail-close:hover { color: #fff; }
#detail-meta {
  display: flex; flex-wrap: wrap; gap: 10px; margin-top: 6px;
  font: 400 10px var(--mono); color: #a8aba6;
}
#detail-badge { color: #f0a06a; }

#detail-body {
  margin: 0; padding: 13px;
  display: grid; grid-template-columns: auto 1fr; gap: 9px 14px;
  font: 400 12.5px/1.35 var(--sans);
  border-bottom: 1px solid #e2e3df;
}
#detail-body dt {
  font: 500 9.5px/1.6 var(--mono); color: var(--muted); letter-spacing: .05em;
  text-transform: uppercase; white-space: nowrap;
}
#detail-body dd { margin: 0; }
#detail-body dd .mono { font-family: var(--mono); font-size: 12px; }
#detail-body dd .dim { color: var(--faint); }

#detail-actions { padding: 13px; display: flex; flex-direction: column; gap: 8px; }
#detail-actions a, #detail-actions button {
  display: block; text-align: center; padding: 9px;
  font: 600 10.5px var(--mono); text-decoration: none;
  border: 1px solid var(--line-2); background: var(--panel); color: var(--ink-2);
}
#detail-actions .primary { background: var(--accent); color: #fff; border-color: var(--accent); }
#detail-actions .primary:hover { background: var(--accent-ink); }
#detail-actions a:not(.primary):hover, #detail-actions button:hover { background: var(--paper-2); }

/* Fixed height so --footer-h (which the fiche overlay offsets against) stays true. */
footer {
  flex: 0 0 auto; box-sizing: border-box; height: var(--footer-h);
  padding: 0 14px;
  display: flex; align-items: center; gap: 4px;
  font: 400 11px var(--sans); color: var(--ink-2);
  background: var(--paper); border-top: 1px solid var(--line);
}
/* nowrap: the links are the footer's fixed content. #footer-source is the only
   flexible item, so it is what crops -- the links never break onto a second line. */
footer a { color: var(--accent-ink); white-space: nowrap; }

/* Pushed to the right edge by the flex gap. One line only: the footer's height is fixed
   so --footer-h stays true, so it ellipsises on narrow screens rather than wrapping out
   of the bar. min-width:0 lets it shrink below its text instead of squeezing the links. */
#footer-source {
  margin-left: auto; min-width: 0; padding-left: 14px;
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  color: var(--faint);
}

/* ---- hint panel ----------------------------------------------------------- */

/* Floats at the top-centre of the map area instead of taking a full-width strip out of
   the flow. Centres on #map-col (not the window) because that is what it is positioned
   against, which also keeps it clear of Leaflet's zoom control at the top-left (10px
   inset, 34x65, so it ends at x=44/y=75). Above the map and its controls (~800-1000),
   below the table (1100) and the fiche (1200). */
#hint {
  position: absolute; z-index: 1050;
  top: 12px; left: 50%; transform: translateX(-50%);
  max-width: min(560px, calc(100% - 120px));
  padding: 11px 34px 11px 14px;
  background: var(--ink); color: #fff;
  font: 400 12px/1.45 var(--sans);
}
#hint p { margin: 0; }
#hint label {
  display: flex; align-items: center; gap: 6px; margin-top: 8px;
  font: 400 11px var(--sans); color: #c9ccc7; cursor: pointer;
}
#hint input { accent-color: var(--accent); cursor: pointer; }
#hint-close {
  position: absolute; top: 6px; right: 8px;
  background: none; border: 0; color: #a8aba6;
  font: 400 15px/1 var(--sans); cursor: pointer; padding: 4px 6px;
}
#hint-close:hover { color: #fff; }

/* ---- content pages (pourquoi / comment / mentions-legales) ---------------- */

.page {
  max-width: 82ch;
  margin: 0 auto;
  padding: 32px 20px 60px;
  line-height: 1.6;
  background: var(--panel);
}
.page h1 { font-size: 22px; }
.page h2 { font-size: 16px; margin-top: 32px; }
.page a { color: var(--accent-ink); }
.page .back { display: inline-block; margin-bottom: 20px; font-size: 13px; }
/* min-width:0 undoes the directory table's 860px floor (see `table` above), which would
   otherwise force a prose table wider than the page column and spill it off-screen. */
.page table { border-collapse: collapse; width: 100%; min-width: 0; margin: 12px 0; font-size: 13px; table-layout: auto; }
/* The directory table above is styled hard (sticky dark sticky header, mono figures,
   fixed widths); a prose table on a content page must opt back out of all of it. */
.page th, .page td {
  text-align: left; padding: 5px 10px; border-bottom: 1px solid var(--line);
  white-space: normal; background: none; color: inherit;
  font: inherit; position: static; width: auto;
}
.page th { font-weight: 600; cursor: auto; }
.page th::after { content: ""; }
.page tbody tr:hover { background: none; cursor: auto; }

/* ---- mid ------------------------------------------------------------------
   Between 720px and the ~1150px the desktop header actually needs, the single row
   ran out of width: the .header-side flanks (flex-basis:0) collapse first, so the
   brand slid under CENTRER SUR and RAYON collided with CARTE/TABLEAU. Two rows
   here, three below 720px. */
@media (max-width: 1150px) {
  /* The footer goes to two lines in this block, so the constant the fiche offsets
     against grows with it -- same coupling as the 720px block below. MEASURED, not
     derived: 28px at the top of the range, but the licence sentence itself wraps
     below ~830px and takes the footer to 41. The constant has to cover the worst
     case in the range, or the bottom sheet sits on the links at the narrow end. */
  :root { --footer-h: 41px; }

  /* Two rows: brand+actions / locate+geoloc+radius. flex-wrap does the stacking. */
  header { height: auto; flex-wrap: wrap; gap: 10px; padding: 8px 14px; }
  /* The equal-width flanks are a desktop centring device (see .header-side): here
     they would reserve empty space around every row. */
  .header-side { flex: 0 0 auto; order: -2; }
  /* Both flanks belong on row 1, but the end flank is LAST in the DOM and flex-wrap
     cannot reorder -- hence `order`. -1 lifts it onto row 1; the brand flank needs a
     lower value still, or `order` alone would leave the actions on the LEFT and push
     the brand to the right edge. margin-left:auto on the end flank then holds the
     actions at the right, so row 1 reads brand-left / actions-right. */
  .header-side-end { order: -1; margin-left: auto; }

  /* Row 2 is forced, not hoped for. The 100% basis is what breaks the line: it cannot
     share row 1 with the brand and the actions, so the split lands here at every width
     in the range. Letting the group merely grow (flex:1 1 auto) left it on row 1 and
     wrapped the RADIUS group down instead, which is the opposite split.
     A full 100% basis is too much, though: alone on its line there is nothing to
     shrink against, so geoloc and the radius group were pushed onto a third row.
     65% is the middle of the window that actually works, and the window is narrow:
     below ~62% the group still fits beside the brand and the actions at the TOP of
     the range (1146-1150px), which stranded geoloc and the radius on a row of their
     own -- the "intermediary step" this value exists to kill; at 70% it is too wide
     to share row 2 with its own neighbours at the BOTTOM of the range (~720px) and
     they wrap again. Measured at both ends, not derived -- the flanks' widths change
     as they wrap, so the arithmetic does not predict it. grow reclaims the leftover
     width on row 2; min-width:0 lets the cell shrink below Y ALLER's intrinsic width. */
  #locate-group { flex: 1 1 65%; min-width: 0; }
  #locate-group input { min-width: 0; flex: 1 1 auto; }
  .radius-group { flex: 0 0 auto; }

  /* Two lines: the links, then the source sentence under them (same rules as the
     720px block). Height goes auto, so --footer-h above is what keeps the fiche
     clear of it. */
  footer { height: auto; flex-wrap: wrap; row-gap: 2px; }
  #footer-source {
    flex: 0 0 100%; margin-left: 0; padding-left: 0;
    white-space: normal; overflow: visible;
    font-size: 10px; line-height: 1.3;
  }
}

/* ---- narrow ---------------------------------------------------------------
   The fiche is an overlay at every width now (see #detail-panel), so there is no 1100px
   breakpoint for it any more. Below 720px the rail stacks above the map instead of
   beside it, and the fiche becomes a bottom sheet. #map keeps its layout throughout
   (never display:none, see the note above #map). */
@media (max-width: 720px) {
  /* The footer wraps to two lines down here (links, then the source sentence), so the
     constant the fiche offsets against has to grow with it -- otherwise the bottom sheet
     sits on top of the links. Keep in sync with the footer rule at the end of this block. */
  :root { --footer-h: 46px; }

  /* Three rows: brand / locate / radius+view. flex-wrap does the stacking; each row is
     forced by giving its first item flex-basis:100%. */
  header { height: auto; flex-wrap: wrap; gap: 8px; padding: 8px 10px; }
  /* The equal-width flanks are a desktop centring device (see .header-side): here they
     would reserve empty space on both sides of every row. order:0 undoes the mid block
     above, which reorders both flanks onto one row; down here every row is a full-width
     stack, so DOM order is the right order. */
  .header-side { flex: 0 0 100%; order: 0; }
  /* Only the brand flank takes a full row. The end flank holds the view toggle, which
     rides beside the radius group on row 3 -- with the inherited 100% basis it claimed a
     fourth row of its own. margin-left:auto then pushes it to the right edge, so row 3
     reads radius-left / view-right rather than both huddled at the left. */
  /* order:0 undoes the mid block above, which pulls this flank onto row 1; here it
     belongs on row 3 beside the radius group, i.e. back in DOM order. */
  .header-side-end { flex: 0 0 auto; margin-left: auto; order: 0; }

  /* Row 2: the text field takes the leftover width and the GPS button keeps its square.
     calc() reserves the button (40px) and the header's flex gap (8px) so Y ALLER cannot
     be pushed past the right edge -- flex:1 alone let the input's own intrinsic width win
     and clipped the button off the row. The label's text lives on in the input's
     placeholder and title, so hiding the cell costs no meaning. */
  /* Row 2 is the search field + GPS button. The basis reserves the button (40px) and the
     header's gap (8px), plus 2px of slack -- at exactly 48px the group rounds up to 324
     against a 370px content box and 324+8+40 overflows by two pixels, wrapping the button
     onto row 3. min-width:0 is what lets the group shrink to the basis at all, since
     Y ALLER's intrinsic width would otherwise hold it wider. */
  #locate-group { flex: 1 1 calc(100% - 50px); min-width: 0; max-width: calc(100% - 50px); height: 40px; }
  /* The text cell yields; Y ALLER keeps its label. */
  #locate-group input { min-width: 0; flex: 1 1 auto; }
  #locate-group .btn-accent { flex: 0 0 auto; }
  #locate-group .field-label { display: none; }
  .field-group input { width: 100%; min-width: 0; }
  #geoloc { width: 40px; height: 40px; flex: 0 0 40px; }

  /* Row 3: radius and the view toggle share one line, both at their natural width.
     Neither may carry a 100% basis or it takes the whole row and pushes the other down --
     that is what .header-side gives .header-side-end, hence the override below. */
  .radius-group { flex: 0 0 auto; display: flex; }
  .radius-group .field-label { display: none; }
  /* No flex:1 on either the group or its cells: the radius is a minor filter and three
     two-digit labels stretched across 390px made it the loudest control on the screen.
     Height stays 40px and the cells get wider padding than desktop's 7px, so shrinking
     the visual size does not shrink the tap target. */
  .segmented { height: 40px; }
  .segmented button { padding: 0 12px; font-size: 12px; }
  #view-toggle { flex: 0 0 auto; height: 40px; }
  #view-toggle button { padding: 0 14px; }
  /* A CSV download on a phone lands somewhere the user cannot reach, and this is the
     widest control in the row. The table view still works. */
  #export-csv { display: none; }

  /* One row: count left, TOUT EFFACER right. The spacer does the pushing (it is
     display:none in the old narrow rules, which is why the bar used to wrap). */
  #statusbar { flex-wrap: nowrap; gap: 8px; padding: 7px 10px; }
  .header-spacer { display: block; flex: 1 1 auto; min-width: 0; }
  /* The count and TOUT EFFACER are the two things that must survive a 390px line, so they
     are the only items that do not shrink; the scope sentence ellipsises into whatever is
     left. min-width:0 is what lets a flex item shrink below its own text at all. */
  #count { flex: none; }
  #clear-all { flex: none; }
  #count-scope, #locate-status {
    flex: 0 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  }
  /* The fold bar carries the filter state down here, so the status bar's copy would be a
     duplicate competing for a 390px line. */
  #active-count { display: none; }

  #layout { flex-direction: column; }
  /* Stacked, the rail yields height first: it scrolls, whereas the map going short (or
     the footer being pushed off the bottom of the window) has no recovery. It is closed
     on load now, so it costs one 40px bar until tapped -- hence 60vh when open, up from
     the 42vh it needed when it was permanently expanded. */
  #rail {
    width: auto; flex: 0 1 auto; min-height: 0; max-height: 60vh;
    border-right: 0; border-bottom: 1px solid var(--line);
  }

  /* The fold becomes a real control: one bar reading "FILTRES / n FILTRES ACTIFS" with
     the same chevron as the sections it hides. */
  #rail-fold > summary {
    display: flex; align-items: center; gap: 8px;
    list-style: none;
    min-height: 40px; padding: 0 12px;
    background: var(--paper-2); border-bottom: 1px solid var(--line);
    cursor: pointer; user-select: none;
  }
  #rail-fold > summary::-webkit-details-marker { display: none; }
  /* Same affordance as .filter-menu > summary::after, so the bar reads as the parent of
     the sections below it rather than as a different kind of thing. */
  #rail-fold > summary::after {
    content: "\25BC"; margin-left: auto;
    font: 400 9px/1 var(--sans); color: var(--muted); flex: none;
  }
  #rail-fold[open] > summary::after { content: "\25B2"; }
  /* Mirrors the status bar's count (hidden down here). 11px, not the 10px its desktop
     twin uses: on a phone this bar is the only thing saying whether filters are on, and
     it has the width to spare now that the status bar gave it up. */
  #active-count-fold {
    font: 500 11px var(--mono); color: var(--muted); letter-spacing: .05em; white-space: nowrap;
  }
  /* Same reason, same bar: FILTRES is the label of the one control standing between the
     user and the map. */
  #rail-fold > summary .sum-label { font-size: 11px; }

  /* No room, and the footer already carries attribution. */
  #rail-credit { display: none; }

  /* 260 for the map itself plus the footer bar, which is now inside this column. */
  #map-col { flex: 1; min-height: 290px; }

  /* Two lines: the links, then the source sentence under them. Height goes auto, so
     --footer-h above is what keeps the fiche clear of it. */
  footer { height: auto; flex-wrap: wrap; padding: 4px 10px; row-gap: 2px; }
  #footer-source {
    flex: 0 0 100%; margin-left: 0; padding-left: 0;
    white-space: normal; overflow: visible;
    font-size: 10px; line-height: 1.3;
  }

  /* Bottom sheet in both views, still clear of the footer (see --footer-h). Written
     with .floating's specificity (0,1,1) so it also overrides the floating card's
     top/right/max-height, which a bare #detail-panel rule would lose to. */
  #detail-panel, #detail-panel.floating {
    width: auto; inset: auto 0 var(--footer-h) 0; max-height: 60vh;
    border: 0; border-top: 1px solid var(--line);
    box-shadow: none;
  }

  .page { padding: 20px 16px 40px; }
}
