:root {
	--bg: #f5f8f7;
	--surface: #ffffff;
	--surface-2: #f0f3f2;
	--border: #e3e9e7;
	/* One step darker, for the edge of something being pointed at. */
	--border-strong: #cfd8d5;
	--text: #1f2933;
	--text-muted: #6b7684;
	--accent: #0f9d8c;
	--accent-dark: #0b7d70;
	--accent-soft: #e6f5f2;
	--danger: #d64545;
	--danger-soft: #fdeaea;
	--success: #1f9d55;
	--radius: 12px;
	--radius-sm: 8px;
	/* Every control on a row — input, select, button — is sized off these, so
	   they always share one height and one baseline. */
	--control-h: 40px;
	--control-h-sm: 34px;
	/* How much of the map's asset sheet shows when it is down — the grab bar
	   and nothing else. The MapLibre attribution is lifted by the same amount
	   so the sheet never sits on top of it. */
	--sheet-peek-h: 52px;
	--shadow: 0 1px 2px rgba(16, 24, 32, 0.04), 0 8px 24px rgba(16, 24, 32, 0.06);
	--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

/* A class-level `display` beats the UA stylesheet's [hidden] rule, so make the
   hidden attribute authoritative — several panels rely on it. */
[hidden] { display: none !important; }

html, body {
	height: 100%;
}

body {
	margin: 0;
	font-family: var(--font);
	background: var(--bg);
	color: var(--text);
	font-size: 15px;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
	-webkit-tap-highlight-color: rgba(15, 157, 140, 0.14);
}

a { color: var(--accent-dark); }

h1, h2, h3 { margin: 0 0 8px; font-weight: 700; }

.muted { color: var(--text-muted); }

/* --- buttons & inputs --- */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	border: none;
	border-radius: var(--radius-sm);
	/* A fixed height rather than vertical padding: a button has to line up
	   with the inputs and selects it sits next to, whatever its font size. */
	height: var(--control-h);
	padding: 0 18px;
	/* Buttons don't inherit the page font on their own. */
	font-family: var(--font);
	font-size: 14px;
	font-weight: 600;
	line-height: 1;
	white-space: nowrap;
	cursor: pointer;
	background: var(--accent);
	color: #ffffff;
	transition: background 0.15s ease, color 0.15s ease;
	text-decoration: none;
}
.btn:hover { background: var(--accent-dark); }
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn:disabled { opacity: 0.6; cursor: not-allowed; }
/* The inset ring gives a secondary button the same edge as an input, so the
   two read as one family when they share a row. */
.btn.btn-secondary {
	background: var(--surface-2);
	color: var(--text);
	box-shadow: inset 0 0 0 1px var(--border);
}
.btn.btn-secondary:hover { background: var(--border); }
.btn.btn-danger { background: var(--danger); }
.btn.btn-danger:hover { background: #b93a3a; }
.btn.btn-block { width: 100%; }
.btn.btn-sm { height: var(--control-h-sm); padding: 0 12px; font-size: 13px; }
/* Square, for a button holding a single glyph — a remove "×", usually. */
.btn.btn-icon { width: var(--control-h); padding: 0; font-size: 17px; }
.btn.btn-sm.btn-icon { width: var(--control-h-sm); font-size: 16px; }
/* Quiet and borderless, for a secondary action in an already busy row. */
.btn.btn-link { background: transparent; color: var(--text-muted); padding: 0 10px; box-shadow: none; }
.btn.btn-link:hover { background: var(--surface-2); color: var(--text); }
/* A disclosure button's chevron points down when closed, up when open. */
.btn .chev { transition: transform 0.15s ease; }
.btn[aria-expanded="true"] .chev { transform: rotate(180deg); }
.btn[aria-expanded="true"].btn-secondary {
	background: var(--accent-soft);
	color: var(--accent-dark);
	box-shadow: inset 0 0 0 1px rgba(15, 157, 140, 0.35);
}
.btn[aria-expanded="true"].btn-secondary:hover { background: #d6efea; }

label {
	display: block;
	font-size: 13px;
	font-weight: 600;
	margin-bottom: 6px;
	color: var(--text);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
input[type="search"],
input[type="tel"],
input[type="url"],
input[type="date"],
select,
textarea {
	width: 100%;
	height: var(--control-h);
	padding: 0 12px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface);
	color: var(--text);
	font-family: var(--font);
	font-size: 14px;
	line-height: 1.4;
	transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

/* Strip the native chrome off the two inputs that come with their own, so a
   search or date box keeps the same border, rounding and height as the rest. */
input[type="search"], input[type="date"] { -webkit-appearance: none; appearance: none; }
input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; }
input[type="date"]::-webkit-calendar-picker-indicator { opacity: 0.45; cursor: pointer; }
input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 0.9; }

textarea {
	height: auto;
	min-height: 84px;
	padding: 10px 12px;
	line-height: 1.5;
	resize: vertical;
}

/* One chevron for every select, so they read as a set with the inputs instead
   of as three different browsers' native widgets. */
select {
	-webkit-appearance: none;
	appearance: none;
	padding-right: 34px;
	background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236b7684' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
	background-repeat: no-repeat;
	background-position: right 11px center;
	background-size: 15px;
	cursor: pointer;
}
select:disabled { background-color: var(--surface-2); color: var(--text-muted); cursor: not-allowed; }

input[type="color"] {
	width: 100%;
	height: var(--control-h);
	padding: 4px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface);
	cursor: pointer;
}
input[type="color"]::-webkit-color-swatch-wrapper { padding: 0; }
input[type="color"]::-webkit-color-swatch { border: none; border-radius: 4px; }
input[type="color"]::-moz-color-swatch { border: none; border-radius: 4px; }

::placeholder { color: var(--text-muted); opacity: 1; }

input:focus, select:focus, textarea:focus {
	outline: none;
	border-color: var(--accent);
	box-shadow: 0 0 0 3px var(--accent-soft);
}
input:disabled, textarea:disabled { background: var(--surface-2); color: var(--text-muted); }

.field { margin-bottom: 16px; }
.field:last-child { margin-bottom: 0; }

/* Controls sharing one row — latitude and longitude, say. The row itself owns
   the spacing to whatever follows, so the fields in it carry none. */
.field-row { display: flex; gap: 10px; }
.field-row > .field { flex: 1 1 0; min-width: 0; margin: 0; }

/* An input with an action button welded to its right. */
.input-with-button { display: flex; gap: 8px; }
.input-with-button input { flex: 1; min-width: 0; }
.input-with-button .btn { flex-shrink: 0; }

.field-hint { margin: 8px 0 0; font-size: 12px; line-height: 1.5; color: var(--text-muted); }
.field-hint:empty { display: none; }

.alert {
	border-radius: var(--radius-sm);
	padding: 10px 14px;
	font-size: 13px;
	margin-bottom: 16px;
}
.alert-error { background: var(--danger-soft); color: #a12f2f; }
.alert-success { background: var(--accent-soft); color: var(--accent-dark); }

/* --- auth pages --- */

.auth-page {
	min-height: 100%;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
}
.auth-card {
	width: 100%;
	max-width: 380px;
	background: var(--surface);
	border-radius: var(--radius);
	box-shadow: var(--shadow);
	padding: 32px;
}
.auth-logo {
	display: flex;
	/* Baseline, not centre — centring measures the line box, whose middle sits
	   above the middle of a lowercase word because the font's ascent dwarfs its
	   descent. On the baseline the dot lands in the x-height band instead, which
	   holds across the whole fallback stack. */
	align-items: baseline;
	gap: 8px;
	font-weight: 800;
	font-size: 20px;
	color: var(--accent-dark);
	margin-bottom: 24px;
}
.auth-logo .dot { width: 10px; height: 10px; border-radius: 50%; background: var(--accent); }
.auth-card h1 { font-size: 20px; }
.auth-card .subtitle { font-size: 13px; color: var(--text-muted); margin-bottom: 20px; }
.auth-foot { margin-top: 20px; font-size: 13px; color: var(--text-muted); text-align: center; }
.altcha-wrap { margin-bottom: 16px; }
.code-input {
	height: 52px;
	font-size: 24px;
	text-indent: 8px;
	letter-spacing: 8px;
	text-align: center;
	font-weight: 700;
}

/* --- app shell --- */

.app-shell {
	/* vh is the viewport at its tallest, which on a phone is the viewport
	   with the address bar scrolled away — sizing by it puts the bottom of
	   the shell under that bar. dvh is the part actually on screen. */
	height: 100vh;
	height: 100dvh;
	display: flex;
	flex-direction: column;
}
.topbar {
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0 20px;
	height: 56px;
	background: var(--surface);
	border-bottom: 1px solid var(--border);
	flex-shrink: 0;
}
.topbar-logo {
	display: flex;
	/* Baseline, for the reason spelled out on .auth-logo above. */
	align-items: baseline;
	gap: 8px;
	font-weight: 800;
	font-size: 17px;
	color: var(--accent-dark);
	text-decoration: none;
}
.topbar-logo .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); }
.topbar-nav { display: flex; align-items: center; gap: 4px; }
.topbar-nav a {
	color: var(--text-muted);
	text-decoration: none;
	font-size: 14px;
	font-weight: 600;
	padding: 8px 12px;
	border-radius: var(--radius-sm);
}
.topbar-nav a:hover { background: var(--surface-2); color: var(--text); }
.topbar-nav a.active { background: var(--accent-soft); color: var(--accent-dark); }
/* Separates navigation from the actions that follow it. */
.topbar-divider {
	width: 1px;
	height: 22px;
	background: var(--border);
	margin: 0 8px;
	flex-shrink: 0;
}

.sr-only {
	position: absolute;
	width: 1px;
	height: 1px;
	padding: 0;
	margin: -1px;
	overflow: hidden;
	clip: rect(0, 0, 0, 0);
	white-space: nowrap;
	border: 0;
}

.app-body {
	flex: 1;
	display: flex;
	min-height: 0;
}

#map { flex: 1; min-width: 0; background: #e9edec; }

.sidebar {
	width: 360px;
	flex-shrink: 0;
	background: var(--surface);
	border-left: 1px solid var(--border);
	display: flex;
	flex-direction: column;
	min-height: 0;
}
.sidebar-header { padding: 16px; border-bottom: 1px solid var(--border); }
.sidebar-header .hint { font-size: 12px; color: var(--text-muted); margin-top: 10px; }
.filters { display: flex; gap: 8px; margin-top: 10px; }
.filters select { flex: 1 1 0; min-width: 0; }

.asset-list { flex: 1; overflow-y: auto; }
.asset-row {
	display: flex;
	align-items: center;
	gap: 10px;
	padding: 12px 16px;
	border-bottom: 1px solid var(--border);
	cursor: pointer;
}
.asset-row:hover { background: var(--surface-2); }
.asset-row.selected { background: var(--accent-soft); }
.asset-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
/* The text column has to be allowed to shrink, or a long address pushes the
   status badge past the edge of the list instead of being cut short. */
.asset-row > div { min-width: 0; }
.asset-row .name { font-weight: 600; font-size: 14px; }
.asset-row .meta {
	font-size: 12px;
	color: var(--text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.status-badge {
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	padding: 2px 8px;
	border-radius: 999px;
	margin-left: auto;
}
.status-active { background: #e6f5ec; color: var(--success); }
.status-maintenance { background: #fdf3e0; color: #b3740c; }
.status-retired { background: var(--surface-2); color: var(--text-muted); }

.empty-state { padding: 32px 20px; text-align: center; color: var(--text-muted); font-size: 13px; }

/* --- side panel (add/edit asset) --- */

.panel-overlay {
	position: fixed;
	inset: 0;
	background: rgba(20, 30, 28, 0.25);
	display: none;
	z-index: 50;
}
.panel-overlay.open { display: block; }
.side-panel {
	position: fixed;
	top: 0;
	bottom: 0;
	right: -420px;
	width: 400px;
	max-width: 92vw;
	/* top+bottom (not height:100%) so this stays pinned to the real visible
	   viewport on mobile browsers, where height:100% on a fixed element can
	   overshoot the dynamic address-bar area and push the footer off-screen. */
	background: var(--surface);
	box-shadow: -8px 0 30px rgba(16, 24, 32, 0.12);
	transition: right 0.2s ease;
	z-index: 51;
	display: flex;
	flex-direction: column;
}
.side-panel.open { right: 0; }
.panel-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	padding: 16px 20px;
	border-bottom: 1px solid var(--border);
	flex-shrink: 0;
}
.panel-header h2 { margin: 0; font-size: 16px; }
/* A bare glyph is a poor target, so give it the same footprint as the other
   small controls on the panel. */
.panel-close {
	display: flex;
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 30px;
	height: 30px;
	margin-right: -6px;
	border: none;
	border-radius: var(--radius-sm);
	background: none;
	color: var(--text-muted);
	font-size: 22px;
	line-height: 1;
	cursor: pointer;
	transition: background 0.12s ease, color 0.12s ease;
}
.panel-close:hover { background: var(--surface-2); color: var(--text); }
/* The <form> sits between .side-panel and .panel-body/.panel-footer, so it has
   to be a flex container too — otherwise .panel-body's flex:1 + overflow have
   no bounded height to work against and the footer buttons fall off-screen. */
.side-panel form {
	display: flex;
	flex-direction: column;
	flex: 1;
	min-height: 0;
}
.panel-body { padding: 20px; overflow-y: auto; flex: 1; min-height: 0; }
.panel-footer {
	padding: 16px 20px;
	border-top: 1px solid var(--border);
	display: flex;
	gap: 10px;
	flex-wrap: wrap;
	flex-shrink: 0;
}
/* Save and Cancel lead; Delete sits apart at the far end. */
.panel-footer .btn-danger { margin-left: auto; }

/* A titled section of related controls inside the panel: the location, the
   configured asset fields, the one-off fields. A rule and the heading divide
   them rather than a box, so the controls all run to the same panel width.
   The head carries the section's single action on the heading's own line, so
   that action keeps one fixed spot instead of moving with what's below it. */
.field-group {
	/* Column gap rather than margins on the children: a field hidden because
	   it isn't in scope for the chosen category then takes no space at all,
	   so the spacing stays even whichever fields are showing. */
	display: flex;
	flex-direction: column;
	/* 16px between fields, here and outside a section alike; the heading then
	   reads as further from its fields than they are from each other, and the
	   rule plus its 24/20 margins separates one section from the next. */
	gap: 16px;
	margin: 24px 0 0;
	padding-top: 20px;
	border-top: 1px solid var(--border);
}
.field-group > .field, .field-group > .field-hint { margin: 0; }
.field-group-head {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
	/* Matches the small controls that sit in it, so a head is the same height
	   whether it holds a button, a link or nothing at all — and the Location
	   heading doesn't jump when its Navigate link appears. */
	min-height: var(--control-h-sm);
}
.field-group-title {
	margin: 0;
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--text-muted);
}

/* Opens the coordinates the panel currently holds in Google Maps. */
.directions-link {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	flex-shrink: 0;
	height: var(--control-h-sm);
	padding: 0 12px;
	border-radius: 999px;
	background: var(--accent-soft);
	color: var(--accent-dark);
	font-size: 12px;
	font-weight: 600;
	line-height: 1;
	white-space: nowrap;
	text-decoration: none;
	transition: background 0.12s ease, color 0.12s ease;
}
.directions-link:hover { background: var(--accent); color: #ffffff; }
.directions-link svg { flex-shrink: 0; }

.meta-rows { display: flex; flex-direction: column; gap: 8px; }
.meta-rows:empty { display: none; }
.meta-row { display: flex; gap: 8px; align-items: center; }
.meta-row input { flex: 1; min-width: 0; }
.meta-row .btn { flex-shrink: 0; }
/* The explanatory line only earns its place while the group is still empty. */
.meta-rows:not(:empty) + .field-hint { display: none; }

.maplibregl-popup-content { font-family: var(--font); font-size: 13px; padding: 8px 10px; }
.maplibregl-ctrl-attrib { font-size: 11px; }

/* --- settings --- */

.settings-shell {
	flex: 1;
	min-height: 0;
	display: flex;
	background: var(--bg);
}

.settings-nav {
	width: 252px;
	flex-shrink: 0;
	border-right: 1px solid var(--border);
	background: var(--surface);
	padding: 24px 16px;
	overflow-y: auto;
}
.settings-nav-label {
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.07em;
	color: var(--text-muted);
	padding: 0 12px;
	margin-bottom: 12px;
}
.settings-nav a {
	display: block;
	padding: 11px 14px;
	border-radius: var(--radius-sm);
	text-decoration: none;
	color: var(--text);
	margin-bottom: 4px;
	transition: background 0.12s ease;
}
.settings-nav a:hover { background: var(--surface-2); }
.settings-nav a.active { background: var(--accent-soft); }
.settings-nav-title { display: block; font-size: 14px; font-weight: 600; line-height: 1.3; }
.settings-nav-sub {
	display: block;
	font-size: 12px;
	color: var(--text-muted);
	margin-top: 3px;
	line-height: 1.4;
}
.settings-nav a.active .settings-nav-title { color: var(--accent-dark); }
.settings-nav a.active .settings-nav-sub { color: var(--accent-dark); opacity: 0.75; }

.settings-content {
	flex: 1;
	min-width: 0;
	overflow-y: auto;
	padding: 32px 36px 64px;
}
/* The readable width is capped on the children, not on this box. This is the
   element that scrolls, so a max-width here would put its scrollbar wherever
   that cap ended — stranded in the middle of a wide window instead of against
   its edge. 1048px is the measure that cap used to produce: 1120px of
   border-box minus the 36px gutters on either side. */
.settings-content > * { max-width: 1048px; }
.section-head { max-width: 760px; margin-bottom: 24px; }
.section-head h1 { font-size: 20px; margin-bottom: 6px; }
.section-head p { margin: 0; font-size: 13px; line-height: 1.6; color: var(--text-muted); }

.panel {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	margin-bottom: 20px;
	/* Deliberately not overflow:hidden — the category dropdown is absolutely
	   positioned and would be clipped by it. Children round their own corners. */
}
.panel-title-row {
	padding: 14px 20px;
	border-bottom: 1px solid var(--border);
	background: var(--surface-2);
	border-radius: var(--radius) var(--radius) 0 0;
}
.record:last-child .record-edit { border-radius: 0 0 var(--radius) var(--radius); }
.panel-title-row h2 {
	margin: 0;
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--text-muted);
}

.record-list { list-style: none; margin: 0; padding: 0; }
.record { border-bottom: 1px solid var(--border); }
.record:last-child { border-bottom: none; }
.record-view { display: flex; align-items: center; gap: 12px; padding: 14px 20px; }
.record-main { flex: 1; min-width: 0; }
.record-title { font-size: 14px; font-weight: 600; }
.record-sub {
	font-size: 12px;
	color: var(--text-muted);
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	margin-top: 2px;
}
.record-empty { padding: 20px; font-size: 13px; color: var(--text-muted); }

.record-edit {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
	align-items: flex-end;
	padding: 16px 20px;
	background: var(--surface-2);
	border-left: 2px solid var(--accent);
}
.record-edit .field { margin-bottom: 0; flex: 1; min-width: 150px; }
.record-edit-actions { display: flex; gap: 8px; }
.record-note {
	flex-basis: 100%;
	margin: 0;
	font-size: 12px;
	color: var(--text-muted);
}
.record-note code {
	font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: 4px;
	padding: 1px 5px;
}

.record-form {
	display: flex;
	gap: 12px;
	align-items: flex-end;
	flex-wrap: wrap;
	padding: 18px 20px;
}
.record-form .field { margin-bottom: 0; flex: 1; min-width: 160px; }
.record-form .field-type, .record-edit .field-type { flex: 0 0 140px; min-width: 140px; }
.record-form .field-color, .record-edit .field-color { flex: 0 0 64px; min-width: 64px; }

/* Keep every control on these rows the same height so labels and the submit
   button line up on one baseline. */
.record-form input, .record-form select, .record-form .btn,
.record-edit input, .record-edit select, .record-edit .btn {
	height: var(--control-h);
}
.record-form .btn, .record-edit .btn { flex-shrink: 0; padding: 0 16px; font-size: 14px; }

/* Choices: chip entry, one hidden input per chip (no comma parsing). */
/* Both of these are wide controls — side by side when there's room, wrapping
   onto their own line when there isn't. */
.field-choices, .field-scope { flex: 1 1 320px; }

/* The two field forms are the exception: their chip controls grow a line
   taller with every choice or category added, and on a shared flex row that
   left the four controls squeezed thin and — bottom-aligned — sitting at four
   different heights. A grid instead: the two short inputs share the first
   row, each chip control gets the full width on a row of its own, and every
   control starts at the same top edge however tall it grows. */
.field-form { display: grid; grid-template-columns: minmax(0, 1fr) 140px; align-items: start; gap: 14px 12px; }
.field-form .field { min-width: 0; }
.field-form .field-choices,
.field-form .field-scope,
.field-form .record-edit-actions,
.field-form .record-note,
.field-form > .btn { grid-column: 1 / -1; }
.field-form > .btn { justify-self: start; }
.chips {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
	min-height: var(--control-h);
	padding: 5px 8px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface);
}
.chips:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.chip {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	background: var(--accent-soft);
	color: var(--accent-dark);
	border-radius: 999px;
	padding: 3px 4px 3px 10px;
	font-size: 13px;
	font-weight: 600;
}
.chip-remove {
	border: none;
	background: transparent;
	color: var(--accent-dark);
	cursor: pointer;
	font-size: 15px;
	line-height: 1;
	padding: 0 4px;
	border-radius: 999px;
	opacity: 0.65;
}
.chip-remove:hover { opacity: 1; background: rgba(11, 125, 112, 0.12); }
.chips .chip-input {
	flex: 1;
	min-width: 140px;
	border: none;
	outline: none;
	box-shadow: none;
	padding: 4px 2px;
	height: auto;
	background: transparent;
}
.chips .chip-input:focus { border: none; box-shadow: none; }

/* Which categories a field applies to */
.multiselect { position: relative; }
.multiselect .chips { cursor: text; }
.ms-menu {
	position: absolute;
	z-index: 20;
	top: calc(100% + 4px);
	left: 0;
	right: 0;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	box-shadow: var(--shadow);
	max-height: 230px;
	overflow-y: auto;
	padding: 4px;
}
.ms-option {
	display: flex;
	align-items: center;
	gap: 9px;
	width: 100%;
	text-align: left;
	background: none;
	border: none;
	padding: 8px 10px;
	border-radius: 6px;
	cursor: pointer;
	font-family: var(--font);
	font-size: 13px;
	font-weight: 500;
	color: var(--text);
	height: auto;
}
.ms-option:hover, .ms-option.active { background: var(--surface-2); }
.ms-empty { padding: 10px; font-size: 12px; color: var(--text-muted); }
.chip.chip-all { background: var(--surface-2); color: var(--text-muted); }
.chip.chip-all .chip-remove { color: var(--text-muted); }
.ms-option-all { font-weight: 600; border-bottom: 1px solid var(--border); border-radius: 6px 6px 0 0; }

.scope-tag {
	font-size: 11px;
	font-weight: 600;
	color: var(--text-muted);
	background: var(--surface-2);
	border-radius: 999px;
	padding: 2px 8px;
	margin-left: 8px;
	vertical-align: 1px;
}
.scope-tag.scope-all { background: transparent; color: var(--text-muted); padding-left: 0; }
.scope-tag.verified { background: #e6f5ec; color: var(--success); }
.scope-tag.unverified { background: var(--danger-soft); color: var(--danger); }

/* Account details */
.detail-list { margin: 0; }
.detail-row {
	display: flex;
	gap: 20px;
	align-items: baseline;
	padding: 14px 20px;
	border-bottom: 1px solid var(--border);
}
.detail-row:last-child { border-bottom: none; }
.detail-row dt {
	flex: 0 0 140px;
	margin: 0;
	font-size: 13px;
	font-weight: 600;
	color: var(--text-muted);
}
.detail-row dd { margin: 0; font-size: 14px; }

/* A form that stacks instead of running along one row */
.record-form.stacked { flex-direction: column; align-items: stretch; max-width: 420px; }
.record-form.stacked .field { flex: none; width: 100%; }
.record-form.stacked .btn { align-self: flex-start; margin-top: 4px; }

.swatch { width: 14px; height: 14px; border-radius: 4px; flex-shrink: 0; }
.type-badge {
	font-size: 11px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	padding: 3px 8px;
	border-radius: 6px;
	background: var(--surface-2);
	color: var(--text-muted);
	flex-shrink: 0;
	min-width: 64px;
	text-align: center;
}
.type-text { background: var(--accent-soft); color: var(--accent-dark); }
.type-number { background: #eaf0fd; color: #2f6fed; }
.type-date { background: #fdf3e0; color: #b3740c; }
.type-select { background: #f1ebfd; color: #6d43c8; }

.btn.btn-ghost { background: transparent; color: var(--text-muted); padding: 0 10px; box-shadow: none; }
.btn.btn-ghost:hover { background: var(--danger-soft); color: var(--danger); }

/* Settings page also holds the account card */
.settings-page { max-width: 640px; margin: 32px auto; padding: 0 20px; }
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 24px; margin-bottom: 20px; }

/* --- assets table page --- */

.assets-page {
	flex: 1;
	min-height: 0;
	display: flex;
	flex-direction: column;
	padding: 20px 24px;
	overflow: hidden;
}
.assets-toolbar {
	display: flex;
	align-items: center;
	gap: 8px;
	margin-bottom: 12px;
	flex-wrap: wrap;
}
/* The search box takes the slack; the rest keep a fixed share of the row so
   every control in the toolbar stands at the same height and edge. */
.assets-toolbar input[type="search"] { flex: 1 1 200px; max-width: 300px; }
.assets-toolbar select { flex: 0 1 180px; }
.assets-count { font-size: 13px; color: var(--text-muted); white-space: nowrap; margin-left: auto; }

/* Badge on the "More filters" button, counting the active field rules. */
.filter-count {
	display: inline-block;
	min-width: 18px;
	padding: 0 5px;
	border-radius: 999px;
	background: var(--accent);
	color: #ffffff;
	font-size: 11px;
	font-weight: 700;
	line-height: 18px;
	text-align: center;
}

/* --- bulk selection (assets page) --- */

/* Only on screen while something is ticked, so it never takes a row of the
   page from the table it belongs to. */
.bulk-bar {
	display: flex;
	align-items: center;
	gap: 12px;
	flex-wrap: wrap;
	margin-bottom: 12px;
	padding: 10px 14px;
	border: 1px solid var(--accent);
	border-radius: var(--radius);
	background: var(--accent-soft);
}
.bulk-count { font-size: 13px; font-weight: 600; color: var(--accent-dark); }
/* How much of the selection the current filter is hiding. Present only when
   that number isn't zero, so the bar reads plainly the rest of the time. */
.bulk-hidden {
	font-size: 12px;
	color: var(--text-muted);
	padding-left: 12px;
	border-left: 1px solid var(--border-strong);
}
.bulk-bar .btn-link { margin-left: auto; }
.bulk-summary { margin: 0 0 16px; font-size: 13px; color: var(--text-muted); }

/* The tick box column. Fixed and narrow: it holds one control at one size. */
.assets-table th.col-select, .assets-table td.col-select {
	width: 40px;
	padding-left: 14px;
	padding-right: 0;
	cursor: pointer;
}
.assets-table th.col-select:hover { color: inherit; }
.col-select input[type="checkbox"] {
	width: 16px;
	height: 16px;
	margin: 0;
	display: block;
	accent-color: var(--accent);
	cursor: pointer;
}

/* --- field-level filter rules (assets page) --- */

/* The panel drops out of the toolbar, so it reads as one surface with the
   button that opened it: same rounding, a shadow to lift it off the table
   behind, and a short slide to say where it came from. */
.filter-panel {
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	box-shadow: var(--shadow);
	padding: 14px 16px 16px;
	margin-bottom: 12px;
	animation: filter-panel-in 0.14s ease-out;
}
@keyframes filter-panel-in {
	from { opacity: 0; transform: translateY(-4px); }
}
@media (prefers-reduced-motion: reduce) {
	.filter-panel { animation: none; }
}

.filter-panel-head {
	display: flex;
	align-items: baseline;
	gap: 16px;
	margin-bottom: 12px;
	padding-bottom: 10px;
	border-bottom: 1px solid var(--border);
}
.filter-panel-title {
	font-size: 12px;
	font-weight: 700;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	color: var(--text-muted);
}
/* The hint explains what the sentence below can't: that these rules narrow
   what the toolbar has already filtered. It is the first thing to go when
   the row gets tight. */
.filter-panel-head .field-hint { margin: 0 0 0 auto; text-align: right; }

.filter-rules { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.filter-rules:empty { margin-bottom: 0; }

/* Each rule is a card, so a stack of them reads as a list of conditions
   rather than a field of loose selects. */
.filter-rule {
	display: flex;
	gap: 8px;
	align-items: center;
	flex-wrap: wrap;
	padding: 8px 10px;
	border-radius: var(--radius-sm);
	/* A rule with nothing filled in yet filters nothing, and looks the part:
	   an outline waiting to be completed. Finishing it fills the card in. */
	border: 1px dashed var(--border);
	background: transparent;
	transition: background 0.12s ease, border-color 0.12s ease;
}
.filter-rule.is-active {
	border-style: solid;
	background: var(--surface-2);
}
.filter-rule:hover { border-color: var(--border-strong); }

/* "Where … and … and …", so a stack of rules reads as one sentence. The
   fixed width also lines the first control of every row up with the next. */
.filter-rule::before {
	content: "and";
	flex: 0 0 38px;
	font-size: 12px;
	line-height: var(--control-h-sm);
	color: var(--text-muted);
	text-align: right;
}
.filter-rule:first-child::before { content: "Where"; font-weight: 600; }

/* Rules are a compact row: smaller than the toolbar above them, but every
   control within the row on one height. */
.filter-rule select, .filter-rule input, .filter-rule .btn {
	height: var(--control-h-sm);
	font-size: 13px;
}
.filter-rule select { padding-right: 30px; background-position: right 9px center; }
.filter-rule .rule-target { flex: 0 1 190px; }
.filter-rule .rule-op { flex: 0 1 170px; }
/* The value control is swapped out as the field and operator change, so the
   slot owns the sizing rather than whatever input happens to be inside it. */
.filter-rule .rule-value { flex: 1 1 180px; min-width: 0; display: flex; }
.filter-rule .rule-value:empty { display: none; }
.filter-rule .rule-value > * { min-width: 0; }

/* Removing a rule is a corner action: quiet until it is aimed at, and red
   only then — it sits one tab stop away from the value being typed. */
.filter-rule .rule-remove {
	flex-shrink: 0;
	margin-left: auto;
	background: transparent;
	color: var(--text-muted);
	box-shadow: none;
}
.filter-rule .rule-remove:hover { background: var(--danger-soft); color: var(--danger); }

/* Reads as the next, empty row of the list rather than as a button parked
   under it — and lines up past the "Where" gutter. */
.filter-add {
	width: 100%;
	max-width: 300px;
	margin-left: 46px;
	justify-content: center;
	background: transparent;
	border: 1px dashed var(--border);
	box-shadow: none;
	color: var(--text-muted);
	transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.filter-add:hover {
	background: var(--accent-soft);
	border-color: var(--accent);
	color: var(--accent-dark);
}

/* --- toolbar menus: saved views & the column picker --- */

/* Both menus are a button with a panel hanging under it, inside the toolbar
   row, so the panel has to be positioned against the button rather than the
   page. */
.toolbar-menu { position: relative; display: inline-flex; }
.menu-panel {
	position: absolute;
	top: calc(100% + 6px);
	left: 0;
	z-index: 30;
	min-width: 260px;
	max-width: calc(100vw - 32px);
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
	box-shadow: var(--shadow);
	padding: 8px;
}
.menu-panel-wide { min-width: 320px; }
.menu-list { max-height: 320px; overflow-y: auto; }
.menu-empty { margin: 6px 8px 10px; font-size: 13px; color: var(--text-muted); }

.menu-row { display: flex; align-items: center; gap: 4px; }
/* The name takes the row; the manage buttons only appear for a view this
   account may change, so the row has to survive both widths. */
.menu-item {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 8px;
	border: none;
	background: transparent;
	border-radius: var(--radius-sm);
	padding: 8px 10px;
	font: inherit;
	font-size: 14px;
	color: var(--text);
	text-align: left;
	cursor: pointer;
}
.menu-item:hover { background: var(--surface-2); }
.menu-row.is-active .menu-item { background: var(--accent-soft); color: var(--accent-dark); font-weight: 600; }
.menu-item-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-tag {
	flex-shrink: 0;
	margin-left: auto;
	font-size: 11px;
	font-weight: 600;
	color: var(--text-muted);
	background: var(--surface-2);
	border-radius: 999px;
	padding: 1px 8px;
	max-width: 140px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.menu-row.is-active .menu-tag { background: var(--surface); }
/* Rename and delete stay out of the way until the row is being used. Faded
   rather than hidden: a `visibility: hidden` button can't take focus, which
   would put them out of reach of the keyboard entirely. */
.menu-row .btn-icon { flex-shrink: 0; opacity: 0; transition: opacity 0.12s ease; }
.menu-row:hover .btn-icon,
.menu-row:focus-within .btn-icon,
.menu-row .btn-icon:focus { opacity: 1; }
.menu-delete:hover { background: var(--danger-soft); color: var(--danger); }

.menu-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 8px;
	border-top: 1px solid var(--border);
	margin-top: 8px;
	padding-top: 8px;
}
.menu-actions .field-hint { margin: 0; flex: 1 1 160px; }

/* Marks a view whose filters or columns have been changed since it was
   saved — the "Update view" action is what settles it. */
.view-dirty { color: var(--accent); font-size: 18px; line-height: 1; }
#views-toggle.has-view { color: var(--accent-dark); }

.column-list { max-height: 340px; overflow-y: auto; }
.column-row { display: flex; align-items: center; gap: 8px; padding: 2px 4px; }
/* A row's label wraps the checkbox, so the whole name is a hit target. The
   base label rule's block layout and bottom margin are both in the way. */
.column-row label {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 0;
	font-size: 14px;
	font-weight: 500;
	cursor: pointer;
}
.column-row label > span:first-of-type { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.column-row input[type="checkbox"] { flex-shrink: 0; width: 15px; height: 15px; accent-color: var(--accent); }
/* Chosen columns sit at the top of the list in the order they are drawn, so
   they get the weight that says "this is the table". */
.column-row.is-on label { font-weight: 600; }
.column-tag {
	flex-shrink: 0;
	font-size: 11px;
	color: var(--text-muted);
	background: var(--surface-2);
	border-radius: 999px;
	padding: 1px 8px;
}
.column-moves { display: flex; gap: 4px; flex-shrink: 0; }
.column-moves .btn { width: 26px; height: 26px; font-size: 12px; }
.column-moves .btn:disabled { opacity: 0.3; cursor: default; }

/* Coordinates and dates read as one token; wrapping them helps nobody. */
.assets-table td.nowrap { white-space: nowrap; }

/* --- modal dialogs (confirm / alert) --- */

.modal-backdrop {
	position: fixed;
	inset: 0;
	background: rgba(20, 30, 28, 0.35);
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
	z-index: 100;
}
body.modal-open { overflow: hidden; }
.modal {
	width: 100%;
	max-width: 400px;
	background: var(--surface);
	border-radius: var(--radius);
	box-shadow: 0 12px 44px rgba(16, 24, 32, 0.24);
	padding: 24px;
	animation: modal-in 0.14s ease-out;
}
@keyframes modal-in {
	from { opacity: 0; transform: translateY(8px) scale(0.98); }
}
@media (prefers-reduced-motion: reduce) {
	.modal { animation: none; }
}
.modal-title { font-size: 16px; margin: 0 0 8px; }
.modal-message { margin: 0; font-size: 14px; color: var(--text-muted); }
.modal-actions { display: flex; justify-content: flex-end; gap: 10px; margin-top: 22px; }

/* The prompt variant: one text box, and optionally one checkbox under it. */
.modal-field { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
.modal-field > label { margin: 0; font-size: 13px; font-weight: 600; }
.modal-field input[type="text"] { width: 100%; }
.modal-check {
	display: flex;
	align-items: center;
	gap: 8px;
	margin: 4px 0 0;
	font-size: 13px;
	font-weight: 500;
	color: var(--text-muted);
	cursor: pointer;
}
.modal-check input { width: 15px; height: 15px; accent-color: var(--accent); }

.assets-table-wrap {
	flex: 1;
	min-height: 0;
	overflow: auto;
	background: var(--surface);
	border: 1px solid var(--border);
	border-radius: var(--radius);
}
.assets-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.assets-table th {
	position: sticky;
	top: 0;
	background: var(--surface-2);
	text-align: left;
	font-size: 12px;
	text-transform: uppercase;
	letter-spacing: 0.03em;
	color: var(--text-muted);
	padding: 10px 16px;
	cursor: pointer;
	white-space: nowrap;
	user-select: none;
	border-bottom: 1px solid var(--border);
}
.assets-table th:hover { color: var(--text); }
.assets-table th.sort-asc::after { content: " \25B2"; font-size: 10px; }
.assets-table th.sort-desc::after { content: " \25BC"; font-size: 10px; }
.assets-table td {
	padding: 12px 16px;
	border-bottom: 1px solid var(--border);
	vertical-align: middle;
}
.assets-table tbody tr { cursor: pointer; }
.assets-table tbody tr:hover { background: var(--surface-2); }
.assets-table tbody tr:last-child td { border-bottom: none; }
.assets-table td.truncate {
	max-width: 260px;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

/* "View on map" icon column */
.assets-table th.col-action, .assets-table td.col-action {
	width: 52px;
	text-align: center;
	padding-left: 6px;
	padding-right: 12px;
	cursor: default;
}
.row-action {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	width: 30px;
	height: 30px;
	border-radius: var(--radius-sm);
	color: var(--text-muted);
	text-decoration: none;
	transition: background 0.12s ease, color 0.12s ease;
}
.row-action:hover { background: var(--accent-soft); color: var(--accent-dark); }

/* --- organizations & admin --------------------------------------------- */

/* The organization the current session works in, next to the logo so it's
   always clear whose assets are on screen. */
.topbar-org {
	margin-left: 12px;
	padding: 3px 10px;
	border-radius: 999px;
	background: var(--surface-2);
	color: var(--text-muted);
	font-size: 12px;
	font-weight: 600;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
	max-width: 220px;
}
/* The logo and the org tag travel together at the left of the bar. */
.topbar > .topbar-org { margin-right: auto; }

/* --- demo instance ------------------------------------------------------ */

/* The demo runs the same binary against its own throwaway database, and each
   visitor works in a copy of their own, so the only thing telling any of it
   apart from the real dashboard on screen is this tag. */
.topbar-demo {
	margin-left: 12px;
	padding: 3px 10px;
	border-radius: 999px;
	background: var(--accent-soft);
	color: var(--accent-dark);
	font-size: 12px;
	font-weight: 700;
	white-space: nowrap;
}
/* Two spellings of the same tag: the phone bar has no room for the long one. */
.topbar-demo-short { display: none; }

/* The credentials on the demo's login page — there is no email to send them
   in, so the page is where they live. */
.demo-note {
	margin: 0 0 20px;
	padding: 14px 16px;
	border: 1px solid var(--border);
	border-radius: var(--radius-sm);
	background: var(--surface-2);
	font-size: 14px;
}
.demo-note p { margin: 0; }
.demo-note .muted { margin-top: 10px; font-size: 13px; }
.demo-creds {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: 4px 12px;
	margin: 10px 0 0;
}
.demo-creds dt { color: var(--text-muted); }
.demo-creds dd {
	margin: 0;
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	/* The password is copied by hand more often than it is typed. */
	user-select: all;
}

/* "12 / 500 assets" — a usage figure against an organization's limit. */
.usage {
	display: inline-block;
	margin-right: 12px;
	font-variant-numeric: tabular-nums;
}
.usage-full { color: var(--danger); font-weight: 600; }

/* Limit inputs are narrow: they hold a number, not a sentence. */
.record-form .field-limit, .record-edit .field-limit { flex: 0 0 120px; min-width: 120px; }

/* A closing note under a panel's form, explaining what the action will do. */
.panel-note {
	margin: 0;
	padding: 0 20px 16px;
	font-size: 12px;
	line-height: 1.6;
	color: var(--text-muted);
}

/* Invite state badges, sharing .type-badge's shape. */
.badge-warn, .badge-expired { background: var(--danger-soft); color: var(--danger); }
.badge-pending { background: #fdf3e0; color: #b3740c; }

/* Role badges, sharing .type-badge's shape. */
.badge-admin { background: var(--accent-soft); color: var(--accent-dark); }
.badge-member { background: var(--surface-2); color: var(--text-muted); }
.badge-superadmin { background: #f1ebfd; color: #6d43c8; }

/* The role picker holds two fixed choices, not free text. */
.record-form .field-role, .record-edit .field-role { flex: 0 0 160px; min-width: 160px; }

/* A locked asset panel: the values still read normally, but every control
   that could change one is inert. */
.panel-read-only input[readonly], .panel-read-only textarea[readonly],
.panel-read-only select[disabled] {
	background: var(--surface-2);
	color: var(--text);
	cursor: default;
}
.panel-read-only select[disabled] { opacity: 1; }

/* --- audit log ---------------------------------------------------------- */

/* One badge per group of activity, sharing .type-badge's shape. */
.badge-account { background: var(--surface-2); color: var(--text-muted); }
.badge-assets { background: var(--accent-soft); color: var(--accent-dark); }
.badge-settings { background: #fdf3e0; color: #b3740c; }
.badge-people { background: #eaf0fd; color: #2f6fed; }

/* What happened reads first; what it happened to follows on the same line in
   lighter type, because it is usually a name, an address or an email. */
.audit-summary { font-weight: 400; color: var(--text-muted); }
/* A wrong password or code: still an ordinary row, but it should catch the eye
   of someone scanning for them. */
.audit-failed { color: var(--danger); }

/* The search box takes the filter row; the activity picker holds four fixed
   choices and doesn't need to grow with it. */
.record-form .field-audit-group { flex: 0 0 200px; min-width: 200px; }

/* Paging sits under the list: the count anchors left, the two directions
   right, and either of them can be missing at an end of the trail. */
.audit-pager {
	display: flex;
	align-items: center;
	justify-content: flex-end;
	gap: 12px;
	padding: 14px 20px;
	border-top: 1px solid var(--border);
}
.audit-page-count { margin-right: auto; font-size: 12px; color: var(--text-muted); }

/* --- mobile chrome ------------------------------------------------------ */

/* Three pieces of furniture that only exist under the phone breakpoint: the
   drawer behind the top bar's menu button, the grab bar that turns the map's
   asset list into a sheet, and the plain list the assets table becomes. They
   are laid out here and switched on at the end of this file, so everything
   about the phone layout stays in one place. */
.nav-toggle, .nav-scrim, .mobile-nav, .sheet-handle, .assets-cards { display: none; }

.nav-toggle {
	align-items: center;
	justify-content: center;
	flex-shrink: 0;
	width: 40px;
	height: 40px;
	margin-right: -6px;
	border: none;
	border-radius: var(--radius-sm);
	background: none;
	color: var(--text);
	cursor: pointer;
}
.nav-toggle:hover { background: var(--surface-2); }
.nav-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.mobile-nav-head {
	display: flex;
	align-items: flex-start;
	gap: 12px;
	padding: 16px 16px 14px 18px;
	padding-top: calc(16px + env(safe-area-inset-top));
	border-bottom: 1px solid var(--border);
	flex-shrink: 0;
}
.mobile-nav-who { flex: 1; min-width: 0; }
.mobile-nav-org, .mobile-nav-user { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mobile-nav-org { font-size: 15px; font-weight: 700; }
.mobile-nav-user { font-size: 12px; color: var(--text-muted); margin-top: 2px; }

.mobile-nav-links {
	flex: 1;
	min-height: 0;
	overflow-y: auto;
	display: flex;
	flex-direction: column;
	gap: 2px;
	padding: 12px;
}
.mobile-nav-links a {
	display: flex;
	align-items: center;
	gap: 12px;
	padding: 13px 14px;
	border-radius: var(--radius-sm);
	color: var(--text);
	text-decoration: none;
	font-size: 15px;
	font-weight: 600;
}
.mobile-nav-links a svg { flex-shrink: 0; color: var(--text-muted); }
.mobile-nav-links a.active { background: var(--accent-soft); color: var(--accent-dark); }
.mobile-nav-links a.active svg { color: var(--accent-dark); }

.mobile-nav-foot {
	flex-shrink: 0;
	border-top: 1px solid var(--border);
	padding: 14px 18px;
	padding-bottom: calc(14px + env(safe-area-inset-bottom));
}

/* The grab bar on the map's sheet. It is the whole of the sheet while the
   sheet is down, so it also carries the count — otherwise a lowered sheet
   would say nothing about what raising it would show. */
.sheet-handle {
	width: 100%;
	flex-shrink: 0;
	padding: 8px 16px 10px;
	border: none;
	border-radius: var(--radius) var(--radius) 0 0;
	background: var(--surface);
	color: var(--text);
	font-family: var(--font);
	cursor: pointer;
	/* The sheet is dragged by this bar, so the browser must not claim the
	   vertical gesture for scrolling first. */
	touch-action: none;
}
.sheet-grip {
	display: block;
	width: 38px;
	height: 4px;
	margin: 0 auto 8px;
	border-radius: 999px;
	background: var(--border-strong);
}
.sheet-handle-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.sheet-count { font-size: 14px; font-weight: 600; }
.sheet-handle .chev { color: var(--text-muted); transition: transform 0.18s ease; }
.sidebar[data-sheet="full"] .sheet-handle .chev { transform: rotate(180deg); }

/* One asset in the phone list. The same three facts as a row in the map's
   sidebar, because they are the ones that identify an asset that has no name
   of its own: what it is, where it is, what state it is in. */
.asset-card {
	display: flex;
	align-items: center;
	gap: 10px;
	width: 100%;
	padding: 12px 14px;
	border: 1px solid var(--border);
	border-radius: var(--radius);
	background: var(--surface);
	font-family: var(--font);
	color: var(--text);
	text-align: left;
	cursor: pointer;
}
.asset-card:active { background: var(--surface-2); }
.asset-card-main { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.asset-card .name { font-size: 14px; font-weight: 600; }
.asset-card .meta {
	font-size: 12px;
	color: var(--text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.asset-card .status-badge { margin-left: 0; flex-shrink: 0; }

/* Groups the assets toolbar's controls without changing the row they make on
   a wide screen; the phone layout below turns it into a real box. */
.toolbar-scroll { display: contents; }

/* --- responsive --------------------------------------------------------- */

/* Nothing above this line is written for a narrow screen; everything below it
   is. Three steps: a laptop that isn't wide, a tablet, and a phone. */

@media (max-width: 1024px) {
	/* The map is the point of the page — give the list only what it needs. */
	.sidebar { width: 300px; }
	.settings-nav { width: 208px; }
	.settings-content { padding: 28px 24px 56px; }
}

@media (max-width: 860px) {
	/* The settings sidebar becomes a strip of tabs above the content: at this
	   width a column of links costs more than the content it navigates. */
	.settings-shell { flex-direction: column; }
	.settings-nav {
		width: auto;
		display: flex;
		gap: 6px;
		overflow-x: auto;
		scrollbar-width: none;
		padding: 10px 12px;
		border-right: none;
		border-bottom: 1px solid var(--border);
	}
	.settings-nav::-webkit-scrollbar { display: none; }
	.settings-nav-label { display: none; }
	.settings-nav a { flex: 0 0 auto; margin: 0; padding: 8px 14px; white-space: nowrap; background: var(--surface-2); }
	/* The one-line description under each link has no room on a tab. */
	.settings-nav-sub { display: none; }

	.filter-rule > * { flex-basis: 100%; }
	.filter-rule .rule-remove { flex-basis: auto; margin-left: auto; }
	/* Stacked rules no longer form a sentence, so the connector and the
	   gutter it reserves only get in the way — the card's own outline is
	   what separates one rule from the next at this width. */
	.filter-rule::before { display: none; }
	.filter-add { margin-left: 0; max-width: none; }
	/* Title and hint stop sharing a line long before the rules do. */
	.filter-panel-head { flex-direction: column; gap: 4px; }
	.filter-panel-head .field-hint { margin: 0; text-align: left; }
	/* The toolbar wraps at this width, so a panel anchored to the left of its
	   button can hang off the page; anchoring right keeps it on screen. */
	.menu-panel { left: auto; right: 0; }
}

/* The phone layout. Kept to one breakpoint, matched by PHONE in nav.js. */
@media (max-width: 720px) {
	/* iOS zooms the whole page in when a control it focuses sets its text
	   below 16px, and never zooms back out. The control heights are fixed in
	   px, so the larger text costs no layout. */
	input[type="text"], input[type="email"], input[type="password"],
	input[type="number"], input[type="search"], input[type="tel"],
	input[type="url"], input[type="date"], select, textarea { font-size: 16px; }

	/* A flick that runs off the end of a list shouldn't turn into the
	   browser's pull-to-refresh on the page behind it. */
	.asset-list, .panel-body, .assets-table-wrap, .settings-content,
	.mobile-nav-links, .menu-panel { overscroll-behavior: contain; }

	/* --- top bar --- */

	.topbar {
		height: calc(54px + env(safe-area-inset-top));
		padding: env(safe-area-inset-top) max(10px, env(safe-area-inset-right)) 0 max(14px, env(safe-area-inset-left));
		gap: 8px;
	}
	.topbar-logo { font-size: 16px; }
	.topbar-org { margin-left: 8px; max-width: 32vw; font-size: 11px; padding: 3px 8px; }
	.topbar-demo { margin-left: 8px; font-size: 11px; padding: 3px 8px; }
	.topbar-demo-long { display: none; }
	.topbar-demo-short { display: inline; }
	/* The links, the rule between them and logging out all move to the
	   drawer; adding an asset is the one action that stays on the bar. */
	.topbar-nav > a, .topbar-divider, .topbar-logout { display: none; }
	.topbar-nav { gap: 6px; }
	.nav-toggle { display: inline-flex; }
	#new-asset-btn { width: var(--control-h-sm); padding: 0; font-size: 20px; font-weight: 400; }
	#new-asset-btn .btn-label { display: none; }

	/* --- the drawer --- */

	.mobile-nav {
		display: flex;
		flex-direction: column;
		position: fixed;
		top: 0;
		bottom: 0;
		right: 0;
		width: min(320px, 86vw);
		z-index: 71;
		background: var(--surface);
		box-shadow: -8px 0 30px rgba(16, 24, 32, 0.16);
		/* Off screen and out of the tab order until it is opened; visibility
		   rather than display so the slide still animates. */
		transform: translateX(100%);
		visibility: hidden;
		transition: transform 0.22s ease, visibility 0.22s;
	}
	body.nav-open .mobile-nav { transform: none; visibility: visible; }
	.nav-scrim {
		display: block;
		position: fixed;
		inset: 0;
		z-index: 70;
		background: rgba(20, 30, 28, 0.35);
		opacity: 0;
		pointer-events: none;
		transition: opacity 0.22s ease;
	}
	body.nav-open .nav-scrim { opacity: 1; pointer-events: auto; }

	/* --- map: the list becomes a sheet over a full-bleed map --- */

	.app-body { position: relative; }
	#map { position: absolute; inset: 0; height: auto; }
	.sidebar {
		position: absolute;
		left: 0;
		right: 0;
		bottom: 0;
		top: auto;
		z-index: 5;
		width: auto;
		border-left: none;
		border-top: 1px solid var(--border);
		border-radius: var(--radius) var(--radius) 0 0;
		box-shadow: 0 -6px 24px rgba(16, 24, 32, 0.14);
		padding-bottom: env(safe-area-inset-bottom);
	}
	.sheet-handle { display: block; }
	/* Down: the grab bar and nothing else, so the map is the page. Raised:
	   a fixed slice of the screen, because .asset-list is the only part that
	   can be given a height without measuring what's in it. */
	.sidebar[data-sheet="peek"] .sidebar-header,
	.sidebar[data-sheet="peek"] .asset-list { display: none; }
	.sidebar .asset-list { flex: 0 0 auto; height: 32dvh; }
	.sidebar[data-sheet="full"] .asset-list { height: 58dvh; }
	.sidebar-header { padding: 0 14px 14px; }
	.sidebar-header .hint { display: none; }
	/* Keep the attribution clear of the lowered sheet — it has to stay
	   readable, and it is the map's own credit, not ours to cover. */
	.maplibregl-ctrl-bottom-left, .maplibregl-ctrl-bottom-right {
		bottom: calc(var(--sheet-peek-h) + env(safe-area-inset-bottom));
	}

	/* --- assets page --- */

	.assets-page { padding: 12px 14px calc(12px + env(safe-area-inset-bottom)); }
	.assets-toolbar { gap: 8px; margin-bottom: 10px; }
	/* Two rows, always: the search box and the count share the first, and
	   everything else rides one scrollable line below it. The count sits
	   after the strip in the markup, so `order` is what pulls it up — a flex
	   line is filled in order, not in source order. */
	.assets-toolbar input[type="search"] { order: -2; flex: 1 1 auto; max-width: none; min-width: 0; }
	.assets-count { order: -1; flex: 0 0 auto; margin-left: 0; }
	.toolbar-scroll {
		display: flex;
		flex: 1 1 100%;
		gap: 8px;
		overflow-x: auto;
		scrollbar-width: none;
		padding-bottom: 2px;
	}
	.toolbar-scroll::-webkit-scrollbar { display: none; }
	.toolbar-scroll > * { flex: 0 0 auto; }
	/* A fixed width rather than the content's: a long category name would
	   otherwise stretch the select across most of the strip. */
	.assets-toolbar select { flex: 0 0 auto; width: 158px; }
	/* Which columns the table shows means nothing to the list below, which
	   always shows the same three things. */
	.toolbar-menu-columns { display: none; }
	/* A dropdown inside the scrolling line would be clipped by it, so both
	   menus stand off the page instead. */
	.menu-panel {
		position: fixed;
		left: 12px;
		right: 12px;
		top: auto;
		bottom: calc(12px + env(safe-area-inset-bottom));
		z-index: 60;
		min-width: 0;
		max-width: none;
		max-height: 70dvh;
		overflow-y: auto;
	}

	/* The table's columns are a desktop choice; a phone gets the list. */
	.assets-table { display: none; }
	.assets-cards { display: flex; flex-direction: column; gap: 8px; }
	.assets-table-wrap { border: none; border-radius: 0; background: transparent; }

	/* --- the asset panel --- */

	/* Full width, and slid by transform rather than by `right`, which has a
	   fixed offscreen offset that a full-width panel would outgrow. */
	.side-panel {
		width: 100%;
		max-width: 100%;
		right: 0;
		transform: translateX(100%);
		transition: transform 0.22s ease;
	}
	.side-panel.open { transform: none; }
	.panel-header { padding: 14px 16px; padding-top: calc(14px + env(safe-area-inset-top)); }
	.panel-body { padding: 16px; }
	.panel-footer {
		padding: 12px 16px;
		padding-bottom: calc(12px + env(safe-area-inset-bottom));
		gap: 8px;
	}
	/* Save and Cancel split the row; Delete is deliberate enough to earn its
	   own, away from the two a thumb is aiming at. */
	.panel-footer .btn { flex: 1 1 0; }
	.panel-footer .btn-danger { flex: 1 1 100%; margin-left: 0; order: 3; }

	/* --- settings & admin --- */

	.settings-content { padding: 16px 14px calc(48px + env(safe-area-inset-bottom)); }
	.section-head h1 { font-size: 18px; }
	.panel-title-row { padding: 12px 16px; }
	.record-view { flex-wrap: wrap; padding: 14px 16px; }
	/* The name takes the row and the actions drop under it, rather than three
	   things fighting over 340px. */
	.record-main { flex: 1 1 100%; }
	.record-edit, .record-form { padding: 14px 16px; }
	.record-form .field, .record-edit .field,
	.record-form .field-type, .record-edit .field-type,
	.record-form .field-role, .record-edit .field-role,
	.record-form .field-limit, .record-edit .field-limit { flex: 1 1 100%; min-width: 0; }
	.record-form .field-color, .record-edit .field-color { flex: 0 0 76px; min-width: 76px; }
	.record-form .btn, .record-edit .btn { flex: 0 0 auto; }
	/* Not enough width for label and type side by side. */
	.field-form { grid-template-columns: minmax(0, 1fr); }
	.detail-row { flex-direction: column; gap: 4px; padding: 12px 16px; }
	.detail-row dt { flex: none; }
	.panel-note { padding: 0 16px 14px; }
	.audit-pager { flex-wrap: wrap; padding: 14px 16px; }

	/* --- dialogs --- */

	.modal-backdrop { padding: 16px; }
	.modal { padding: 20px; }
	.modal-actions .btn { flex: 1 1 0; }
}

/* On a touch screen there is no hover, so anything that waits for one is
   simply missing — and every target has to be a fingertip wide. */
@media (pointer: coarse) {
	:root { --control-h: 44px; --control-h-sm: 38px; }
	.panel-close, .nav-toggle { width: 40px; height: 40px; }
	.row-action { width: 38px; height: 38px; }
	.column-moves .btn { width: 32px; height: 32px; }
	/* Renaming and deleting a saved view are hover-revealed on a desktop;
	   here that would put them permanently out of reach. */
	.menu-row .btn-icon { opacity: 1; }
}

@media (max-width: 480px) {
	.auth-page { padding: 16px; }
	.auth-card { padding: 24px 20px; }
}
