/* MyPaperMaps - design system (green map theme) */
:root {
  --ink: #e8eef4;
  --ink-soft: #9fb0c3;
  --ink-dim: #5d6f82;
  --green: #3ecf8e;
  --green-dark: #1f8e67;
  --green-soft: rgba(62, 207, 142, 0.16);
  --blue: #4ea1f7;
  --blue-soft: rgba(78, 161, 247, 0.18);
  --paper: #0f1620;
  --paper-warm: #17212d;
  --paper-raised: #1d2937;
  --line: #2d3a48;
  --danger: #e0604a;
  /* Error TEXT on dark surfaces: --danger is only 4.17:1 on --paper-raised,
     below the 4.5:1 AA minimum for small text. Keep --danger for fills. */
  --danger-text: #e8796a;
  --warn: #e3b341;
  --radius: 10px;
  --radius-lg: 16px;
  --shadow: 0 8px 30px rgba(0, 0, 0, 0.45);
  font-size: 16px;
}
*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* One consistent, visible keyboard focus ring everywhere (UA defaults are
   faint on these dark surfaces). Inputs keep their own stronger treatment. */
button:focus-visible, a:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--green); outline-offset: 2px;
}
/* Visually-hidden text that stays in the accessibility tree - same rule as the
   holding page (gate.php). Used for labels the visual design carries by
   position or placeholder alone; a placeholder is not an accessible name and
   disappears the moment the field is typed into. */
.sr-only {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap;
}
/* Skip link: the first focusable element in the document. Kept off-screen
   until it takes focus, then pinned over the sticky header (which is z-index
   50) so it is actually readable when it appears. */
.skip-link {
  /* fixed, not absolute: reached by Shift+Tab from a scrolled position an
     absolutely-positioned link lands at document top, outside the viewport. */
  position: fixed; top: 0; left: -9999px; z-index: 200;
  background: var(--green); color: #07120c;
  font-weight: 700; font-size: 14px; text-decoration: none;
  padding: 10px 18px; border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus, .skip-link:focus-visible { left: 0; }
/* <main> is focusable only so the skip link can land on it; a green ring
   round the entire page region would be noise, not feedback. */
main[tabindex="-1"]:focus, main[tabindex="-1"]:focus-visible { outline: none; }
body {
  margin: 0;
  font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
  background: var(--paper);
  color: var(--ink);
  line-height: 1.55;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
main { flex: 1; width: 100%; max-width: 1280px; margin: 0 auto; padding: 24px clamp(16px, 4vw, 48px) 64px; }
h1, h2, h3 { line-height: 1.15; margin: 0 0 12px; }
h1 { font-size: clamp(34px, 5vw, 56px); }
h2 { font-size: 26px; }
h3 { font-size: 17px; }
a { color: var(--green); }
code { background: var(--paper-warm); border: 1px solid var(--line); border-radius: 5px; padding: 1px 6px; font-size: 0.9em; }
[hidden] { display: none !important; }

/* Header */
.site-header {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 14px clamp(16px, 4vw, 48px);
  border-bottom: 1px solid var(--line);
  background: rgba(15, 22, 32, 0.95);
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(6px);
}
.brand img { height: 46px; display: block; }
.main-nav { display: flex; gap: 4px; flex-wrap: wrap; }
.main-nav a {
  color: var(--ink-soft); text-decoration: none; font-weight: 600; font-size: 14px;
  padding: 8px 14px; border-radius: 999px; transition: 0.15s;
}
.main-nav a:hover { color: var(--ink); background: var(--paper-warm); }
.main-nav a.is-active { color: var(--paper); background: var(--green); }
.header-account { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.stats-badge { font-size: 13px; color: var(--ink-soft); background: var(--paper-warm); border: 1px solid var(--line); padding: 6px 12px; border-radius: 999px; }
.stats-badge strong { color: var(--green); }
.user-menu { position: relative; }
.user-btn { display: flex; align-items: center; gap: 8px; background: none; border: 1px solid var(--line); border-radius: 999px; color: var(--ink); padding: 5px 14px 5px 5px; cursor: pointer; font: inherit; font-size: 14px; }
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--green-dark); color: #fff; display: grid; place-items: center; font-weight: 700; font-size: 13px; }
.user-dropdown { position: absolute; right: 0; top: calc(100% + 6px); background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); min-width: 160px; overflow: hidden; }
.user-dropdown button { display: block; width: 100%; text-align: left; background: none; border: 0; color: var(--ink); padding: 11px 16px; cursor: pointer; font: inherit; font-size: 14px; }
.user-dropdown button:hover { background: var(--paper-warm); }

/* Buttons */
.button {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font: inherit; font-weight: 600; font-size: 15px;
  border-radius: 999px; border: 1px solid transparent;
  padding: 11px 24px; cursor: pointer; text-decoration: none; transition: 0.15s;
}
.button.small { padding: 7px 16px; font-size: 13px; }
.button.primary { background: var(--green); color: #07120c; }
.button.primary:hover { background: #56dd9f; }
.button.secondary { background: var(--blue); color: #051120; }
.button.ghost { background: transparent; color: var(--ink); border-color: var(--line); }
.button.ghost:hover { border-color: var(--green); color: var(--green); }
.button.ghost.danger { color: var(--danger-text); border-color: var(--danger); }
.button.ghost.danger:hover { color: #fff; background: var(--danger); }
.icon-button {
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--paper-warm); color: var(--ink); font-size: 17px; cursor: pointer; transition: 0.15s;
}
.icon-button:hover { border-color: var(--green); color: var(--green); }
.link-button { background: none; border: 0; color: var(--green); cursor: pointer; font: inherit; font-size: 13px; padding: 4px 0; }

/* Notice bars */
.notice-bar {
  display: flex; align-items: center; justify-content: center; gap: 14px; flex-wrap: wrap;
  background: var(--warn); color: #2a2104; font-size: 14px; font-weight: 600; padding: 8px 16px;
}
.notice-bar.success { background: var(--green); color: #07120c; }
/* Preview-access explainer: informational, so deliberately quieter than the
   warning/holding bars. */
.notice-bar.preview { background: var(--paper-warm); color: var(--ink); border-bottom: 1px solid var(--line); font-weight: 500; }
.notice-bar.preview .button.ghost { border-color: var(--line); }
.notice-dismiss {
  background: none; border: 0; color: var(--ink-soft); font-size: 18px; line-height: 1;
  padding: 4px 8px; cursor: pointer; border-radius: 6px;
}
.notice-dismiss:hover { color: var(--ink); background: var(--paper-raised); }
/* Holding mode: deliberately the loudest bar on the page - an administrator
   must not be able to forget that the public site is blocked. */
.notice-bar.holding { background: var(--danger); color: #2a0a05; }
.notice-bar .button.ghost { border-color: rgba(0,0,0,0.35); color: inherit; }

/* Holding-page admin panel */
.holding-panel { border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-warm); padding: 16px; margin-bottom: 26px; }
.holding-panel h3 { margin: 22px 0 10px; font-size: 15px; }
.holding-state { font-size: 14px; margin: 0 0 6px; }
.holding-panel label { font-size: 13px; color: var(--ink-soft); }
.holding-new-pass { border: 1px solid var(--warn); border-radius: 8px; background: rgba(227,179,65,0.10); padding: 12px; margin: 12px 0; font-size: 13px; }
.holding-new-pass input { width: 100%; max-width: 34rem; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; }
#holding-marker-warning { display: block; color: var(--danger); font-weight: 600; }

/* Hero */
.hero { display: grid; grid-template-columns: minmax(300px, 560px) minmax(280px, 1fr); gap: clamp(20px, 4vw, 60px); align-items: center; padding-top: 40px; }
.hero-copy p { color: var(--ink-soft); font-size: 18px; max-width: 52ch; }
.hero-actions { display: flex; gap: 14px; margin: 22px 0; flex-wrap: wrap; }
.hero-points { list-style: none; padding: 0; margin: 0; color: var(--ink-soft); font-size: 15px; }
.hero-points li { padding: 6px 0 6px 28px; position: relative; }
.hero-points li::before { content: '✓'; position: absolute; left: 4px; color: var(--green); font-weight: 700; }
/* Support link: present but visually secondary to the product actions. */
.hero-map img { width: 100%; max-height: 70vh; object-fit: contain; }

/* Call-to-arms stats band */
.intro-band {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px;
  margin-top: 34px; padding: 26px clamp(18px, 3vw, 40px);
  background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius-lg);
  text-align: center;
}
.intro-band .stat { display: block; font-size: clamp(30px, 4vw, 44px); font-weight: 800; color: var(--green); line-height: 1.1; }
.intro-band p { margin: 6px 0 0; color: var(--ink-soft); font-size: 14px; }
.intro-band-cta { grid-column: 1 / -1; border-top: 1px solid var(--line); padding-top: 16px; font-size: 14px !important; }

/* Sections */
.view.narrow { max-width: 760px; margin: 0 auto; }
.section-heading { margin: 18px 0 22px; }
.section-heading p { color: var(--ink-soft); max-width: 70ch; margin: 6px 0 0; }
.filters-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 18px; }
select, input[type="search"], input[type="text"], input[type="email"], input[type="password"], input[type="number"], textarea {
  background: var(--paper-warm); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; padding: 9px 12px; font: inherit; font-size: 14px;
}
select:focus, input:focus, textarea:focus { outline: 2px solid var(--green-dark); border-color: var(--green-dark); }
label { display: flex; flex-direction: column; gap: 6px; font-size: 13px; color: var(--ink-soft); font-weight: 600; }
label.check, .check { flex-direction: row; align-items: center; gap: 8px; color: var(--ink); font-weight: 500; font-size: 14px; }
.hint { color: var(--ink-soft); font-size: 13px; }
.status-message { min-height: 20px; font-size: 14px; color: var(--green); }
.status-message.error { color: var(--danger-text); }
/* Prominent success banner in the auth dialog (e.g. registration): a filled
   panel, visually unmistakable and clearly distinct from error text. */
.auth-success {
  margin: 12px 0; padding: 12px 14px; border-radius: 8px; font-weight: 600;
  background: rgba(62, 207, 142, 0.14); border: 1px solid rgba(62, 207, 142, 0.55);
  color: var(--green);
}

/* Map view */
.map-layout { display: flex; gap: 18px; align-items: stretch; }
.map-side { width: 200px; flex: none; display: flex; flex-direction: column; gap: 14px; }
.map-key {
  background: var(--paper-warm); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 12px 14px;
}
.map-key h3 {
  font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 10px;
}
.legend { display: flex; flex-direction: column; gap: 8px; font-size: 13px; color: var(--ink-soft); }
.legend-item { display: flex; align-items: center; gap: 8px; }
.swatch { width: 14px; height: 14px; border-radius: 4px; display: inline-block; border: 1px solid var(--line); flex: none; }
.swatch.paper { background: var(--green-soft); border-color: var(--green); }
.swatch.digital { background: var(--blue-soft); border-color: var(--blue); }
.swatch.both { background: linear-gradient(135deg, var(--green-soft) 50%, var(--blue-soft) 50%); border-color: var(--green); }
.swatch.outline { background: transparent; border: 1.5px dashed rgba(159, 176, 195, 0.75); border-radius: 3px; }
.map-options { display: flex; flex-direction: column; gap: 8px; }
.map-toggle { flex-direction: row; align-items: center; gap: 7px; font-size: 13px; }
/* Persistent visible labels for the Coverage radios and the Series filter:
   the same small-caps treatment as the key's heading, so the three sidebar
   sections read as one system. */
.map-control-label {
  display: block; font-size: 11px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--ink-soft); margin: 0 0 6px; padding: 0;
}
/* Coverage: one mutually exclusive choice (radios), laid out like the
   toggles beneath it. No transition anywhere in the sidebar controls (see
   the series panel note below). */
.map-coverage { border: 0; margin: 0; padding: 0; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
.map-coverage .map-toggle input { margin: 0; }
.map-coverage[aria-invalid="true"] .map-control-label { color: var(--warn); }
.map-control-label-secondary { margin-top: 6px; }
.map-control-hint { color: var(--ink-soft); font-size: 12px; line-height: 1.35; margin: 0; }
.map-coverage input, .series-option input { color-scheme: dark; accent-color: var(--green); }
.map-toggle.is-disabled { color: var(--ink-soft); cursor: not-allowed; }
/* Under "Maps I don't own" the key dims the owned-copy swatches: owned
   colouring AND the owned maps' outlines are hidden, so no entry applies to
   the user's own maps. The "Outline mapped" entry (last) still applies to
   the outlines that ARE drawn. Under "My collection" it is the outline entry
   that dims - only the collection is drawn. Presentation only. */
.map-key.is-muted .legend-item { opacity: 0.45; }
.map-key.is-muted .legend-item:last-child { opacity: 1; }
.map-key.is-mine .legend-item:last-child { opacity: 0.7; }
.zoom-controls { display: flex; gap: 8px; align-items: center; }
/* The live zoom multiplier, between + and -. Tabular figures and a min-width
   stop the two buttons shuffling sideways as the text changes 1x -> 12.5x. */
.zoom-level {
  min-width: 4.2em; text-align: center;
  font-size: 13px; font-weight: 700; color: var(--ink);
  font-variant-numeric: tabular-nums;
  background: var(--paper-warm); border: 1px solid var(--line);
  border-radius: 999px; padding: 6px 8px;
}
.icon-button:disabled { opacity: 0.4; cursor: not-allowed; }
.icon-button:disabled:hover { border-color: var(--line); color: var(--ink); }
/* Series filter: compact, and obviously the map's primary control. */
/* Outline-load failure notice: deliberately part of the control column, not
   an overlay, so it reads as a statement about the toggle above it. */
.outline-error {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px;
  font-size: 12px; line-height: 1.4;
  color: var(--warn); background: var(--paper-warm);
  border: 1px solid var(--warn); border-radius: var(--radius);
  padding: 7px 10px;
}
.outline-error .link-button { color: var(--warn); font-weight: 700; }
.map-series-row { display: flex; }
.map-series { width: 100%; font-size: 13px; padding: 7px 10px; }
/* Series multi-select (Map page). The trigger looks like the select it
   replaced; the panel is an ordinary block that opens BELOW it in the
   sidebar flow (not absolutely positioned), so it can never sit over the
   zoom controls or the empty-state notice, on any width. No transition
   anywhere here: js/map.js never measures it, but the map shell beside it
   must stay free of write-then-measure hazards. */
.series-picker { position: relative; width: 100%; }
.series-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  background: var(--paper-warm); border: 1px solid var(--line); color: var(--ink);
  border-radius: 8px; font: inherit; font-size: 13px; text-align: left; cursor: pointer;
}
.series-toggle:hover { border-color: var(--green); }
.series-toggle[aria-expanded="true"] { border-color: var(--green-dark); }
.series-caret { color: var(--ink-soft); font-size: 11px; }
.series-panel {
  margin-top: 6px; padding: 8px 10px; background: var(--paper-raised);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow);
  max-height: 46vh; overflow: auto;
}
.series-panel-actions { display: flex; gap: 12px; margin-bottom: 6px; font-size: 13px; }
/* The series TREE. Publishers are top-level fieldsets whose visible row is
   a tri-state checkbox (the legend is visually hidden - the row IS the
   heading); each nested level is indented and carries a hairline so the
   hierarchy is legible at a glance, including on a phone where the panel is
   a full-width block. Indeterminate parents are drawn by the browser. */
.series-group { border: 0; margin: 4px 0 0; padding: 0; min-width: 0; }
.series-group + .series-group { margin-top: 8px; border-top: 1px solid var(--line); padding-top: 6px; }
.series-option { flex-direction: row; align-items: center; gap: 8px; color: var(--ink); font-weight: 500; font-size: 13px; padding: 4px 0; min-height: 30px; cursor: pointer; }
.series-option input { margin: 0; width: 16px; height: 16px; flex: none; }
.series-parent { font-weight: 700; }
.series-parent.depth-1 { font-weight: 600; }
.series-children { margin-left: 8px; padding-left: 12px; border-left: 2px solid var(--ink-dim); }
.series-children .series-children { margin-left: 8px; }

/* Tooltips (map toggles, Reset). Deliberately NOT the title attribute: this
   is real DOM text, referenced by aria-describedby, so assistive technology
   always has it. It is never display:none or visibility:hidden - either would
   drop it out of the accessibility tree and make aria-describedby resolve to
   nothing - so it is hidden with opacity alone and revealed on hover OR
   keyboard focus. focus-within covers the checkbox inside the label and a tap
   on touch, which focuses the control. */
.has-tip { position: relative; }
.has-tip .tip {
  position: absolute; left: 0; top: calc(100% + 6px); z-index: 30;
  width: max-content; max-width: 15rem;
  background: var(--paper-raised); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 7px 10px;
  font-size: 12px; font-weight: 400; line-height: 1.35; text-align: left;
  opacity: 0; pointer-events: none;
}
.has-tip:hover .tip, .has-tip:focus-within .tip { opacity: 1; }
/* The wrapper is now a block/inline box around the control, not the control
   itself, so it must not disturb the sidebar's layout. */
.has-tip > .map-toggle { display: flex; }
.zoom-controls .has-tip .tip { left: auto; right: 0; }
.map-hint { color: var(--ink-dim); font-size: 13px; margin: 0; }
.map-stage { position: relative; flex: 1; min-width: 0; }
.map-shell {
  overflow: auto; height: calc(100vh - 150px); min-height: 420px; border: 1px solid var(--line);
  border-radius: var(--radius-lg); background: #0c1219;
  /* The wheel zoom resizes the surface and sets the scroll offsets itself
     (MapCamera): the browser's scroll anchoring must not "helpfully" adjust
     them again afterwards, or the anchored point creeps every notch. */
  overflow-anchor: none;
  /* Slippy-map idiom: panning is drag/wheel/buttons, never scrollbars. The
     padded surface (MapCamera's anchor gutters) would otherwise pin BOTH
     scrollbars on permanently, even in the fitted view. */
  scrollbar-width: none;
}
.map-shell::-webkit-scrollbar { display: none; }
/* The surface is the padded scroll canvas MapCamera sizes (explicit
   width/height + gutters as padding, content-box so the two add): it gives
   the shell scroll range on every side, which is what lets a wheel-zoom
   anchor hold even while the SVG is narrower than the shell. Centring is
   done with scroll offsets (MapCamera.centreOn), not margin: 0 auto - auto
   margins leave no room to compensate on the clamped side. */
.map-surface { box-sizing: content-box; cursor: grab; }
/* The camera (js/map-camera.js) writes these elements' sizes/padding and
   MEASURES the result synchronously in the same task, so they must never
   transition geometry: a transition of any duration starts at the OLD value,
   and the same-task readback sees it (the freshly-padded surface then has no
   scroll range yet, the centring clamps to 0,0 and the map lands off in a
   gutter). Kept as defence-in-depth: the reduced-motion block at the end of
   this file no longer creates transitions, so this is now belt-and-braces
   rather than the fix, and it documents the hazard where the risk lives.
   Do the same for any future element whose geometry JS writes and then
   immediately measures. */
.map-surface, #main-map, #review-map-svg { transition: none !important; }
#main-map, #review-map-svg { display: block; cursor: grab; user-select: none; }
.map-shell.is-dragging .map-surface,
.map-shell.is-dragging #main-map, .map-shell.is-dragging #review-map-svg { cursor: grabbing; }
.sheet-outline { fill: none; stroke: rgba(159, 176, 195, 0.4); stroke-width: 0.9; stroke-dasharray: 3 2; pointer-events: none; }
.bng-grid-line { stroke: rgba(159, 176, 195, 0.14); stroke-width: 0.7; }
.bng-grid-label { fill: rgba(159, 176, 195, 0.4); font-size: 9px; }
.bng-square-label { fill: rgba(159, 176, 195, 0.3); font-size: 26px; font-weight: 700; text-anchor: middle; }
.cov { stroke-width: 1; }
.cov.paper { fill: rgba(62, 207, 142, 0.35); stroke: var(--green); }
.cov.digital { fill: rgba(78, 161, 247, 0.35); stroke: var(--blue); }
.cov.both { fill: rgba(62, 207, 142, 0.35); stroke: var(--blue); stroke-width: 1.6; stroke-dasharray: 4 2; }
/* Map labels: PLAIN text inside each map part (js/map.js renderLabels), sized
   from the part's own geometry in SVG user units so it grows with the map,
   capped on screen by capLabelSizes(). The Review Map draws its pending parts
   with the same class. */
.cov-label { fill: #dff7ec; font-size: 9px; text-anchor: middle; pointer-events: none; }
/* Transient selection state on the base layers: the OTHER maps listed in
   the results panel step back while one is selected. Hue = ownership,
   always - only opacity changes, and never so far that paper-green and
   digital-blue stop being distinguishable. */
.cov.is-dimmed { fill-opacity: 0.12; stroke-opacity: 0.55; }
.sheet-outline.is-dimmed { stroke: rgba(159, 176, 195, 0.22); }
/* Each public-map label is also a SELECTOR: a <g role="button"> whose only
   visible part is the text. The hit area is an invisible rect sized by
   capLabelSizes() (never below 14 px on screen); hover, focus and selected
   states change the text alone - no box, no background, no leader, and no
   transition (camera-managed elements never ease). The layer is
   pointer-transparent except for the labels themselves; a press that moves
   still pans (js/map.js onPointerDown), and the wheel bubbles to the shell. */
#label-layer { pointer-events: none; }
.map-label { pointer-events: auto; cursor: pointer; }
.map-label:focus { outline: none; }
.label-hit { fill: transparent; stroke: none; pointer-events: all; }
.map-label .cov-label { pointer-events: none; paint-order: stroke; stroke: rgba(12, 18, 25, 0.6); stroke-width: 0.12em; stroke-linejoin: round; }
.map-label:hover .cov-label { fill: #ffffff; }
.map-label:focus-visible .label-hit { stroke: var(--green); stroke-width: 2px; vector-effect: non-scaling-stroke; }
/* Listed in the results panel (not selected): the amber "answered your
   click" cue; selected: bright white and bold. Neither is an ownership hue. */
.map-label.is-listed .cov-label { fill: var(--warn); }
.map-label.is-selected .cov-label { fill: #ffffff; font-weight: 700; }
/* Place-name overlay. Each label is a <g> counter-scaled by MapView so the
   marker and text keep a constant CSS-pixel size at every zoom; the font
   size is written inline per tier (an author rule on the element would beat
   an inherited one, exactly as for .cov-label). The whole layer is
   pointer-transparent: clicks, drags and wheel gestures pass straight through
   to the map. paint-order draws the dark halo UNDER the glyphs so a name stays
   readable across outline strokes and coverage fills. */
.place-layer, .place-layer * { pointer-events: none; }
.place-marker { fill: #f5e9c9; stroke: rgba(12, 18, 25, 0.9); stroke-width: 1px; }
.place-label {
  fill: #f5e9c9; font-weight: 500; text-anchor: start;
  paint-order: stroke; stroke: rgba(12, 18, 25, 0.85); stroke-width: 3px; stroke-linejoin: round;
}
.place-label.tier-0 { font-weight: 700; fill: #fff5d6; }
.place-label.tier-1 { font-weight: 600; }
/* Hamlets and suburban localities (tiers 5-6) sit a shade quieter than the
   villages around them; still fully legible over the dark ground. */
.place-label.tier-5, .place-label.tier-6 { font-weight: 400; fill: #e6dcc0; }
.hit-shape { fill: rgba(227, 179, 65, 0.25); stroke: var(--warn); stroke-width: 1.6; pointer-events: none; }
/* With one map selected: the other listed maps keep the amber "answered your
   click" meaning as a thin dashed hint; the chosen map is near-white over a
   dark halo (drawn beneath it) with a barely-there fill, so the ownership
   colour underneath still shows and nothing implies a change of ownership. */
.hit-shape.is-other { fill: none; stroke: var(--warn); stroke-opacity: 0.85; stroke-width: 1.5px; stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; }
.hit-halo { fill: none; stroke: rgba(8, 12, 18, 0.75); stroke-width: 4.5; pointer-events: none; }
.hit-shape.is-selected { fill: rgba(242, 246, 250, 0.10); stroke: #f2f6fa; stroke-width: 2.2; }
.sheet-popover {
  position: absolute; top: 16px; right: 16px; width: min(330px, 90%);
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: 18px; z-index: 10;
  /* A column with a scrolling body, so a long list of overlapping maps
     scrolls inside the panel (the close button stays put) and never
     overflows the stage. The panel is a SIBLING of #map-shell, so a wheel
     over it never reaches the camera; overscroll-behavior keeps a wheel at
     the end of the list from chaining to the page. */
  display: flex; flex-direction: column; max-height: calc(100% - 32px);
}
#popover-body { overflow-y: auto; overscroll-behavior: contain; min-height: 0; }
#main-map:focus { outline: none; }
.popover-close, .modal-close {
  position: absolute; top: 10px; right: 12px; background: none; border: 0;
  color: var(--ink-soft); font-size: 22px; cursor: pointer; line-height: 1;
}
.popover-sheet { border-bottom: 1px solid var(--line); padding: 10px 0 14px 10px; border-left: 3px solid transparent; cursor: pointer; }
.popover-sheet:last-child { border-bottom: 0; }
.popover-sheet .series-chip { font-size: 11px; }
.popover-sheet h3 { margin: 6px 0 2px; }
/* A result card is a map SELECTOR: its heading is a toggle button (aria-
   pressed), and any non-interactive part of the card selects too - the
   embedded controls keep their own cursor and their own actions. Hover,
   focus-within (subtle: the report form lives inside the card, so it fires
   while typing) and selected are three distinct treatments. */
.popover-sheet input, .popover-sheet button, .popover-sheet a, .popover-sheet label, .popover-sheet textarea, .popover-sheet select { cursor: auto; }
.popover-sheet button, .popover-sheet a, .popover-sheet label { cursor: pointer; }
.popover-sheet:hover { background: rgba(232, 238, 244, 0.03); }
.popover-sheet:focus-within { border-left-color: var(--line); }
.popover-sheet.is-selected { border-left-color: #f2f6fa; background: rgba(242, 246, 250, 0.06); }
.map-title { margin: 6px 0 2px; }
.map-select {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px;
  width: 100%; text-align: left; font: inherit; font-size: 17px; font-weight: 700;
  color: inherit; background: none; border: 0; border-radius: 8px; padding: 4px 6px; cursor: pointer;
}
.map-select-scale { font-size: 13px; font-weight: 400; color: var(--ink-soft); }
/* The visible mark derives from the ARIA state, so the two can never
   disagree; it is aria-hidden because AT already hears "pressed". */
.map-selected-mark {
  display: none; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  background: #f2f6fa; color: #0c1219; border-radius: 999px; padding: 2px 8px;
}
.map-select[aria-pressed="true"] .map-selected-mark { display: inline-block; }
.popover-sheet .own-row { display: flex; gap: 14px; margin: 10px 0; flex-wrap: wrap; align-items: center; }
.popover-sheet input[type="number"] { width: 90px; padding: 5px 8px; }
.buy-link { font-size: 13px; }
.popover-empty { color: var(--ink-soft); font-size: 14px; }

/* Collection */
.collection-stats { display: flex; gap: 12px; flex-wrap: wrap; font-size: 13px; color: var(--ink-soft); }
.collection-stats span strong { color: var(--green); font-size: 16px; margin-right: 4px; }
/* One full-width row per map record - never two records side by side. The
   row's inner pieces (code, name, ownership controls) wrap on narrow screens. */
.collection-grid { display: flex; flex-direction: column; gap: 10px; }
.map-card {
  background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 16px; width: 100%;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; transition: 0.15s;
}
.map-card:hover { border-color: var(--green-dark); }
.map-card.unowned { opacity: 0.55; }
.map-card .top-row { display: flex; align-items: center; gap: 10px; flex: none; min-width: 150px; }
.map-card .name { flex: 1 1 240px; }
.map-card .own-row { margin-left: auto; }
.series-chip {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  background: var(--green-soft); color: var(--green); border-radius: 5px; padding: 2px 8px; white-space: nowrap;
}
.series-chip.harvey { background: rgba(227, 179, 65, 0.15); color: var(--warn); }
/* Personal-map badge: blue, so a personal map can never be mistaken for a
   catalogue map next to the green/amber series chips. The full word is the
   distinction - never colour alone. */
.personal-chip {
  font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  background: var(--blue-soft); color: var(--blue); border: 1px solid var(--blue);
  border-radius: 5px; padding: 1px 7px; white-space: nowrap;
}
.map-card .code { font-size: 19px; font-weight: 800; }
.map-card .name { font-size: 13.5px; color: var(--ink-soft); flex: 1; }
.map-card .own-row { display: flex; gap: 12px; align-items: center; font-size: 13px; }
.map-card .own-row input[type="number"] { width: 76px; padding: 3px 7px; font-size: 12px; }
.empty-state { color: var(--ink-dim); padding: 30px 10px; text-align: center; grid-column: 1 / -1; }

/* Tabs / add panels */
.tabs { display: flex; gap: 6px; margin-bottom: 18px; border-bottom: 1px solid var(--line); }
.tab { background: none; border: 0; border-bottom: 2px solid transparent; color: var(--ink-soft); font: inherit; font-weight: 600; font-size: 14px; padding: 10px 16px; cursor: pointer; }
.tab.is-active { color: var(--green); border-bottom-color: var(--green); }
/* Four method tabs: wrap like the admin/account strips so all four stay
   reachable with no horizontal page scroll on narrow phones. */
.add-tabs { flex-wrap: wrap; }
.add-panel { display: flex; flex-direction: column; gap: 14px; }
.add-row { display: flex; gap: 14px; align-items: center; flex-wrap: wrap; }
.add-panel input[type="text"], .add-panel textarea { width: 100%; }
.drop-zone {
  border: 2px dashed var(--line); border-radius: var(--radius-lg); padding: 40px 20px;
  text-align: center; color: var(--ink-soft); cursor: pointer; transition: 0.15s;
}
.drop-zone:hover, .drop-zone.dragover { border-color: var(--green); color: var(--green); }
/* The keyboard-reachable trigger inside the drop zone: styled as text so the
   zone still reads as one block, but it is a real button (see index.html). */
.drop-zone-btn {
  background: none; border: 0; padding: 0; color: inherit; font: inherit;
  cursor: pointer; text-decoration: underline; text-underline-offset: 3px;
}
.drop-zone-btn:hover { color: var(--green); }
.add-result { background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px; font-size: 14px; margin-top: 16px; }
.add-result .warn { color: var(--warn); }
#csv-preview table { border-collapse: collapse; font-size: 13px; width: 100%; }
#csv-preview td, #csv-preview th { border: 1px solid var(--line); padding: 5px 10px; text-align: left; }

/* Contribute */
.contribute-layout { display: grid; grid-template-columns: minmax(300px, 1fr) minmax(300px, 420px); gap: 26px; align-items: start; }
.map-results { display: flex; flex-direction: column; gap: 6px; max-height: 420px; overflow: auto; border: 1px solid var(--line); border-radius: var(--radius); padding: 8px; background: var(--paper-warm); }
.map-result { display: flex; gap: 10px; align-items: center; background: none; border: 1px solid transparent; border-radius: 8px; color: var(--ink); padding: 8px 10px; cursor: pointer; font: inherit; font-size: 13.5px; text-align: left; }
.map-result:hover { border-color: var(--line); }
.map-result.is-selected { border-color: var(--green); background: var(--green-soft); }
.map-result .code { font-weight: 800; min-width: 44px; }
.map-result .done { margin-left: auto; color: var(--green); font-size: 12px; }
.selected-map { background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px; display: flex; flex-direction: column; gap: 2px; margin-bottom: 14px; }
.selected-map span { color: var(--ink-soft); font-size: 12px; }
#ctr-form, .missing-form { display: flex; flex-direction: column; gap: 13px; }
.own-claim { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; margin: 0; display: flex; flex-direction: column; gap: 8px; }
.own-claim legend { font-size: 13px; color: var(--ink-soft); font-weight: 600; padding: 0 6px; }
.help-methods { list-style: none; padding: 0; margin: 0 0 12px; display: flex; flex-direction: column; gap: 10px; }
.help-methods li { color: var(--ink-soft); padding-left: 24px; position: relative; }
.help-methods li::before { content: '›' / ''; position: absolute; left: 6px; color: var(--green); font-weight: 700; }
.missing-form { margin-top: 14px; background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.my-submissions { margin-top: 26px; }
/* Personal maps: clearly separated from the shared-catalogue picker. */
.personal-entry {
  margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--line);
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
.personal-entry .hint { margin: 0; }
#personal-list { width: 100%; }
#personal-list .personal-chip { align-self: flex-start; }
.personal-row strong { font-size: 14px; }
/* Entry card inside the Personal map method panel on the Add-maps view. */
.add-personal {
  background: var(--paper-warm); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 16px 18px;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-start;
}
.add-personal h3 { margin: 0; }
.add-personal .hint { margin: 0; }
/* The personal-map year input: wide enough for the full "Year (optional)"
   placeholder (em-based so it scales with zoom), fixed against flex squeeze -
   the wrapping .add-row moves it to its own line on narrow screens instead.
   (By number / name has no shared year field: a year is per map.) */
#ctr-p-year { width: 12em; flex: 0 0 auto; }

/* Add maps by number / name: tokens for the chosen maps + the autocomplete
   listbox. The listbox is positioned under the field and never animated (no
   transition to defer a same-task measurement, see the reduced-motion note). */
.map-picker { display: flex; flex-direction: column; gap: 10px; }
.token-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.token {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--green-soft); color: var(--ink); border: 1px solid var(--green-dark);
  border-radius: 999px; padding: 2px 2px 2px 12px; font-size: 13px; line-height: 1.3;
}
.token-remove {
  background: none; border: 0; color: var(--ink-soft); font: inherit; font-size: 17px; line-height: 1;
  cursor: pointer; padding: 3px 8px; border-radius: 999px; min-width: 36px; min-height: 36px;
}
.token-remove:hover { color: var(--danger-text); background: var(--paper-warm); }
/* In normal flow, deliberately: an absolutely-positioned list would sit over
   the Add maps button below the field and swallow a click meant for it. */
.suggestions {
  margin: 4px 0 0; padding: 6px; list-style: none; max-height: 320px; overflow: auto;
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.suggestions [role="option"] {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 10px;
  padding: 9px 10px; border-radius: 8px; cursor: pointer; font-size: 14px;
  border: 1px solid transparent;
}
.suggestions [role="option"]:hover { border-color: var(--line); }
/* The keyboard highlight (aria-selected) is the visible focus of the combobox:
   a filled row plus a ring, so it is never conveyed by colour alone. */
.suggestions [role="option"][aria-selected="true"] {
  background: var(--green-soft); border-color: var(--green);
  outline: 2px solid var(--green); outline-offset: -2px;   /* survives forced-colours modes */
}
.suggestions .sug-main { font-weight: 700; }
.suggestions .sug-meta { color: var(--ink-soft); font-size: 12px; }
.suggestions .sug-flag {
  margin-left: auto; font-size: 11px; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--green); white-space: nowrap;
}
.suggestions [data-picked] .sug-main { color: var(--ink-soft); }
.suggestion-empty, .suggestion-more { padding: 8px 10px; color: var(--ink-dim); font-size: 13px; }

/* Map page: a chosen series with no drawable outline at all. */
.map-empty {
  margin: 0; font-size: 12px; line-height: 1.4; color: var(--ink-soft);
  background: var(--paper-warm); border: 1px dashed var(--line); border-radius: var(--radius); padding: 7px 10px;
}
.feed-item { border: 1px solid var(--line); border-radius: 8px; padding: 10px 13px; margin-bottom: 8px; font-size: 13px; display: flex; flex-direction: column; gap: 2px; }
.feed-item .review-chip { align-self: flex-start; font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 7px; }
.review-chip.accepted { background: var(--green-soft); color: var(--green); }
.review-chip.to_review { background: rgba(227,179,65,0.15); color: var(--warn); }
.review-chip.disputed { background: rgba(224,96,74,0.15); color: var(--danger); }
.review-chip.rejected { background: rgba(224,96,74,0.25); color: var(--danger); }
.review-chip.active { background: var(--green-soft); color: var(--green); }
.review-chip.expired { background: rgba(227,179,65,0.15); color: var(--warn); }
.review-chip.revoked { background: rgba(224,96,74,0.25); color: var(--danger); }
.feed-actions { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }

/* Help */
#view-help details { border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-warm); margin-bottom: 10px; padding: 0 18px; }
#view-help summary { cursor: pointer; font-weight: 700; padding: 14px 0; color: var(--ink); }
#view-help details p { color: var(--ink-soft); margin-top: 0; }

/* Modal */
.modal-backdrop { position: fixed; inset: 0; background: rgba(5, 9, 14, 0.72); display: grid; place-items: center; z-index: 100; padding: 18px; }
.modal { position: relative; background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius-lg); box-shadow: var(--shadow); width: min(420px, 100%); padding: 26px; }
.auth-panel { display: flex; flex-direction: column; gap: 14px; }
/* Password field with its show/hide toggle: the toggle overlays the right
   edge of the input, which reserves room for it via padding. */
.password-field { position: relative; display: flex; width: 100%; }
.password-field input { flex: 1; width: 100%; padding-right: 58px; }
/* Suppress Edge's built-in reveal eye - the custom toggle is the ONE
   show-password affordance, and the two would collide visually. */
.password-field input::-ms-reveal { display: none; }
.password-toggle {
  position: absolute; right: 5px; top: 50%; transform: translateY(-50%);
  background: none; border: 0; border-radius: 6px; cursor: pointer;
  color: var(--ink-soft); font: inherit; font-size: 12.5px; font-weight: 600;
  padding: 8px 10px;
}
.password-toggle:hover { color: var(--ink); }
.password-toggle:focus-visible { outline: 2px solid var(--green-dark); }
.oauth-block { display: flex; flex-direction: column; gap: 12px; }
.oauth-divider { display: flex; align-items: center; gap: 12px; color: var(--ink-soft); font-size: 12px; }
.oauth-divider::before, .oauth-divider::after { content: ''; flex: 1; border-top: 1px solid var(--line); }
.google-btn { display: flex; justify-content: center; min-height: 40px; }
/* Cloudflare Turnstile widget centred in the modal ("flexible" size on
   ordinary viewports, official compact widget under 420px - see core.js). */
.turnstile-slot { display: flex; justify-content: center; min-height: 65px; max-width: 100%; }

/* Cookie banner */
.cookie-banner {
  position: fixed; left: 16px; right: 16px; bottom: 16px; z-index: 90;
  display: flex; gap: 18px; align-items: center; justify-content: space-between; flex-wrap: wrap;
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius-lg);
  box-shadow: var(--shadow); padding: 14px 22px; max-width: 880px; margin: 0 auto;
}
.cookie-banner p { margin: 0; font-size: 14px; color: var(--ink-soft); max-width: 56ch; }
.cookie-banner div { display: flex; gap: 10px; }

/* Footer */
.site-footer {
  border-top: 1px solid var(--line); padding: 20px clamp(16px, 4vw, 48px);
  display: flex; justify-content: space-between; align-items: center; gap: 16px; flex-wrap: wrap;
  color: var(--ink-dim); font-size: 13px;
}
.site-footer div { display: flex; align-items: center; gap: 10px; }
.site-footer nav { display: flex; gap: 18px; }
.site-footer a { color: var(--ink-soft); text-decoration: none; }
.site-footer a:hover { color: var(--green); }

/* Admin */
/* The overlay-nudge buttons are wrapped in a labelled group, so the wrapper
   has to reproduce the filters-row spacing its children used to inherit. */
.nudge-group { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
/* Sub-menu tab strip: reuses the .tabs look; wraps so all four tabs stay
   reachable (no horizontal page scroll) on a 320px viewport. */
.admin-tabs { flex-wrap: wrap; margin: 4px 0 20px; }
/* Review feeds reuse the cat-row disclosure layout; the detail lists each
   coverage part on its own line. */
#admin-subs, #admin-missing, #admin-reports { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
#reports-heading { margin: 20px 0 10px; }
.review-parts { margin: 0 0 8px; padding-left: 18px; font-size: 13px; color: var(--ink-soft); }
.review-parts li { margin: 2px 0; overflow-wrap: anywhere; }
/* Map-issue reports: neutral blue chip; the message keeps the reporter's
   line breaks (pre-line) and cannot force horizontal scrolling. */
.review-chip.report { background: var(--blue-soft); color: var(--blue); }
.review-chip.resolved { background: var(--green-soft); color: var(--green); }
.review-chip.dismissed { background: rgba(159, 176, 195, 0.18); color: var(--ink-soft); }
.report-message { white-space: pre-line; overflow-wrap: anywhere; font-size: 13.5px; color: var(--ink-soft); margin: 0 0 4px; }

/* Inline "report an issue" mini-form (map popover + collection rows). */
.report-form { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; border: 1px dashed var(--line); border-radius: var(--radius); padding: 10px 12px; width: 100%; }
.report-form textarea { width: 100%; }
.report-form .status-message { margin: 0; min-height: 0; }
.report-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* Collection row actions (Show on map / Report an issue) and the full-width
   slot the report form opens into. */
.map-card .card-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.map-card .report-slot { flex-basis: 100%; }
.map-card .report-slot:empty { display: none; }
/* The popover's report trigger sits under the ownership row. */
.popover-sheet .link-button[data-report] { align-self: flex-start; padding: 2px 0; }

/* ---------- admin Review Map ---------- */
/* Pending outlines are clickable SVG elements (no pointer-events:none, unlike
   the public map's outlines); the selected one is redrawn on top in blue so
   it stands out even where submissions overlap. */
.pending-shape { fill: rgba(227, 179, 65, 0.18); stroke: var(--warn); stroke-width: 1.1; cursor: pointer; }
.pending-shape:hover { fill: rgba(227, 179, 65, 0.34); }
.pending-shape.is-selected { fill: rgba(78, 161, 247, 0.3); }
.pending-selected { fill: none; stroke: var(--blue); stroke-width: 2.2; pointer-events: none; }
/* Users-map mode: blue base so it cannot be mistaken for the amber pending
   queue; the selected outline flips to amber for contrast. */
.um-shape { fill: rgba(78, 161, 247, 0.16); stroke: var(--blue); stroke-width: 1.1; cursor: pointer; }
.um-shape:hover { fill: rgba(78, 161, 247, 0.3); }
.um-shape.is-selected { fill: rgba(227, 179, 65, 0.28); }
.um-selected { fill: none; stroke: var(--warn); stroke-width: 2.2; pointer-events: none; }
.pending-label { fill: #f3e2b3; }
.rm-side { width: 240px; }
.rm-side .zoom-controls { flex-wrap: wrap; }
.rm-card-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.rm-list-heading { margin: 4px 0 0; font-size: 14px; }
.rm-list { display: flex; flex-direction: column; gap: 6px; max-height: 46vh; overflow: auto; list-style: none; margin: 0; padding: 0; }
.rm-item {
  width: 100%; text-align: left; background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 8px 10px; color: var(--ink); font: inherit; cursor: pointer; display: flex; flex-direction: column; gap: 2px;
}
.rm-item:hover { border-color: var(--green); }
.rm-item.is-selected { border-color: var(--blue); background: var(--blue-soft); }
.rm-item .code { font-weight: 800; }
.rm-item .rm-name, .rm-item .hint { font-size: 12.5px; color: var(--ink-soft); overflow-wrap: anywhere; }
.rm-meta { display: grid; grid-template-columns: max-content 1fr; gap: 3px 12px; margin: 10px 0 6px; font-size: 13px; }
.rm-meta dt { color: var(--ink-dim); }
.rm-meta dd { margin: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
.rm-meta .review-chip { font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 7px; }
.rm-notes { overflow-wrap: anywhere; }
.rm-others { display: flex; flex-direction: column; align-items: flex-start; }
.rm-card-sheet h3:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; border-radius: 4px; }

@media (max-width: 900px) {
  .hero { grid-template-columns: 1fr; }
  .contribute-layout { grid-template-columns: 1fr; }
  .main-nav { order: 3; width: 100%; justify-content: center; }
  .site-header { flex-wrap: wrap; }
  .intro-band { grid-template-columns: 1fr; gap: 22px; }
  .map-layout { flex-direction: column; }
  .map-side { width: 100%; flex-direction: row; flex-wrap: wrap; align-items: flex-start; }
  /* The control column keeps its reading order on a phone: each block takes
     the full width instead of wrapping beside the key or the zoom controls. */
  .map-side .map-options, .map-side .map-series-row, .map-side .map-empty, .map-side .outline-error { flex-basis: 100%; }
  .rm-side .rm-list-heading, .rm-side .rm-list, .rm-side .status-message { flex-basis: 100%; }
  .rm-list { max-height: 30vh; }
  .map-shell { height: calc(100vh - 260px); }
  .series-panel { max-height: 40vh; }
  /* On a phone the panel spans the stage instead of covering its top-right
     corner, and leaves most of the map visible beneath it. */
  .sheet-popover { left: 8px; right: 8px; top: 8px; width: auto; max-height: 60%; }
}

/* ---------- in-app messages ---------- */
.msg-badge {
  background: var(--blue-soft); color: var(--blue); border: 1px solid var(--blue);
  font: inherit; font-size: 12px; font-weight: 700; padding: 3px 10px;
  border-radius: 999px; white-space: nowrap; cursor: pointer;
}
.msg-card {
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-warm);
  padding: 12px 14px; margin: 0 0 10px;
}
.msg-card.is-unread { border-color: var(--blue); }
.msg-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 4px; }
.msg-head .review-chip { font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 7px; }
/* An administrator's earlier replies inside a report row. */
.rm-reply { border-left: 3px solid var(--green); padding-left: 10px; margin: 6px 0; }
.rm-reply .hint { display: block; margin-top: 2px; }

/* ---------- admin attention badge (nav) ---------- */
.admin-alerts { position: relative; display: flex; align-items: center; }
.nav-badge {
  background: var(--warn); color: #1d1405; border: 0; cursor: pointer;
  font: inherit; font-size: 12px; font-weight: 800; line-height: 1;
  min-width: 22px; height: 22px; padding: 0 7px; border-radius: 999px;
}
.nav-badge.is-quiet { background: var(--paper-warm); color: var(--ink-soft); border: 1px solid var(--line); }
.admin-alerts-panel {
  position: absolute; top: calc(100% + 8px); left: 0; z-index: 40; min-width: 16rem;
  background: var(--paper-raised); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 10px 12px; text-align: left;
}
.admin-alerts-panel h3 { margin: 0 0 6px; font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft); }
.admin-alerts-panel ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.admin-alerts-panel li a { display: flex; justify-content: space-between; gap: 12px; padding: 6px 4px; border-radius: 6px; color: var(--ink); text-decoration: none; font-size: 14px; }
.admin-alerts-panel li a:hover, .admin-alerts-panel li a:focus-visible { background: var(--paper-warm); }
.admin-alerts-panel li a strong { min-width: 1.6em; text-align: right; }
.admin-alerts-panel .hint { margin: 8px 0 0; }
/* Narrow layouts: the badge sits at the right end of the wrapped nav, so the
   panel hangs from its right edge and never runs off the screen. Declared
   AFTER the base rule above (same specificity - source order decides). */
@media (max-width: 900px) {
  .admin-alerts-panel { left: auto; right: 0; max-width: calc(100vw - 24px); }
}
/* The admin stats strip's "online now" figure carries its definition as a
   tooltip (real DOM text, aria-describedby) - see .has-tip. */
.collection-stats .has-tip { display: inline-block; }

/* ---------- admin-view toggle + badge ---------- */
.admin-badge {
  background: var(--warn); color: #1d1405;
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
  padding: 3px 10px; border-radius: 999px; white-space: nowrap;
}
/* Unobtrusive marker that the gate is being passed on an access pass. */
.preview-badge {
  background: var(--blue-soft); color: var(--blue); border: 1px solid var(--line);
  font-size: 12px; font-weight: 700; letter-spacing: 0.02em;
  padding: 3px 10px; border-radius: 999px; white-space: nowrap;
}
.view-toggle-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 8px 12px; border-bottom: 1px solid var(--line);
  font-size: 13px; color: var(--ink-soft);
}
#menu-admin-toggle {
  display: inline-flex; padding: 2px; border: 1px solid var(--line);
  border-radius: 999px; background: var(--paper-warm); cursor: pointer;
}
#menu-admin-toggle:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }
#menu-admin-toggle .vt-opt {
  padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 600;
  color: var(--ink-dim);
}
#menu-admin-toggle[aria-checked="false"] .vt-user,
#menu-admin-toggle[aria-checked="true"] .vt-admin {
  background: var(--green-dark); color: #fff;
}

/* Header map-count pill: a real link to My Collection. */
.stats-link { text-decoration: none; display: inline-flex; align-items: center; cursor: pointer; transition: 0.15s; }
.stats-link:hover { border-color: var(--green-dark); color: var(--ink); }
.stats-link:focus-visible { outline: 2px solid var(--green); outline-offset: 2px; }


/* Contribute: per-side and inset editors. */
.side-editor {
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 12px 14px; margin: 0 0 14px; min-width: 0;
}
.side-editor legend { font-weight: 700; font-size: 14px; padding: 0 6px; }
.inset-block { border: 1px dashed var(--line); border-radius: var(--radius); padding: 10px 12px; margin: 0 0 12px; min-width: 0; }
.inset-block legend { font-size: 13px; font-weight: 600; color: var(--ink-soft); padding: 0 6px; }

/* Coverage preview: distinct, colour-independent styling per part type
   (fill + dash patterns, not colour alone) with the automatic closing edge
   drawn dashed so a missing corner is visible before submission. */
.ctr-preview-wrap { border: 1px solid var(--line); border-radius: var(--radius); padding: 12px 14px; margin: 0 0 14px; }
.ctr-preview-wrap h3 { margin: 0 0 8px; font-size: 14px; }
#ctr-preview, .cat-preview {
  width: 100%; max-height: 320px; background: var(--paper-warm);
  border: 1px solid var(--line); border-radius: var(--radius); display: block;
}
.prev-shape { fill-opacity: 0.25; stroke-width: 1.5; vector-effect: non-scaling-stroke; }
.prev-side1, .swatch.prev-side1 { fill: #3ecf8e; stroke: #3ecf8e; background: rgba(62, 207, 142, 0.45); }
.prev-side2, .swatch.prev-side2 { fill: #5aa9e6; stroke: #5aa9e6; background: rgba(90, 169, 230, 0.45); }
.prev-inset, .swatch.prev-inset { fill: #e3b341; stroke: #e3b341; background: rgba(227, 179, 65, 0.45); stroke-dasharray: 6 3; }
.prev-close, .swatch.prev-close { stroke: #e0604a; stroke-width: 1.5; stroke-dasharray: 3 4; vector-effect: non-scaling-stroke; background: rgba(224, 96, 74, 0.55); }
.prev-label { fill: var(--ink-soft); }
.preview-legend { display: flex; flex-wrap: wrap; gap: 8px 16px; margin: 8px 0 0; font-size: 12px; color: var(--ink-soft); }
.preview-issues .issue-error { color: #e8796a; font-size: 13px; margin: 8px 0 0; }
.preview-issues .issue-warn { color: var(--warn); font-size: 13px; margin: 8px 0 0; }

/* Admin map catalogue: one collapsed summary row per physical map. */
.cat-list { display: flex; flex-direction: column; gap: 8px; }
.cat-row { background: var(--paper-warm); border: 1px solid var(--line); border-radius: var(--radius); width: 100%; }
.cat-row.is-open { border-color: var(--green-dark); }
.cat-row-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px;
  width: 100%; padding: 9px 12px; background: none; border: 0; color: var(--ink);
  font: inherit; font-size: 13.5px; text-align: left; cursor: pointer;
}
.cat-row-summary:focus-visible { outline: 2px solid var(--green); outline-offset: -2px; border-radius: var(--radius); }
.cat-row-summary .disclosure { flex: none; color: var(--ink-dim); width: 14px; }
.cat-row-summary .code { font-weight: 800; min-width: 44px; }
.cat-row-summary .cat-name { flex: 1 1 220px; color: var(--ink-soft); }
.cat-row-summary .hint { font-size: 12px; }
.cat-chip {
  font-size: 11px; font-weight: 700; border-radius: 5px; padding: 2px 7px; white-space: nowrap;
  background: var(--paper-raised); border: 1px solid var(--line); color: var(--ink-soft);
}
.cat-chip.ok { color: var(--green); border-color: var(--green-dark); }
.cat-chip.warn { color: var(--warn); }
.cat-detail { border-top: 1px solid var(--line); padding: 12px 14px; }
.cat-detail dl { display: grid; grid-template-columns: minmax(110px, max-content) 1fr; gap: 4px 14px; margin: 0 0 10px; font-size: 13.5px; }
.cat-detail dt { color: var(--ink-dim); }
.cat-detail dd { margin: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
.cat-edit-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; margin: 0 0 10px; }
.cat-edit-form .grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0 14px; }
.cat-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* Admin user management: one collapsed disclosure row per account. The
 * summary reuses the cat-row layout (flex-wrap keeps it usable at 320px). */
/* Aligned columns: each summary is a grid with explicit tracks, so name,
   email, chips, dates and counts line up between rows instead of drifting
   with each row's content. minmax(0,…) lets long names/emails wrap inside
   their track; the fixed floors on the right-hand tracks pin them to a
   common width across rows. The row itself is flex so the selection
   checkbox sits beside the summary button and the detail spans full width. */
.user-row { display: flex; align-items: stretch; flex-wrap: wrap; }
.user-row > .user-select { flex: none; display: flex; align-items: center; padding: 0 0 0 10px; }
.user-row > .user-row-summary { flex: 1 1 0; min-width: 0; }
.user-row > .user-detail { flex-basis: 100%; }
.user-row-summary {
  display: grid; align-items: center; gap: 4px 14px;
  grid-template-columns:
    14px                        /* disclosure  */
    minmax(0, 1.4fr)            /* name        */
    minmax(0, 2fr)              /* email       */
    max-content                 /* status chip */
    max-content                 /* verified    */
    minmax(6rem, max-content)   /* sign-in     */
    minmax(7rem, max-content)   /* role chip   */
    minmax(8rem, max-content)   /* registered  */
    minmax(9.5rem, max-content) /* last login  */
    minmax(9rem, max-content);  /* counts      */
}
.user-row-summary > * { min-width: 0; }
.user-row-summary .user-name { font-weight: 700; overflow-wrap: anywhere; }
.user-row-summary .user-email { color: var(--ink-soft); overflow-wrap: anywhere; }
@media (max-width: 900px) {
  /* Narrow widths: the aligned grid cannot hold the nowrap chips without
     overflow, so fall back to a wrapping row - name and email on their own
     lines, chips and dates flowing naturally beneath. */
  .user-row-summary { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; }
  .user-row-summary .disclosure { width: 14px; }
  .user-row-summary .user-name { flex-basis: calc(100% - 30px); }
  .user-row-summary .user-email { flex-basis: 100%; }
}
.user-row-summary .review-chip { font-size: 11px; font-weight: 700; border-radius: 4px; padding: 1px 7px; }
.review-chip.suspended { background: rgba(224,96,74,0.25); color: var(--danger); }
/* Role chips: distinct border treatment AND the full word - never colour alone. */
.role-chip {
  font-size: 11px; font-weight: 700; border-radius: 5px; padding: 2px 7px; white-space: nowrap;
  background: var(--paper-raised); border: 1px solid var(--line); color: var(--ink-soft);
}
.role-chip.admin { border: 2px solid var(--green-dark); color: var(--green); }
.role-chip.super { border: 3px double var(--warn); color: var(--warn); text-transform: uppercase; letter-spacing: 0.02em; }
.user-history-heading { margin: 12px 0 6px; font-size: 13.5px; }
.user-history { margin: 0 0 10px; padding-left: 18px; font-size: 13px; color: var(--ink-soft); }
.user-history li { margin: 2px 0; overflow-wrap: anywhere; }
/* Inline confirm+reason mini-form for suspension. */
.suspend-form { border: 1px dashed var(--danger); border-radius: var(--radius); padding: 10px 12px; margin-top: 10px; }
.suspend-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; margin: 0; }
.suspend-form .cat-actions { margin-top: 8px; }
/* The Admin review headline "N users" doubles as a jump link to the Users
 * section: keep the stat look but make the interactivity visible. */
#admin-users-jump { padding: 0; font-size: inherit; color: inherit; text-decoration: underline dotted; text-underline-offset: 3px; }
#admin-users-jump:hover, #admin-users-jump:focus-visible { color: var(--green); }
@media (max-width: 560px) {
  .map-card .own-row { margin-left: 0; width: 100%; }
  .cat-detail dl { grid-template-columns: 1fr; }
}

/* ---------- affiliate disclosure ----------
 * Rendered by js/map.js only when a retailer link is actually rendered, and
 * repeated once in Help. Quiet, but never hidden: a disclosure that needs
 * hunting for is not a disclosure. */
.affiliate-note { margin: 4px 0 0; font-size: 12px; color: var(--ink-dim); }
.popover-sheet .affiliate-note { margin: 2px 0 6px; }

/* ---------- destructive confirm forms ----------
 * Shared by the super-administrator's permanent deletion (js/admin-users.js)
 * and self-deletion in the account view. Dashed danger border + explicit
 * warning copy, never colour alone. */
.delete-form {
  border: 1px dashed var(--danger); border-radius: var(--radius);
  padding: 12px 14px; margin-top: 12px;
}
.delete-form label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; margin: 8px 0 0; }
.delete-form input[type="text"] { width: 100%; max-width: 26rem; }
.delete-warning { font-size: 13px; color: var(--ink-soft); margin: 0 0 8px; }
.delete-warning strong { color: var(--danger-text); }
.delete-preview { margin: 4px 0 0; padding-left: 18px; font-size: 13px; }
.delete-preview strong { color: var(--ink); }
.danger-heading { color: var(--danger-text); margin-top: 26px; }

/* ---------- account settings ---------- */
.account-tabs { flex-wrap: wrap; margin: 4px 0 20px; }
#view-account h3 { margin: 24px 0 8px; font-size: 15px; }
#view-account h3:first-child { margin-top: 0; }
.account-facts {
  display: grid; grid-template-columns: minmax(120px, max-content) 1fr;
  gap: 4px 14px; margin: 0 0 10px; font-size: 13.5px;
}
.account-facts dt { color: var(--ink-dim); }
.account-facts dd { margin: 0; color: var(--ink-soft); overflow-wrap: anywhere; }
.account-form {
  display: flex; flex-direction: column; gap: 10px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--paper-warm); padding: 14px 16px; margin: 0 0 8px; max-width: 34rem;
}
.account-form label { margin: 0; }
.account-form input { width: 100%; }
.account-form .cat-actions { margin-top: 2px; }
.account-form .hint { margin: 0; }
.account-pending {
  border: 1px solid var(--warn); border-radius: var(--radius);
  background: rgba(227, 179, 65, 0.10); padding: 12px 14px; font-size: 13.5px; max-width: 34rem;
}
.account-pending p { margin: 0 0 8px; }
/* "Confirm it's you" / two-factor prompt: raised, so it reads as the thing
   that must be dealt with before the action continues. */
.assurance-prompt {
  border: 1px solid var(--green-dark); border-radius: var(--radius);
  background: var(--paper-raised); padding: 14px 16px; margin: 0 0 16px; max-width: 34rem;
}
.assurance-prompt h3 { margin: 0 0 6px; font-size: 15px; }
.assurance-prompt label { display: flex; flex-direction: column; gap: 4px; font-size: 13px; margin: 8px 0 0; }
.assurance-prompt input { width: 100%; max-width: 22rem; }
.assurance-prompt .status-message { margin: 8px 0 0; min-height: 0; }
.totp-steps { margin: 0 0 12px; padding-left: 20px; color: var(--ink-soft); font-size: 13.5px; }
.totp-qr {
  display: inline-block; background: #fff; border-radius: 8px; padding: 10px; margin: 0 0 10px;
  line-height: 0;
}
.totp-qr svg, .totp-qr img { display: block; width: 200px; height: 200px; }
/* One-time recovery codes: loud on purpose - they are shown exactly once. */
.recovery-block {
  border: 1px solid var(--warn); border-radius: var(--radius);
  background: rgba(227, 179, 65, 0.10); padding: 14px 16px; margin: 0 0 18px; max-width: 34rem;
}
.recovery-block h3 { margin: 0 0 6px; }
.recovery-block p { font-size: 13.5px; color: var(--ink-soft); margin: 0 0 10px; }
.recovery-codes {
  width: 100%; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 13px; resize: vertical;
}
.recovery-block .check { margin: 10px 0 0; }
#account-panel-sessions .feed-item { max-width: 34rem; }

/* ---------- reduced motion ---------- */
/* Honour the OS "reduce motion" preference: motion can trigger nausea,
   dizziness or migraine for vestibular users. Everything animated here is
   decoration (0.15s colour/border transitions) or the smooth page scroll, so
   all of it can simply be switched off.

   TRANSITIONS ARE KILLED OUTRIGHT (transition: none), NOT SHORTENED.
   This block previously set transition-duration: 0.01ms. That was actively
   harmful, and not because 0.01ms is slow: transition-property's INITIAL
   value is "all", and this file declares transition-property nowhere, so
   naming a duration on the universal selector GAVE EVERY ELEMENT IN THE
   DOCUMENT an all-property transition it never had. Under "reduce" a plain
   <div> transitioned its geometry; under no-preference it did not. Code that
   writes a style and measures the result in the same task then read the OLD
   value, because a transition - however short - starts at the old one.

   That cost us twice. The map camera (js/map-camera.js) was patched with the
   element-specific exception above; the map SHELL was not, so a window resize
   or a phone rotation left the public map permanently off-centre with its
   horizontal scroll range clamped to zero, unrecoverable without a reload.

   The shorthand resets transition-property, -duration, -delay and
   -timing-function together, so no future non-zero delay can defer a layout
   change either. Nothing in the app waits on a transitionend/animationend
   (there are no such listeners, and no @keyframes at all), so there is
   nothing left to receive an event.

   Animations keep the near-zero duration rather than "none": that is the
   standard defensive idiom for CSS animations added later, and unlike a
   transition it carries no write-then-measure hazard.

   NOTE: the smooth scroll called from js/core.js
   (section.scrollIntoView({ behavior: "smooth" })) overrides scroll-behavior
   and CANNOT be neutralised from CSS - it needs a matchMedia check in JS
   (prefersReducedMotion(), js/core.js). */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition: none !important;
    scroll-behavior: auto !important;
  }
}
