/* Interactive map styling (PROJECT_BRIEF §6). Tokens from base.html's inline
   :root block (was web/css/tokens.css, inlined for render-blocking perf). */

/* Leaflet's own panes/controls use z-index up to 1000 (leaflet.css:
   .leaflet-marker-pane 600, .leaflet-popup-pane 700, .leaflet-top/bottom
   controls 1000) — well above the site-wide .modal's z-index:80 (app.css),
   so on any page with a live Leaflet map the map painted over the confirm
   modal instead of under it. This file loads after app.css, so the higher
   value here wins on source order without needing !important. */
.modal {
	z-index: 2000;
}

.map-stage {
	position: relative;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	overflow: hidden;
	box-shadow: var(--shadow-card);
	background: var(--surface-sunken);
}
.map-canvas {
	width: 100%;
	height: min(72vh, 90vh);
}
.map-canvas--summary {
	height: min(50vh, 420px);
}
.map-stage--summary {
	margin-bottom: 1rem;
}
/* Leaflet paints tiles/overlays; keep its own controls on-brand. */
.leaflet-container {
	background: var(--surface-sunken);
	font-family: var(--font-body);
}
.leaflet-bar a {
	color: var(--ink);
	background: var(--surface);
	border-bottom-color: var(--hairline);
}
.leaflet-bar a:hover {
	background: var(--row-hover);
}

/* --- World map vs. detail map ---------------------------------------------
   Two different objects that happen to share a Leaflet engine, so they read
   differently on purpose:

   * World map (§6.1) -- an illustrated overworld. Warm parchment surround, no
     framing chrome competing with the art, node icons that rescale with zoom.
   * Detail map (§6.2) -- a *plan* of one location you place pins on. Cool
     sunken surround with an inner shadow so the image reads as inset paper,
     crisp pixel rendering (these backgrounds are game screenshots, and
     smoothing them at high zoom turns tile edges to mush), and pins that stay
     one size at every zoom because they're UI, not scenery. */
.map-stage--world {
	background: var(--surface-alt);
}
.map-stage--world .leaflet-container {
	background: var(--surface-alt);
}

.map-stage--detail {
	background:
		linear-gradient(0deg, rgba(27, 34, 43, 0.035), rgba(27, 34, 43, 0.035)),
		var(--surface-sunken);
}
.map-stage--detail .leaflet-container {
	background: transparent;
}
.map-stage--detail::after {
	/* Inset edge, over the canvas but never in the way of a click. */
	content: "";
	position: absolute;
	inset: 0;
	pointer-events: none;
	border-radius: inherit;
	box-shadow: inset 0 1px 6px rgba(27, 34, 43, 0.1);
	z-index: 500;
}
.map-stage--detail .map-bg-image {
	image-rendering: pixelated;
	/* Lifts the screenshot off the sunken surround so its own edges are legible
     when zoomed out past the image's bounds. */
	box-shadow: 0 2px 10px rgba(27, 34, 43, 0.18);
}
.map-canvas--editor {
	height: min(70vh, 78vh);
}

/* Gesture hint for an embedded canvas (see map-core.js initEmbeddedGestures:
   the wheel only zooms after a click, one-finger drag stays with the page).
   Fades out once the map is armed -- by then the user knows. */
.map-stage__hint {
	position: absolute;
	left: 10px;
	bottom: 8px;
	z-index: 600;
	margin: 0;
	padding: 3px 9px;
	border-radius: var(--r-pill);
	background: rgba(255, 255, 255, 0.86);
	color: var(--ink-muted);
	font-size: 0.74rem;
	pointer-events: none;
	transition: opacity 0.2s ease;
}
.map-stage--armed .map-stage__hint {
	opacity: 0;
}
.map-stage__hint-touch {
	display: none;
}
@media (hover: none) {
	.map-stage__hint-desk {
		display: none;
	}
	.map-stage__hint-touch {
		display: inline;
	}
}
@media (max-width: 560px) {
	/* Nothing left to explain on a phone: pinch is the native gesture and the
     Expand button says its own name. (It would also collide with the legend,
     which moves out of the overlay at this width.) */
	.map-stage__hint {
		display: none;
	}
}

/* Full-screen: the same map instance, its stage promoted to a fixed overlay
   (map-core.js ExpandControl) -- above the §6.2 detail panel (1200) and its
   tooltip (1300) so an expanded map can't be painted over by either. */
.map-stage--fullscreen {
	position: fixed;
	inset: 0;
	z-index: 1500;
	border: none;
	border-radius: 0;
	margin: 0;
}
.map-stage--fullscreen .map-canvas {
	height: 100%;
}
.map-stage--fullscreen::after {
	border-radius: 0;
}
body.map-fullscreen-open {
	overflow: hidden;
}

.map-expand__btn {
	border: 1px solid var(--hairline);
	background: var(--surface);
	color: var(--ink);
	font: inherit;
	font-size: 0.8rem;
	padding: 7px 12px;
	min-height: 34px;
	border-radius: var(--r-btn);
	box-shadow: var(--shadow-lift);
	cursor: pointer;
	margin: 7px !important;
}
.map-expand__btn:hover {
	background: var(--row-hover);
}

/* Marker-type legend, pinned inside the stage. */
.map-legend {
	position: absolute;
	right: 10px;
	bottom: 8px;
	z-index: 600;
	list-style: none;
	margin: 0;
	padding: 6px 9px;
	display: flex;
	flex-wrap: wrap;
	gap: 4px 12px;
	max-width: min(60%, 420px);
	border-radius: var(--r-btn);
	background: rgba(255, 255, 255, 0.9);
	box-shadow: var(--shadow-lift);
	font-size: 0.74rem;
	color: var(--ink-muted);
	pointer-events: none;
}
.map-legend__item {
	display: inline-flex;
	align-items: center;
	gap: 5px;
}
.map-legend__pin {
	position: static;
	width: 12px;
	height: 12px;
	border-width: 1.5px;
	cursor: default;
}
.map-legend__pin::before {
	display: none;
}
@media (max-width: 560px) {
	/* A phone's map is small enough that a floating legend covers real markers,
     and unlike the gesture hint the legend is permanent -- so it drops out of
     the overlay and sits under the canvas instead. */
	.map-legend {
		position: static;
		max-width: none;
		border-radius: 0;
		box-shadow: none;
		background: var(--surface);
		border-top: 1px solid var(--hairline);
		justify-content: center;
		padding: 8px 10px;
	}
}

/* Zoom control: +/- buttons flanking the slider, plus a fit-to-map button.
   The bare rotated slider was a 4px-wide hit target -- unusable on touch --
   and with a fit-derived zoom range (map-core.js detailZoomRange) there has to
   be one press that always returns to "the whole map, centred". */
.zoom-ctl {
	display: flex;
	flex-direction: column;
	align-items: center;
	gap: 4px;
	margin: 7px !important;
}
.zoom-ctl__btn {
	display: grid;
	place-items: center;
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	background: var(--surface);
	color: var(--ink);
	font: inherit;
	font-size: 1.15rem;
	line-height: 1;
	cursor: pointer;
	box-shadow: var(--shadow-lift);
}
.zoom-ctl__btn:hover {
	background: var(--row-hover);
}
.zoom-ctl__btn--fit {
	color: var(--accent-deep, var(--accent));
}

/* Vertical zoom slider. A plain horizontal <input type=range>, rotated -90deg
   -- the standard cross-browser trick for a vertical slider without a separate
   widget library. */
.zoom-slider {
	background: var(--surface);
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	box-shadow: var(--shadow-lift);
	width: 34px;
	height: 118px;
	display: flex;
	align-items: center;
	justify-content: center;
}
.zoom-slider__input {
	-webkit-appearance: none;
	appearance: none;
	width: 96px;
	height: 4px;
	background: var(--hairline);
	border-radius: 999px;
	transform: rotate(-90deg);
	cursor: pointer;
	outline: none;
}
/* Small embedded previews can't spare 118px of vertical run for the track. */
.map-canvas--summary .zoom-slider {
	height: 84px;
}
.map-canvas--summary .zoom-slider__input {
	width: 66px;
}
@media (max-width: 560px) {
	/* On a phone the whole control column would cover a fifth of the map, and
     pinch is the zoom people actually reach for -- so the slider goes and the
     three buttons (in / out / fit) stay. */
	.zoom-slider {
		display: none;
	}
	.zoom-ctl {
		gap: 6px;
	}
}
.zoom-slider__input::-webkit-slider-thumb {
	-webkit-appearance: none;
	width: 16px;
	height: 16px;
	border-radius: 50%;
	background: var(--accent);
	border: 2px solid var(--surface);

	cursor: pointer;
}
.zoom-slider__input::-moz-range-track {
	background: var(--hairline);
	height: 4px;
	border-radius: 999px;
}
.zoom-slider__input::-moz-range-thumb {
	width: 16px;
	height: 16px;
	border-radius: 50%;
	background: var(--accent);
	border: 2px solid var(--surface);

	cursor: pointer;
}

/* --- Location nodes (divIcon markers) ------------------------------------ */
.node {
	display: block;
	width: 20px;
	height: 20px;
	border-radius: 50% 50% 50% 0;
	transform: rotate(-45deg);
	border: 2px solid var(--surface);

	background: var(--accent);
	cursor: pointer;
	transition: transform 0.12s ease;
}
.leaflet-marker-icon:hover .node {
	transform: rotate(-45deg) scale(1.18);
}

/* Node art (layer 3, spec 2026-07-29 §Rendering). The icon box is the node's
   stored WORLD size; --world-scale (set on the map container by map-core.js,
   one write per zoom event) converts that to screen pixels, so the art is
   pinned to the terrain and zooms with it. transform-origin is the centre
   because that is also iconAnchor -- the point roads connect to.

   There is deliberately no desktop-breakpoint scale bump here. The old fixed
   screen-pixel icons needed one; world-pixel art must not have one, or the art
   would no longer line up with the terrain it is pinned to. */
.node-art {
	display: block;
	position: relative;
	width: 100%;
	height: 100%;
	cursor: pointer;
	transform: scale(var(--world-scale, 1)) rotate(var(--node-rot, 0deg));
	transform-origin: 50% 50%;
}
.node-art img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
	filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35));
	pointer-events: none;
}
/* Layer 4 (phase 4, spec 2026-07-29 §"Label styling"): the name plate, its
   own Leaflet layer in labelPane -- NOT a child of .node-art any more, so it
   paints above every node regardless of anchor-based z-order (see
   map-core.js's labelPane comment). Every length below is written inline, in
   WORLD pixels, by map-label-render.js; this one transform scales the whole
   thing together with --world-scale, the same trick .node-art already used
   before phase 4 split them into separate layers (plan: "How label geometry
   and scaling work"). transform-origin 0 0 is the anchor point itself: percent
   values inside `translate()` resolve against the element's OWN unscaled
   width, so translateX(-50%) centres correctly at any zoom without a second,
   manual calculation. */
.map-label {
	position: absolute;
	left: 0;
	top: 0;
	transform: scale(var(--world-scale, 1)) translateX(-50%);
	transform-origin: 0 0;
	white-space: nowrap;
	pointer-events: none;
}
.map-label__text {
	display: inline-block;
	font-family: var(--font-display);
	line-height: 1.05;
}
.leaflet-marker-icon:hover .node-art {
	transform: scale(calc(var(--world-scale, 1) * 1.06)) rotate(var(--node-rot, 0deg));
}
.node-art.node--dragging {
	opacity: 0.65;
}
/* Editor selection. A border or outline would be scaled by --world-scale along
   with everything else -- a 2px ring becomes 32px at max zoom -- so the
   selected state is a glow on the art itself, which scaling only makes softer.
   The inspector panel names the selected node too, so this only has to read as
   "this one". filter does not cascade additively: this declaration replaces the
   base rule's entirely, so the ambient drop-shadow has to be repeated here or a
   selected node loses it. */
.node-art--selected img {
	filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35))
		drop-shadow(0 0 0.06em var(--accent)) drop-shadow(0 0 0.12em var(--accent))
		brightness(1.06);
}
/* icon_type palette — distinct, legible on parchment backgrounds */
.node--town {
	background: var(--accent);
}
.node--town_nocoin {
	background: var(--accent);
}
.node--plains {
	background: var(--success, #4f9d5a);
}
.node--mountains {
	background: #6b4fa0;
}
.node--ruins {
	background: #8a7b52;
}
.node--forest {
	background: #2f6b3a;
}
.node--special {
	background: var(--ink-muted);
}

/* dragging state in the editor */
.node--dragging {
	opacity: 0.65;
}

/* Roads: a rotated div per segment, CSS-tiling the stone-path texture along
   its width (map-core.js roadMarker) -- not an SVG stroke, so a diagonal
   connection (real in-game world maps have them) just needs one
   transform: rotate(), no per-angle SVG pattern math. */
.road-tile {
	background-repeat: repeat-x;
	background-position: center;
	filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.25));
}
/* Hovering a Placed-locations row highlights its roads -- entry.layer is the
   L.marker itself, so the class lands on the .leaflet-marker-icon wrapper
   that Leaflet generates around our .road-tile div. */
.leaflet-marker-icon.road--highlight .road-tile {
	filter: brightness(1.25) sepia(1) hue-rotate(-25deg) saturate(4)
		drop-shadow(0 1px 2px rgba(0, 0, 0, 0.35));
}

/* "Recall" pulse when a Placed-locations row is clicked. */
@keyframes potum-node-pulse {
	0%,
	100% {
		transform: scale(1);
	}
	35% {
		transform: scale(1.35);
	}
}
.node-pulse {
	animation: potum-node-pulse 0.7s ease;
}

.leaflet-tooltip.node-tip {
	background: var(--ink);
	color: var(--surface);
	border: none;
	border-radius: var(--r-btn);
	font-family: var(--font-display);
	font-size: 0.8rem;
	padding: 2px 8px;
	box-shadow: var(--shadow-lift);
}
.leaflet-tooltip.node-tip::before {
	display: none;
}

/* --- Region switcher / empty state --------------------------------------- */
.region-switch {
	display: flex;
	flex-wrap: wrap;
	gap: 6px;
}
.chip--on {
	background: var(--accent);
	color: #fff;
	border-color: transparent;
}

.staff-tools {
	display: flex;
	flex-wrap: wrap;
	gap: 8px;
	margin: 0 0 1rem;
}

.empty-state {
	text-align: center;
	padding: 2.5rem 1.25rem;
}
.empty-state__title {
	font-family: var(--font-display);
	font-size: 1.15rem;
	margin: 0 0 0.35rem;
}
.empty-state__body {
	color: var(--ink-muted);
	margin: 0 auto;
	max-width: 42ch;
}

/* --- Editor -------------------------------------------------------------- */
.editor__tools {
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
}

/* §6.2 marker editor's tool bar. A real bar, not a loose row of inputs: it's
   the "what will the next pin be" panel, so it stays visible (sticky) while
   the map is being panned, and its own controls stay finger-sized. */
.editor__bar {
	position: sticky;
	top: 0;
	z-index: 30;
	display: flex;
	align-items: flex-end;
	flex-wrap: wrap;
	gap: 10px 12px;
	margin: 0 0 8px;
	padding: 12px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface);
	box-shadow: var(--shadow-card);
}
.editor__place {
	flex: 0 0 auto;
	min-height: 38px;
}
.editor__place[aria-pressed="true"] {
	/* Armed: the button now describes a live mode, so it reads as engaged and
     matches the crosshair the canvas has taken on. */
	background: var(--danger-ink, #b0485f);
	box-shadow: 0 2px 0 #8d3549;
}
.editor__checks {
	display: flex;
	flex-wrap: wrap;
	gap: 4px 14px;
	margin-left: auto;
}
.editor__field-label {
	font-size: 0.78rem;
	color: var(--ink-muted);
}
.editor__status {
	min-height: 1.2em;
	margin: 0 0 8px;
	font-size: 0.85rem;
	color: var(--ink-muted);
}
.editor__status--error {
	color: var(--danger-ink, #b0485f);
}
/* Placement armed: the cursor is the mode indicator on desktop; the status
   line and the button state carry it on touch. */
.map-stage--placing .leaflet-container {
	cursor: crosshair;
}
.map-stage--placing {
	outline: 2px solid var(--accent);
	outline-offset: -2px;
}
.map-stage--placing .leaflet-marker-icon {
	cursor: crosshair;
}

@media (max-width: 720px) {
	.editor__bar {
		padding: 10px;
		gap: 8px 10px;
	}
	.editor__bar .editor__field,
	.editor__bar .editor__search-wrap {
		flex: 1 1 140px;
	}
	.editor__place {
		flex: 1 1 100%;
	}
	.editor__checks {
		margin-left: 0;
		width: 100%;
	}
	.map-canvas--editor {
		height: 62vh;
	}
}
.editor__snap {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	color: var(--ink-muted);
	font-size: 0.9rem;
}
/* Undo/redo is decor-layer only (see the stack's comment in
   world-map-editor.js). A CSS sibling selector cannot reach this group from
   `.editor--layer-*` -- `.page-head` sits BEFORE `.editor__layout`, not
   after it, and there is no parent combinator -- so `setLayer` toggles
   `.editor__undo--idle` directly instead. The buttons fade rather than
   vanish: a control that disappears reads as a bug. */
.editor__undo {
	display: flex;
	gap: 4px;
}
.editor__undo--idle,
.editor__undo button:disabled {
	opacity: 0.55;
}
.editor__hint {
	color: var(--ink-muted);
	font-size: 0.9rem;
	margin: 0.25rem 0 1rem;
}
.btn-ghost[aria-pressed="true"] {
	background: var(--accent);
	color: #fff;
	border-color: transparent;
}

.editor__layout {
	display: grid;
	grid-template-columns: 240px 1fr;
	gap: 14px;
	align-items: start;
}
@media (max-width: 720px) {
	.editor__layout {
		grid-template-columns: 1fr;
	}
}

.editor__sidebar {
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface);
	padding: 12px;
	/* height, not max-height: percentage flex-basis on the two sections below
     only resolves against a definite height, not one driven purely by a
     max-height cap — with max-height alone it fell back to content sizing
     and the 547-row unplaced list rendered at full, unclipped height. */
	height: min(72vh, 90vh);
	display: flex;
	flex-direction: column;
	gap: 10px;
}
/* Unplaced + Placed each get exactly half the sidebar's height, independently
   scrollable. A fixed flex-basis (not flex-grow equal-split) on purpose:
   with 547 unplaced rows vs. a handful of placed ones, flex-grow distribution
   let the huge list's content pull far more than its share before the other
   side's overflow:auto kicked in, so it doesn't reliably split 50/50. */
.editor__section {
	display: flex;
	flex-direction: column;
	flex: 0 0 calc(50% - 5px);
	min-height: 0;
}
.editor__section:first-child {
	border-bottom: 1px solid var(--hairline-soft);
	padding-bottom: 10px;
}
.editor__search-label {
	font-size: 0.78rem;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--ink-muted);
}
.editor__search {
	width: 100%;
	margin: 6px 0 10px;
	padding: 7px 10px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	font: inherit;
	background: var(--canvas);
	color: var(--ink);
}
.editor__unplaced,
.editor__placed {
	list-style: none;
	margin: 0;
	padding: 0;
	overflow-y: auto;
	flex: 1;
	min-height: 0;
}
.editor__unplaced li,
.editor__placed li {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 8px;
	border-radius: var(--r-btn);
	cursor: grab;
	font-size: 0.9rem;
	border: 1px solid transparent;
}
.editor__placed li {
	cursor: default;
	justify-content: space-between;
}
.editor__unplaced li:hover,
.editor__placed li:hover {
	background: var(--row-hover);
	border-color: var(--hairline-soft);
}
.editor__unplaced li[aria-grabbed="true"] {
	opacity: 0.5;
}
.editor__unplaced .dot,
.editor__placed .dot {
	width: 12px;
	height: 12px;
	border-radius: 50% 50% 50% 0;
	transform: rotate(-45deg);
	flex: 0 0 auto;
}
.editor__placed-main {
	display: flex;
	align-items: center;
	gap: 8px;
	flex: 1;
	min-width: 0;
	cursor: pointer;
}
.editor__placed-main span:nth-child(2) {
	/* the name label is always the 2nd child (1st = dot, optional 3rd = the
     "-> main map" text on a grouped row) -- truncate it, not whichever
     span happens to be last. */
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.editor__placed-del {
	flex: 0 0 auto;
	border: none;
	background: none;
	cursor: pointer;
	color: var(--ink-muted);
	font-size: 1.1rem;
	line-height: 1;
	padding: 2px 7px;
	border-radius: var(--r-btn);
}
.editor__placed-del:hover {
	background: var(--danger-bg, #fde2e2);
	color: var(--danger-ink, #b42318);
}

/* --- §6.2 map markers (round badges, coloured + lettered per type) --------
   Colour alone doesn't survive a busy screenshot background (or a colour-blind
   reader), so each type also carries a mono initial -- the same "ITM"/"DRP"
   tile language the catalog cards use -- and the legend spells them out. */
.marker {
	display: grid;
	place-items: center;
	width: 24px;
	height: 24px;
	border-radius: 50%;
	border: 2px solid var(--surface);
	background: var(--ink-muted);
	color: #fff;
	font-family: var(--font-mono);
	font-size: 11px;
	font-weight: 700;
	line-height: 1;
	cursor: pointer;
	position: relative;
	box-shadow: 0 1px 3px rgba(27, 34, 43, 0.45);
	transition: transform 0.12s ease;
}
/* Finger-sized hit area without a finger-sized badge: 44px of invisible
   padding centred on the pin (WCAG 2.5.5 target size). */
.marker::after {
	content: "";
	position: absolute;
	top: 50%;
	left: 50%;
	width: 44px;
	height: 44px;
	transform: translate(-50%, -50%);
}
.leaflet-marker-icon:hover .marker {
	transform: scale(1.18);
}
.marker--monster_spawn {
	background: #c2542f;
}
.marker--boss {
	background: #8a1f1f;
}
.marker--gather_node {
	background: var(--success, #4f9d5a);
}
.marker--portal {
	background: #3a6ea5;
}
.marker--npc {
	background: var(--accent);
}
.marker--monster_spawn::before {
	content: "M";
}
.marker--boss::before {
	content: "B";
}
.marker--gather_node::before {
	content: "G";
}
.marker--portal::before {
	content: "P";
}
.marker--npc::before {
	content: "N";
}

/* Marker popup in the editor: label, what it links to, and the delete action
   (which is how deleting works on touch, where there's no right-click). */
.marker-pop-wrap .leaflet-popup-content-wrapper {
	border-radius: var(--r-card);
	box-shadow: var(--shadow-lift);
	background: var(--surface);
	color: var(--ink);
}
.marker-pop-wrap .leaflet-popup-content {
	margin: 12px 14px;
}
.marker-pop {
	display: grid;
	gap: 6px;
	min-width: 150px;
}
.marker-pop__name {
	font-family: var(--font-display);
	font-weight: 600;
}
.marker-pop__meta {
	color: var(--ink-muted);
	font-size: 0.78rem;
}
.marker-pop__del {
	justify-self: start;
	border: 1px solid var(--danger-bg, #fbeaee);
	background: var(--danger-bg, #fbeaee);
	color: var(--danger-ink, #b0485f);
	font: inherit;
	font-size: 0.8rem;
	padding: 6px 11px;
	min-height: 34px;
	border-radius: var(--r-btn);
	cursor: pointer;
}
.marker-pop__del:hover {
	filter: brightness(0.97);
}

/* The map's own grid (Map.grid_cols/grid_rows), drawn as an SVG overlay in
   image coordinates so it scales with the background (map-core.js gridOverlay).
   Editor-only: it exists to make "snap to grid" visible. */
.map-grid line {
	stroke: rgba(44, 124, 182, 0.45);
}
.map-grid {
	pointer-events: none;
}

/* --- §6.2 detail panel: modal (centred) + tray (right slide-out) --------- */
.panel-overlay {
	position: fixed;
	inset: 0;
	z-index: 1200;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 1rem;
	background: rgba(20, 16, 12, 0.42);
}
/* display:flex above outranks the UA [hidden]{display:none}; restore it so a
   closed overlay does not blanket the page and swallow every map click. */
.panel-overlay[hidden] {
	display: none;
}
.panel {
	position: relative;
	background: var(--surface);
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	box-shadow: var(--shadow-lift);
	max-width: 460px;
	width: 100%;
	max-height: 84vh;
	overflow-y: auto;
	padding: 20px;
}
.panel-overlay--tray {
	justify-content: flex-end;
	padding: 0;
}
.panel-overlay--tray .panel {
	max-width: 340px;
	height: 100vh;
	max-height: 100vh;
	border-radius: 0;
	border-right: none;
}
/* Wide variant hosts monster/NPC .card grids, which are --surface (white) --
   same as .panel's own default background, so cards had zero contrast against
   the modal. Giving the modal the page's --canvas tone instead (the same
   canvas/card split used everywhere else on the site) restores it. */
.panel-overlay--wide .panel {
	max-width: min(920px, 94vw);
	background: var(--canvas);
}
.map-summary .mstat__name {
	margin-bottom: 4px;
}
.map-summary .card-grid {
	margin-bottom: 1rem;
}
/* The 4-icon action bar (Comments/Save/Share/Edit) fits a full-width card page
   but overflows the narrower card columns inside this modal on mobile -- Edit
   is the one non-essential action here (moderators already reach editing from
   the entity's own page), so it's dropped only in this context. */
.map-summary .act--edit {
	display: none;
}
.modal__close {
	position: absolute;
	top: 8px;
	right: 10px;
	width: 28px;
	height: 28px;
	display: grid;
	place-items: center;
	border: none;
	border-radius: 8px;
	box-shadow: none;
	cursor: pointer;
	padding-bottom: 1rem;
	font-size: 2rem;
	line-height: 1;
	color: var(--ink-muted);
	align-content: center;
	justify-content: center;
}
.modal__close:hover {
	color: var(--ink);
}
body.panel-open {
	overflow: hidden;
}

.mstat__head,
.npc__head {
	display: flex;
	align-items: center;
	gap: 10px;
	margin-bottom: 12px;
}
.mstat__name,
.npc__name {
	font-family: var(--font-display);
	font-size: 1.2rem;
	margin: 0;
}
.mstat__name a,
.map-section__heading a {
	color: inherit;
	text-decoration: none;
}
.mstat__name a:hover,
.map-section__heading a:hover {
	text-decoration: underline;
}
.mstat__grid {
	display: grid;
	grid-template-columns: repeat(2, 1fr);
	gap: 8px 16px;
	margin: 0 0 14px;
}
.mstat__grid div {
	display: flex;
	justify-content: space-between;
	border-bottom: 1px solid var(--hairline-soft);
	padding-bottom: 4px;
}
.mstat__grid dt {
	color: var(--ink-muted);
	margin: 0;
}
.mstat__grid dd {
	margin: 0;
	font-variant-numeric: tabular-nums;
}
.mstat__sub {
	font-family: var(--font-display);
	font-size: 0.95rem;
	margin: 14px 0 8px;
}
.mstat__count,
.npc__quests {
	color: var(--ink-muted);
}
.mstat__count {
	font-size: 0.8rem;
	background: var(--surface-alt);
	border-radius: var(--r-pill);
	padding: 1px 8px;
	margin-left: 4px;
}

.drop-pool {
	list-style: none;
	margin: 0;
	padding: 0;
}
.drop-pool__row {
	display: flex;
	justify-content: space-between;
	gap: 8px;
	padding: 6px 4px;
	border-bottom: 1px solid var(--hairline-soft);
}
.drop-pool__item {
	cursor: help;
	border-bottom: 1px dotted var(--ink-faint);
}
.drop-pool__dye {
	color: var(--ink-muted);
	font-size: 0.82rem;
	font-variant-numeric: tabular-nums;
}

.npc__quests {
	list-style: none;
	margin: 0;
	padding: 0;
}
.npc__quest {
	padding: 8px 4px;
	border-bottom: 1px solid var(--hairline-soft);
}
.npc__quest-name {
	display: block;
}
.npc__quest-meta {
	color: var(--ink-muted);
	font-size: 0.82rem;
}
.npc__where {
	color: var(--ink-muted);
	font-size: 0.85rem;
	margin: -6px 0 10px;
}

/* NPC cards list every NPC that lives on the map (Monster.is_npc), placed or
   not -- so each says which it is. */
.npc-card__place {
	margin: 8px 0 0;
	font-size: 0.76rem;
	color: var(--ink-faint);
}
.npc-card__pin {
	font-size: 0.8rem;
}

.map-tooltip {
	position: fixed;
	z-index: 1300;
	transform: translate(-50%, -100%);
	background: var(--ink);
	color: var(--surface);
	padding: 4px 9px;
	border-radius: var(--r-btn);
	font-size: 0.8rem;
	pointer-events: none;
	box-shadow: var(--shadow-lift);
	max-width: 260px;
}

/* --- §6.2 editor extra fields ------------------------------------------- */
.editor__field {
	display: inline-flex;
	flex-direction: column;
	gap: 3px;
	font-size: 0.78rem;
	color: var(--ink-muted);
}
.editor__field select,
.editor__field input {
	font: inherit;
	color: var(--ink);
	padding: 6px 8px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	background: var(--canvas);
}
.editor__search-wrap {
	position: relative;
	display: inline-flex;
	flex-direction: column;
	gap: 3px;
}
/* display:inline-flex above outranks the UA's [hidden]{display:none}, so the
   target picker stayed on screen while "Link to" was — none —. */
.editor__search-wrap[hidden] {
	display: none;
}
.editor__results {
	position: absolute;
	top: 100%;
	left: 0;
	right: 0;
	z-index: 20;
	list-style: none;
	margin: 2px 0 0;
	padding: 4px;
	background: var(--surface);
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	box-shadow: var(--shadow-lift);
	max-height: 220px;
	overflow-y: auto;
}
.editor__results li {
	padding: 6px 8px;
	border-radius: 6px;
	cursor: pointer;
}
.editor__results li:hover {
	background: var(--row-hover);
}
.editor__chosen {
	font-size: 0.78rem;
	color: var(--accent-deep, var(--accent));
}

/* --- Individual map page ------------------------------------------------- */
.map-detail__bg {
	width: 100%;
	max-height: 420px;
	object-fit: contain;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface-sunken);
	margin-bottom: 1.25rem;
}
.section-title {
	font-family: var(--font-display);
	font-size: 1.1rem;
	margin: 1.25rem 0 0.5rem;
}
.table-wrap {
	overflow-x: auto;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
}
.data-table {
	width: 100%;
	border-collapse: collapse;
	font-size: 0.92rem;
}
.data-table th,
.data-table td {
	text-align: left;
	padding: 8px 12px;
	border-bottom: 1px solid var(--hairline-soft);
}
.data-table th {
	background: var(--surface-alt);
	font-family: var(--font-display);
	font-weight: 600;
}
.data-table tr:last-child td {
	border-bottom: none;
}
.data-table .num {
	text-align: right;
	font-variant-numeric: tabular-nums;
}
.data-table tbody tr:hover,
.data-table tbody tr:hover {
	background: var(--row-hover);
}

/* --- Sub-map sections (MapGrouping): a main map's page/modal is one or more
   of these -- itself, then each grouped sub-map in order. --------------- */
.map-section {
	margin-bottom: 1.5rem;
}
.map-section__heading {
	border-top: 1px solid var(--hairline-soft);
	padding-top: 1rem;
	margin-top: 1.25rem !important;
}

/* --- Manage sub-maps admin panel ----------------------------------------- */
.group-admin {
	margin: 0.75rem 0 1.25rem;
}
.group-admin__panel {
	position: relative;
	margin-top: 8px;
	padding: 12px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface-alt);
}
.group-admin__search-wrap {
	position: relative;
	margin-bottom: 10px;
}
.group-admin__list {
	list-style: none;
	margin: 0;
	padding: 0;
}
.group-admin__list li {
	display: flex;
	align-items: center;
	gap: 8px;
	padding: 7px 8px;
	border-radius: var(--r-btn);
	border: 1px solid transparent;
	font-size: 0.9rem;
	background: var(--surface);
	margin-bottom: 4px;
}
.group-admin__handle {
	color: var(--ink-faint);
	cursor: grab;
	flex: 0 0 auto;
}
.group-admin__name {
	flex: 1;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.group-admin__row--dragging {
	opacity: 0.5;
}
.group-admin__empty {
	color: var(--ink-faint);
	font-style: italic;
	background: none !important;
}

/* --- Grouped sub-maps inside the Placed-locations sidebar list ----------- */
.editor__placed-subof {
	font-size: 0.78rem;
	color: var(--ink-muted);
	flex: 0 0 auto;
}
.editor__placed--grouped {
	background: var(--surface-alt);
}

/* --- Asset library gallery modal (spec 2026-07-29 §Asset library) --------
   Launched from the world-map editor's toolbar (Task 7). One collapsible
   .sec-bar/.sec-body per MapAssetCategory, filled by editor-assets.js. */
.asset-gallery__toolbar {
	display: flex;
	align-items: center;
	flex-wrap: wrap;
	gap: 8px 10px;
	margin-bottom: 10px;
}
.asset-gallery__toolbar .editor__search {
	flex: 1 1 200px;
	margin: 0;
}
.asset-gallery__manage-tools {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
}
.asset-gallery__manage-tools[hidden] {
	display: none;
}

/* Inline "new category" / "upload art" forms -- revealed in place of a
   dialog, per the no-window.prompt/confirm/alert constraint. */
.asset-gallery__form {
	display: flex;
	flex-direction: column;
	gap: 4px;
	padding: 12px;
	margin-bottom: 10px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface-alt);
}
.asset-gallery__form[hidden] {
	display: none;
}
.asset-gallery__form .editor__field-label {
	margin-top: 6px;
}
.asset-gallery__form .editor__field-label:first-child {
	margin-top: 0;
}
.asset-gallery__preview {
	display: block;
	width: 96px;
	height: 96px;
	object-fit: contain;
	margin: 4px 0;
	border-radius: var(--r-btn);
	border: 1px solid var(--hairline);
	/* Same checkerboard as the gallery tiles below -- the preview is where a
	   moderator first notices a botched alpha channel, before the upload
	   even completes. */
	background-image:
		linear-gradient(45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(-45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(45deg, transparent 75%, var(--surface-sunken) 75%),
		linear-gradient(-45deg, transparent 75%, var(--surface-sunken) 75%);
	background-size: 16px 16px;
	background-position: 0 0, 0 8px, 8px -8px, -8px 0;
	background-color: var(--surface);
}
.asset-gallery__preview[hidden] {
	display: none;
}

/* Dropzone: click, drag-drop, or paste (Ctrl/Cmd+V) all land here. The real
   <input type=file> is a .sr-only sibling (see the template comment) so
   keyboard/AT users keep native file-picker access; this <label for=...>
   carries all the visible styling and is what drag/drop events bind to. */
.asset-gallery__dropzone {
	display: flex;
	flex-direction: column;
	align-items: center;
	justify-content: center;
	gap: 6px;
	min-height: 110px;
	padding: 14px;
	border: 2px dashed var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface-sunken);
	color: var(--ink-muted);
	font-size: 0.82rem;
	text-align: center;
	cursor: pointer;
	transition: border-color 0.15s ease, background 0.15s ease;
}
.asset-gallery__dropzone:hover {
	border-color: var(--accent);
}
/* Set by editor-assets.js while a dragged file is over the zone. */
.asset-gallery__dropzone--active {
	border-color: var(--accent);
	background: var(--surface);
}
.asset-gallery__dropzone-text,
.asset-gallery__dropzone span {
	pointer-events: none;
}
.asset-gallery__dropzone .asset-gallery__preview {
	margin: 0;
	pointer-events: none;
}
/* The file input itself is visually .sr-only, not display:none -- it can
   still take keyboard focus, so give that a visible ring on the dropzone
   next to it rather than leaving keyboard users with no focus indicator at
   all (the input's own box is 1px, offscreen). */
input.sr-only:focus-visible + .asset-gallery__dropzone {
	outline: 2px solid var(--accent);
	outline-offset: 2px;
}
.asset-gallery__form-actions {
	display: flex;
	gap: 10px;
	margin-top: 8px;
}
.asset-gallery__form-actions .btn-primary,
.asset-gallery__form-actions .btn-social {
	flex: 1;
	justify-content: center;
}

/* Gallery body: independently scrollable so the toolbar/forms/error slot
   above it stay put while a long asset list scrolls. */
.asset-gallery__body {
	max-height: 60vh;
	overflow-y: auto;
}
.asset-gallery__cat {
	margin-bottom: 4px;
}
.asset-gallery__cat-head {
	display: flex;
	align-items: center;
	gap: 6px;
}
.asset-gallery__cat-head .sec-bar {
	flex: 1;
	min-width: 0;
}
.asset-gallery__cat-del {
	flex: 0 0 auto;
}
/* Pencil-glyph trigger for the category's inline rename/reorder form -- same
   icon-only convention as the asset tile's edit button (aria-label carries
   "Edit category <name>"), just inline in the header row instead of floated
   over a thumbnail. */
.asset-gallery__cat-edit-btn {
	flex: 0 0 auto;
	border: none;
	background: none;
	cursor: pointer;
	color: var(--ink-muted);
	font-size: 0.95rem;
	line-height: 1;
	padding: 2px 7px;
	border-radius: var(--r-btn);
}
.asset-gallery__cat-edit-btn:hover {
	background: var(--surface-alt);
	color: var(--accent);
}
/* The category edit form reuses .asset-gallery__form's box (border, padding,
   background) as-is -- it renders directly beneath the header row, same
   position new-category/upload-art forms take beneath the toolbar. */

.asset-gallery__grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
	gap: 10px;
	padding: 6px 2px 10px;
}
.asset-tile {
	position: relative;
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.asset-tile__thumb {
	position: relative;
	aspect-ratio: 1 / 1;
	border-radius: var(--r-btn);
	border: 1px solid var(--hairline);
	overflow: hidden;
	/* The checkerboard is the whole point: these assets are transparent
	   PNG/WebP art, and a translucent/white asset is invisible on a plain
	   white tile -- this is the only place a moderator can confirm the
	   alpha channel actually survived the upload pipeline. */
	background-image:
		linear-gradient(45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(-45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(45deg, transparent 75%, var(--surface-sunken) 75%),
		linear-gradient(-45deg, transparent 75%, var(--surface-sunken) 75%);
	background-size: 16px 16px;
	background-position: 0 0, 0 8px, 8px -8px, -8px 0;
	background-color: var(--surface);
}
.asset-tile__thumb img {
	display: block;
	width: 100%;
	height: 100%;
	object-fit: contain;
}
.asset-tile__name {
	font-size: 0.78rem;
	color: var(--ink-2);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
/* Edit + Delete float over the top-right corner of the thumb as a pair --
   position now lives on the wrapper so the two buttons sit side by side
   instead of stacking on the same absolute corner. */
.asset-tile__controls {
	position: absolute;
	top: 2px;
	right: 2px;
	display: flex;
	gap: 2px;
}
.asset-tile__del {
	background: rgba(255, 255, 255, 0.88);
	box-shadow: var(--shadow-lift);
}
/* Pencil-glyph trigger for the inline rename/recategorise form -- same
   footprint as the "×" delete button next to it, but hover reads as a
   neutral edit action (accent) rather than destructive (danger, see
   .editor__placed-del:hover). Icon-only: the accessible name comes from
   aria-label ("Edit <name>"), matching the delete button's convention. */
.asset-tile__edit-btn {
	border: none;
	cursor: pointer;
	color: var(--ink-muted);
	font-size: 0.95rem;
	line-height: 1;
	padding: 2px 7px;
	border-radius: var(--r-btn);
	background: rgba(255, 255, 255, 0.88);
	box-shadow: var(--shadow-lift);
}
.asset-tile__edit-btn:hover {
	background: var(--surface);
	color: var(--accent);
}
/* The inline edit form takes the same box treatment as the toolbar's New
   category / Upload art forms (.asset-gallery__form) but spans the full grid
   row instead of sitting in a single 96px tile column, so the name input and
   category <select> have room to breathe. */
.asset-tile__edit-form {
	grid-column: 1 / -1;
}

/* Picker mode (window.PotumAssets.open) -- tiles become real, keyboard-
   accessible buttons; the delete control and management toolbar are hidden
   by editor-assets.js rather than styled here. */
.asset-tile--pick {
	cursor: pointer;
	border-radius: var(--r-btn);
	outline-offset: 2px;
}
.asset-tile--pick:hover .asset-tile__thumb,
.asset-tile--pick:focus-visible .asset-tile__thumb {
	border-color: var(--accent);
}

/* --- Node inspector (spec 2026-07-29 §"Editor UX", phase 2) -------------- */
/* The editor grows from sidebar + canvas to sidebar + canvas + inspector. The
   inspector column collapses first (below 1080px it drops under the canvas,
   below 720px everything stacks) -- on a narrow screen the canvas is what has
   to keep its width. */
@media (min-width: 1081px) {
	.editor__layout {
		grid-template-columns: 240px 1fr 260px;
	}
}
/* Between 721px and 1080px the layout is still two columns, so without this the
   inspector would land in row 2 of the 240px sidebar column and be crushed. */
@media (max-width: 1080px) {
	.editor__inspector {
		grid-column: 1 / -1;
	}
}

.editor__inspector {
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background: var(--surface);
	padding: 12px;
	display: flex;
	flex-direction: column;
	gap: 14px;
}
/* The UA's [hidden] { display: none } is a type-less selector, so any class rule
   setting display beats it — the panel, its blocks and its buttons would all
   stay visible with the attribute set. Everything in this panel is shown and
   hidden by the hidden attribute, so restate it at a specificity that wins. */
.editor__inspector[hidden],
.editor__inspector [hidden] {
	display: none;
}
.editor__inspector-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	border-bottom: 1px solid var(--hairline-soft);
	padding-bottom: 8px;
}
.editor__inspector-name {
	font-family: var(--font-display);
	font-weight: 700;
	overflow-wrap: anywhere;
}
.editor__inspector-error {
	color: var(--danger-ink);
	background: var(--danger-bg);
	border-radius: var(--r-btn);
	padding: 7px 10px;
	font-size: 0.85rem;
	margin: 0;
}
.editor__inspector-block {
	display: flex;
	flex-direction: column;
	gap: 8px;
}
/* Checkerboard, same reason as the gallery tiles: this art is transparent, and
   a pale icon on a plain surface is invisible. */
.editor__inspector-thumb {
	width: 100%;
	height: 96px;
	object-fit: contain;
	border: 1px solid var(--hairline);
	border-radius: var(--r-card);
	background-image:
		linear-gradient(45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(-45deg, var(--surface-sunken) 25%, transparent 25%),
		linear-gradient(45deg, transparent 75%, var(--surface-sunken) 75%),
		linear-gradient(-45deg, transparent 75%, var(--surface-sunken) 75%);
	background-size: 12px 12px;
	background-position: 0 0, 0 6px, 6px -6px, -6px 0;
}
.editor__inspector-none,
.editor__inspector-hint {
	color: var(--ink-muted);
	font-size: 0.8rem;
	margin: 0;
}
.editor__inspector-actions {
	display: flex;
	gap: 8px;
	flex-wrap: wrap;
}
.editor__inspector-size {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 8px;
}
.editor__inspector-field {
	display: flex;
	flex-direction: column;
	gap: 2px;
	font-size: 0.78rem;
	color: var(--ink-muted);
}
.editor__inspector-field .editor__search {
	margin: 0;
}
/* Label-style fields (phase 4): shared between the node inspector's override
   disclosure and the preset modal (label_style_fields.html, plan Decision 8). */
.editor__label-fields {
	display: grid;
	gap: 8px;
	margin-top: 8px;
}
.editor__label-style-body {
	display: grid;
	grid-template-columns: 200px 1fr;
	gap: 16px;
}
.editor__label-style-list {
	display: flex;
	flex-direction: column;
	gap: 4px;
}
.editor__label-style-row {
	text-align: left;
	padding: 8px 10px;
	border-radius: var(--r-btn);
	border: 1px solid var(--hairline);
	background: var(--surface);
	cursor: pointer;
}
.editor__label-style-row[aria-pressed="true"] {
	border-color: var(--accent);
	background: var(--accent-tint);
}
.editor__label-style-preview {
	margin-top: 12px;
	padding: 20px;
	background: var(--surface-sunken);
	border-radius: var(--r-card);
	display: flex;
	justify-content: center;
}

/* --- Layer 2: decorations (spec 2026-07-29 §Rendering, phase 3) ---------- */
/* decorPane is created by map-core.js at z-index 350 -- below overlayPane (400)
   and markerPane (600), so decorations sit under both the roads and the nodes.
   Leaflet names the pane's class from createPane("decorPane"), which strips the
   "Pane" suffix: .leaflet-decor-pane.

   The pane is click-through by default so a decoration can never eat a click
   meant for a node or for the map itself; the editor opts individual layers
   back in by adding .leaflet-interactive (Task 7). */
.leaflet-decor-pane {
	pointer-events: none;
}
.leaflet-decor-pane .leaflet-interactive {
	pointer-events: auto;
	cursor: pointer;
}
/* No drop-shadow here, unlike .node-art img: a node is an object sitting ON the
   terrain and reads as one because of its shadow, while a decoration IS the
   terrain. Any shadow a decoration wants is authored per element through the
   drop_shadow filter. */
.decor-art {
	-webkit-user-drag: none;
	user-select: none;
}

/* --- Editor layer tabs (spec 2026-07-29 §"Editor UX", phase 3) ----------- */
.editor__tabs {
	display: flex;
	gap: 4px;
	border-bottom: 1px solid var(--hairline);
	padding-bottom: 8px;
	margin-bottom: 4px;
}
.editor__tab {
	flex: 1 1 0;
	padding: 7px 10px;
	border: 1px solid var(--hairline);
	border-radius: var(--r-btn);
	background: var(--canvas);
	color: var(--ink-muted);
	font: inherit;
	font-weight: 600;
	cursor: pointer;
}
.editor__tab[aria-selected="true"] {
	background: var(--accent);
	border-color: transparent;
	color: #fff;
}
/* The sidebar is a fixed-height flex column (see .editor__sidebar); the visible
   panel has to be the thing that grows and scrolls inside it, or the two
   sections below collapse to their content height again. */
.editor__layer-panel {
	display: flex;
	flex-direction: column;
	gap: 10px;
	flex: 1 1 auto;
	min-height: 0;
}
.editor__layer-panel[hidden] {
	display: none;
}
.editor__dim {
	display: flex;
	align-items: center;
	gap: 8px;
	font-size: 0.78rem;
	color: var(--ink-muted);
}
.editor__dim input[type="range"] {
	flex: 1 1 auto;
	min-width: 0;
}
.editor__decor-gallery {
	overflow: auto;
	min-height: 0;
	flex: 1 1 auto;
}

/* --- Which layer the canvas edits --------------------------------------- */
/* Leaflet fixes a layer's `interactive` option at creation, so toggling it per
   tab would mean tearing down and rebuilding every marker on a switch. One
   class on the layout does the same job with no rebuild.

   Roads live in markerPane too (they are L.markers with zIndexOffset -1000),
   so they follow the nodes for DIMMING (the opacity rule below applies to the
   whole pane) -- but they are created with interactive: false (map-core.js's
   roadMarker()), so they never picked up .leaflet-interactive and were never
   clickable in the first place; nothing extra is needed to make them
   click-through.

   This targets .leaflet-interactive specifically, not the bare pane: Leaflet's
   own stylesheet sets `.leaflet-marker-icon.leaflet-interactive { pointer-events:
   auto }` directly on the icon element, which -- being an explicit rule on the
   element itself -- wins over an inherited `pointer-events: none` from an
   ancestor pane. A pane-level rule alone (i.e. `.editor--layer-decor
   .leaflet-marker-pane { pointer-events: none; }`) verifiably does NOT
   disable node clicks; confirmed in the browser during Task 7 verification. */
.editor--layer-decor .leaflet-marker-pane .leaflet-interactive {
	pointer-events: none;
	cursor: default;
}
.editor--layer-nodes .leaflet-decor-pane .leaflet-interactive {
	pointer-events: none;
	cursor: default;
}
/* Per-layer dim. Defaults to 1 so a container that has never had a slider
   touched renders at full strength. */
.leaflet-decor-pane {
	opacity: var(--decor-dim, 1);
}
.leaflet-marker-pane {
	opacity: var(--nodes-dim, 1);
}
/* Selected decoration. Same reasoning as .node-art--selected: a border would be
   scaled with the art, so the state is a glow the scaling only softens. Unlike a
   node, a decoration has no ambient shadow to preserve, so this sets filter
   outright -- and it is applied to the element Leaflet positions, whose own
   filter the renderer writes, so it uses a box-shadow-free outline via
   drop-shadow to avoid a specificity fight with the inline style. */
.decor-art--selected {
	outline: 2px dashed var(--accent);
	outline-offset: 0;
}

/* The docked decor gallery reuses the modal gallery's tiles at sidebar width,
   so the grid needs a smaller minimum than the modal's. */
.editor__decor-gallery .asset-gallery__grid {
	grid-template-columns: repeat(auto-fill, minmax(72px, 1fr));
	gap: 6px;
}
.editor__decor-gallery .asset-tile__name {
	font-size: 0.72rem;
}
.asset-tile--draggable {
	cursor: grab;
}
.asset-tile--draggable[aria-grabbed="true"] {
	opacity: 0.5;
}

/* --- Decor inspector sliders (spec 2026-07-29 §"Editor UX" tab 2) -------- */
/* .editor__inspector already covers the panel chrome -- the decor inspector
   reuses it wholesale, so only the slider rows are new. */
.editor__slider {
	display: grid;
	grid-template-columns: 68px 1fr 44px;
	align-items: center;
	gap: 8px;
	font-size: 0.78rem;
	color: var(--ink-muted);
}
.editor__slider input[type="range"] {
	min-width: 0;
}
.editor__slider output {
	text-align: right;
	font-variant-numeric: tabular-nums;
}
/* The rotation row is a range plus a number field, not a range plus an output,
   so it needs its own column split. */
.editor__slider:has(> .editor__slider-num) {
	grid-template-columns: 1fr 68px;
}
.editor__slider-num {
	margin: 0;
}
/* Four stacking buttons at sidebar width wrap to two rows rather than shrinking
   their labels to nothing. */
.editor__inspector-actions .btn-social {
	flex: 1 1 calc(50% - 4px);
	padding-inline: 6px;
}

/* --- moveable handles (vendored 0.53.0) --------------------------------- */
/* moveable injects its own <style> for layout; these override the colours only,
   so a version bump changes geometry but never the palette. */
.moveable-line {
	background: var(--accent) !important;
}
.moveable-control {
	background: var(--surface) !important;
	border-color: var(--accent) !important;
}
.moveable-rotation .moveable-control {
	background: var(--accent) !important;
}
/* moveable appends its control box as a child of the container element we
   construct it with (here, the Leaflet map container) and positions it
   absolutely. The plan's own stacking-order concern: without an explicit
   z-index the box could in principle paint under a later, positioned sidebar
   panel. In the one edge case this session actually reproduced -- a
   decoration's screen box wider than the map container -- the real fix
   turned out to be a JS reflow-timing bug (see select()'s attachHandles()
   ordering comment), not this rule: past the container's own edge,
   .map-stage's overflow:hidden clips the handle for hit-testing regardless of
   z-index. This override is still correct defensive CSS per the plan and
   costs nothing; moveable's own injected stylesheet lands in <head> after
   this one, so !important is what would make it win if the stacking case
   above the clipped boundary ever does occur. */
.moveable-control-box {
	z-index: 700 !important;
}
