/* ===========================================================================
   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; }
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); }

.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; }
.alert.danger { border-left-color: #c0392b; }
.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; }

/* 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%; height: 10rem; 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; }

.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); }
/* 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; }
.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). */
.step-map, .form-map { height: 24rem; border: 1px solid var(--border); border-radius: var(--radius-sm); }
.step-map.picking { 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 marketing entry pages: dark top bar, one dissolving photograph, dark
   footer. The band is white in every colour mode — the one place that does
   not follow [data-theme]. */

/* 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;
	--cover-ink:          #1d2734;   /* the dark bands: top bar, hero, footer */
	--cover-paper:        #ffffff;   /* the light band between them */
}
.cover { background: var(--cover-paper); color: var(--cover-ink); }
.cover .topbar { background: var(--cover-ink); border-bottom-color: rgb(255 255 255 / .12); }
.cover .topbar .brand, .cover .topbar .iconbtn { color: #aec6de; }
.cover .topbar .iconbtn:hover { background: rgb(255 255 255 / .1); color: #fff; }
.cover .topbar .topbar-auth a { color: #aec6de; }
.cover .topbar .topbar-auth a:hover { background: rgb(255 255 255 / .1); color: #fff; }

/* Not clipped to the hero: it overhangs on purpose, top-anchored at its natural
   3:2 height, while the mask dissolves its last third into the white page.
   Never re-add `overflow: hidden` here, or a z-index on this or .cover-section
   — both are deliberately `z-index: auto` so they paint in DOM order and the
   heading's `difference` blend still reaches the photograph behind it. */
.cover-hero {
	position: relative;
	display: flex; flex-direction: column; justify-content: center;
	color: #fff;
	/* Puts the heading roughly where the mask starts to fade; the 34rem floor
	   covers the fixed-800px branch below. Any shorter and the cards sit on an
	   opaque photograph instead of a dissolving one. */
	min-height: clamp(34rem, 44vw, 40rem);
}
.cover-hero picture {
	position: absolute; top: 0; left: 0; display: block; width: 100%;
	pointer-events: none;
}
.cover-hero picture img {
	display: block; width: 100%; height: auto;
	-webkit-mask-image: linear-gradient(black, black, black, transparent);
	mask-image: linear-gradient(black, black, black, transparent);
}
/* Below 1200px the natural height is too short to read as a hero, so crop a
   tall one instead. */
@media (max-width: 1199.98px) {
	.cover-hero picture { height: 800px; }
	.cover-hero picture img { height: 100%; object-fit: cover; object-position: left top; }
}
/* Left-aligned, not a centred column — the copy is authored with hard line
   breaks and reads as a block on the photograph. It does need a real inset,
   though: with padding alone it sat 24px from the edge, where the old design
   carried roughly 6rem. */
.cover-hero .cover-copy {
	position: relative;
	padding: var(--sp-6) var(--sp-5);
	padding-inline-start: clamp(var(--sp-5), 6vw, 6rem);
}
/* 3.5rem at weight 300 — the size and weight of the display heading this
   replaced. It was 3.2rem at 400, which read as both smaller and heavier. */
.cover-hero h1 { font-size: clamp(2rem, 6vw, 3.5rem); font-weight: 300; line-height: 1.2; margin: 0 0 var(--sp-4); }
.cover-hero .lead { font-size: clamp(1rem, 2.4vw, 1.25rem); font-weight: 300; margin: 0 0 var(--sp-5); }

/* buttons on the photograph: outlined light, legible on any frame */
.btn.on-dark { background: transparent; color: #fff; border-color: rgb(255 255 255 / .7); border-width: 2px; font-weight: 600; }
.btn.on-dark:hover { background: #fff; color: var(--cover-ink); border-color: #fff; }

/* The lessons band. No background of its own — the page's white paper shows
   through wherever the photograph above has finished fading, so the top of this
   section is still photo. `position: relative` (z-index left at auto, see the
   hero) is what paints it over the picture. The small top padding keeps the
   heading high in the gradient, where the photo is still dark. */
.cover-section {
	position: relative;
	padding: var(--sp-3) var(--sp-4) var(--sp-6);
}
/* Spans the section rather than the card grid, or it wraps to two lines
   between 768px and 1200px. Needs `position` and no background of its own —
   a stacking context above it would isolate the `difference` blend to white. */
.cover-section > h2 {
	position: relative;
	font-size: clamp(1.3rem, 3.5vw, 2.5rem); font-weight: 400; text-align: center;
	color: #fff; mix-blend-mode: difference;
	margin: 0 auto var(--sp-4);
}
/* 1 / 2 / 3 columns, explicit rather than `auto-fill, minmax()`: that fitted a
   fourth column and shrank every card. */
.cover-grid { display: grid; grid-template-columns: 1fr; gap: var(--sp-4); margin-inline: auto; }
@media (min-width: 576px)  { .cover-grid { grid-template-columns: repeat(2, 1fr); max-width: 33rem; } }
@media (min-width: 768px)  { .cover-grid { max-width: 43.5rem; } }
@media (min-width: 992px)  { .cover-grid { grid-template-columns: repeat(3, 1fr); max-width: 58.5rem; } }
@media (min-width: 1200px) { .cover-grid { max-width: 69.75rem; } }
@media (min-width: 1400px) { .cover-grid { max-width: 81rem; } }
/* Both the grid item and the card need to fill the row, or a card with a
   one-line title ends shorter than its neighbours. */
.cover-grid > article { display: flex; height: 100%; }
.cover-card {
	display: flex; flex-direction: column; overflow: hidden; width: 100%;
	border: 1px solid rgb(0 0 0 / .18);   /* the old card's hairline, which is what
	                                         separates a pale card from the paper */
	border-radius: var(--radius); color: #fff; box-shadow: var(--shadow);
}
/* 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 {
	width: 100%; aspect-ratio: 16 / 10; height: auto;
	object-fit: cover; background: rgb(0 0 0 / .18);
}
.cover-card-body { flex: 1 1 auto; padding: var(--sp-4); }
.cover-card-body p {
	margin: 0; text-align: justify; text-transform: uppercase;
	font-weight: 300; font-size: 1.5rem; line-height: 1.5;
}
.cover-card-foot {
	display: flex; justify-content: space-between; align-items: center; gap: var(--sp-2);
	padding: var(--sp-2) var(--sp-4); background: rgb(0 0 0 / .15);
}
.cover-card-foot small { font-size: .875rem; font-weight: 300; }
.cover-card.politics { color: #264172; }
.cover-card.politics .btn.on-dark { color: #264172; border-color: #264172; }
.cover-card.politics .btn.on-dark:hover { background: #264172; color: #fff; }

/* The closing dark band. The border is what makes it read as a bar of its own
   where the section above is white — and it still does if that section ever
   changes colour. */
.cover-footer {
	background: var(--cover-ink); color: #aec6de;
	padding: var(--sp-4) var(--sp-3);
	border-top: 3px solid rgb(255 255 255 / .18);
}
.cover-footer nav { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-2); margin-bottom: var(--sp-3); }
.cover-footer p { text-align: center; 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` = 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 */
.rail-cats { padding: var(--sp-1) var(--sp-2) var(--sp-2); display: flex; flex-direction: column; gap: 2px; }
/* 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); }
.rail-cat-actions button { background: transparent; border: none; color: var(--fg-muted); text-align: left; padding: var(--sp-1); font-size: .76rem; }
.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); }
.leaflet-tile-pane { filter: var(--map-filter); }
.leaflet-container { background: var(--surface-2); font-family: var(--font); }

/* the historical-borders toggle is now a Leaflet control (engine addControlBorders),
   so it inherits the .leaflet-bar look below; only its active state is custom. */

/* Make Leaflet's own layers control match the map-control buttons. */
.leaflet-control-layers { border: 1px solid var(--border) !important; border-radius: var(--radius-sm) !important; box-shadow: var(--shadow) !important; background: var(--surface) !important; }
.leaflet-control-layers-toggle { width: 2.4rem !important; height: 2.4rem !important; background-size: 20px 20px !important; }
.leaflet-control-layers-expanded { padding: var(--sp-2) var(--sp-3) !important; color: var(--fg) !important; }

/* 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 for the borders control (mirrors the old .map-ctrl-btn.active) */
.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; }
.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; }

/* blurry borders for imprecise geometry (mirrors styles.css hcg-bp*) */
.hcg-bp1 { filter: blur(3px); }
.hcg-bp2 { filter: blur(1.5px); }

/* ---- 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. */
.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);
}
.lesson-dock.float {
	position: absolute; left: var(--sp-4); bottom: calc(var(--sp-4) + 1.25rem);
	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; }
/* 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; }
/* 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); }
.dock-nav .readout { font-variant-numeric: tabular-nums; }
.dock-audio { flex: 1 1 12rem; min-width: 0; max-width: 22rem; }
.dock-audio audio { display: block; width: 100%; height: 2rem; }
.dock-controls .era { margin-left: auto; }

/* ---- 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 */
	.dwh-borders-control { display: none; }         /* borders → rail */
	.app-page .lang-menu { display: none; }        /* language → rail */
	.leaflet-control-layers { display: none; }     /* base-map layers → 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 --- */
@media (max-width: 400px) {
	/* `.app-page` only: fact.html's location map and lesson.html's route preview
	   are small Leaflet pickers that need their own zoom control. */
	.app-page .leaflet-control-zoom, .app-page .leaflet-control-scale { display: none; }
}
@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.
   ==================================================================== */
#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);
}
