/* ===========================================================================
   Pure CSS custom properties + CSS grid.
   Theming via [data-theme]; respects prefers-color-scheme as the default.
   =========================================================================== */

/* ---- Design tokens -------------------------------------------------------- */
:root {
	/* spacing scale (rem-based so it scales with user/browser zoom) */
	--sp-1: 0.25rem;  --sp-2: 0.5rem;  --sp-3: 0.75rem;
	--sp-4: 1rem;     --sp-5: 1.5rem;  --sp-6: 2rem;

	--radius: 0.5rem;
	--radius-sm: 0.3rem;
	--font: "Segoe UI", system-ui, -apple-system, Roboto, Helvetica, Arial, sans-serif;

	/* shell sizing */
	--topbar-h: 2.75rem;
	--bottom-h: 2rem;
	--rail-w: 3.5rem;          /* icons-only */
	--rail-w-expanded: 15rem;  /* icons + labels — fits the longest category name
	                              ("Infraestructura e Industria") on one line; at
	                              13rem it was silently clipped by .rail-items */
	--right-w: clamp(20rem, 26vw, 26rem);

	--transition: 160ms ease;

	/* light theme (default) */
	--bg:        #f4f6f9;
	--surface:   #ffffff;
	--surface-2: #eef1f5;
	--border:    #d7dde6;
	--fg:        #1d2734;
	--fg-muted:  #5b6776;
	--brand:     #1d6fe0;
	--brand-fg:  #ffffff;
	--accent:    #e2a300;
	--shadow:    0 1px 3px rgba(16, 24, 40, .12), 0 1px 2px rgba(16, 24, 40, .08);
	--shadow-lg: 0 8px 24px rgba(16, 24, 40, .16);
	--map-filter: none;
}

[data-theme="dark"] {
	--bg:        #11151c;
	--surface:   #1a2029;
	--surface-2: #232b36;
	--border:    #313b48;
	--fg:        #e7ecf2;
	--fg-muted:  #9aa7b6;
	--brand:     #4f9bff;
	--brand-fg:  #07101d;
	--accent:    #ffc94d;
	--shadow:    0 1px 3px rgba(0, 0, 0, .5);
	--shadow-lg: 0 10px 30px rgba(0, 0, 0, .55);
	/* gently darken the OSM raster tiles so they sit in a dark UI */
	--map-filter: brightness(.78) contrast(1.05) saturate(.85) hue-rotate(180deg) invert(.92);
}

/* Playful "vibrant" mode — vivid, rounder, friendlier (3rd colour mode). */
[data-theme="vibrant"] {
	--bg:        #eaf3ff;
	--surface:   #ffffff;
	--surface-2: #fff2d4;
	--border:    #ffd277;
	--fg:        #2a2350;
	--fg-muted:  #6b6a8a;
	--brand:     #7b3ff2;   /* purple */
	--brand-fg:  #ffffff;
	--accent:    #ff7a59;   /* coral */
	--radius:    0.9rem;
	--radius-sm: 0.6rem;
	--shadow:    0 2px 6px rgba(123, 63, 242, .18);
	--shadow-lg: 0 10px 28px rgba(123, 63, 242, .28);
	--map-filter: saturate(1.18);   /* a touch more vivid */
}

/* OS preference only applies before JS sets an explicit data-theme (avoids a
   dark-OS overriding the chosen light/vibrant theme via specificity). */
@media (prefers-color-scheme: dark) {
	:root:not([data-theme]) {
		--bg: #11151c; --surface: #1a2029; --surface-2: #232b36; --border: #313b48;
		--fg: #e7ecf2; --fg-muted: #9aa7b6; --brand: #4f9bff; --brand-fg: #07101d;
		--accent: #ffc94d; --shadow: 0 1px 3px rgba(0,0,0,.5); --shadow-lg: 0 10px 30px rgba(0,0,0,.55);
		--map-filter: brightness(.78) contrast(1.05) saturate(.85) hue-rotate(180deg) invert(.92);
	}
}

/* ---- Reset ---------------------------------------------------------------- */
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
	font-family: var(--font);
	color: var(--fg);
	background: var(--bg);
	overflow: hidden;
	-webkit-font-smoothing: antialiased;
}
button { font: inherit; color: inherit; cursor: pointer; }
a { color: var(--brand); }
[hidden] { display: none !important; }
[v-cloak] { display: none; }

/* visible focus for keyboard users */
:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
	* { transition: none !important; animation: none !important; }
}

/* ---- App shell (CSS grid) ------------------------------------------------- */
#app {
	display: grid;
	position: relative;            /* anchors the overlay rail at XL */
	height: 100vh;                 /* fallback */
	height: 100dvh;                /* tracks mobile browser toolbars */
	/* default = XL layout. The rail is an OVERLAY, not a grid column: the map
	   spans full width underneath it, so toggling the rail reveals/hides map on
	   the left instead of resizing & shifting the whole map. */
	grid-template-columns: 1fr var(--right-w);
	grid-template-rows: var(--topbar-h) 1fr var(--bottom-h);
	grid-template-areas:
		"topbar topbar"
		"map    right"
		"bottom bottom";
}
/* grid items default to min-width/min-height:auto, letting a wide/tall child (the
   bottom legend, or a long fact list) blow the track past the viewport — pin them
   so tracks honour the grid and inner content (e.g. .pane-body) scrolls instead. */
.topbar, .rail, .map-wrap, .right, .bottombar { min-width: 0; min-height: 0; }

/* ---- Brand webfont (Fira Sans, SIL OFL 1.1) ------------------------------- */
/* Wordmark and footer name: EDU = weight 300, MAPS = weight 400. */
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url("../fonts/fira-sans-300-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 300;
	font-display: swap;
	src: url("../fonts/fira-sans-300-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/fira-sans-400-latin.woff2") format("woff2");
	unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
	font-family: "Fira Sans";
	font-style: normal;
	font-weight: 400;
	font-display: swap;
	src: url("../fonts/fira-sans-400-latin-ext.woff2") format("woff2");
	unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---- Top bar -------------------------------------------------------------- */
.topbar {
	grid-area: topbar;
	display: flex; align-items: center; gap: var(--sp-3);
	/* `min-height`, not `height`: the token is the bar's height everywhere, but a
	   wrapping `.brand small` must be able to grow the bar rather than spill out
	   of it. Without this line the bar only got its height from /m's grid row and
	   from .docs-topbar, so every .plain-page rendered at its intrinsic 37px
	   (36px controls + the border) — two different bars for one piece of chrome. */
	min-height: var(--topbar-h);
	padding: 0 var(--sp-3);
	background: var(--surface);
	border-bottom: 1px solid var(--border);
	z-index: 1200;
}
.brand {
	display: flex; align-items: center; gap: var(--sp-2);
	font-weight: 300; font-size: 1.15rem; letter-spacing: .02em;
	color: var(--fg); text-decoration: none; white-space: nowrap;
}
/* Wordmark in Fira Sans to match the marketing pages: EDU 300, MAPS 400. */
.brand .name { font-family: "Fira Sans", var(--font); }
.brand b { font-weight: 400; }
.brand img { width: 28px; height: 28px; }

.brand         { flex: 1 1 0; }
.topbar-center { flex: 0 1 auto; display: flex; justify-content: center; }
/* Labelled nav links. `.iconbtn` is a fixed 2.25rem square built for icon-only
   controls, so a label inside one overflows its box — these widen to fit and
   gain the gap between icon and text. When the label is hidden below 900px they
   collapse back to the square. */
.topbar-nav { display: flex; align-items: center; gap: var(--sp-2); }
.topbar-nav a.iconbtn {
	width: auto; min-width: 2.25rem; gap: var(--sp-2);
	padding-inline: var(--sp-2); white-space: nowrap;
	font-size: .85rem; text-decoration: none;
}
@media (max-width: 900px) {
	.topbar-nav a.iconbtn { width: 2.25rem; padding-inline: 0; }
}
.topbar-right { display: flex; align-items: center; gap: var(--sp-2); flex: 1 1 0; justify-content: flex-end; }
/* /m keeps the original arrangement — all three rules, or the change makes its
   bar worse: with only the right track restored, `.brand`'s `flex: 1 1 0` eats
   the free space and shoves the date control across to the icons. Its centre was
   already page-centred, because a growing `.topbar-center` between a narrow
   brand and a narrow control group centres at about the same place.
   The reason /m opts out at all is `.topbar-right`: `docs-build` element-crops
   it for a documentation screenshot with an anatomy badge anchored to it
   (manifest/shots.yaml, manifest/anatomy.yaml), and a half-bar-wide box would
   put a broad empty strip into that image. */
.app-page .brand         { flex: 0 1 auto; }
.app-page .topbar-center { flex: 1 1 auto; min-width: 0; }
.app-page .topbar-right  { flex: 0 0 auto; margin-left: auto; }
/* The brand's page title: present on the docs and the editors, absent elsewhere. */
.brand small { font-size: .8rem; color: var(--fg-muted); font-weight: 400; }

/* Hide `.topbar-nav`, NEVER `.topbar-center`: on `/m` the centre holds the date
   control, which must survive every width. */
@media (max-width: 900px) {
	.topbar-nav a span { display: none; }
	.lang-code { display: none; }
	.brand small { display: none; }
}
@media (max-width: 560px) {
	.topbar-nav { display: none; }
}
/* `/m` drops its wordmark earlier (<=768, in the RESPONSIVE section) because its
   bar also carries the date control; everywhere else it survives to here. */
@media (max-width: 400px) {
	.brand .name { display: none; }
}

/* Both auth states ship in the markup and only their `hidden` attribute is
   flipped, so the correct one is on screen in the first frame and the links
   still work with no JS. The two wrappers are real flex containers rather than
   `display: contents`, or the gap below would space the wrapper instead of the
   links inside it. */
.topbar-auth { display: flex; align-items: center; font-size: .85rem; white-space: nowrap; }
.topbar-auth > span { display: flex; align-items: center; gap: var(--sp-1); }
.topbar-auth a {
	display: inline-flex; align-items: center; gap: var(--sp-1);
	padding: var(--sp-1) var(--sp-2); border-radius: var(--radius-sm);
	color: var(--fg-muted); text-decoration: none;
}
.topbar-auth a:hover { background: var(--surface-2); color: var(--fg); }
/* `.topbar-menu-pop a` is declared after these and at equal specificity, so the
   pop's own padding and colour win for the menu entries. */

/* top-bar dropdowns (language, share, user) via <details> — no JS */
.topbar-menu { position: relative; }
.topbar-menu > summary { list-style: none; cursor: pointer; }
.topbar-menu > summary::-webkit-details-marker { display: none; }
/* A summary that carries a label as well as its glyph — the language control's
   active code. `.iconbtn` is a fixed 2.25rem square built for icon-only buttons,
   so it clips anything beside the icon; the same widen-to-fit treatment as
   `.topbar-nav a.iconbtn`. Collapses back to the square once `.lang-code` is
   hidden at <=900. */
.topbar-menu > summary.iconbtn:has(.lang-code) {
	width: auto; min-width: 2.25rem; gap: var(--sp-1); padding-inline: var(--sp-2);
}
@media (max-width: 900px) {
	.topbar-menu > summary.iconbtn:has(.lang-code) { width: 2.25rem; padding-inline: 0; }
}
/* The active language, beside the glyph. Not a flag: the control is generic
   because the site is heading past two languages, and the flags live in the pop
   where each one is named. */
.lang-code { font-size: .8rem; font-weight: 600; letter-spacing: .04em; }
.topbar-menu-pop {
	position: absolute; right: 0; top: calc(100% + 4px); z-index: 1300;
	display: flex; flex-direction: column; min-width: 10rem; padding: var(--sp-1);
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
/* Buttons need the resets `button { font: inherit }` in the base does not cover:
   a transparent background, no border, full width and a left-aligned text run. */
.topbar-menu-pop a,
.topbar-menu-pop button {
	display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-2);
	border-radius: var(--radius-sm); color: var(--fg); text-decoration: none; font-size: .85rem; white-space: nowrap;
}
.topbar-menu-pop button {
	width: 100%; text-align: left; background: transparent; border: 0;
}
.topbar-menu-pop a:hover,
.topbar-menu-pop button:hover { background: var(--surface-2); }
.topbar-menu-pop .menu-sep {
	margin-top: var(--sp-1); padding-top: calc(var(--sp-2) + var(--sp-1));
	border-top: 1px solid var(--border);
}
.topbar-menu-pop a.active,
.topbar-menu-pop button.active {
	background: color-mix(in srgb, var(--brand) 16%, transparent);
	color: var(--brand); font-weight: 600;
	pointer-events: none;
}
/* A non-interactive first row: the signed-in reader's name above their actions.
   It is filled with textContent (never innerHTML) — the string comes from the
   account — so it must be able to hold a long name without widening the pop. */
.topbar-menu-pop .menu-head {
	padding: var(--sp-2); margin-bottom: var(--sp-1);
	border-bottom: 1px solid var(--border);
	color: var(--fg-muted); font-size: .8rem; font-weight: 600;
	max-width: 14rem; overflow: hidden; text-overflow: ellipsis;
}

/* time control */
.timectl { display: flex; align-items: center; gap: var(--sp-2); }
.timectl .year {
	width: 6.5rem; text-align: center;   /* definite width — type=number's intrinsic width is too wide */
	font-variant-numeric: tabular-nums; font-size: 1.4rem; font-weight: 700;
	background: transparent; border: 1px solid transparent; border-radius: var(--radius-sm);
	color: var(--fg); padding: var(--sp-1) var(--sp-2);
}
.timectl .year:hover { border-color: var(--border); }
.timectl .era { color: var(--fg-muted); font-size: .8rem; }

/* search */
.search { position: relative; }
.search input {
	width: clamp(10rem, 18vw, 18rem);
	padding: var(--sp-2) var(--sp-2) var(--sp-2) 2rem;
	background: var(--surface-2); color: var(--fg);
	border: 1px solid var(--border); border-radius: 999px; outline: none;
}
.search i { position: absolute; left: .7rem; top: 50%; transform: translateY(-50%); color: var(--fg-muted); }

.iconbtn {
	display: inline-flex; align-items: center; justify-content: center;
	width: 2.25rem; height: 2.25rem; border-radius: var(--radius-sm);
	background: transparent; border: 1px solid transparent; color: var(--fg-muted);
	transition: background var(--transition), color var(--transition);
	text-decoration: none;
}
.iconbtn:hover { background: var(--surface-2); color: var(--fg); }
.iconbtn.primary { background: var(--brand); color: var(--brand-fg); }
.iconbtn.primary:hover { filter: brightness(1.08); }

/* generic toggle switch — a real checkbox drives a CSS track + knob, so keyboard and
   assistive-tech behaviour stays native (role="switch" names it correctly).
   The input is transparent but stretched over the whole label rather than shrunk to 1px:
   it stays the click target, so `#someId.click()` keeps working for tooling and tests.
   Set --switch-on to re-colour a single switch (the rail categories use their own colour). */
.switch { position: relative; display: flex; align-items: center; gap: var(--sp-2); font-size: .8rem; color: var(--fg); cursor: pointer; }
.switch input { position: absolute; top: 0; left: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.switch-track {
	flex: 0 0 auto; position: relative; width: 2rem; height: 1.1rem; border-radius: 999px;
	background: var(--surface-2); border: 1px solid var(--border);
	transition: background var(--transition), border-color var(--transition);
}
.switch-track::after {
	content: ""; position: absolute; top: 1px; left: 1px; width: .85rem; height: .85rem;
	border-radius: 50%; background: var(--fg-muted);
	transition: transform var(--transition), background var(--transition);
}
.switch input:checked + .switch-track { background: color-mix(in srgb, var(--switch-on, var(--brand)) 30%, var(--surface-2)); border-color: var(--switch-on, var(--brand)); }
.switch input:checked + .switch-track::after { transform: translateX(.9rem); background: var(--switch-on, var(--brand)); }
.switch input:focus-visible + .switch-track { outline: 2px solid var(--switch-on, var(--brand)); outline-offset: 2px; }
.switch input:disabled { cursor: not-allowed; }
.switch input:disabled + .switch-track { opacity: .5; }

/* ---- Forms & buttons ------------------------------------------------------
   Used by the account pages under /user. */

/* An ordinary scrolling document rather than the map app's fixed shell, which
   pins body to the viewport. The documentation does the same in css/docs.css. */
.plain-page { overflow: auto; height: auto; min-height: 100%; }
.plain-page #app { display: block; height: auto; }
.btn {
	display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-4);
	border: 1px solid var(--border); border-radius: var(--radius-sm);
	background: var(--surface-2); color: var(--fg);
	font-size: .9rem; line-height: 1.2; text-decoration: none; white-space: nowrap;
	transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.btn:hover { border-color: var(--brand); }
.btn:disabled { opacity: .6; cursor: not-allowed; }
.btn.primary { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); }
.btn.primary:hover { filter: brightness(1.08); }
.btn.danger { color: #c0392b; border-color: color-mix(in srgb, #c0392b 45%, var(--border)); background: transparent; }
.btn.danger:hover { background: #c0392b; color: #fff; border-color: #c0392b; }
.btn.block { display: flex; width: 100%; }

/* single-column form column, centred under the top bar */
.form-main { display: flex; justify-content: center; padding: var(--sp-5) var(--sp-3) var(--sp-6); }
.form-card {
	width: 100%; max-width: 27rem;
	padding: var(--sp-5);
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); box-shadow: var(--shadow);
}
.form-card h1 { font-size: 1.25rem; font-weight: 600; margin: 0 0 var(--sp-4); }
.form-card h2 { font-size: .95rem; font-weight: 600; margin: var(--sp-5) 0 var(--sp-3); }
.form-card hr { border: 0; border-top: 1px solid var(--border); margin: var(--sp-4) 0; }

.field { display: flex; flex-direction: column; gap: var(--sp-1); margin-bottom: var(--sp-3); }
.field-row { display: flex; gap: var(--sp-3); }
.field-row > .field { flex: 1 1 0; min-width: 0; }
@media (max-width: 480px) { .field-row { flex-direction: column; gap: 0; } }
.field label, .field-label { font-size: .8rem; color: var(--fg-muted); }
.field .value { margin: 0; padding-left: 2px; }
.input, .select {
	width: 100%; padding: var(--sp-2) var(--sp-3);
	background: var(--bg); color: var(--fg);
	border: 1px solid var(--border); border-radius: var(--radius-sm);
	font: inherit; font-size: .9rem;
}
.input:focus-visible, .select:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand); }
.input::placeholder { color: var(--fg-muted); }

/* Native-constraint validation feedback: the per-field reason, revealed only
   once the form has been submitted at least once (`.was-validated`, set by the
   submit handler). */
.invalid-feedback { display: none; color: #c0392b; font-size: .78rem; }
form.was-validated :invalid ~ .invalid-feedback { display: block; }
/* For error slots the sibling rule above cannot reach: a widget that is not itself a
   form control (the date picker), and the repeatable rows, where the input sits inside
   an .inline-group and the message does not. The page adds `shown` from its own error
   state instead of relying on :invalid. */
.invalid-feedback.shown { display: block; }
/* Each entry in the save-problem panel jumps to the field it names. A <button>, not an
   <a>: it navigates nothing, it acts (see the menu-entry rule in CLAUDE.md). */
.problem-list button.linklike {
	padding: 0; border: 0; background: none; font: inherit; text-align: left;
	color: inherit; text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.problem-list button.linklike:hover { color: var(--brand); }
form.was-validated .input:invalid, form.was-validated .select:invalid { border-color: #c0392b; }

/* In-form disclosure — <details>/<summary>. Keyboard and assistive-tech behaviour is native; the
   chevron is an ordinary <i> so no icon font is baked into a ::before. */
.disclosure {
	margin-bottom: var(--sp-4);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.disclosure > summary {
	display: flex; align-items: center; gap: var(--sp-2);
	padding: var(--sp-3) var(--sp-4);
	list-style: none; cursor: pointer; font-weight: 600; color: var(--fg);
}
.disclosure > summary::-webkit-details-marker { display: none; }
.disclosure > summary:hover { color: var(--brand); }
.disclosure > summary:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.disclosure > summary .chev { transition: transform var(--transition); color: var(--fg-muted); }
.disclosure[open] > summary .chev { transform: rotate(90deg); }
.disclosure-body { padding: 0 var(--sp-4) var(--sp-4); }
/* nested panels sit on the disclosure's own surface, so drop their doubled fill */
.disclosure-body > .panel { background: var(--surface-2); }
/* A disclosure that must STAY open. */
.disclosure.locked > summary { cursor: default; }
.disclosure.locked > summary .chev { opacity: .45; }

.thumb { max-height: 3rem; max-width: 6rem; border-radius: var(--radius-sm); object-fit: cover; }

.form-links { display: flex; flex-direction: column; align-items: center; gap: var(--sp-2); margin-top: var(--sp-4); }
.form-links a { color: var(--fg-muted); text-decoration: none; font-size: .82rem; }
.form-links a:hover { color: var(--brand); }
/* One prominent invitation, distinct from the muted `.form-links` row above.
   Sign-in used to offer "Registrarse" only as one more grey link among three,
   which is not enough for the reader who has no account yet — the single most
   likely reason someone is on that page and cannot proceed. */
.form-note {
	margin-top: var(--sp-4); padding: var(--sp-3);
	background: var(--surface-2); border-radius: var(--radius-sm);
	text-align: center; font-size: .88rem; color: var(--fg);
}
.form-note a { color: var(--brand); font-weight: 600; }

/* "— or —" rule around the social sign-in options */
.form-divider { display: flex; align-items: center; gap: var(--sp-2); margin: var(--sp-4) 0; color: var(--fg-muted); font-size: .8rem; }
.form-divider::before, .form-divider::after { content: ""; flex: 1 1 auto; border-top: 1px solid var(--border); }

/* This is closed by js/auth.js, so the button is a real control. */
.alert {
	display: flex; align-items: flex-start; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3); margin-bottom: var(--sp-3);
	border: 1px solid var(--border); border-left-width: 3px;
	border-radius: var(--radius-sm);
	background: var(--surface-2); color: var(--fg); font-size: .85rem;
}
.alert p { margin: 0; flex: 1 1 auto; }
/* An alert whose content stacks (a heading over a list) rather than sitting on one
   line. .alert is display:flex, which laid a heading and its <ul> side by side — every
   such alert on lesson_draft.html carried an inline `style="display:block"` to undo it. */
.alert.stack { display: block; }
.alert.stack ul { margin: var(--sp-1) 0 0; padding-left: 1.1rem; }
.alert.stack li { margin: 2px 0; }
.alert.danger { border-left-color: #c0392b; }
/* The neutral one. It existed only under #messages (as .alert-info), so the draft
   reviewer's critique banner had no colour in its default state. */
.alert.info { border-left-color: var(--brand); }
.alert.warning { border-left-color: var(--accent); }
.alert.success { border-left-color: #2e9e5b; }
/* Excluded from .btn: `.alert button` outranks `.btn`, which would otherwise
   strip a real button placed inside an alert of its border and background. */
.alert button:not(.btn) { flex: 0 0 auto; padding: 0 var(--sp-1); border: 0; background: none; color: var(--fg-muted); font-size: 1.1rem; line-height: 1; }
.alert button:not(.btn):hover { color: var(--fg); }

.spinner {
	display: inline-block; width: .9rem; height: .9rem; border-radius: 50%;
	border: 2px solid currentColor; border-right-color: transparent;
	animation: spinner-turn .7s linear infinite;
}
@keyframes spinner-turn { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2s; } }

/* ---- Content pages --------------------------------------------------------
   The lesson browser and the draft review table. Wider than a form, but the
   same shell: .plain-page on <body>, .topbar, .page-main. */
.page-main { max-width: 72rem; margin: 0 auto; padding: var(--sp-5) var(--sp-4) var(--sp-6); }
.page-main > h1 { font-size: 1.4rem; font-weight: 600; margin: 0 0 var(--sp-2); }
.page-title-note { color: var(--fg-muted); margin: 0 0 var(--sp-4); }

/* two typography helpers, deliberately the only ones: the alternative is
   repeating `color: var(--fg-muted)` on a hundred spans. */
.muted { color: var(--fg-muted); }
.small { font-size: .82rem; }

.panel {
	padding: var(--sp-4); margin-bottom: var(--sp-4);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
}
.toolbar { display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--sp-3); }
.toolbar > * { flex: 1 1 14rem; min-width: 0; }
.toolbar > .toolbar-fit { flex: 0 0 auto; }
.row-between { display: flex; justify-content: space-between; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.inline-group { display: flex; align-items: center; gap: var(--sp-2); }
.chip-row { display: flex; flex-wrap: wrap; gap: var(--sp-1); }

/* segmented control (card/list view, content language) */
.seg { display: inline-flex; }
.seg .btn { border-radius: 0; }
.seg .btn:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.seg .btn:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }
.seg .btn + .btn { margin-left: -1px; }
.seg .btn.on { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); z-index: 1; }

/* pill label; category colours arrive inline from js/categories.js */
.badge {
	display: inline-flex; align-items: center; gap: var(--sp-1);
	padding: .1rem .5rem; border-radius: 999px;
	background: var(--surface-2); border: 1px solid var(--border);
	color: var(--fg); font-size: .72rem; white-space: nowrap;
}

/* Never signal state by hue alone — every one of these also carries an icon. */
.badge.info { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 16%, var(--surface)); }
.badge.ok   { border-color: #2e9e5b; background: color-mix(in srgb, #2e9e5b 16%, var(--surface)); }
.badge.warn { border-color: #d99a00; background: color-mix(in srgb, #d99a00 18%, var(--surface)); }
.badge.bad  { border-color: #d65745; background: color-mix(in srgb, #d65745 18%, var(--surface)); }
.panel.flag-warn { border-color: #d99a00; }
.panel.flag-bad  { border-color: #d65745; }
/* The same two flags on a LIST ROW — a step in the lesson editor that failed validation.
   `.flag-*` was scoped to .panel only, so a row carrying the class got nothing. The
   .badge inside it is what actually names the problem; this is the at-a-glance half, and
   it must never be the only signal (hue alone is not one). */
.row-item.flag-warn { border-color: #d99a00; background: color-mix(in srgb, #d99a00 7%, var(--surface)); }
.row-item.flag-bad  { border-color: #d65745; background: color-mix(in srgb, #d65745 7%, var(--surface)); }

/* lesson browser — vertical cards, distinct from the right pane's horizontal
   .card, which is a fact row and would fight this layout. */
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr)); gap: var(--sp-4); margin-bottom: var(--sp-4); }
.tile {
	display: flex; flex-direction: column; overflow: hidden;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius); box-shadow: var(--shadow);
	transition: border-color var(--transition), transform var(--transition), box-shadow var(--transition);
}
.tile:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: var(--shadow-lg); }
/* picked out of a set — the candidate-image grid on the draft reviewer */
.tile.on { border-color: var(--brand); box-shadow: 0 0 0 2px color-mix(in srgb, var(--brand) 45%, transparent); }
.tile-img { width: 100%; aspect-ratio: 16 / 10; height: auto; object-fit: cover; background: var(--surface-2); }
.tile-body { flex: 1 1 auto; padding: var(--sp-3); min-width: 0; }
.tile-title { font-weight: 600; margin: var(--sp-2) 0 var(--sp-1); }
.tile-foot {
	display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3); border-top: 1px solid var(--border); background: var(--surface-2);
}

/* list view of the same data */
.rows { display: flex; flex-direction: column; gap: var(--sp-2); margin-bottom: var(--sp-4); }
.row-item {
	display: flex; gap: var(--sp-3); align-items: center; padding: var(--sp-2) var(--sp-3);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.row-item:hover { border-color: var(--brand); }
.row-item img { width: 64px; height: 64px; border-radius: var(--radius-sm); object-fit: cover; flex: 0 0 auto; }
.row-item .grow { flex: 1 1 auto; min-width: 0; }
/* dense rows that carry several badges (the draft reviewer's suggestion lists)
   need to wrap rather than squeeze; ordinary rows keep the single line. */
.row-item.wrap { flex-wrap: wrap; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- Whole-card click ------------------------------------------------------
   A card that looks clickable must be clickable, but a card holding two links
   (open + edit) cannot simply be wrapped in an <a> — nested anchors are invalid
   and the parser hoists the inner one out. So one anchor inside the card grows an
   invisible overlay covering it, and every other control is lifted above that
   overlay. The link stays a real <a>, so keyboard focus, Enter, middle-click and
   "copy link address" all keep working, and :focus-visible (§base) draws the ring.

   `.linked` is a MODIFIER, applied per page: `.tile` and `.row-item` are shared
   with lesson.html, fact.html, lesson_draft.html and the styleguide, several of
   which put their own buttons in a row — those must not become click-through. */
.linked { position: relative; }
.linked .stretch-link::after { content: ''; position: absolute; inset: 0; }
.linked :is(a, button):not(.stretch-link) { position: relative; z-index: 1; }
/* `filter` — and `transform`, `backdrop-filter`, `will-change` — make an element the
   containing block for its own absolutely positioned descendants. On a stretched link
   that silently collapses the overlay from the whole card down to the button's own box:
   the pointer enters the overlay, `.btn.primary:hover` applies `filter: brightness()`,
   the overlay shrinks out from under the pointer, hover drops, and it grows back. The
   card flickers between clickable and not, and a click whose mousedown and mouseup land
   on different elements is delivered to their common ancestor instead of the link — so
   the card silently does nothing. Express the hover without establishing a containing
   block; `color-mix` with white is what `brightness(1.08)` was doing anyway. */
.linked .stretch-link:hover { filter: none; }
.linked .btn.primary.stretch-link:hover { background: color-mix(in srgb, #fff 8%, var(--brand)); }

.empty { text-align: center; color: var(--fg-muted); padding: var(--sp-6) 0; }
.empty i { font-size: 2.2rem; display: block; margin-bottom: var(--sp-3); opacity: .6; }

/* review table (the HITL draft list) */
.table-wrap { overflow-x: auto; }
.table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.table th, .table td { padding: var(--sp-2) var(--sp-3); text-align: left; border-bottom: 1px solid var(--border); vertical-align: middle; }
.table thead th { background: var(--surface-2); color: var(--fg-muted); font-weight: 600; white-space: nowrap; }
.table thead th.sortable { cursor: pointer; user-select: none; }
.table tbody tr:hover { background: var(--surface-2); }
.table .num { text-align: center; }
.table .end { text-align: right; }

/* Native <dialog>: focus trapping, Esc-to-close. Open
   with .showModal(), close with .close(). */
dialog.modal {
	width: min(32rem, calc(100vw - 2rem)); padding: 0;
	background: var(--surface); color: var(--fg);
	border: 1px solid var(--border); border-radius: var(--radius);
	box-shadow: var(--shadow-lg);
}
dialog.modal::backdrop { background: rgb(0 0 0 / .45); }
.modal-head, .modal-foot { display: flex; align-items: center; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); }
.modal-head { border-bottom: 1px solid var(--border); font-weight: 600; }
.modal-head .grow { flex: 1 1 auto; }
.modal-body { padding: var(--sp-4); }
.modal-foot { border-top: 1px solid var(--border); justify-content: flex-end; }
/* Wide variant for dialogs that host a whole pane rather than a question — the
   fact picker on lesson.html / fact.html. Its body
   scrolls so the head and foot stay put on a short screen. */
dialog.modal.wide { width: min(60rem, calc(100vw - 2rem)); max-height: calc(100dvh - 3rem); display: flex; flex-direction: column; }
dialog.modal.wide:not([open]) { display: none; }
dialog.modal.wide .modal-body { flex: 1 1 auto; overflow-y: auto; }

/* ---- Lesson editor --------------------------------------------------------
   The ordered step list beside the step form, with a Leaflet preview of the
   whole lesson underneath. */
.editor-split { display: grid; grid-template-columns: minmax(14rem, 20rem) 1fr; gap: var(--sp-3); align-items: start; }
@media (max-width: 860px) { .editor-split { grid-template-columns: 1fr; } }

/* the list scrolls on its own so the step form stays in view beside it */
.step-list { max-height: 60dvh; overflow-y: auto; margin-bottom: var(--sp-2); }
.step-list .row-item { cursor: pointer; align-items: flex-start; gap: var(--sp-2); padding: var(--sp-2); }
.step-list .row-item.on { border-color: var(--brand); background: var(--surface-2); }
.step-list .step-year { font-variant-numeric: tabular-nums; font-weight: 600; margin-right: var(--sp-1); }
/* A broken step must still read as broken while it is the one open in the editor.
   `.step-list .row-item.on` and `.row-item.on.flag-bad` have equal specificity, so the
   later of the two wins — this has to be scoped the same way and sit after it. */
.step-list .row-item.flag-bad,
.step-list .row-item.on.flag-bad {
	border-color: #d65745; background: color-mix(in srgb, #d65745 7%, var(--surface));
}
/* four row actions share a narrow column with the step name — shrink them so
   the name keeps most of the width instead of truncating to three letters */
.step-list .inline-group { flex: 0 0 auto; gap: 0; }
.step-list .iconbtn { width: 1.7rem; height: 1.7rem; font-size: .8rem; }
.step-list .iconbtn:disabled { opacity: .35; cursor: not-allowed; }
/* Reordering by drag. `touch-action: none` is load-bearing: without it the browser
   claims the gesture for scrolling and no pointermove ever reaches the handle on a
   touch screen. The up/down buttons above stay as the keyboard path — a drag alone
   is unreachable without a pointer. */
.step-list .step-drag { cursor: grab; touch-action: none; color: var(--muted); }
.step-list .step-drag:active { cursor: grabbing; }
.step-list .row-item.dragging { opacity: .5; }
.step-list .row-item.drop-before { box-shadow: inset 0 3px 0 0 var(--brand); }
/* dropping past the last row appends, and that slot has no row of its own to mark */
.step-list .row-item.drop-after { box-shadow: inset 0 -3px 0 0 var(--brand); }
.step-num {
	flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
	width: 1.6rem; height: 1.6rem; border-radius: 50%;
	background: var(--surface-2); border: 1px solid var(--border);
	font-size: .72rem; font-variant-numeric: tabular-nums;
}
.step-list .row-item.on .step-num { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); }

/* preview map: plain Leaflet, same tiles and dark-mode tint as /m.
   `.form-map` is the same box on an ordinary form (fact.html's location map).
   `isolation: isolate` because .leaflet-container is positioned without a z-index, so
   its panes (400..1000) otherwise join the page's stacking context and paint over the
   sticky `#messages` banner (z-index 20) as the reader scrolls past the map. */
.step-map, .form-map { height: 24rem; border: 1px solid var(--border); border-radius: var(--radius-sm); isolation: isolate; }
/* Scoped through the wrapper because the class cannot live on the map element itself —
   see the comment in lesson.html. Three classes, so it still outranks Leaflet's own
   single-class `.leaflet-grab { cursor: grab }` on the same element. */
.step-map-wrap.picking .step-map { cursor: crosshair; }
/* numbered step pin. `fact` = a step bound to a published fact, `nofact` = an
   ad-hoc location typed into the step itself; `sel` is the selected step. */
.step-pin {
	display: flex; align-items: center; justify-content: center;
	width: 26px; height: 26px; border-radius: 50%;
	border: 2px solid var(--surface); box-shadow: var(--shadow);
	color: #fff; font-size: .72rem; font-weight: 600; font-variant-numeric: tabular-nums;
}
.step-pin.fact   { background: var(--brand); }
.step-pin.nofact { background: var(--accent); color: #1d2734; }
.step-pin.sel { box-shadow: 0 0 0 3px var(--surface), 0 0 0 5px var(--fg); z-index: 1000; }
/* legend under the preview, one entry per step family */
.step-key { display: inline-flex; align-items: center; gap: var(--sp-1); }
.step-key .dot.fact { background: var(--brand); }
.step-key .dot.nofact { background: var(--accent); }

/* ---- Cover ----------------------------------------------------------------
   The entry pages (index.html, cover_en.html). The first screen IS the app: a
   recording of /m (docs-build `cover`) plays behind the welcome copy, and the
   scrim over it is made of --bg, so the page follows every colour mode. */

/* Category palette. The map app gets these from js/categories.js at runtime and
   never needs them in CSS; the covers are static HTML, so they do. Values must
   match js/categories.js — it is the authority. */
:root {
	--cat-art:            #0074FF;
	--cat-biography:      #E25405;
	--cat-infrastructure: #CC0606;
	--cat-politics:       #FFCE00;
	--cat-science:        #059696;
	--cat-war:            #8900AA;
	--cat-sports:         #E200A8;
	--cat-crime:          #575757;
	--cat-nature:         #5EAF00;
}
@media (prefers-reduced-motion: no-preference) {
	html:has(body.cover) { scroll-behavior: smooth; }
}

.cover-hero {
	position: relative; isolation: isolate; overflow: hidden;
	display: flex; align-items: center;
	min-height: calc(100svh - var(--topbar-h));
}
/* A link, so a click on the "app" opens the app; tabindex=-1 + aria-hidden keep it
   out of the keyboard and screen-reader path, which #coverStart already serves. */
/* right-anchored: a narrower window crops the rail, which sits under the scrim anyway,
   and keeps the fact pane; top-anchored: a wide window crops the legend bar, never
   the pane's tabs. The background is the still (js/cover.js), under the video and
   anchored like it, so it shows whenever the video has no frame to paint. */
.cover-bg {
	--cover-anchor: right top;
	position: absolute; inset: 0; z-index: -1; display: block;
	background: var(--bg) no-repeat var(--cover-anchor) / cover;
}
.cover-bg video { display: block; width: 100%; height: 100%; object-fit: cover; object-position: var(--cover-anchor); }
/* Stops tied to the copy's column — 34rem, or 60% of a small screen (the inset is
   inside it): near-solid under the text, clear by 52rem (88%). The recording frames
   its action — the lesson panel, the facts it flies to — in the part this leaves
   visible. At 640px a fixed 34rem column covered almost the whole video. */
.cover-bg::after {
	content: ''; position: absolute; inset: 0;
	background: linear-gradient(90deg,
		color-mix(in srgb, var(--bg) 96%, transparent) 0,
		color-mix(in srgb, var(--bg) 92%, transparent) min(34rem, 60%),
		color-mix(in srgb, var(--bg) 40%, transparent) min(42rem, 72%),
		transparent min(52rem, 88%));
}
.cover-copy {
	position: relative; width: min(34rem, 60%);
	padding: var(--sp-6) var(--sp-5);
	padding-inline-start: clamp(var(--sp-5), 6vw, 6rem);
}
.cover-eyebrow {
	margin: 0 0 var(--sp-2);
	font-size: .8rem; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
	color: var(--brand);
}
.cover-hero h1 {
	margin: 0 0 var(--sp-4);
	font-size: clamp(2rem, 3.6vw, 2.9rem); font-weight: 700; line-height: 1.1; letter-spacing: -.01em;
}
.cover-hero .lead { margin: 0 0 var(--sp-5); max-width: 33rem; font-size: clamp(1rem, 1.5vw, 1.15rem); line-height: 1.55; }
.btn.cover-cta {
	padding: var(--sp-3) var(--sp-5); border-radius: var(--radius);
	font-size: 1.1rem; font-weight: 600; box-shadow: var(--shadow-lg);
}
.cover-note { margin: var(--sp-2) 0 0; font-size: .85rem; color: var(--fg-muted); }
.cover-cta .fa-arrow-right { transition: transform var(--transition); }
.cover-cta:hover .fa-arrow-right { transform: translateX(3px); }
/* The alternatives: smaller, neutral, and introduced as such, so the one
   brand-coloured button above them stays the obvious way in. */
.cover-alt { margin-top: var(--sp-5); }
.cover-alt-label { margin: 0 0 var(--sp-2); font-size: .85rem; color: var(--fg-muted); }
.cover-alt-list { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.cover-alt .btn { background: var(--surface); font-size: .85rem; }
.cover-alt .btn i { color: var(--brand); }
/* WCAG 2.2.2: moving content that starts on its own needs a pause control */
.iconbtn.cover-motion {
	position: absolute; right: var(--sp-4); bottom: var(--sp-4);
	background: var(--surface); border-color: var(--border); color: var(--fg); box-shadow: var(--shadow);
}

/* The video and its pause control. A layout no-op in landscape (both are
   positioned against the hero); portrait and the framed variant turn it into the
   window. Declared before both, which override it. */
.cover-stage { display: contents; }

/* Portrait: a window above the copy, not an overlay. The recording plays edge to
   edge with nothing over it, and the copy follows on plain --bg. Phones get the
   map-only phone take, portrait tablets the landscape one (see js/cover.js); each
   window takes its take's own shape. The stage comes after the copy in the markup,
   so the pause control follows the call to action in tab order. */
@media (orientation: portrait) {
	.cover-hero { flex-direction: column; align-items: stretch; min-height: 0; }
	.cover-stage { display: block; position: relative; order: -1; }
	/* an explicit width: with `auto`, a max-height cap shrinks the width too, to keep the ratio */
	.cover-bg { --cover-anchor: center top; position: relative; inset: auto; z-index: auto; width: 100%; aspect-ratio: 1920 / 1036; }
	.cover-bg::after { content: none; }
	.cover-copy { width: 100%; padding: var(--sp-5) var(--sp-4) var(--sp-6); }
	.cover-hero h1 { font-size: clamp(1.75rem, 7vw, 2.5rem); }
	.iconbtn.cover-motion { right: var(--sp-3); bottom: var(--sp-3); }
}
/* Phones: the window dissolves into the welcome, and the copy starts over the fade —
   the landscape scrim, vertically. That is what makes it read as a glimpse of the
   app with the welcome laid over it, not as an embedded video. The copy needs its
   z-index: it comes BEFORE the stage in the markup, so the later stage would paint
   over it. The take keeps its attribution top right, clear of the fade. Portrait
   tablets keep the plain window: the landscape take's attribution is on its bottom
   edge, which a fade would hide. */
@media (orientation: portrait) and (max-width: 767.98px) {
	.cover-hero { --cover-overlap: 5rem; }
	.cover-bg {
		aspect-ratio: 640 / 624; max-height: 52svh;   /* the phone take's map */
		-webkit-mask-image: linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
		mask-image: linear-gradient(to bottom, #000 0, #000 62%, transparent 100%);
	}
	.cover-copy { z-index: 1; margin-top: calc(-1 * var(--cover-overlap)); }
	.iconbtn.cover-motion { bottom: calc(var(--cover-overlap) + var(--sp-2)); }
}
/* Short phones (an iPhone SE is 667px): keep "Abrir el mapa" in the first screen.
   The eyebrow goes first — the headline already says "mapa interactivo". */
@media (orientation: portrait) and (max-height: 740px) {
	.cover-bg { max-height: 46svh; }
	.cover-eyebrow { display: none; }
	.cover-copy { padding-top: var(--sp-4); }
	.cover-hero h1 { margin-bottom: var(--sp-2); }
	.cover-hero .lead { margin-bottom: var(--sp-4); font-size: .95rem; }
}
@media (max-width: 575.98px) {
	.cover-alt-list { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
	.cover-alt .btn { padding-inline: var(--sp-2); }
}

/* Framed variant, `body.cover-framed`: the recording plays in a window beside the
   copy (above it in portrait) instead of behind it. */
.cover-framed .cover-hero {
	display: grid; grid-template-columns: minmax(0, 32rem) minmax(0, 1fr);
	grid-template-areas: "copy stage"; align-items: center; gap: var(--sp-6);
	padding: var(--sp-6) clamp(var(--sp-4), 5vw, 5rem);
}
.cover-framed .cover-copy { grid-area: copy; width: auto; padding: 0; background: none; }
.cover-framed .cover-hero h1 { font-size: clamp(2rem, 3.4vw, 3rem); }
.cover-framed .cover-stage { display: block; position: relative; grid-area: stage; }
.cover-framed .cover-bg {
	--cover-anchor: center top; position: relative; inset: auto; z-index: auto;
	aspect-ratio: 1920 / 1036; overflow: hidden;   /* the landscape take's own shape */
	border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.cover-framed .cover-bg::after { content: none; }
.cover-framed .iconbtn.cover-motion { top: auto; right: var(--sp-3); bottom: var(--sp-3); }
@media (orientation: portrait), (max-width: 767.98px) {
	.cover-framed .cover-hero {
		grid-template-columns: minmax(0, 1fr); grid-template-areas: "stage" "copy";
		align-content: start; gap: var(--sp-5); padding: var(--sp-4);
	}
}

.cover-section { padding: var(--sp-6) var(--sp-4); }
.cover-section.alt { background: var(--surface); border-top: 1px solid var(--border); }
.cover-wrap { max-width: 81rem; margin-inline: auto; }
.cover-section-head {
	display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between;
	gap: var(--sp-2) var(--sp-4); margin-bottom: var(--sp-4);
}
.cover-section h2 { margin: 0; font-size: clamp(1.4rem, 2.6vw, 2rem); font-weight: 600; }
.cover-section-head p { margin: var(--sp-1) 0 0; color: var(--fg-muted); }
.cover-more { font-weight: 600; text-decoration: none; white-space: nowrap; }
.cover-more:hover { text-decoration: underline; }

/* 3 / 2 columns; on phones one row that scrolls sideways, so six cards do not
   turn the page into a long column. */
.cover-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 991.98px) { .cover-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 575.98px) {
	.cover-grid {
		grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 82%;
		overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
		margin-inline: calc(-1 * var(--sp-4)); padding: 0 var(--sp-4) var(--sp-2);
		scroll-padding-inline: var(--sp-4);
	}
	.cover-grid > article { scroll-snap-align: start; }
}
/* Both the grid item and the card fill the row, or a one-line title leaves its
   card shorter than its neighbours. */
.cover-grid > article { display: flex; }
/* `.linked` makes the whole card the link; nothing here may put a filter or a
   transform on the .stretch-link itself (see "Whole-card click"). The category is
   the top border; the level and duration say the rest in text. */
.cover-card {
	position: relative;
	display: flex; flex-direction: column; width: 100%; overflow: hidden;
	background: var(--surface); color: var(--fg);
	border: 1px solid var(--border); border-top: 4px solid var(--card-cat, var(--brand));
	border-radius: var(--radius); box-shadow: var(--shadow);
	transition: transform var(--transition), box-shadow var(--transition);
}
.cover-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
/* Assets in images/cover/ are cut for 16/10; an asset far off that ratio gets
   sliced by `cover` — crop the file, don't relax this rule. */
.cover-card img { display: block; width: 100%; aspect-ratio: 16 / 10; height: auto; object-fit: cover; background: var(--surface-2); }
.cover-card-body { flex: 1 1 auto; padding: var(--sp-3) var(--sp-4) 0; }
.cover-card-body h3 { margin: 0; font-size: 1.05rem; font-weight: 600; line-height: 1.35; }
.cover-card-foot {
	display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2);
	padding: var(--sp-3) var(--sp-4); color: var(--fg-muted); font-size: .85rem;
}
.cover-card-foot small { font-size: inherit; }

/* The four ways in, each explained: what it is, how long it takes, one link. The
   whole card is the link (`.linked`); no transform on the .stretch-link itself. */
.cover-paths { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-4); }
@media (max-width: 991.98px) { .cover-paths { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/* on phones one sideways-scrolling row, like the lesson cards, so four cards do not
   stretch the page */
@media (max-width: 575.98px) {
	.cover-paths {
		grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: 78%;
		overflow-x: auto; scroll-snap-type: x mandatory; overscroll-behavior-x: contain;
		margin-inline: calc(-1 * var(--sp-4)); padding: 0 var(--sp-4) var(--sp-2);
		scroll-padding-inline: var(--sp-4);
	}
	.cover-paths > li { scroll-snap-align: start; }
}
.cover-path {
	display: flex; flex-direction: column; gap: var(--sp-2);
	padding: var(--sp-4); background: var(--surface);
	border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow);
	transition: transform var(--transition), box-shadow var(--transition);
}
.cover-path:hover { transform: translateY(-2px); box-shadow: var(--shadow-lg); }
.cover-path > i { font-size: 1.4rem; color: var(--brand); }
.cover-path h3 { margin: 0; font-size: 1.05rem; font-weight: 600; }
.cover-path p { margin: 0; line-height: 1.5; }
.cover-path .cover-path-meta { color: var(--fg-muted); font-size: .85rem; }
.cover-path-go {
	margin-top: auto; padding: 0; border: 0; background: none; text-align: left;
	color: var(--brand); font: inherit; font-weight: 600; text-decoration: none;
}

/* Two columns: the idea on the left, its points (uses, method) on the right. */
.cover-split { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: var(--sp-5); align-items: start; }
@media (max-width: 767.98px) { .cover-split { grid-template-columns: minmax(0, 1fr); } }
.cover-split > div > p { margin: var(--sp-3) 0 0; max-width: 40rem; line-height: 1.6; }
.cover-items { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.cover-items li {
	display: flex; gap: var(--sp-3); align-items: flex-start;
	padding: var(--sp-3) var(--sp-4); background: var(--bg);
	border: 1px solid var(--border); border-radius: var(--radius);
}
.cover-section:not(.alt) .cover-items li { background: var(--surface); }
.cover-items i { flex: 0 0 1.5rem; margin-top: .2rem; text-align: center; font-size: 1.1rem; color: var(--brand); }
.cover-items h3 { margin: 0 0 var(--sp-1); font-size: 1rem; font-weight: 600; }
.cover-items p { margin: 0; line-height: 1.5; }
.cover-items h3 + p { color: var(--fg-muted); }
.cover-inline-links { display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); }
.cover-inline-links a { font-weight: 600; text-decoration: none; }
.cover-inline-links a:hover { text-decoration: underline; }
.cover-about-foot {
	display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
	gap: var(--sp-3); margin-top: var(--sp-5); padding-top: var(--sp-4); border-top: 1px solid var(--border);
}
.cover-about-foot p { margin: 0; }
.cover-join i { margin-right: .35rem; color: var(--brand); }
.cover-section + .cover-section:not(.alt) { border-top: 1px solid var(--border); }

.cover-footer {
	padding: var(--sp-5) var(--sp-4); text-align: center;
	background: var(--surface-2); color: var(--fg-muted);
	border-top: 1px solid var(--border);
}
.cover-footer nav { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2) var(--sp-5); margin-bottom: var(--sp-3); }
.cover-footer nav a { color: var(--fg); font-size: .9rem; text-decoration: none; }
.cover-footer nav a:hover { color: var(--brand); text-decoration: underline; }
.cover-footer p { margin: 0; font-size: .85rem; }
.cover-footer .name { font-family: "Fira Sans", var(--font); font-weight: 300; }
.cover-footer .name b { font-weight: 400; }

.site-footer {
	padding: var(--sp-3); text-align: center;
	color: var(--fg-muted); font-size: .85rem;
	border-top: 1px solid var(--border); background: var(--surface);
}
.site-footer .name { font-family: "Fira Sans", var(--font); font-weight: 300; }
.site-footer .name b { font-weight: 400; }

/* ---- Left rail ------------------------------------------------------------ */
.rail {
	/* no grid-area: the rail is always an absolute (landscape) / fixed (portrait)
	   overlay — a dangling grid-area:rail mis-anchored its containing block. */
	display: flex; flex-direction: column;
	background: var(--surface); border-right: 1px solid var(--border);
	overflow: hidden; z-index: 1100;
}
/* overflow-x must be explicit: with only overflow-y set, the x axis computes to `auto` and the
   collapsed rail (labels hidden with opacity, so they keep their width) grows a scrollbar. */
.rail-items { display: flex; flex-direction: column; gap: 2px; padding: var(--sp-2) var(--sp-1); flex: 1 1 auto; overflow-y: auto; overflow-x: hidden; min-height: 0; }
.rail-item {
	display: flex; align-items: center; gap: var(--sp-3);
	padding: var(--sp-2) var(--sp-3); border-radius: var(--radius-sm);
	background: transparent; border: none; color: var(--fg-muted); width: 100%;
	white-space: nowrap; text-decoration: none; text-align: left;
	transition: background var(--transition), color var(--transition);
}
.rail-item:hover { background: var(--surface-2); color: var(--fg); }
.rail-item.active { background: color-mix(in srgb, var(--brand) 16%, transparent); color: var(--brand); }
.rail-item i { width: 1.5rem; text-align: center; font-size: 1.05rem; flex: 0 0 auto; }
.rail-item .label { opacity: 0; transition: opacity var(--transition); }
#app.rail-expanded .rail-item .label { opacity: 1; }
.rail-toggle { border-top: 1px solid var(--border); color: var(--fg-muted); flex-shrink: 0; }

/* Overflow popout — only shown in short landscape; holds the items the
   inline rail hides when vertical space is tight. */
.rail-more { display: none; }
.rail-more > summary { list-style: none; cursor: pointer; }
.rail-more > summary::-webkit-details-marker { display: none; }
.rail-more-pop {
	/* fixed so it escapes the rail's overflow:hidden + the rail-items scroll clip
	   (which would otherwise hide an absolutely-positioned popout). */
	position: fixed; left: calc(var(--rail-w) + var(--sp-1)); bottom: var(--sp-3);
	min-width: 11rem; background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius-sm); box-shadow: var(--shadow-lg); padding: var(--sp-1);
	display: flex; flex-direction: column; gap: 2px; z-index: 1200;
}
#app.rail-expanded .rail-more-pop {   /* sit right of the wide landscape rail; cap width so it never runs off-screen */
	left: calc(var(--rail-w-expanded) + var(--sp-1));
	min-width: 0;
	max-width: calc(100vw - var(--rail-w-expanded) - var(--sp-3));
}
/* in the portrait drawer the summary is mid-list — expand inline beneath it (no off-canvas popout) */
#app.drawer-open .rail-more-pop {
	position: static; inset: auto; min-width: 0; max-width: none;
	margin: 2px 0 2px var(--sp-4); padding: 0;
	background: transparent; border: none; box-shadow: none;
}
.rail-more[open] > summary { background: var(--surface-2); color: var(--fg); }
.rail-more-pop .rail-item .label { opacity: 1; display: inline; }   /* always labelled in the popout */
/* The rail's language menu. It reuses `.topbar-menu-pop`, whose `position:
   absolute; right: 0` is written for a bar at the top of the page — inside the
   rail that would open leftwards, off-screen, and be clipped by the rail's own
   overflow anyway. Same escape as `.rail-more-pop`: fixed, and to the right. */
.rail-lang > .topbar-menu-pop {
	position: fixed; left: calc(var(--rail-w) + var(--sp-1)); right: auto; top: auto;
	max-width: calc(100vw - var(--rail-w) - var(--sp-3));
}
#app.rail-expanded .rail-lang > .topbar-menu-pop {
	left: calc(var(--rail-w-expanded) + var(--sp-1));
	max-width: calc(100vw - var(--rail-w-expanded) - var(--sp-3));
}
/* in the portrait drawer the summary is mid-list — expand inline beneath it */
#app.drawer-open .rail-lang > .topbar-menu-pop {
	position: static; inset: auto; max-width: none;
	margin: 2px 0 2px var(--sp-4); padding: 0;
	background: transparent; border: none; box-shadow: none;
}
.rail-lang[open] > summary { background: var(--surface-2); color: var(--fg); }
/* popout groups: `more-secondary` = base maps + colour mode + borders (their home on narrow screens);
   `more-optional` = add-fact + tour (overflow, only when the rail is too short for them inline) */
.rail-more-pop .more-optional { display: none; }
@media (min-width: 768px) { .rail-more-pop .more-secondary { display: none; } }

/* categories filter panel inside the rail.
   Belt and braces with the `railExpanded` watcher in dwh.js, which closes the panel on
   collapse: `.rail-cats` is a SIBLING of the `.rail-item`s, so not one of the seven
   `.rail-item .label` rules reaches its contents. The category names were kept out of a
   collapsed rail only by arithmetic — they start at 56px against a 55px content box — so
   any font-size, zoom or spacing change would have exposed them. */
.rail-cats { padding: var(--sp-1) var(--sp-2) var(--sp-2); display: flex; flex-direction: column; gap: 2px; }
/* landscape only: in portrait the rail is a full-width drawer whose labels always show,
   and `railExpanded` is not even reachable there (#railToggle is display:none) — an
   unscoped rule would delete the categories from the drawer after a rotation. */
@media (orientation: landscape) {
	#app:not(.rail-expanded) .rail-cats { display: none; }
}
/* a .switch whose track carries the category colour, so it replaces the old checkbox + .dot pair.
   The tinted ring keeps that colour readable while the category is switched off, which is what
   the always-visible .dot used to do. */
.rail-cat { padding: var(--sp-1); white-space: nowrap; }
.rail-cat .switch-track { border-color: color-mix(in srgb, var(--switch-on) 55%, var(--border)); }
.rail-cat-actions { display: flex; flex-direction: column; gap: 2px; margin-top: var(--sp-1); }
/* nowrap is load-bearing: these carry BARE TEXT NODES, not a `.label` span, so none of
   the label rules reach them. Wrapping let the trailing word ("todos" / "all") sit fully
   visible inside the 56px collapsed rail — the reported "text shown when collapsing". */
.rail-cat-actions button { background: transparent; border: none; color: var(--fg-muted); text-align: left; padding: var(--sp-1); font-size: .76rem; white-space: nowrap; }
.rail-cat-actions button:hover { color: var(--brand); }

/* category dot */
.dot { display: inline-block; width: .7rem; height: .7rem; border-radius: 50%; flex: 0 0 auto; }

/* ---- Map ------------------------------------------------------------------ */
.map-wrap { grid-area: map; position: relative; display: flex; flex-direction: column; }
#map { flex: 1 1 auto; min-height: 0; background: var(--surface-2); }
/* our own tiles (basemap-own) carry their dark look baked in; only third-party tiles are filtered */
.leaflet-tile-pane .leaflet-layer:not(.basemap-own) { filter: var(--map-filter); }
.leaflet-container { background: var(--surface-2); font-family: var(--font); }

/* Layers panel: base-map thumbnails (the selected one carries the brand border, no radio dot)
   and the historical-borders switch. Vue-owned, not a Leaflet control. >= 768px only: below
   that the same picker is `.rail-layers` in the rail's More options. */
.layers-panel { position: absolute; top: var(--sp-3); right: var(--sp-3); z-index: 900; display: flex; flex-direction: column; align-items: flex-end; gap: var(--sp-1); }
.layers-toggle {
	width: 2.4rem; height: 2.4rem; display: grid; place-items: center; cursor: pointer;
	background: var(--surface); color: var(--fg-muted);
	border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow);
}
.layers-toggle:hover, .layers-panel.open .layers-toggle { background: var(--surface-2); color: var(--fg); }
.layers-body {
	display: none; flex-direction: column; gap: var(--sp-2); padding: var(--sp-2);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.layers-panel:hover .layers-body, .layers-panel:focus-within .layers-body, .layers-panel.open .layers-body { display: flex; }
.layers-panel .layer-tiles { grid-template-columns: repeat(4, 3.5rem); }
.layer-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--sp-1); }
.layer-tile {
	display: flex; flex-direction: column; align-items: center; gap: 2px; min-width: 0; padding: 0;
	background: none; border: none; color: var(--fg-muted); font: inherit; cursor: pointer;
}
.layer-tile img {
	display: block; width: 100%; height: auto; aspect-ratio: 1; object-fit: cover;
	background: var(--surface-2); border: 2px solid var(--border); border-radius: var(--radius-sm);
	transition: border-color var(--transition);
}
.layer-tile .name { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: .62rem; line-height: 1.2; }
.layer-tile:hover img { border-color: var(--fg-muted); }
.layer-tile.active img { border-color: var(--brand); }
.layer-tile.active .name { color: var(--brand); font-weight: 600; }
.layer-tile:focus-visible { outline: none; }
.layer-tile:focus-visible img { outline: 2px solid var(--brand); outline-offset: 2px; }
.layers-switch { font-size: .78rem; white-space: nowrap; justify-content: center; }
/* the rail's copy: the colour modes are one icon row, the borders one switch row */
.rail-layers { display: flex; flex-direction: column; gap: var(--sp-2); padding: var(--sp-2); }
.rail-modes { display: flex; gap: 2px; }
.rail-mode {
	flex: 1 1 0; height: 2.25rem; display: grid; place-items: center; cursor: pointer;
	background: transparent; border: none; border-radius: var(--radius-sm); color: var(--fg-muted);
	transition: background var(--transition), color var(--transition);
}
.rail-mode:hover { background: var(--surface-2); color: var(--fg); }
.rail-mode.active { background: color-mix(in srgb, var(--brand) 16%, transparent); color: var(--brand); }

/* Selectors are doubled with `.leaflet-touch` to outrank Leaflet's own
   `.leaflet-touch .leaflet-bar …` sizing/radius rules on touch devices. */
.leaflet-bar, .leaflet-touch .leaflet-bar { border: 1px solid var(--border); border-radius: var(--radius-sm); box-shadow: var(--shadow); }
.leaflet-bar a, .leaflet-touch .leaflet-bar a {
	width: 2.4rem; height: 2.4rem; line-height: 2.4rem;
	background: var(--surface); color: var(--fg-muted);
	border-bottom: 1px solid var(--border);
}
.leaflet-bar a:hover, .leaflet-touch .leaflet-bar a:hover { color: var(--fg); background: var(--surface-2); }
.leaflet-bar a:first-child, .leaflet-touch .leaflet-bar a:first-child { border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm); }
.leaflet-bar a:last-child, .leaflet-touch .leaflet-bar a:last-child { border-bottom: none; border-bottom-left-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
.leaflet-bar a.leaflet-disabled { background: var(--surface); color: var(--border); }
/* toggled-on state of a map button (the locate control) */
.leaflet-bar a.leaflet-active, .leaflet-bar a.leaflet-active:hover { background: var(--brand); color: var(--brand-fg); }
.rail-toggle .label { letter-spacing: .03em; }
.pane-empty a { color: var(--brand); }

/* Date control re-skinned to style.css tokens; overrides dwh-date-control.css. */
.dwh-dc-trigger { color: var(--fg) !important; background: transparent !important; border-color: transparent !important; font-family: var(--font); border-radius: var(--radius-sm) !important; font-weight: 700; }
.dwh-dc-trigger:hover { border-color: var(--border) !important; }
/* Inside a form (.dc-form-wrap, the editors) the trigger is a field, not a toolbar
   label: the bare-text look above hid that it can be clicked. Same box as `.input`,
   plus a caret; the `!important`s are needed to outrank the toolbar rule. The
   no-value state (`.dwh-dc-empty`, "select a date") reads as a placeholder. */
.dc-form-wrap .dwh-dc-trigger {
	display: inline-flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
	width: 100%; max-width: 16rem; margin: 0; padding: var(--sp-2) var(--sp-3);
	background: var(--bg) !important; border: 1px solid var(--border) !important;
	font-size: .9rem; font-weight: 400; text-align: left; cursor: pointer;
}
.dc-form-wrap .dwh-dc-trigger::after { content: "\25BE"; flex: 0 0 auto; color: var(--fg-muted); }
.dc-form-wrap .dwh-dc-trigger:hover { border-color: var(--brand) !important; }
.dc-form-wrap .dwh-dc-trigger:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; border-color: var(--brand) !important; }
.dc-form-wrap .dwh-dc-trigger.dwh-dc-empty { color: var(--fg-muted) !important; }
.dwh-dc-panel { background: var(--surface) !important; border-color: var(--border) !important; color: var(--fg) !important; font-family: var(--font); }
.dwh-dc-tile, .dwh-dc-tile-group, .dwh-dc-tile-main, .dwh-dc-tile-confirm, .dwh-dc-breadcrumb, .dwh-dc-panel input {
	background-color: var(--surface-2) !important; color: var(--fg) !important; border-color: var(--border) !important;
}
.dwh-dc-tile:hover:not(:disabled), .dwh-dc-tile-main:hover { background-color: color-mix(in srgb, var(--brand) 14%, var(--surface-2)) !important; }
.dwh-dc-tile.dc-active, .dwh-dc-tile-group.dc-active { background-color: var(--brand) !important; color: var(--brand-fg) !important; border-color: var(--brand) !important; }
.dwh-dc-tile.dc-in-range, .dwh-dc-tile-group.dc-in-range { background-color: color-mix(in srgb, var(--brand) 22%, var(--surface-2)) !important; border-color: var(--brand) !important; }
.dwh-dc-tile.dc-current { border-color: var(--brand) !important; }
.dwh-dc-backdrop { background: rgba(0, 0, 0, .45) !important; }

/* layout-utility shim (only the classes the template actually uses) */
.dwh-dc-panel .d-flex { display: flex; }
.dwh-dc-panel .flex-wrap { flex-wrap: wrap; }
.dwh-dc-panel .align-items-center { align-items: center; }
.dwh-dc-panel .gap-1 { gap: var(--sp-1); }
.dwh-dc-panel .gap-2 { gap: var(--sp-2); }
.dwh-dc-panel .mb-0 { margin-bottom: 0; }
.dwh-dc-panel .mb-1 { margin-bottom: var(--sp-1); }
.dwh-dc-panel .ms-auto { margin-left: auto; }
.dwh-dc-panel .w-100 { width: 100%; }
.dwh-dc-panel .small { font-size: .8rem; }
.dwh-dc-panel .text-muted { color: var(--fg-muted); font-size: .8rem; }
.dwh-dc-panel .row { display: flex; }
.dwh-dc-panel .row.g-1 { gap: var(--sp-1); }
.dwh-dc-panel .col-6 { flex: 1 1 0; min-width: 0; }
/* tighten the header/body so the top rows aren't cramped-then-loose */
.dwh-dc-panel-header { padding: var(--sp-2) var(--sp-3); gap: var(--sp-2); }
.dwh-dc-panel-body { padding: var(--sp-2) var(--sp-3) var(--sp-3); }

.dwh-dc-panel-header .btn-close {
	margin-left: auto; flex: 0 0 auto; width: 1.6rem; height: 1.6rem; padding: 0;
	display: inline-flex; align-items: center; justify-content: center;
	border: none; border-radius: 999px; background: var(--surface-2); color: var(--fg-muted);
	cursor: pointer; opacity: 1;
}
.dwh-dc-panel-header .btn-close::before { content: "\00d7"; font-size: 1.1rem; line-height: 1; }
.dwh-dc-panel-header .btn-close:hover { background: var(--brand); color: var(--brand-fg); }

.dwh-dc-panel input[type="text"], .dwh-dc-panel .form-control {
	width: 100%; box-sizing: border-box;
	padding: var(--sp-1) var(--sp-2); font-size: .9rem; font-family: var(--font);
	border-radius: var(--radius-sm);
}
.dwh-dc-panel input[type="text"]:focus { outline: 2px solid var(--brand); outline-offset: -1px; }

/* Token colours so text shows in every theme. */
.dwh-dc-panel .btn {
	padding: var(--sp-1) var(--sp-3); font-size: .82rem; line-height: 1.2; font-family: var(--font);
	border: 1px solid var(--border); border-radius: var(--radius-sm);
	background: var(--surface-2); color: var(--fg); cursor: pointer;
}
.dwh-dc-panel .btn:hover { border-color: var(--brand); }
.dwh-dc-panel .btn-primary { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); }
.dwh-dc-panel .btn-outline-primary, .dwh-dc-panel .btn-outline-info,
.dwh-dc-panel .btn-outline-secondary, .dwh-dc-panel .btn-outline-success {
	background: transparent; color: var(--fg);
}
.dwh-dc-panel .btn-outline-primary:hover, .dwh-dc-panel .btn-outline-info:hover,
.dwh-dc-panel .btn-outline-secondary:hover, .dwh-dc-panel .btn-outline-success:hover {
	background: color-mix(in srgb, var(--brand) 14%, var(--surface-2)); border-color: var(--brand);
}
.dwh-dc-panel .btn-link { background: none; border: none; color: var(--fg-muted); padding: 0 var(--sp-1); text-decoration: none; }
.dwh-dc-panel .btn-link:hover { color: var(--brand); }

.dwh-dc-panel .switch { font-size: .82rem; }

/* theme the remaining hardcoded-colour bits (badge / breadcrumb) for dark/vibrant */
.dwh-dc-selected-badge { background: var(--surface-2) !important; color: var(--fg) !important; }
.dwh-dc-breadcrumb-btn { color: var(--brand) !important; }
.dwh-dc-breadcrumb-btn.dc-current { color: var(--fg) !important; }

/* ---- Lesson dock ---------------------------------------------------------- */
/* Soft brand highlight signals the dock is live and interactive. */
.lesson-dock { background: var(--surface); display: flex; flex-direction: column; z-index: 600; animation: dock-pulse 1.5s ease 1; }
/* The dock's own height is auto — head + measured body + controls, all stable for the lesson
   (the body's height is set from JS, see measureDockHeight). max-height is only a backstop so a
   long lesson can never eat the map. */
/* `docked` = does not overlay the map (bottom strip OR left column); `float` overlays.
   Both docked placements are flex siblings that SHRINK #map, which is why Leaflet's own
   centre is already the visible centre for them and only float needs camera padding. */
.lesson-dock.docked {
	flex: 0 0 auto; max-height: 60%;
	border-top: 2px solid var(--brand);
	box-shadow: 0 -3px 16px color-mix(in srgb, var(--brand) 26%, transparent);
}
/* Left column: the map-based-narrative pattern (Google Earth Voyager, ArcGIS
   StoryMaps). A row-flex .map-wrap puts the panel BESIDE #map instead of under it, so
   the map keeps its full height and the narrative gets a comfortable line length —
   the bottom strip forces ~110 characters, well past the 45-75 that reads well.
   Nothing overlays the map, so this mode needs no camera padding at all. */
.map-wrap:has(> .lesson-dock.dock-left) { flex-direction: row; }
/* in a row the flex base axis is width, and min-width defaults to auto — without this
   a wide Leaflet canvas refuses to shrink and pushes the panel off the grid track. */
.map-wrap:has(> .lesson-dock.dock-left) > #map { min-width: 0; }
/* `#app .leaflet-left` is shifted by the rail width because the rail OVERLAYS the map. In
   this placement it does not: the panel sits between them, so #map already starts clear of
   the rail and the shift was counted twice — the controls sat 56px further right than the
   map's own edge (measured: map at 408, zoom at 474). Wins on specificity over both
   `#app .leaflet-left` and `#app.rail-expanded .leaflet-left`. */
#app .map-wrap:has(> .lesson-dock.dock-left) .leaflet-left { margin-left: 0; }
.lesson-dock.dock-left {
	order: -1;                                  /* before #map in the row */
	/* min-width:0 is required, not cosmetic: a row flex item defaults to
	   min-width:auto, which floors it at its min-content width and overrode the
	   basis entirely (measured 761px instead of 352px, leaving the map 367px). */
	flex: 0 0 clamp(18rem, 22vw, 24rem); min-width: 0;
	max-height: none; height: 100%;
	border-top: none; border-right: 2px solid var(--brand);
	box-shadow: 3px 0 16px color-mix(in srgb, var(--brand) 26%, transparent);
}
.lesson-dock.dock-left .dock-body { max-height: none; }
.lesson-dock.dock-left .dock-controls { flex-direction: column; align-items: stretch; }
.lesson-dock.dock-left .dock-nav { margin-inline: 0; }
.lesson-dock.dock-left .dock-nav .btn { flex: 1 1 0; justify-content: center; }
.lesson-dock.dock-left .dock-audio { max-width: none; }

.lesson-dock.float {
	/* clears the bottom-left scale control (leaflet.css: 5px margins, two stacked lines) */
	position: absolute; left: var(--sp-4); bottom: calc(var(--sp-4) + 2.5rem);
	width: min(26rem, 60%); max-height: 60%;
	border: 1px solid var(--brand); border-radius: var(--radius);
	box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 16%, transparent), var(--shadow-lg);
}
@keyframes dock-pulse {
	0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 55%, transparent); }
	100% { box-shadow: 0 -3px 16px color-mix(in srgb, var(--brand) 26%, transparent); }
}
.dock-head {
	display: flex; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3); border-bottom: 1px solid var(--border);
	background: color-mix(in srgb, var(--brand) 8%, var(--surface));
}
.dock-head > i { color: var(--brand); }
.lesson-dock.float .dock-head { cursor: grab; }
.lesson-dock.float .dock-head:active { cursor: grabbing; }
.dock-title { flex: 1 1 auto; min-width: 0; font-weight: 700; font-size: .85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dock-head .iconbtn { width: 1.9rem; height: 1.9rem; }
.lesson-dock.collapsed { height: auto; }
/* Collapsing must return the axis the placement actually SPENDS. The bottom strip and the
   float panel spend height, and `height: auto` above is enough for them. The left column
   spends WIDTH, and it is pinned by `flex: 0 0 <basis>` — which cannot shrink — so it used
   to collapse into a full-size blank column (measured 352x824 both ways). Note the rule
   above could not have helped even on the right axis: it ties `.dock-left` on specificity
   and loses on source order, so it never applied here at all. */
.lesson-dock.dock-left.collapsed {
	flex: 0 0 2.75rem;
	align-items: center;
}
/* The head becomes the whole panel: a column of the collapse/close buttons with the title
   running up the side, so the lesson stays identifiable at 44px wide. */
.lesson-dock.dock-left.collapsed .dock-head {
	flex: 1 1 auto; flex-direction: column; align-items: center;
	padding: var(--sp-2) 0; gap: var(--sp-2);
	border-bottom: none;
}
.lesson-dock.dock-left.collapsed .dock-title {
	writing-mode: vertical-rl;
	flex: 1 1 auto; min-height: 0; max-height: 100%;
	text-align: center;
}
/* No placement switching from a 44px spine — expand first. */
.lesson-dock.dock-left.collapsed #lesson-dock-mode { display: none; }
/* min/max bound whatever measureDockHeight computes; overflow only bites past the max */
.dock-body { flex: 1 1 auto; min-height: 4.5rem; max-height: 22rem; padding: var(--sp-3); overflow-y: auto; }
.dock-step-name { font-weight: 600; font-size: .95rem; margin-bottom: var(--sp-1); }
.dock-text { font-size: .88rem; line-height: 1.5; margin: 0; }
/* The step's year, at the foot of the text it belongs to rather than trailing the buttons.
   There was no bare `.era` rule, so inside .dock-controls it inherited full --fg at body
   size and read louder than the step counter beside it. */
.dock-body .era { display: block; margin-top: var(--sp-2); color: var(--fg-muted); font-size: .8rem; }
/* One wrapping row instead of media queries: the dock's width depends on `dockPos`
   (float is min(26rem, 60%)) as much as on the viewport, so it must adapt to its own box.
   Wide  → [<] 3/8 [>]  [audio]  1519  on one line, audio capped so it can't stretch.
   Narrow → the 14rem basis can't be met and the audio wraps to its own full-width line. */
.dock-controls { flex: 0 0 auto; display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-3); border-top: 1px solid var(--border); }
.dock-nav { display: flex; align-items: center; gap: var(--sp-2); }
/* "6 de 18", on one line: "Paso 6 de 18" wrapped between the two labelled buttons in
   the 22rem panels (left and float); the long form is the title */
.dock-nav .readout { font-variant-numeric: tabular-nums; white-space: nowrap; }
.dock-audio { flex: 1 1 12rem; min-width: 0; max-width: 22rem; }
.dock-audio audio { display: block; width: 100%; height: 2rem; }
/* Centre the step nav in the docked panel: it is the panel's primary action, and
   left-aligned among the audio player it read as one more piece of chrome. */
.lesson-dock.docked .dock-controls .dock-nav { margin-inline: auto; }
.dock-nav .btn { gap: var(--sp-1); padding: var(--sp-2) var(--sp-3); }

/* Step progress. A bar plus the "Step 3 of 12" readout: the bare `3 / 12` gave no
   sense of how much was left. `role="presentation"` — the readout beside it already
   carries the same information as text. */
.lesson-progress { flex: 0 0 auto; height: 3px; background: var(--surface-2); overflow: hidden; }
.lesson-progress-bar { display: block; height: 100%; background: var(--brand); transition: width var(--transition); }
.lesson-progress-label { margin: 0; font-size: .78rem; color: var(--fg-muted); }

/* One-shot nudge on Next when a lesson opens, cleared on the first step (engine
   `lessonNudge`). Never the only signal — the global prefers-reduced-motion rule
   disables it, and the labelled primary button carries the meaning on its own. */
@keyframes lesson-nudge {
	0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--brand) 55%, transparent); }
	50%      { box-shadow: 0 0 0 6px color-mix(in srgb, var(--brand) 0%, transparent); }
}
.btn.primary.nudge { animation: lesson-nudge 1.4s ease-out 3; }

/* ---- Lesson in the right pane (stacked layouts) ---------------------------
   The dock is not rendered below the stacked breakpoint — topbar + map + dock +
   pane do not fit — so the narrative and its step nav become a pane tab, sitting
   above the step's fact in one scrollable column. Reuses the dock's own type
   classes so both presentations stay in step. */
/* The step controls are a `flex: 0 0 auto` SIBLING of .pane-body, not a child of it,
   so they never scroll. `position: sticky` inside the scroller would not do: a sticky
   element sticks only while its own parent is in view, and .pane-lesson ends where the
   fact takeover begins — it would scroll away exactly when the reader moves into the
   fact. Measured on the worst lesson (L1003 at 563x726) the Next button used to sit
   187px BELOW the fold on arrival. Sits at ~72% of screen height: inside the one-handed
   thumb range and clear of the iOS home-indicator strip. */
.pane-lesson-bar {
	flex: 0 0 auto;
	background: color-mix(in srgb, var(--brand) 6%, var(--surface));
	border-bottom: 1px solid var(--border);
}
/* One line: a step button on each edge, the title taking whatever is left between them.
   Vertical room is the scarce resource here — the pane is ~250px of a 726px phone. */
.pane-lesson-row {
	display: flex; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-2); min-width: 0; font-size: .8rem;
}
.pane-lesson-name {
	flex: 1 1 auto; min-width: 0; font-weight: 700;
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pane-lesson-name > i { color: var(--brand); margin-right: var(--sp-1); }
.pane-lesson-row .readout { flex: 0 0 auto; color: var(--fg-muted); font-variant-numeric: tabular-nums; }
/* Icon-only, but still a .btn: .iconbtn has no :disabled rule at all, so the first and
   last step would look identical to a live control. Square and thumb-sized. */
.pane-step-btn { flex: 0 0 auto; width: 2.5rem; padding-inline: 0; justify-content: center; }
/* The readout is the first thing to go when the title needs the room; the arrows and the
   progress bar still carry position. The icon goes next — see lessonNameIconMax. */
@media (max-width: 400px) {
	.pane-lesson-row .readout { display: none; }
}

/* The scrolling half. `touch-action: pan-y` keeps the pane's own vertical scroll while
   the horizontal axis is free for the step swipe (same idiom as .fact-carousel). */
.pane-lesson { border-bottom: 1px solid var(--border); margin-bottom: var(--sp-3); padding-bottom: var(--sp-3); touch-action: pan-y; }
.pane-lesson .era { display: block; margin-top: var(--sp-2); color: var(--fg-muted); font-size: .8rem; }
/* One-time swipe affordance, touch only — the buttons above are the real control.
   prefers-reduced-motion disables all animation globally, which is why this is a hint
   and never the only signal. */
@media (hover: none) {
	.pane-lesson.hint .dock-text::after {
		content: '\00ab\2003\00bb'; display: block; margin-top: var(--sp-2);
		text-align: center; color: var(--brand); letter-spacing: .4em;
		animation: lesson-swipe-hint 1.6s ease-in-out 3;
	}
}
@keyframes lesson-swipe-hint {
	0%, 100% { opacity: .35; transform: translateX(0); }
	50%      { opacity: 1;   transform: translateX(-6px); }
}

/* ---- Right-click "add fact" menu ------------------------------------------ */
/* Position and visibility come from Vue (contextMenu state); these were inline styles. */
.dropdown-context {
	position: absolute; z-index: 2500; list-style: none; margin: 0; padding: .35rem;
	background: var(--surface); border: 1px solid var(--border);
	border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
}
.dropdown-context a { text-decoration: none; color: var(--fg); }

/* ---- Right pane ----------------------------------------------------------- */
.right {
	grid-area: right;
	display: flex; flex-direction: column;
	background: var(--surface); border-left: 1px solid var(--border);
	overflow: hidden;
}
.pane-tabs { display: flex; border-bottom: 1px solid var(--border); }
.pane-tab {
	flex: 1 1 0; display: flex; flex-direction: column; align-items: center; gap: 2px;
	padding: var(--sp-2) var(--sp-1); background: transparent; border: none;
	border-bottom: 2px solid transparent; color: var(--fg-muted); font-size: .7rem;
}
.pane-tab i { font-size: 1rem; }
.pane-tab:hover { color: var(--fg); }
.pane-tab.active { color: var(--brand); border-bottom-color: var(--brand); }
.pane-tab .count {
	font-size: .6rem; background: var(--surface-2); color: var(--fg-muted);
	border-radius: 999px; padding: 0 .35rem; min-width: 1.1rem;
}
.pane-body { flex: 1 1 auto; overflow-y: auto; padding: var(--sp-3); }
.pane-empty { color: var(--fg-muted); text-align: center; padding: var(--sp-6) var(--sp-3); font-size: .9rem; }
.pane-cap { padding: var(--sp-2) var(--sp-3); font-size: .72rem; color: var(--fg-muted); text-align: center; border-top: 1px solid var(--border); font-variant-numeric: tabular-nums; }

.card {
	display: flex; gap: var(--sp-3); padding: var(--sp-3);
	background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
	margin-bottom: var(--sp-3); cursor: pointer; box-shadow: var(--shadow);
	transition: border-color var(--transition), transform var(--transition), box-shadow var(--transition);
}
.card:hover { border-color: var(--brand); transform: translateY(-1px); box-shadow: var(--shadow-lg); }
.card.active { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand) inset; }
.card-thumb { width: 64px; height: 64px; border-radius: var(--radius-sm); object-fit: cover; flex: 0 0 auto; background: var(--surface-2); }
.card-body { min-width: 0; flex: 1 1 auto; }
.card-title { margin: 0 0 var(--sp-1); font-size: .95rem; display: flex; align-items: center; gap: var(--sp-2); }
.card-title .cat-ico {
	display: inline-flex; align-items: center; justify-content: center;
	width: 1.4rem; height: 1.4rem; border-radius: 50%; color: #fff; font-size: .7rem; flex: 0 0 auto;
}
.card-desc { margin: 0; font-size: .82rem; color: var(--fg-muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.card-meta { margin-top: var(--sp-2); font-size: .72rem; color: var(--fg-muted); text-align: right; font-variant-numeric: tabular-nums; }

/* image carousel (detail hero + list/search cards) */
.fact-carousel { position: relative; }
.fact-carousel-thumb { flex: 0 0 auto; }
.fact-carousel .card-thumb, .fact-carousel .detail-hero { touch-action: pan-y; }
.fact-dots { display: flex; justify-content: center; gap: var(--sp-2); flex-wrap: wrap; }
.fact-carousel-hero .fact-dots { margin-top: calc(-1 * var(--sp-2)); margin-bottom: var(--sp-3); }
.fact-carousel-thumb .fact-dots { gap: 4px; margin-top: 4px; }
.fact-dot {
	width: 8px; height: 8px; padding: 0; border-radius: 50%; cursor: pointer;
	background: transparent; border: 1px solid var(--fg-muted);
	transition: background var(--transition), border-color var(--transition);
}
.fact-carousel-thumb .fact-dot { width: 6px; height: 6px; }
.fact-dot:hover { border-color: var(--brand); }
.fact-dot.active { background: var(--brand); border-color: var(--brand); }

/* hero: tap/click opens the full uncropped image in a lightbox */
.fact-carousel-hero .detail-hero { cursor: zoom-in; }
.fact-zoom {
	position: absolute; top: var(--sp-2); right: var(--sp-2);
	display: inline-flex; align-items: center; justify-content: center;
	width: 1.9rem; height: 1.9rem; border: none; border-radius: var(--radius-sm);
	background: color-mix(in srgb, var(--surface) 70%, transparent); color: var(--fg);
	cursor: zoom-in; opacity: 0; transition: opacity var(--transition);
}
.fact-carousel-hero:hover .fact-zoom, .fact-zoom:focus-visible { opacity: 1; }
@media (hover: none) { .fact-zoom { opacity: 1; } }   /* always visible on touch */

.fact-lightbox {
	position: fixed; inset: 0; z-index: 2400;
	display: flex; align-items: center; justify-content: center;
	background: rgba(0, 0, 0, .85); padding: var(--sp-4);
}
.fact-lightbox-img { max-width: 92vw; max-height: 88vh; object-fit: contain; border-radius: var(--radius-sm); }
.fact-lightbox-close {
	position: absolute; top: var(--sp-3); right: var(--sp-3);
	display: inline-flex; align-items: center; justify-content: center;
	width: 2.4rem; height: 2.4rem; border: none; border-radius: var(--radius-sm);
	background: rgba(255, 255, 255, .15); color: #fff; font-size: 1.1rem; cursor: pointer;
}
.fact-lightbox-close:hover { background: rgba(255, 255, 255, .28); }
.fact-lightbox-nav {
	position: absolute; top: 50%; transform: translateY(-50%);
	display: inline-flex; align-items: center; justify-content: center;
	width: 2.6rem; height: 2.6rem; border: none; border-radius: 50%;
	background: rgba(255, 255, 255, .15); color: #fff; font-size: 1.1rem; cursor: pointer;
}
.fact-lightbox-nav.prev { left: var(--sp-3); }
.fact-lightbox-nav.next { right: var(--sp-3); }
.fact-lightbox-nav:hover:not(:disabled) { background: rgba(255, 255, 255, .28); }
.fact-lightbox-nav:disabled { opacity: .35; cursor: default; }
.fact-lightbox .fact-dots { position: absolute; bottom: var(--sp-4); left: 0; right: 0; }
.fact-lightbox .fact-dot { border-color: rgba(255, 255, 255, .6); }
.fact-lightbox .fact-dot.active { background: #fff; border-color: #fff; }

.detail-hero { width: 100%; height: 9rem; object-fit: cover; object-position: 50% 20%; border-radius: var(--radius); background: var(--surface-2); margin-bottom: var(--sp-3); display: block; }
.detail-title { margin: 0 0 var(--sp-2); font-size: 1.2rem; }
.detail-section h4 { margin: var(--sp-4) 0 var(--sp-1); font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-muted); }
.detail-text { font-size: .9rem; line-height: 1.5; text-align: justify; }
.detail-list { list-style: none; padding: 0; margin: 0; font-size: .88rem; line-height: 1.5; }
.detail-list li { margin-bottom: var(--sp-1); }
.chip {
	display: inline-block; padding: .15rem .55rem; margin: 0 .25rem .25rem 0;
	background: var(--surface-2); border: 1px solid var(--border); border-radius: 999px;
	font-size: .78rem; color: var(--fg); text-decoration: none;
}
.chip:hover { border-color: var(--brand); color: var(--brand); }
/* back bar shown above a selected fact — signals the list is still behind it */
.detail-back {
	display: flex; align-items: center; gap: var(--sp-2); width: 100%;
	margin: 0 0 var(--sp-3); padding: var(--sp-2) var(--sp-3);
	background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius-sm);
	color: var(--brand); font-size: .85rem; text-align: left;
}
.detail-back:hover { border-color: var(--brand); }
.detail-back .detail-back-hint { margin-left: auto; color: var(--fg-muted); font-size: .72rem; font-style: italic; }

/* ---- Bottom bar ----------------------------------------------------------- */
.bottombar {
	grid-area: bottom;
	display: flex; align-items: center; gap: var(--sp-4);
	padding: 0 var(--sp-3); font-size: .78rem; color: var(--fg-muted);
	background: var(--surface); border-top: 1px solid var(--border);
	overflow: hidden; white-space: nowrap;
}
.legend { display: flex; align-items: center; gap: var(--sp-3); flex: 1 1 auto; overflow-x: auto; }
.legend-item {
	display: inline-flex; align-items: center; gap: var(--sp-1);
	/* themed chip so it reads as clickable and contrasts in every mode
	   (the default <button> face was light-grey on light-grey text in dark mode) */
	background: var(--surface-2); border: 1px solid var(--border); color: var(--fg);
	padding: 1px var(--sp-2); border-radius: 999px;
}
.legend-item:hover { border-color: var(--brand); color: var(--brand); }
.legend-item.off { opacity: .45; }
.bottombar .readout { font-variant-numeric: tabular-nums; }
/* The population readout is the first thing to go when the bar gets tight: it is context,
   not state. Hidden below the full-XL tier; see the breakpoint block further down. */
.pop-readout { flex: 0 0 auto; }

/* ---- Leaflet marker (custom div icon, no awesome-markers dependency) ------ */
.em-pin {
	display: flex; align-items: center; justify-content: center;
	width: 30px; height: 30px; border-radius: 50% 50% 50% 0;
	transform: rotate(-45deg); border: 2px solid rgba(255,255,255,.9);
	box-shadow: var(--shadow); color: #fff;
}
.em-pin i { transform: rotate(45deg); font-size: .8rem; }
.em-pin.sel { outline: 3px solid var(--accent); outline-offset: 1px; z-index: 1000; }

/* ---- Responsive helpers (revealed only in the relevant mode) -------------- */
.hamburger, .search-btn, .counts-row, .rail-extra { display: none; }
.drawer-backdrop { position: fixed; inset: 0; z-index: 1900; background: rgba(0, 0, 0, .4); }
.counts-row {
	align-items: center; gap: var(--sp-2);
	padding: var(--sp-1) var(--sp-3); font-size: .72rem; color: var(--fg-muted);
	border-bottom: 1px solid var(--border); white-space: nowrap; overflow-x: auto;
	font-variant-numeric: tabular-nums;
}
.counts-row b { color: var(--fg); }
.pane-search {
	display: none;   /* shown when the top-bar search is unavailable (narrow / portrait) */
	width: 100%; margin-bottom: var(--sp-3); padding: var(--sp-2) var(--sp-3);
	background: var(--surface-2); color: var(--fg);
	border: 1px solid var(--border); border-radius: 999px; outline: none;
}

/* search pane (forked dwh-search-pane-next) */
.search-pane .pane-search { display: block; margin-bottom: var(--sp-2); }
.search-bar { display: flex; align-items: center; gap: var(--sp-1); }
.search-advanced { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin: var(--sp-2) 0; font-size: .8rem; color: var(--fg-muted); }
.search-advanced input { width: 5rem; padding: var(--sp-1) var(--sp-2); background: var(--surface-2); color: var(--fg); border: 1px solid var(--border); border-radius: var(--radius-sm); }
/* each switch takes a full row under the year pair; its transparent input must keep
   covering the label, not inherit the 5rem year-input width above */
.search-advanced .switch { flex: 1 1 100%; }
.search-advanced .switch input { width: 100%; padding: 0; border: none; background: none; }
.search-advanced label.is-disabled { opacity: .5; }
.search-advanced input:disabled { cursor: not-allowed; }
.search-order { display: flex; align-items: center; flex-wrap: wrap; gap: 2px; margin: var(--sp-2) 0; }
.search-order .iconbtn { width: 2rem; height: 1.9rem; font-size: .8rem; }
.search-count { flex: 1 1 100%; font-size: .72rem; color: var(--fg-muted); margin-bottom: var(--sp-1); }
.search-special { padding: var(--sp-2) 0; }
.search-special h4 { margin: 0 0 var(--sp-2); font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--fg-muted); }
.search-special .chip { cursor: pointer; }
.rail-extra { flex-direction: column; gap: 2px; padding: 0 var(--sp-1) var(--sp-2); border-top: 1px solid var(--border); }
.too-small {
	display: none; position: fixed; inset: 0; z-index: 9999;
	flex-direction: column; align-items: center; justify-content: center;
	gap: var(--sp-3); padding: var(--sp-5); text-align: center;
	background: var(--bg); color: var(--fg);
}
.too-small i { font-size: 2.5rem; color: var(--brand); }

/* ============================================================================
   RESPONSIVE — arrangement by ORIENTATION, feature richness by WIDTH.
     Landscape → side-by-side (rail | map | right pane), at every width.
     Portrait  → stacked (map over the info pane); rail = hamburger drawer.
   ============================================================================ */

/* --- SIDE-BY-SIDE (landscape): rail overlays the map's left --- */
@media (orientation: landscape) {
	.rail {
		position: absolute; left: 0; top: var(--topbar-h); bottom: var(--bottom-h);
		width: var(--rail-w); z-index: 1100; transition: width var(--transition);
	}
	#app .leaflet-left { transition: margin-left var(--transition); margin-left: var(--rail-w); }
	.lesson-dock.docked { margin-left: var(--rail-w); transition: margin-left var(--transition); }
	.lesson-dock.float { left: calc(var(--rail-w) + var(--sp-4)); }
}
/* full (>=1024 landscape): rail can expand, right pane can collapse */
@media (min-width: 1024px) and (orientation: landscape) {
	#app.rail-expanded .rail { width: var(--rail-w-expanded); }
	#app.rail-expanded .leaflet-left { margin-left: var(--rail-w-expanded); }
	#app.rail-expanded .lesson-dock.docked { margin-left: var(--rail-w-expanded); }
	#app.rail-expanded .lesson-dock.float { left: calc(var(--rail-w-expanded) + var(--sp-4)); }
	#app.right-collapsed { grid-template-columns: 1fr 0; }
}
/* compact (<1024 landscape): icon rail, no expand, narrower pane */
@media (max-width: 1023px) and (orientation: landscape) {
	:root { --right-w: clamp(16rem, 30vw, 20rem); }
	.pop-readout { display: none; }                            /* world population: full XL only */
	.rail-item .label { display: none; }                       /* icon-only by default */
	#app.rail-expanded .rail { width: var(--rail-w-expanded); }/* expand toggle DOES widen it */
	#app.rail-expanded .rail-item .label { display: inline; }
}

/* --- STACKED (portrait) --- */
@media (orientation: portrait) {
	#app {
		grid-template-columns: 1fr;
		grid-template-rows: var(--topbar-h) 60dvh 1fr;
		grid-template-areas: "topbar" "map" "right";
	}
	.right { grid-area: right; border-left: none; border-top: 1px solid var(--border); }
	.rail {
		position: fixed; left: 0; top: var(--topbar-h); bottom: 0; width: var(--rail-w-expanded);
		transform: translateX(-100%); transition: transform var(--transition);
		z-index: 2000; border-right: 1px solid var(--border);
	}
	#app.drawer-open .rail { transform: translateX(0); }
	.rail-item .label { opacity: 1; }                    /* labelled drawer */
	.rail-toggle { display: none; }
	.hamburger { display: inline-flex; }
	.bottombar { display: none; }
	.counts-row { display: flex; }
	.topbar .search { display: none; }
	.pane-search { display: block; }
	/* lesson dock: stays at the bottom of the 60dvh map; float disabled (cramped) */
	.lesson-dock.docked { margin-left: 0; }
	.lesson-dock.float {
		position: static; left: auto; bottom: auto; width: auto;
		border: none; border-top: 2px solid var(--brand); border-radius: 0; box-shadow: none;
	}
	/* the map row is only 60dvh here, so cap both modes harder */
	.lesson-dock.docked, .lesson-dock.float { max-height: 42%; }
	.dock-body { max-height: 11rem; }
	.lesson-dock.float .dock-head { cursor: default; }
}
/* comfortable portrait (>=768): keep a search button in the top bar */
@media (orientation: portrait) and (min-width: 768px) {
	.search-btn { display: inline-flex; }
}

/* --- NARROW (<768, either orientation): move top-bar chrome into the menu ---
   Anything here that hides a control shared with the rest of the site MUST be
   scoped with `.app-page` — only /m has a rail to relocate it into. */
@media (max-width: 767px) {
	:root { --topbar-h: 2.5rem; }
	.bottombar { display: none; }                  /* too cramped — counts move to the pane */
	.counts-row { display: flex; }
	.topbar .search, .search-btn { display: none; }               /* search via the Search tab */
	.app-page .theme-btn { display: none; }        /* colour mode → >> rail popout */
	.app-page .lang-menu { display: none; }        /* language → rail */
	.layers-panel { display: none; }               /* base maps + borders → rail */
	.pane-tab-nolocation { display: none; }        /* hide 'unknown location' tab to save space */
	.rail-extra { display: flex; }                 /* language as a direct rail item */
	.rail-more { display: block; }                 /* >> popout home for colour-mode + borders */
	.pane-search { display: block; }
	.topbar { gap: var(--sp-2); padding: 0 var(--sp-2); }
	.timectl .year { font-size: 1.1rem; width: 4.5rem; }
	.timectl .era { display: none; }
	.app-page .brand .name { display: none; }      /* /m only: the date control needs the room.
	                                                  Elsewhere the wordmark survives to <=400. */
	/* compact pane tabs only on small screens (column design is fine on larger ones) */
	.pane-tab { flex-direction: row; justify-content: center; gap: var(--sp-1); font-size: .76rem; white-space: nowrap; overflow: hidden; }
	.pane-tab i { font-size: .92rem; }
}

/* landscape counterpart of the portrait grid override above: .bottombar is
   already display:none here (main 767px block), this reclaims its row. The
   rail is an absolute overlay, not a grid item (bottom: var(--bottom-h) above),
   so it needs the same reclaim or it stops short and leaves the old bottombar
   gap under it. */
@media (max-width: 767px) and (orientation: landscape) {
	#app {
		grid-template-rows: var(--topbar-h) 1fr;
		grid-template-areas: "topbar topbar" "map right";
	}
	.rail { bottom: 0; }
}

@media ((orientation: landscape) and (max-width: 768px)) or ((orientation: portrait) and (max-width: 480px)) {
	.pane-tab-label { display: none; }
}

/* Tiny screens (both axes < 768): the pane tabs already carry per-pane counts,
   so drop the redundant counts-row. */
@media (max-width: 767px) and (max-height: 767px) {
	.counts-row { display: none; }
}

/* Short landscape (no hamburger ⇒ icon rail): not enough height for every item
   AND the MENU toggle — drop the optional items so the toggle stays visible. */
@media (orientation: landscape) and (max-height: 480px) {
	.rail-item.rail-item-optional { display: none; }   /* moved into the >> popout */
	.rail-more { display: block; }                     /* reveal the overflow popout */
	.rail-more-pop .more-optional { display: flex; }   /* add-fact + tour join the popout here */
}

/* --- Progressive degradation --- */
/* Either axis, not just width: a 640x388 landscape phone leaves the map 384px wide (the
   right pane takes the rest) — as cramped as a 388px portrait one, which already hid these.
   Keying on width alone showed the controls on one orientation and hid them on the other.
   `:not(.dwh-locate-control)` is load-bearing: the locate control reuses Leaflet's
   zoom-bar class for its look, so a bare `.leaflet-control-zoom` selector also hid it —
   and it has no rail home to be relocated into, so it simply disappeared.
   `.app-page` only: fact.html's location map and lesson.html's route preview are small
   Leaflet pickers that need their own zoom control. */
@media (max-width: 400px), (max-height: 400px) {
	.app-page .leaflet-control-zoom:not(.dwh-locate-control),
	.app-page .leaflet-control-scale { display: none; }
}
/* ---- Map popup ------------------------------------------------------------
   `.ss` is the class the engine already passes to bindPopup (js/dwh.js `_popupOptions`);
   it had no styles at all, and neither did the `popup-body-text` / `popup-footer-text`
   hooks in the popup's own markup. Leaflet's stock content margin is 13px 24px 13px 20px
   — 44px of horizontal box on a map that can be 384px wide. The width itself is set from
   JS (`maxWidth`) because Leaflet writes it inline and a CSS max-width would fight it. */
.ss .leaflet-popup-content { margin: 10px 12px; }
.ss .popup-body-text { font-size: .95rem; margin: 0; }
.ss .popup-footer-text { color: var(--fg-muted); }
.ss .popup-modifier {
	position: absolute; top: 0; left: 30px; transform: translateY(-50%);
	padding: .2em .7em; border-radius: 999px; white-space: nowrap;
	font-size: .72rem; font-weight: 600; line-height: 1.3;
	background: var(--surface-2); color: var(--fg);
	/*background: var(--brand); color: var(--brand-fg);*/
	box-shadow: var(--shadow);
}
@media (max-width: 400px), (max-height: 400px) {
	.ss .leaflet-popup-content { font-size: .82rem; margin: 8px 10px; }
}
@media (max-width: 330px) {
	.leaflet-popup-content { font-size: .8rem; max-width: 180px; }
}

/* --- Device too small: show a notice, hide the (unusable) app chrome --- */
@media (max-width: 319px), (max-height: 319px) {
	.too-small { display: flex; }
}

/* ===================================================================
   Guided tour (em-tour.js) — theme-aware.
   ==================================================================== */
/* Sits above all app chrome (shell uses z-index up to ~2000). Geometry is set
   inline by em-tour.js; `.full` dims everything (intro), `.spotlight` cuts a
   transparent hole over the target via a huge box-shadow. */
.tour-overlay {
	position: fixed; z-index: 3000; pointer-events: none;
	border-radius: var(--radius-sm);
	transition: top .2s ease, left .2s ease, width .2s ease, height .2s ease;
}
.tour-overlay.full { background: rgba(8, 12, 18, .55); border-radius: 0; }
.tour-overlay.spotlight {
	background: transparent;
	box-shadow: 0 0 0 9999px rgba(8, 12, 18, .55);
	outline: 2px solid var(--brand); outline-offset: 0;
}

.popover-tour {
	position: fixed; z-index: 3001;
	width: min(420px, calc(100vw - 16px));
	background: var(--surface); color: var(--fg);
	border: 1px solid var(--border); border-radius: var(--radius);
	box-shadow: var(--shadow-lg);
	font-size: .9rem;
}
.popover-tour.popover-tour-center {
	top: 50%; left: 50%; transform: translate(-50%, -50%);
}
.popover-tour .hidden { display: none !important; }

.popover-tour .tour-exit {
	position: absolute; top: var(--sp-2); right: var(--sp-2);
	width: 1.6rem; height: 1.6rem; padding: 0; line-height: 1;
	border: none; border-radius: 999px;
	background: var(--surface-2); color: var(--fg-muted); cursor: pointer;
}
.popover-tour .tour-exit::before { content: "\00d7"; font-size: 1.1rem; }
.popover-tour .tour-exit:hover { background: var(--brand); color: var(--brand-fg); }

.popover-tour .popover-header {
	margin: 0; padding: var(--sp-3) var(--sp-5) var(--sp-3) var(--sp-3);
	font-size: 1rem; font-weight: 700;
	background: var(--brand); color: var(--brand-fg);
	border-radius: var(--radius) var(--radius) 0 0;
}
.popover-tour .popover-body { padding: var(--sp-3); line-height: 1.45; }

.popover-tour .tour-tab-links {
	display: flex; justify-content: center; gap: var(--sp-1);
	list-style: none; margin: var(--sp-3) 0 0; padding: 0;
}
.popover-tour .tour-tab-links li { display: inline-block; }
.popover-tour .tour-tab-links a {
	display: block; width: 8px; height: 8px; border-radius: 999px;
	background: var(--border); cursor: pointer; transition: width .15s, background .15s;
}
.popover-tour .tour-tab-links a.active { width: 20px; background: var(--brand); }

.popover-tour .popover-footer {
	display: flex; align-items: center; gap: var(--sp-2);
	padding: var(--sp-3); border-top: 1px solid var(--border);
}
.popover-tour .popover-footer .btn {
	padding: var(--sp-1) var(--sp-4); border-radius: var(--radius-sm);
	border: 1px solid var(--border); background: var(--surface-2); color: var(--fg);
	font-size: .85rem; cursor: pointer;
}
.popover-tour .popover-footer .btn:hover { border-color: var(--brand); }
.popover-tour .popover-footer .tour-next,
.popover-tour .popover-footer .tour-finish {
	margin-left: auto; background: var(--brand); color: var(--brand-fg); border-color: var(--brand);
}

/* ===================================================================
   Toasts (dwh-messages) — scoped to #messages.
   ==================================================================== */
/* On the editors the verdict has to be readable from wherever the reader pressed Save.
   #messages sits at the top of <main> and the save button is far below it, so a fixed
   in-flow banner was simply off-screen at the moment it mattered. Sticky rather than
   fixed: it stays in the document flow, so it never covers the first field.
   Scoped to .plain-page — /m positions its own copy as a fixed overlay (see `m`). */
/* z-index 20: above the form's own content, far below the top bar (1200) — the bar
   must paint over it, and `top` already parks the banner just under the bar. */
.plain-page #messages { position: sticky; top: calc(var(--topbar-h) + var(--sp-2)); z-index: 20; }
#messages .row { display: block; margin: 0 0 var(--sp-2); }
#messages .alert {
	position: relative; margin: 0;
	padding: var(--sp-3) 2.2rem var(--sp-3) var(--sp-3);
	background: var(--surface); color: var(--fg);
	border: 1px solid var(--border); border-left: 4px solid var(--fg-muted);
	border-radius: var(--radius-sm); box-shadow: var(--shadow-lg);
	font-size: .9rem; line-height: 1.4;
}
#messages .alert a { color: var(--brand); }
#messages .alert-info    { border-left-color: var(--brand); }
#messages .alert-success { border-left-color: #2e9e5b; }
#messages .alert-warning { border-left-color: #d99a00; }
#messages .alert-danger  { border-left-color: #d65745; }
/* The inline (non-overlay) branch lays the text and the close button to sit side by
   side, or the button drops under the message. Used by the editor pages. */
#messages .alert > .row { display: flex; align-items: flex-start; gap: var(--sp-2); margin: 0; }
#messages .alert > .row > .col-11 { flex: 1 1 auto; min-width: 0; }
#messages .alert > .row > .col { flex: 0 0 auto; }
/* close X (top-right) + full-width message — neutralise the BS float layout */
#messages .float-start { float: none; display: block; }
#messages .float-end { position: absolute; top: var(--sp-1); right: var(--sp-1); float: none; }
#messages .btn-close {
	width: 1.6rem; height: 1.6rem; padding: 0; opacity: 1; cursor: pointer;
	display: inline-flex; align-items: center; justify-content: center;
	border: none; border-radius: 999px; background: transparent; color: var(--fg-muted);
}
#messages .btn-close::before { content: "\00d7"; font-size: 1.1rem; line-height: 1; }
#messages .btn-close:hover { background: var(--surface-2); color: var(--fg); }

/* ===================================================================
   Trace panel (#traceWindow) — troubleshooting overlay, opt-in via
   ?traceEnabled=1. Rendered only when config.traceEnabled is true, so
   it costs nothing in normal use. Theme-aware via tokens.
   ==================================================================== */
#traceWindow {
	position: fixed; right: var(--sp-3); bottom: var(--sp-3);
	width: min(320px, calc(100vw - var(--sp-3) * 2));
	z-index: 2400;                       /* above the panes, below toasts (2200) + context menu (2500) */
	background: var(--surface); color: var(--fg);
	border: 1px solid var(--border); border-radius: var(--radius-sm);
	box-shadow: var(--shadow-lg);
	transition: top .3s, bottom .3s;
}
#traceWindow .trace-header {
	padding: var(--sp-2) var(--sp-3);
	background: var(--brand); color: #fff;
	border-radius: var(--radius-sm) var(--radius-sm) 0 0;
	font-size: .85rem; font-weight: 600; cursor: pointer;
	user-select: none;
}
#trace-body {
	max-height: 40vh; min-height: 6rem;
	padding: var(--sp-2);
	overflow-y: auto; overflow-x: hidden; overflow-wrap: anywhere;
	font-family: ui-monospace, monospace; font-size: .7rem; line-height: 1.5;
	color: var(--fg-muted);
}
