/**
 * TopLife Lodge — management application design system.
 *
 * This file replaces the previous admin stylesheet entirely. It is written
 * against the markup the views already emit, so the whole application changes
 * appearance without a single business-logic file being touched.
 *
 * Three rules held throughout:
 *
 *   1. Status is never carried by colour alone. Every state that matters
 *      operationally also carries a shape — a dot, a rule, an icon-sized mark —
 *      because a receptionist with colour-vision deficiency still has to be able
 *      to tell an occupied room from an available one at a glance.
 *
 *   2. Density over decoration. This is an operations tool used standing at a
 *      desk with a guest waiting. Cards are compact, tables are scannable, and
 *      nothing that matters is more than one screen away.
 *
 *   3. It lives inside wp-admin, so it must out-specify wp-admin without
 *      resorting to !important everywhere. The .lspms scope does that.
 *
 * @package LodgeStay_PMS
 */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

/*
 * THE LODGE'S IDENTITY, AND NOTHING ELSE.
 *
 * Everything that is not identity — the neutral ramp, the semantic ramp, type,
 * space, radius, elevation, layout measurements — moved to toplife.css, which
 * all three systems share. What is left here is the one thing that must NOT be
 * shared: the colour that tells somebody at a glance which of the three
 * businesses they are looking at.
 *
 * A deep teal-slate rather than the usual SaaS indigo: this is a lodge in
 * Tanzania, and the palette leans warm-neutral so photography of the property
 * never fights the interface around it.
 */
.lspms {
	--tl-primary-50:  #eef6f6;
	--tl-primary-100: #d3e8e8;
	--tl-primary-200: #a7d1d2;
	--tl-primary-300: #6fb2b4;
	--tl-primary-400: #3d9093;
	--tl-primary-500: #1f7376;
	--tl-primary-600: #175c5f;
	--tl-primary-700: #124a4c;
	--tl-primary-800: #0e3a3c;
	--tl-primary-900: #0a2b2c;

	/* Accent — used sparingly, for the single most important action on a screen. */
	--tl-accent-100: #fdf0dc;
	--tl-accent-300: #f0c37a;
	--tl-accent-500: #d99b32;
	--tl-accent-700: #a3701c;

	/* The focus ring is the lodge's own colour, so it is declared with it. */
	--tl-ring: 0 0 0 3px rgba(31, 115, 118, 0.22);
}

/* Status tokens. Named for the operational state, not the colour, so a screen
   reads "occupied" rather than "the blue one". */
.lspms {
	--tl-state-available-bg:   var(--tl-success-50);
	--tl-state-available-fg:   var(--tl-success-700);
	--tl-state-available-mark: var(--tl-success-500);

	--tl-state-occupied-bg:    var(--tl-info-50);
	--tl-state-occupied-fg:    var(--tl-info-700);
	--tl-state-occupied-mark:  var(--tl-info-500);

	--tl-state-reserved-bg:    var(--tl-primary-50);
	--tl-state-reserved-fg:    var(--tl-primary-700);
	--tl-state-reserved-mark:  var(--tl-primary-500);

	--tl-state-pending-bg:     var(--tl-warning-50);
	--tl-state-pending-fg:     var(--tl-warning-700);
	--tl-state-pending-mark:   var(--tl-warning-500);

	--tl-state-dirty-bg:       var(--tl-accent-100);
	--tl-state-dirty-fg:       var(--tl-accent-700);
	--tl-state-dirty-mark:     var(--tl-accent-500);

	--tl-state-blocked-bg:     var(--tl-danger-50);
	--tl-state-blocked-fg:     var(--tl-danger-700);
	--tl-state-blocked-mark:   var(--tl-danger-500);

	--tl-state-closed-bg:      var(--tl-neutral-100);
	--tl-state-closed-fg:      var(--tl-neutral-600);
	--tl-state-closed-mark:    var(--tl-neutral-400);
}

/* ==========================================================================
   2. RESET AND SHELL
   ==========================================================================

   The application takes the whole canvas. WordPress still draws its admin bar
   and its own menu around us, and we cannot remove those — but everything
   inside our own frame is ours, and the frame is sized so the PMS reads as the
   application on the page rather than as a panel inside somebody else's.

   Three measurements carry the whole layout and are stated once, as tokens:
   the sidebar width, the top bar height, and the content measure. Every other
   spacing decision hangs off the 4px scale in section 1.
   ========================================================================== */

body.toplevel_page_toplife-pms #wpcontent,
body[class*="toplife-pms"] #wpcontent {
	padding-left: 0;
	background: var(--tl-bg, #f6f7f9);
}

body[class*="toplife-pms"] #wpbody-content {
	padding-bottom: 0;
}

body[class*="toplife-pms"] .wrap > h1:first-child,
body[class*="toplife-pms"] .notice.updated,
body[class*="toplife-pms"] #screen-meta-links {
	/* The app draws its own page heading and its own notices. */
	display: none;
}

.lspms,
.lspms * {
	box-sizing: border-box;
}

.lspms {
	display: flex;
	min-height: calc(100vh - 32px);
	/*
	 * Zero, not -20px. WordPress puts a 20px gutter on #wpcontent and the rule
	 * above already removes it; pulling left as well slid the first 20px of the
	 * sidebar underneath WordPress's own menu, where it stayed permanently
	 * hidden. That is why the section headings looked clipped and the sidebar
	 * looked narrower than the 264px it is.
	 */
	margin: 0;
	background: var(--tl-bg);
	color: var(--tl-text);
	font-family: var(--tl-font);
	font-size: var(--tl-size-base);
	line-height: var(--tl-lh-base);
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

.lspms__main {
	flex: 1 1 auto;
	min-width: 0; /* Without this a wide table stretches the whole shell. */
	display: flex;
	flex-direction: column;
}

.lspms__content {
	flex: 1 1 auto;
	/* A flex item's min-width is `auto` by default, meaning "never shrink below
	   my content". One wide table would then stretch this column and scroll the
	   whole application sideways. Releasing the minimum lets the table's own
	   .lspms-scroll box do the scrolling instead. */
	min-width: 0;
	padding: var(--tl-space-6) var(--tl-space-6) var(--tl-space-12);
	/* A measure, not the whole monitor. Beyond this a KPI row becomes a line of
	   distant islands and a table's first and last columns stop being readable
	   as one row. */
	max-width: var(--tl-measure);
	width: 100%;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-5);
}

/* -- Sidebar --------------------------------------------------------------- */

.lspms__side {
	flex: 0 0 var(--tl-sidebar-w);
	width: var(--tl-sidebar-w);
	background: var(--tl-primary-900);
	background-image: linear-gradient(180deg, var(--tl-primary-900) 0%, #071f20 100%);
	color: rgba(255, 255, 255, 0.82);
	display: flex;
	flex-direction: column;
	position: sticky;
	top: 32px;
	align-self: flex-start;
	height: calc(100vh - 32px);
	border-right: 1px solid rgba(255, 255, 255, 0.06);
}

.lspms__side-scroll {
	flex: 1 1 auto;
	min-height: 0;
	overflow-y: auto;
	overscroll-behavior: contain;
	padding-bottom: var(--tl-space-4);
}

.lspms__side-scroll::-webkit-scrollbar { width: 8px; }
.lspms__side-scroll::-webkit-scrollbar-thumb {
	background: rgba(255, 255, 255, 0.14);
	border-radius: var(--tl-radius-full);
}

/* The brand lockup is a fixed 60px band — the same height as the top bar's
   title block, so the two line up across the seam instead of the sidebar
   starting a few pixels above or below the header. */
.lspms__brand {
	display: flex;
	align-items: center;
	gap: var(--tl-space-3);
	padding: 0 var(--tl-space-4);
	height: var(--tl-topbar-h);
	flex: 0 0 var(--tl-topbar-h);
	border-bottom: 1px solid rgba(255, 255, 255, 0.08);
	text-decoration: none;
}

.lspms__brand:focus-visible {
	outline: 2px solid var(--tl-primary-300);
	outline-offset: -3px;
}

.lspms__brand-mark {
	display: grid;
	place-items: center;
	width: 32px;
	height: 32px;
	flex: 0 0 32px;
	border-radius: var(--tl-radius);
	background: linear-gradient(140deg, var(--tl-primary-300), var(--tl-primary-600));
	color: #fff;
	font-weight: var(--tl-weight-bold);
	font-size: 0.8125rem;
	letter-spacing: 0.04em;
	box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22);
}

/* The brand block is an <a>, so without an explicit colour it inherits the
   link ink meant for content on a light ground and renders teal-on-teal. */
.lspms .lspms__brand,
.lspms .lspms__brand-text {
	color: #fff;
}

.lspms__brand-text {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
	min-width: 0;
}

.lspms .lspms__brand-text strong {
	color: #fff;
	font-weight: var(--tl-weight-semi);
	font-size: 0.875rem;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.lspms__brand-text small {
	font-weight: var(--tl-weight-regular);
	font-size: 0.6875rem;
	color: rgba(255, 255, 255, 0.5);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	margin-top: 1px;
}

/*
 * Each sidebar group is a <details>, so its heading is a <summary>. Browsers
 * give a summary a disclosure triangle and `display: list-item`; both are
 * replaced here with a chevron drawn on the right, which is where a reader
 * expects the control for a full-width section header rather than tucked
 * against the first letter of the label.
 */
.lspms__side-section {
	margin: var(--tl-space-5) 0 0;
}

.lspms__side-section:first-of-type { margin-top: var(--tl-space-4); }

.lspms__side-group {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--tl-space-2);
	margin: 0 0 var(--tl-space-2);
	padding: var(--tl-space-1) var(--tl-space-5);
	font-size: 0.6875rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.36);
	cursor: pointer;
	list-style: none;
	user-select: none;
}

/* Safari still draws the marker unless this pseudo-element is hidden too. */
.lspms__side-group::-webkit-details-marker { display: none; }

.lspms__side-group:hover,
.lspms__side-group:focus-visible {
	color: rgba(255, 255, 255, 0.72);
}

/*
 * A visible focus ring, because a summary is a real tab stop and this sidebar
 * is dark enough that the browser default disappears into it.
 */
.lspms__side-group:focus-visible {
	outline: 2px solid rgba(255, 255, 255, 0.55);
	outline-offset: -2px;
	border-radius: var(--tl-radius-sm);
}

/*
 * The chevron. A CSS triangle rather than an icon font or an SVG, so it needs
 * no extra element in the markup and no second request. It points down when the
 * section is open and right when it is closed.
 */
.lspms__side-group::after {
	content: "";
	flex: 0 0 auto;
	width: 0;
	height: 0;
	border-left: 4px solid transparent;
	border-right: 4px solid transparent;
	border-top: 5px solid currentColor;
	transition: transform 120ms ease;
}

.lspms__side-section:not([open]) > .lspms__side-group::after {
	transform: rotate(-90deg);
}

@media (prefers-reduced-motion: reduce) {
	.lspms__side-group::after { transition: none; }
}

.lspms__nav {
	list-style: none;
	margin: 0;
	padding: 0 var(--tl-space-3);
	display: flex;
	flex-direction: column;
	gap: 1px;
}

.lspms__nav-link {
	display: flex;
	align-items: center;
	gap: var(--tl-space-3);
	padding: 7px var(--tl-space-3);
	border-radius: var(--tl-radius-sm);
	color: rgba(255, 255, 255, 0.76);
	text-decoration: none;
	font-size: 0.8125rem;
	font-weight: var(--tl-weight-medium);
	transition: background var(--tl-transition), color var(--tl-transition);
	position: relative;
}

.lspms__nav-text {
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.lspms__nav-link .dashicons {
	font-size: 17px;
	width: 17px;
	height: 17px;
	opacity: 0.6;
	flex: 0 0 17px;
}

.lspms__nav-link:hover,
.lspms__nav-link:focus {
	background: rgba(255, 255, 255, 0.07);
	color: #fff;
}

.lspms__nav-link:focus-visible {
	outline: 2px solid var(--tl-primary-300);
	outline-offset: -2px;
}

.lspms__nav-link.is-current {
	background: rgba(255, 255, 255, 0.11);
	color: #fff;
	font-weight: var(--tl-weight-semi);
}

/* The current item carries a bar as well as a tint — not colour alone. */
.lspms__nav-link.is-current::before {
	content: "";
	position: absolute;
	left: calc(var(--tl-space-3) * -1 + 2px);
	top: 50%;
	transform: translateY(-50%);
	width: 3px;
	height: 18px;
	border-radius: 0 2px 2px 0;
	background: var(--tl-accent-500);
}

.lspms__nav-link.is-current .dashicons { opacity: 1; }

.lspms__side-foot {
	margin: 0;
	flex: 0 0 auto;
	padding: var(--tl-space-3) var(--tl-space-5);
	font-size: var(--tl-size-xs);
	border-top: 1px solid rgba(255, 255, 255, 0.09);
	line-height: var(--tl-lh-snug);
	background: rgba(0, 0, 0, 0.16);
}

.lspms .lspms__side-foot a {
	color: rgba(255, 255, 255, 0.7);
	display: inline-flex;
	align-items: center;
	gap: 6px;
	text-decoration: none;
}

.lspms .lspms__side-foot a:hover { color: #fff; }
.lspms__side-foot .dashicons { font-size: 14px; width: 14px; height: 14px; }

/* -- Top bar --------------------------------------------------------------- */

.lspms__top {
	display: flex;
	align-items: center;
	gap: var(--tl-space-4);
	padding: 0 var(--tl-space-6);
	background: var(--tl-surface);
	border-bottom: 1px solid var(--tl-border);
	position: sticky;
	top: 32px;
	z-index: 20;
	height: var(--tl-topbar-h);
	flex: 0 0 var(--tl-topbar-h);
}

.lspms__top-title {
	display: flex;
	flex-direction: column;
	justify-content: center;
	min-width: 0;
	flex: 0 1 auto;
	margin-right: auto;
}

.lspms .lspms__top-title h1 {
	margin: 0;
	font-size: 1.25rem;
	font-weight: var(--tl-weight-semi);
	line-height: 1.2;
	letter-spacing: -0.012em;
	color: var(--tl-text);
	padding: 0;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* Management › Operations. Small, quiet, above the title. */
.lspms__crumb {
	display: flex;
	align-items: center;
	gap: 5px;
	margin: 0 0 1px;
	font-size: 11px;
	font-weight: var(--tl-weight-medium);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--tl-text-subtle);
	white-space: nowrap;
}

.lspms__today {
	margin: 1px 0 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
	white-space: nowrap;
}

/* On a short bar the date and the crumb together push the title down. The
   crumb is the one that goes: the date is operational, the crumb is a hint. */
@media ( max-width: 900px ) {
	.lspms__crumb { display: none; }
}

.lspms__menu-toggle {
	display: none;
	align-items: center;
	justify-content: center;
	width: 36px;
	height: 36px;
	flex: 0 0 36px;
	border: 1px solid var(--tl-border);
	background: var(--tl-surface);
	border-radius: var(--tl-radius-sm);
	cursor: pointer;
	color: var(--tl-text);
}

.lspms__search {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	flex: 0 1 400px;
	min-width: 0;
}

.lspms__search-field {
	position: relative;
	display: block;
	flex: 1 1 auto;
	min-width: 0;
}

.lspms__search-field .dashicons {
	position: absolute;
	left: 9px;
	top: 50%;
	transform: translateY(-50%);
	font-size: 16px;
	width: 16px;
	height: 16px;
	color: var(--tl-text-subtle);
	pointer-events: none;
}

.lspms .lspms__search input[type="search"],
.lspms .lspms__search input[type="text"] {
	width: 100%;
	height: 36px;
	padding: 0 var(--tl-space-3) 0 32px;
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius-full);
	background: var(--tl-neutral-50);
	font-size: var(--tl-size-sm);
	color: var(--tl-text);
	box-shadow: none;
	margin: 0;
	transition: border-color var(--tl-transition), box-shadow var(--tl-transition), background var(--tl-transition);
}

.lspms .lspms__search input:focus {
	background: var(--tl-surface);
	border-color: var(--tl-primary-400);
	box-shadow: var(--tl-ring);
	outline: none;
}

.lspms__search-go {
	height: 36px;
	padding: 0 var(--tl-space-4);
	border: 1px solid var(--tl-border);
	background: var(--tl-surface);
	border-radius: var(--tl-radius-full);
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	font-family: inherit;
	color: var(--tl-text);
	cursor: pointer;
	transition: background var(--tl-transition), border-color var(--tl-transition);
}

.lspms__search-go:hover { background: var(--tl-neutral-50); border-color: var(--tl-border-strong); }

.lspms__top-actions {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	flex: 0 0 auto;
}

.lspms__quick {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	height: 36px;
	padding: 0 var(--tl-space-3) 0 var(--tl-space-2);
	border-radius: var(--tl-radius-full);
	background: var(--tl-primary-600);
	color: #fff;
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	text-decoration: none;
	transition: background var(--tl-transition);
}

.lspms .lspms__quick { color: #fff; }
.lspms .lspms__quick:hover { background: var(--tl-primary-700); color: #fff; }
.lspms__quick .dashicons { font-size: 16px; width: 16px; height: 16px; }

.lspms__bell {
	position: relative;
	display: grid;
	place-items: center;
	width: 36px;
	height: 36px;
	border-radius: var(--tl-radius-full);
	color: var(--tl-text-muted);
	text-decoration: none;
	transition: background var(--tl-transition), color var(--tl-transition);
}

.lspms .lspms__bell:hover { background: var(--tl-neutral-100); color: var(--tl-text); }

.lspms__bell-count {
	position: absolute;
	top: 2px;
	right: 1px;
	min-width: 17px;
	height: 17px;
	padding: 0 4px;
	border-radius: var(--tl-radius-full);
	background: var(--tl-danger-500);
	color: #fff;
	font-size: 10px;
	font-weight: var(--tl-weight-bold);
	line-height: 17px;
	text-align: center;
	box-shadow: 0 0 0 2px var(--tl-surface);
}

.lspms__user {
	display: inline-flex;
	align-items: center;
	gap: var(--tl-space-2);
	padding: 3px 10px 3px 3px;
	border-radius: var(--tl-radius-full);
	text-decoration: none;
	max-width: 190px;
	transition: background var(--tl-transition);
}

.lspms .lspms__user { color: var(--tl-text); }
.lspms .lspms__user:hover { background: var(--tl-neutral-100); color: var(--tl-text); }

.lspms__user-mark {
	display: grid;
	place-items: center;
	width: 30px;
	height: 30px;
	flex: 0 0 30px;
	border-radius: var(--tl-radius-full);
	background: var(--tl-primary-100);
	color: var(--tl-primary-700);
	font-size: 0.6875rem;
	font-weight: var(--tl-weight-bold);
	letter-spacing: 0.02em;
}

.lspms__user-text {
	display: flex;
	flex-direction: column;
	line-height: 1.2;
	min-width: 0;
}

.lspms__user-text strong {
	font-size: 0.8125rem;
	font-weight: var(--tl-weight-semi);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.lspms__user-text small {
	font-size: 0.6875rem;
	color: var(--tl-text-muted);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/* -- One source of vertical rhythm ----------------------------------------- */

/*
 * .lspms__content lays its children out with `gap`. The panels, stat rows and
 * KPI grids were written earlier with their own bottom margins, and the two
 * together doubled every seam — which is most of the "excessive whitespace" in
 * the screenshots. The gap wins; the margins are zeroed where they meet it.
 * Two classes deep so this beats the single-class rules that set them.
 */
.lspms .lspms__content > * { margin-bottom: 0; }
.lspms .lspms-cols > .lspms-panel { margin-bottom: 0; }
.lspms .lspms-section > .lspms-panel:last-child { margin-bottom: 0; }

/* ==========================================================================
   3. TYPOGRAPHY
   ========================================================================== */

/* These are DEFAULTS, so they are wrapped in :where() to carry (0,0,1)
   specificity. A bare `.lspms a` is (0,1,1) and silently outranks every
   single-class component rule — which is how the dark sidebar and the dark
   briefing panel ended up painting content ink on a dark ground. Keeping the
   defaults weak means a component that states its own colour always wins. */
:where(.lspms) h1,
:where(.lspms) h2,
:where(.lspms) h3,
:where(.lspms) h4 {
	color: var(--tl-text);
	font-weight: var(--tl-weight-semi);
	line-height: var(--tl-lh-snug);
	letter-spacing: -0.005em;
	margin: 0;
}

:where(.lspms) h2 { font-size: var(--tl-size-lg); }
:where(.lspms) h3 { font-size: var(--tl-size-md); }
:where(.lspms) h4 { font-size: var(--tl-size-base); }

:where(.lspms) p { margin: 0 0 var(--tl-space-3); }
:where(.lspms) p:last-child { margin-bottom: 0; }

:where(.lspms) a { color: var(--tl-primary-600); text-decoration-thickness: 1px; text-underline-offset: 2px; }
:where(.lspms) a:hover { color: var(--tl-primary-700); }

/* The focus ring is a real accessibility guarantee, not a default — it keeps
   full specificity so no component can quietly drop it. */
.lspms a:focus-visible { outline: 2px solid var(--tl-primary-400); outline-offset: 2px; border-radius: 2px; }

.lspms-hint {
	margin: var(--tl-space-1) 0 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
	line-height: var(--tl-lh-snug);
	max-width: 78ch;
}

/*
 * A hint that is not merely informative — something about this record needs a
 * decision. It reads as a warning because of what it says, not because of its
 * colour: the sentence names the problem and the fix.
 */
.lspms-warn {
	display: inline-block;
	margin-top: var(--tl-space-1);
	color: var(--tl-warning-700);
	font-weight: var(--tl-weight-medium);
	line-height: var(--tl-lh-snug);
}

.lspms-heading {
	margin-bottom: var(--tl-space-4);
}

.lspms-heading h2 { margin-bottom: 2px; }

/* ==========================================================================
   4. PANELS AND LAYOUT
   ========================================================================== */

.lspms-panel {
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius-md);
	box-shadow: var(--tl-shadow-xs);
	padding: var(--tl-space-5);
	margin: 0 0 var(--tl-space-5);
}

.lspms-panel > h2:first-child,
.lspms-panel > .lspms-heading:first-child { margin-top: 0; }

/*
 * A panel that is telling you something is wrong.
 *
 * It carried the modifier but no rule, so "1 ticket is past its date" looked
 * exactly like "Lodge details" — the same white card, the same weight. The
 * signal is an amber edge and an amber heading, not a tinted card: a wash of
 * colour behind a list of overdue items makes the items themselves harder to
 * read, which is the opposite of what an alert is for.
 */
/* ==========================================================================
   TODAY'S OPERATIONS
   ==========================================================================

   Nine panels, each a count and the things being counted. Sized so a duty
   manager sees the whole board without scrolling on a laptop: three across at
   1440, two on a tablet, one on a phone.
   ========================================================================== */

.lspms-opsboard {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(min(100%, 268px), 1fr));
	gap: var(--tl-space-3);
	margin-bottom: var(--tl-space-5);
	container-type: inline-size;
	/*
	 * Each panel is as tall as what it has to say. Stretching them to the
	 * tallest in the row put a one-line panel — "Maintenance overdue: 1" — in a
	 * box four times its own height next to a twelve-room list, which reads as
	 * something failing to load rather than as a short answer.
	 */
	align-items: start;
}

.lspms-ops {
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	box-shadow: var(--tl-shadow-xs);
	padding: var(--tl-space-3) var(--tl-space-4) var(--tl-space-4);
	min-width: 0;
	position: relative;
	overflow: hidden;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-2);
}

/* A tone stripe rather than a tinted card — the same device the metric cards
   use, so the two systems read as one. */
.lspms-ops::before {
	content: "";
	position: absolute;
	inset: 0 auto 0 0;
	width: 3px;
	background: var(--tl-neutral-200);
}

.lspms-ops--good::before   { background: var(--tl-success-500); }
.lspms-ops--warn::before   { background: var(--tl-warning-500); }
.lspms-ops--bad::before    { background: var(--tl-danger-500); }
.lspms-ops--active::before { background: var(--tl-primary-500); }

.lspms-ops__head {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--tl-space-2);
}

.lspms-ops__label {
	margin: 0;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
	min-width: 0;
}

/* Two classes deep: `.lspms a` is (0,1,1) and would repaint these. */
.lspms .lspms-ops__label a { color: inherit; text-decoration: none; }
.lspms .lspms-ops__label a:hover { color: var(--tl-primary-700); }

.lspms-ops__count {
	flex: 0 0 auto;
	font-size: 1.35rem;
	font-weight: var(--tl-weight-semi);
	line-height: 1;
	letter-spacing: -0.02em;
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
}

.lspms-ops__list {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 5px;
}

.lspms-ops__list li {
	display: flex;
	flex-direction: column;
	min-width: 0;
	padding-bottom: 5px;
	border-bottom: 1px solid var(--tl-neutral-100);
}

.lspms-ops__list li:last-child { border-bottom: 0; padding-bottom: 0; }

.lspms-ops__list a,
.lspms-ops__list span {
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text);
	text-decoration: none;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.lspms .lspms-ops__list a:hover { color: var(--tl-primary-700); text-decoration: underline; }

.lspms-ops__list small {
	font-size: 11px;
	color: var(--tl-text-subtle);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.lspms-ops__empty {
	margin: 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-subtle);
}

.lspms-ops__more { margin: 0; font-size: var(--tl-size-xs); }

@container ( max-width: 560px ) {
	.lspms-ops__count { font-size: 1.2rem; }
}

/*
 * A segmented period switch: 7 days / 30 days / 90 days / 12 months.
 *
 * One control, four links, no form. Each is a real URL, so the chosen window
 * survives a reload, can be bookmarked, and works with scripting off — which a
 * select-plus-submit would not.
 */
.lspms-spans {
	display: inline-flex;
	padding: 2px;
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	background: var(--tl-surface-2);
}

/* Two classes deep: `.lspms a` is (0,1,1) and would otherwise repaint these. */
.lspms .lspms-spans__item {
	padding: 5px var(--tl-space-3);
	border-radius: var(--tl-radius-sm);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	text-decoration: none;
	white-space: nowrap;
	transition: background var(--tl-transition), color var(--tl-transition);
}

.lspms .lspms-spans__item:hover { color: var(--tl-text); }

.lspms .lspms-spans__item.is-current {
	background: var(--tl-surface);
	color: var(--tl-primary-700);
	box-shadow: var(--tl-shadow-xs);
}

.lspms-panel--alert {
	border-left: 3px solid var(--tl-warning-500);
	background:
		linear-gradient( to right, var(--tl-warning-50), var(--tl-surface) 220px );
}

.lspms-panel--alert > .lspms-head h2,
.lspms-panel--alert > h2 {
	color: var(--tl-warning-700);
}

.lspms-cols {
	display: grid;
	/*
	 * The floor is capped at the container's own width. A bare
	 * `minmax(320px, 1fr)` cannot make a column narrower than 320px, so in the
	 * admin content column at a 320px viewport -- which measures 305px -- the
	 * column is 320px and the page scrolls sideways. Measured on the live lodge
	 * dashboard: 27px of it.
	 *
	 * Above 320px nothing changes: min(100%, 320px) is 320px there.
	 */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 320px), 1fr));
	gap: var(--tl-space-5);
	align-items: start;
}

/*
 * A horizontal scroll region.
 *
 * `position: relative` is not decoration — it is what makes the clipping work.
 * An absolutely positioned descendant is clipped by an ancestor's overflow only
 * when that ancestor is also its containing block, and a `static` scroller is
 * not one. Every table in this system contains WordPress's own
 * `.screen-reader-text`, which is `position: absolute`; inside a static
 * scroller those spans are laid out against a containing block further up,
 * escape the scroller entirely, and add their off-screen width to the page.
 *
 * That is what made the calendar scroll the whole document sideways by 30px at
 * 375px wide while every visible element measured inside the viewport — the
 * elements pushing the page out were the invisible ones.
 */
/*
 * A row of small links that stands on its own — the report archive's six named
 * reports. Wraps, because the number of them is a decision somebody will change
 * and a row that cannot wrap has no narrow layout at all.
 */
.lspms-chips {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin-top: var(--tl-space-3);
}

.lspms-scroll {
	position: relative;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	margin-inline: calc(var(--tl-space-5) * -1);
	padding-inline: var(--tl-space-5);
}

/* ==========================================================================
   5. KPI STATS
   ========================================================================== */

/*
 * The other KPI grid.
 *
 * `.lspms-stat` predates `.lspms-metric` and is still what the operational
 * screens use — reservations, front desk, housekeeping, the report summaries.
 * It needed exactly the same treatment for exactly the same reason: at a 148px
 * minimum a 560px column produced three tracks, and "TSh 175,000" set at
 * 1.75rem broke across two lines, leaving one card taller than its neighbours.
 *
 * Fixing only the newer component would have left half the application still
 * wrapping its figures — which is what happens when a system grows a second way
 * of doing the same thing.
 */
.lspms-stats {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
	gap: var(--tl-space-3);
	margin: 0 0 var(--tl-space-5);
	container-type: inline-size;
}

.lspms-stat {
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	padding: var(--tl-space-3) var(--tl-space-4);
	box-shadow: var(--tl-shadow-xs);
	position: relative;
	overflow: hidden;
	transition: box-shadow var(--tl-transition), transform var(--tl-transition);
}

.lspms-stat:hover { box-shadow: var(--tl-shadow-sm); }

/* A tone bar rather than a tinted card: the figure stays legible and the
   signal is still visible without relying on hue. */
.lspms-stat::before {
	content: "";
	position: absolute;
	inset: 0 auto 0 0;
	width: 3px;
	background: var(--tl-neutral-200);
}

.lspms-stat--good::before { background: var(--tl-success-500); }
.lspms-stat--warn::before { background: var(--tl-warning-500); }
.lspms-stat--bad::before  { background: var(--tl-danger-500); }

.lspms-stat__label {
	margin: 0;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	letter-spacing: 0.01em;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.lspms-stat__value {
	margin: 2px 0 0;
	font-size: var(--tl-size-2xl);
	font-weight: var(--tl-weight-semi);
	line-height: 1.1;
	letter-spacing: -0.02em;
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
}

/* Same two steps as the metric cards, and the same reason: a smaller figure
   reads better than a wrapped one, and keeps a row of four the same height. */
@container ( max-width: 700px ) {
	.lspms-stat__value { font-size: 1.4rem; }
}

@container ( max-width: 460px ) {
	.lspms-stat__value { font-size: 1.2rem; }
}

.lspms-stat__note {
	margin: var(--tl-space-1) 0 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-subtle);
}

/* ==========================================================================
   6. TABLES
   ========================================================================== */

.lspms-table {
	width: 100%;
	border-collapse: separate;
	border-spacing: 0;
	font-size: var(--tl-size-sm);
	background: var(--tl-surface);
}

.lspms-table th,
.lspms-table td {
	padding: var(--tl-space-3);
	text-align: left;
	vertical-align: middle;
	border-bottom: 1px solid var(--tl-border);
}

.lspms-table thead th {
	position: sticky;
	top: 0;
	z-index: 1;
	background: var(--tl-neutral-50);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	text-transform: uppercase;
	letter-spacing: 0.05em;
	color: var(--tl-text-muted);
	border-bottom: 1px solid var(--tl-border-strong);
	white-space: nowrap;
}

.lspms-table tbody tr { transition: background var(--tl-transition); }
.lspms-table tbody tr:hover { background: var(--tl-neutral-25); }
.lspms-table tbody tr:last-child td { border-bottom: 0; }

/* Money and counts line up on the decimal, which makes a column of figures
   comparable at a glance instead of ragged. */
.lspms-table td.is-num,
.lspms-table th.is-num,
.lspms-money {
	text-align: right;
	font-variant-numeric: tabular-nums;
}

.lspms-table caption {
	caption-side: top;
	text-align: left;
	padding: 0 0 var(--tl-space-3);
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

/* ==========================================================================
   7. BADGES AND STATUS
   ========================================================================== */

.lspms-badge {
	display: inline-flex;
	align-items: center;
	gap: 5px;
	padding: 2px 9px 2px 7px;
	border-radius: var(--tl-radius-full);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	line-height: 1.6;
	white-space: nowrap;
	background: var(--tl-neutral-100);
	color: var(--tl-neutral-700);
	border: 1px solid transparent;
}

/*
 * THE DOT IS GONE, AND WHY.
 *
 * There used to be a 6px disc here, described in this file as "the non-colour
 * half of the signal". It was a circle for every state there is; only its
 * colour changed. A mark whose shape never varies says nothing its colour was
 * not already saying, so the element added specifically to avoid depending on
 * hue depended on it completely.
 *
 * LSPMS_Badge now emits a per-state dashicon in its place: a check for paid, a
 * clock for pending, a cross for failed. It is aria-hidden, because the word
 * beside it is the accessible name and "checkmark Paid" is a worse announcement
 * than "Paid".
 */
.lspms-badge__mark {
	font-size: 13px;
	width: 13px;
	height: 13px;
	line-height: 13px;
	flex: 0 0 13px;
	opacity: 0.85;
}

.lspms-badge__label { display: inline; }

.lspms-badge--available,
.lspms-badge--paid,
.lspms-badge--confirmed,
.lspms-badge--clean {
	background: var(--tl-state-available-bg);
	color: var(--tl-state-available-fg);
}

.lspms-badge--occupied,
.lspms-badge--checked_in,
.lspms-badge--in_house {
	background: var(--tl-state-occupied-bg);
	color: var(--tl-state-occupied-fg);
}

.lspms-badge--reserved {
	background: var(--tl-state-reserved-bg);
	color: var(--tl-state-reserved-fg);
}

.lspms-badge--pending,
.lspms-badge--partial,
.lspms-badge--unpaid {
	background: var(--tl-state-pending-bg);
	color: var(--tl-state-pending-fg);
}

.lspms-badge--dirty,
.lspms-badge--cleaning {
	background: var(--tl-state-dirty-bg);
	color: var(--tl-state-dirty-fg);
}

.lspms-badge--maintenance,
.lspms-badge--blocked,
.lspms-badge--out,
.lspms-badge--cancelled,
.lspms-badge--no_show {
	background: var(--tl-state-blocked-bg);
	color: var(--tl-state-blocked-fg);
}

.lspms-badge--checked_out,
.lspms-badge--closed,
.lspms-badge--void {
	background: var(--tl-state-closed-bg);
	color: var(--tl-state-closed-fg);
}

/* ==========================================================================
   8. BUTTONS
   ========================================================================== */

.lspms .button,
.lspms button.button,
.lspms .lspms-btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: 6px;
	height: 34px;
	padding: 0 var(--tl-space-4);
	border-radius: var(--tl-radius-sm);
	border: 1px solid var(--tl-border-strong);
	background: var(--tl-surface);
	color: var(--tl-text);
	font-family: inherit;
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	line-height: 1;
	text-decoration: none;
	cursor: pointer;
	box-shadow: var(--tl-shadow-xs);
	transition: background var(--tl-transition), border-color var(--tl-transition),
		box-shadow var(--tl-transition), color var(--tl-transition);
	text-shadow: none;
	white-space: nowrap;
}

/*
 * A BUTTON WHOSE LABEL IS A SENTENCE MUST NOT DRAG THE PAGE SIDEWAYS.
 *
 * `white-space: nowrap` and `height: 34px` above are right for a button that
 * says "Save". They are wrong for one that says "Or add an empty slide and pick
 * a picture already on the website" — that label is 400px wide and cannot be
 * made to fit any phone, so on the Homepage Slider screen it pushed the whole
 * document 109px past a 320px viewport. Every width from 320 to 414 scrolled
 * sideways; nothing was wrong at 480 and above, which is why nobody saw it.
 *
 * Found by laying all 73 management screens out at eleven real widths
 * (`responsive-check.js`). The stylesheet checker could not have found it: the
 * form declares `flex-wrap: wrap` and satisfies every rule on that checker's
 * list. It is the button that will not wrap, not the row.
 *
 * So below 640px a button may use two lines. `min-height` rather than `height`
 * keeps every one-line button exactly the size it already was — the padding
 * cannot reach 34px on its own — so this changes the appearance of long labels
 * only, which are the ones that were broken.
 */
@media (max-width: 640px) {

	.lspms .button,
	.lspms button.button,
	.lspms .lspms-btn {
		white-space: normal;
		height: auto;
		min-height: 34px;
		max-width: 100%;
		padding-top: var(--tl-space-2);
		padding-bottom: var(--tl-space-2);
		line-height: 1.3;
		text-align: center;
	}
}

.lspms .button:hover {
	background: var(--tl-neutral-50);
	border-color: var(--tl-neutral-400);
	color: var(--tl-text);
}

.lspms .button:focus-visible {
	outline: none;
	box-shadow: var(--tl-ring);
	border-color: var(--tl-primary-400);
}

/*
 * Two classes deep on `.button.button-primary`, and the reason is the base rule
 * directly above.
 *
 * That rule lists `.lspms button.button` — a class AND a type selector, (0,2,1)
 * — so that a `<button>` beats WordPress's own `.wp-core-ui .button` at (0,2,0).
 * It worked, and it also silently beat every modifier written at (0,2,0),
 * because a type selector is a tie-breaker the modifiers did not have.
 *
 * The effect was invisible in review and site-wide: `<a class="button
 * button-primary">` rendered green, because the type selector does not match an
 * anchor, while `<button class="button button-primary">` — Check in, Save,
 * Raise ticket, every submit in the system — rendered as an ordinary secondary
 * button. Two controls side by side, same classes, different colours.
 *
 * `.lspms .button.button-primary` is (0,3,0), which outranks the base rule for
 * both element types. Every element that carries `button-primary` also carries
 * `button`, so nothing is left behind; the cascade check asserts the resolved
 * colour so this cannot come back.
 */
.lspms .button.button-primary,
.lspms .lspms-btn.lspms-btn--primary,
.lspms .lspms-btn--primary {
	background: var(--tl-primary-600);
	border-color: var(--tl-primary-600);
	color: #fff;
	box-shadow: var(--tl-shadow-sm);
}

.lspms .button.button-primary:hover {
	background: var(--tl-primary-700);
	border-color: var(--tl-primary-700);
	color: #fff;
}

/* Same reason as the primary modifier above: two classes deep so a <button>
 * does not fall back to the base rule's border, height and background. */
.lspms .button.button-link,
.lspms .button.button-link-delete {
	border: 0;
	background: none;
	box-shadow: none;
	padding: 0 4px;
	height: auto;
	color: var(--tl-primary-600);
}

.lspms .button.button-link-delete { color: var(--tl-danger-700); }

/* And the size modifier: without this a small submit button stood 34px tall
 * beside a 28px small link, which is what the room board's action rows and
 * every table's inline controls are made of. */
.lspms .button.button-small,
.lspms .lspms-btn.lspms-btn--sm,
.lspms .lspms-btn--sm {
	height: 28px;
	padding: 0 var(--tl-space-3);
	font-size: var(--tl-size-xs);
}

.lspms .lspms-btn.lspms-btn--danger,
.lspms .lspms-btn--danger {
	background: var(--tl-danger-500);
	border-color: var(--tl-danger-500);
	color: #fff;
}

.lspms .lspms-btn.lspms-btn--danger:hover,
.lspms .lspms-btn--danger:hover {
	background: var(--tl-danger-700);
	border-color: var(--tl-danger-700);
	color: #fff;
}

.lspms .button[disabled],
.lspms .button:disabled,
.lspms .button.is-busy {
	opacity: 0.55;
	cursor: not-allowed;
	box-shadow: none;
}

/* A button mid-request says so, so nobody clicks twice. */
.lspms .button.is-busy::after {
	content: "";
	width: 12px;
	height: 12px;
	border-radius: 50%;
	border: 2px solid currentColor;
	border-right-color: transparent;
	animation: lspms-spin 0.6s linear infinite;
}

@keyframes lspms-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
	.lspms *, .lspms *::before, .lspms *::after {
		animation-duration: 0.01ms !important;
		transition-duration: 0.01ms !important;
	}
}

/* ==========================================================================
   9. FORMS
   ========================================================================== */

.lspms-form {
	display: grid;
	/*
	 * `min(100%, 200px)` rather than a bare `200px`.
	 *
	 * A bare minimum is a floor the track keeps even when the grid is narrower
	 * than the floor, so the tracks overflow their own container instead of
	 * collapsing to one column. Wrapping it in `min(100%, …)` caps the floor at
	 * the container's own width, which is the whole point of an auto-fit grid.
	 */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
	gap: var(--tl-space-4);
	align-items: end;
	/*
	 * An implicit column is worth nothing.
	 *
	 * `.lspms-form__grow` asks for `grid-column: span 2`. In a grid that
	 * auto-fit has narrowed to ONE column, `span 2` does not clamp — it invents
	 * a second, content-sized track, and the row then sticks out of the form.
	 * That is what the announcements screen did at 1280px: its form sits in a
	 * 277px column, so there was one real track, and every `__grow` field built
	 * itself another one and pushed the whole document 23px sideways.
	 *
	 * Pinning implicit columns to zero makes `span 2` degrade to "the one
	 * column there is". When the form is genuinely wide enough for two tracks,
	 * both are explicit, no implicit track is created, and this changes nothing.
	 *
	 * (`container-type: inline-size` was tried here first and was wrong: it
	 * applies inline-axis containment, so `.lspms-form--inline` — the same class
	 * used as a shrink-to-fit flex item — measured zero wide and spilled its
	 * button out of the daily report's toolbar.)
	 */
	grid-auto-columns: 0;
}

.lspms-form label {
	display: flex;
	flex-direction: column;
	gap: 5px;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
}

/*
 * The same column behaviour, available on its own.
 *
 * Used two ways: as an extra class on a form that is already `.lspms-form`,
 * where it is a no-op that states intent, and — the case that mattered — on a
 * plain `<div>` inside a settings panel, where it is the only thing asking for
 * columns. With no rule at all those divs were blocks, so the seven weekday
 * checkboxes and the tax fields ran one per line down the page.
 */
.lspms-form--grid {
	display: grid;
	/* Same capped floor, same zero implicit columns, same reasons as
	   `.lspms-form` above. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
	gap: var(--tl-space-4);
	align-items: end;
	grid-auto-columns: 0;
}

/*
 * A form that lives inside a table cell.
 *
 * Ten screens pass this modifier to the shared form helper — assign a cleaner,
 * void a payment, set a staff role, export a range — and it had no rule at all,
 * so every one of them inherited the `minmax(200px, 1fr)` data-entry grid and
 * put its select and its button on separate lines. On Housekeeping that made
 * each of thirteen room rows about 113px tall and pushed a one-screen board onto
 * three. A form in a cell is a row of controls, not a form to fill in.
 *
 * It was invisible to the class-coverage check because the class arrives as a
 * PHP argument to LSPMS_Actions::form_open() rather than in a class attribute;
 * that check now reads those arguments too.
 */
.lspms-form--row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--tl-space-2);
	grid-template-columns: none;
	margin: 0;
}

.lspms-form--row select,
.lspms-form--row input[type="text"],
.lspms-form--row input[type="number"],
.lspms-form--row input[type="date"] {
	width: auto;
	min-width: 108px;
	max-width: 180px;
}

.lspms-form--row label { flex-direction: row; align-items: center; gap: var(--tl-space-2); }

/*
 * One labelled field on its own line, inside the form grid.
 *
 * The bank panel needs a label, a control and a hint stacked in that order, and
 * needs to span the grid: an account number read a digit at a time must not be
 * squeezed into a half-width column beside something else.
 */
.lspms-form__row {
	grid-column: 1 / -1;
	display: grid;
	gap: 4px;
	margin: 0 0 var(--tl-space-3);
	max-width: 32rem;
}

.lspms-form__row input {
	width: 100%;
	max-width: 100%;
	font-variant-numeric: tabular-nums;
}

/*
 * A table's scroll box inside a form grid spans every column.
 *
 * FOUND BY LOOKING AT THE DEPLOYED SCREEN.
 *
 * `.lspms-form` is an auto-fit grid of 200px tracks, and a `.lspms-scroll`
 * dropped straight into it is a grid ITEM: it took one 240px track inside a
 * 1049px panel. The payment methods table was squeezed into that, its name
 * inputs rendered 37px wide (wide enough for "Ba"), and its Key column was
 * pushed out of sight behind a scrollbar nobody would think to use.
 *
 * The overflow rule was working exactly as designed. The box was simply the
 * wrong width, and no test that asks "does this scroll" can see that.
 */
.lspms-form > .lspms-scroll { grid-column: 1 / -1; }

.lspms-form__wide { grid-column: 1 / -1; }
.lspms-form__grow { grid-column: span 2; }

.lspms-form__actions {
	grid-column: 1 / -1;
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	padding-top: var(--tl-space-2);
	border-top: 1px solid var(--tl-border);
	margin-top: var(--tl-space-2);
}

/*
 * A checkbox and its caption, side by side.
 *
 * The selector is two classes deep on purpose. `.lspms-form label` above is
 * (0,1,1) and a bare `.lspms-form__check` is (0,1,0), so the `column` direction
 * meant for a field-with-caption beat the `row` wanted here — silently, because
 * losing a declaration produces no error. On the settings screen that turned the
 * seven weekday checkboxes into seven checkboxes each sitting above its own
 * centred day name, stacked down the middle of the panel.
 */
.lspms-form .lspms-form__check,
.lspms-form--grid .lspms-form__check,
.lspms-form__check {
	display: flex;
	flex-direction: row;
	align-items: center;
	gap: var(--tl-space-2);
	font-size: var(--tl-size-sm);
	color: var(--tl-text);
	/* `.lspms-form` aligns its cells to the end so labels and inputs sit on a
	   common baseline. A checkbox has no input below its label, so end-alignment
	   pushed it up and left its text stranded underneath. */
	align-self: center;
	justify-content: flex-start;
}

.lspms-form__check input[type="checkbox"] { margin: 0; flex: 0 0 auto; }
.lspms-form__check span { flex: 1 1 auto; }

/*
 * The filter bar that sits above a table or a chart.
 *
 * This is a different thing from `.lspms-form`, which is a data-entry grid that
 * wants equal columns. A filter bar is a row of small controls that should take
 * only the width they need and wrap when they run out — a period select, two
 * dates and an Apply button, not a form to fill in.
 *
 * It carried no rule at all until now, so it fell back to `display: block` and
 * the controls stacked full width: the period select ran to 440px and each date
 * input took a line of its own, which made the analytics header four rows tall.
 * Four screens emit this class — analytics, reservations, transactions and the
 * money views — so all four were affected.
 */
.lspms-filters {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: var(--tl-space-3);
	margin: 0 0 var(--tl-space-4);
}

.lspms-filters label {
	display: flex;
	flex-direction: column;
	gap: 5px;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	/* Grow to share slack, but never below a legible control. */
	flex: 0 1 auto;
	min-width: 0;
}

.lspms-filters select,
.lspms-filters input[type="date"],
.lspms-filters input[type="search"],
.lspms-filters input[type="text"] {
	/* A select sized to its longest option ("Custom range") rather than to the
	   whole panel. min-width keeps a date input from collapsing on a phone. */
	width: auto;
	min-width: 148px;
	max-width: 100%;
}

.lspms-filters .button {
	/* Sits on the same baseline as the controls, not on the label above them. */
	flex: 0 0 auto;
	align-self: flex-end;
}

@media ( max-width: 600px ) {
	/* Two controls per row rather than four cramped ones. */
	.lspms-filters label { flex: 1 1 45%; }
	.lspms-filters select,
	.lspms-filters input[type="date"] { width: 100%; min-width: 0; }
}

.lspms input[type="text"],
.lspms input[type="email"],
.lspms input[type="tel"],
.lspms input[type="number"],
.lspms input[type="date"],
.lspms input[type="search"],
.lspms input[type="password"],
.lspms select,
.lspms textarea {
	width: 100%;
	min-height: 36px;
	padding: 7px var(--tl-space-3);
	border: 1px solid var(--tl-border-strong);
	border-radius: var(--tl-radius-sm);
	background: var(--tl-surface);
	color: var(--tl-text);
	font-family: inherit;
	font-size: var(--tl-size-sm);
	line-height: 1.4;
	box-shadow: none;
	transition: border-color var(--tl-transition), box-shadow var(--tl-transition);
}

.lspms textarea { min-height: 84px; resize: vertical; }

.lspms input:focus,
.lspms select:focus,
.lspms textarea:focus {
	border-color: var(--tl-primary-500);
	box-shadow: var(--tl-ring);
	outline: none;
}

.lspms input[aria-invalid="true"],
.lspms select[aria-invalid="true"] {
	border-color: var(--tl-danger-500);
}

.lspms input::placeholder,
.lspms textarea::placeholder { color: var(--tl-text-subtle); }

.lspms-field__error {
	font-size: var(--tl-size-xs);
	color: var(--tl-danger-700);
	font-weight: var(--tl-weight-medium);
}

/* ==========================================================================
   10. NOTICES AND ALERTS
   ========================================================================== */

.lspms-notice,
.lspms-alert {
	display: flex;
	gap: var(--tl-space-3);
	padding: var(--tl-space-3) var(--tl-space-4);
	border-radius: var(--tl-radius);
	border: 1px solid var(--tl-border);
	border-left-width: 3px;
	background: var(--tl-surface);
	font-size: var(--tl-size-sm);
	margin: 0 0 var(--tl-space-4);
	line-height: var(--tl-lh-snug);
}

.lspms-notice--success { background: var(--tl-success-50); border-color: var(--tl-success-100); border-left-color: var(--tl-success-500); color: var(--tl-success-700); }
.lspms-notice--error   { background: var(--tl-danger-50);  border-color: var(--tl-danger-100);  border-left-color: var(--tl-danger-500);  color: var(--tl-danger-700); }
.lspms-notice--warning { background: var(--tl-warning-50); border-color: var(--tl-warning-100); border-left-color: var(--tl-warning-500); color: var(--tl-warning-700); }
.lspms-notice--info    { background: var(--tl-info-50);    border-color: var(--tl-info-100);    border-left-color: var(--tl-info-500);    color: var(--tl-info-700); }

/* ==========================================================================
   11. EMPTY STATES
   ========================================================================== */

.lspms-empty {
	text-align: center;
	padding: var(--tl-space-10) var(--tl-space-5);
	background: var(--tl-surface);
	border: 1px dashed var(--tl-border-strong);
	border-radius: var(--tl-radius-md);
	color: var(--tl-text-muted);
}

.lspms-empty p {
	margin: 0;
	font-size: var(--tl-size-base);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text);
}

.lspms-empty__hint {
	margin-top: var(--tl-space-2) !important;
	font-size: var(--tl-size-sm) !important;
	font-weight: var(--tl-weight-regular) !important;
	color: var(--tl-text-muted) !important;
	max-width: 46ch;
	margin-inline: auto;
}

/* ==========================================================================
   12. FILTER BAR
   ========================================================================== */

.lspms-filterbar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
}

.lspms-filterbar__item {
	display: inline-flex;
	align-items: center;
	gap: var(--tl-space-2);
	padding: 5px var(--tl-space-3);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius-full);
	background: var(--tl-surface);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	text-decoration: none;
	transition: all var(--tl-transition);
}

.lspms-filterbar__item:hover {
	border-color: var(--tl-neutral-400);
	color: var(--tl-text);
}

.lspms-filterbar__item.is-current {
	background: var(--tl-primary-600);
	border-color: var(--tl-primary-600);
	color: #fff;
}

.lspms-filterbar__item.is-empty { opacity: 0.5; }

.lspms-filterbar__count {
	display: inline-grid;
	place-items: center;
	min-width: 20px;
	height: 18px;
	padding: 0 5px;
	border-radius: var(--tl-radius-full);
	background: var(--tl-neutral-100);
	color: var(--tl-text-muted);
	font-size: 11px;
	font-weight: var(--tl-weight-semi);
	font-variant-numeric: tabular-nums;
}

.lspms-filterbar__item.is-current .lspms-filterbar__count {
	background: rgba(255, 255, 255, 0.22);
	color: #fff;
}

/* ==========================================================================
   13. ROOM BOARD
   ========================================================================== */

.lspms-board {
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-6);
}

.lspms-board__group { margin: 0; }

.lspms-board__title {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	margin: 0 0 var(--tl-space-3);
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-semi);
	text-transform: uppercase;
	letter-spacing: 0.06em;
	color: var(--tl-text-muted);
}

.lspms-board__count {
	display: inline-grid;
	place-items: center;
	min-width: 22px;
	height: 20px;
	padding: 0 6px;
	border-radius: var(--tl-radius-full);
	background: var(--tl-neutral-100);
	color: var(--tl-text-muted);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0;
}

.lspms-board__rooms {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
	gap: var(--tl-space-3);
}

.lspms-room {
	position: relative;
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	padding: var(--tl-space-4);
	padding-top: var(--tl-space-4);
	box-shadow: var(--tl-shadow-xs);
	transition: box-shadow var(--tl-transition), transform var(--tl-transition), border-color var(--tl-transition);
	overflow: hidden;
	min-height: 128px;
	display: flex;
	flex-direction: column;
	gap: 3px;
}

/* Room number on the left, state badge on the right, on one baseline. */
.lspms-room__top {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: var(--tl-space-2);
}

.lspms-room__top .lspms-room__name { margin: 0; min-width: 0; }
.lspms-room__top .lspms-badge { flex: 0 0 auto; }

/* Whether the stay is settled. Sits under the dates, where the desk reads it. */
.lspms-room__pay { margin: var(--tl-space-2) 0 0; }

.lspms-room:hover {
	box-shadow: var(--tl-shadow-md);
	transform: translateY(-1px);
	border-color: var(--tl-border-strong);
}

/* The state stripe. Colour AND position, so the board is readable in
   greyscale — the stripe is always the top edge, the shade tells you which. */
.lspms-room::before {
	content: "";
	position: absolute;
	inset: 0 0 auto 0;
	height: 3px;
	background: var(--tl-neutral-300);
}

.lspms-room--available::before   { background: var(--tl-state-available-mark); }
.lspms-room--occupied::before    { background: var(--tl-state-occupied-mark); }
.lspms-room--reserved::before    { background: var(--tl-state-reserved-mark); }
.lspms-room--dirty::before,
.lspms-room--cleaning::before    { background: var(--tl-state-dirty-mark); }
.lspms-room--maintenance::before,
.lspms-room--blocked::before,
.lspms-room--out::before         { background: var(--tl-state-blocked-mark); }

.lspms-room__name {
	margin: 0;
	font-size: var(--tl-size-md);
	font-weight: var(--tl-weight-semi);
	letter-spacing: -0.01em;
}

.lspms-room__type {
	margin: 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

.lspms-room__rate {
	margin: 0;
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
}

.lspms-room__guest {
	margin: var(--tl-space-2) 0 0;
	padding-top: var(--tl-space-2);
	border-top: 1px dashed var(--tl-border);
	font-size: var(--tl-size-xs);
	color: var(--tl-text);
	line-height: var(--tl-lh-snug);
}

.lspms-room__guest small { color: var(--tl-text-muted); }

.lspms-room__hk {
	margin-top: auto;
	padding-top: var(--tl-space-2);
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

/*
 * Quick actions.
 *
 * These replaced a single "Open booking" link that used a full-tile ::after
 * overlay to make the whole card clickable. With more than one action on a
 * tile that overlay becomes a trap — it sits on top of every other control —
 * so it is gone, and the buttons are the click targets.
 *
 * Each form here is a wrapper around one button, so the row lays out its
 * forms and its links identically: display: contents makes the form vanish
 * from the flex layout and the button become the flex item.
 */
.lspms-room__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin-top: var(--tl-space-3);
	padding-top: var(--tl-space-3);
	border-top: 1px solid var(--tl-border);
}

.lspms-room__actions .lspms-form--row { display: contents; }

/* ==========================================================================
   14. PER-ROOM ALLOCATION LIST  (multi-room operations)
   ========================================================================== */

.lspms-allocs {
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-3);
}

.lspms-alloc {
	display: grid;
	grid-template-columns: 1fr auto;
	gap: var(--tl-space-3) var(--tl-space-4);
	align-items: center;
	padding: var(--tl-space-4);
	border: 1px solid var(--tl-border);
	border-left-width: 3px;
	border-left-color: var(--tl-neutral-300);
	border-radius: var(--tl-radius);
	background: var(--tl-surface);
	transition: border-color var(--tl-transition), box-shadow var(--tl-transition);
}

.lspms-alloc:hover { box-shadow: var(--tl-shadow-sm); }

.lspms-alloc--checked_in  { border-left-color: var(--tl-state-occupied-mark); }
.lspms-alloc--confirmed   { border-left-color: var(--tl-state-reserved-mark); }
.lspms-alloc--pending     { border-left-color: var(--tl-state-pending-mark); }
.lspms-alloc--checked_out { border-left-color: var(--tl-state-closed-mark); }
.lspms-alloc--cancelled,
.lspms-alloc--no_show     { border-left-color: var(--tl-state-blocked-mark); }

.lspms-alloc__head {
	display: flex;
	align-items: center;
	gap: var(--tl-space-3);
	flex-wrap: wrap;
}

.lspms-alloc__room {
	margin: 0;
	font-size: var(--tl-size-md);
	font-weight: var(--tl-weight-semi);
}

.lspms-alloc__meta {
	margin: var(--tl-space-1) 0 0;
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-1) var(--tl-space-4);
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

.lspms-alloc__meta span { display: inline-flex; gap: 4px; }
.lspms-alloc__meta b { font-weight: var(--tl-weight-medium); color: var(--tl-text); font-variant-numeric: tabular-nums; }

.lspms-alloc__actions {
	display: flex;
	gap: var(--tl-space-2);
	flex-wrap: wrap;
	justify-content: flex-end;
}

/* The shared form helper emits .lspms-form, which is a grid everywhere else.
   Inside a row of actions it should just be a button. */
.lspms-alloc__actions form,
.lspms-alloc__actions .lspms-form {
	margin: 0;
	display: inline-flex;
	gap: var(--tl-space-2);
	grid-template-columns: none;
}

/* When every room shares the booking's dates, saying so once is clearer than
   repeating the same two dates on every row. */
.lspms-allocs__note {
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
	margin: 0 0 var(--tl-space-1);
}

/* ==========================================================================
   15. TABS
   ========================================================================== */

.lspms-tabs__nav {
	/* Containing block for what it clips — see the note on `.lspms-scroll`. */
	position: relative;
	display: flex;
	gap: var(--tl-space-1);
	border-bottom: 1px solid var(--tl-border);
	margin-bottom: var(--tl-space-5);
	overflow-x: auto;
}

.lspms-tabs__tab {
	padding: var(--tl-space-3) var(--tl-space-4);
	border: 0;
	background: none;
	font-family: inherit;
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	cursor: pointer;
	border-bottom: 2px solid transparent;
	margin-bottom: -1px;
	white-space: nowrap;
	transition: color var(--tl-transition), border-color var(--tl-transition);
}

.lspms-tabs__tab:hover { color: var(--tl-text); }

.lspms-tabs__tab.is-current,
.lspms-tabs__tab[aria-selected="true"] {
	color: var(--tl-primary-700);
	border-bottom-color: var(--tl-primary-600);
	font-weight: var(--tl-weight-semi);
}

/*
 * A tab panel spans the whole form.
 *
 * Settings puts all eight panels inside ONE form, deliberately: the sanitizer
 * resets whatever it is not sent, so eight separate forms would make saving one
 * section wipe the other seven. That form is `.lspms-form`, which is a
 * data-entry grid of `minmax(200px, 1fr)` columns — so each panel became a grid
 * item in a 250px track, and every settings screen rendered in a narrow strip
 * down the left of a 1049px form with the rest of the width empty.
 *
 * `:not([hidden])` is not decoration. The panels are hidden with the HTML
 * `hidden` attribute, and the browser's own `display: none` for it is a plain
 * author-level-beatable rule: writing `display: block` here unguarded would
 * un-hide all eight panels at once. Restricting the rule to panels that are not
 * hidden means the attribute keeps working exactly as the browser intends.
 */
.lspms-tabs__panel:not([hidden]) {
	display: block;
	grid-column: 1 / -1;
	min-width: 0;
}

/* ==========================================================================
   15b. THE REPORT PICKER
   ==========================================================================

   Twenty-odd reports in five groups. With no rule at all this was one column of
   plain underlined links running several screens deep, and choosing a report
   meant scrolling past every other one. Grouped columns let the whole catalogue
   be read at a glance, which is the only reason to group it in the first place.
   ========================================================================== */

.lspms-reportnav {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
	gap: var(--tl-space-5) var(--tl-space-6);
	margin-top: var(--tl-space-4);
}

.lspms-reportnav__group { min-width: 0; }

.lspms-reportnav__group h3 {
	margin: 0 0 var(--tl-space-2);
	padding-bottom: var(--tl-space-2);
	border-bottom: 1px solid var(--tl-border);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-reportnav__group ul {
	margin: 0;
	padding: 0;
	list-style: none;
}

.lspms-reportnav__group li { margin: 0; }

/* Specificity note: `.lspms a` is (0,1,1), so a single-class rule here would
   lose to it and the links would stay underlined blue. Two classes deep wins. */
.lspms-reportnav__group a {
	display: block;
	padding: 5px var(--tl-space-2);
	margin-left: calc(var(--tl-space-2) * -1);
	border-radius: var(--tl-radius-sm);
	font-size: var(--tl-size-sm);
	color: var(--tl-text);
	text-decoration: none;
	transition: background var(--tl-transition), color var(--tl-transition);
}

.lspms-reportnav__group a:hover,
.lspms-reportnav__group a:focus-visible {
	background: var(--tl-surface-2);
	color: var(--tl-primary-700);
}

.lspms-reportnav__group a.is-current {
	background: var(--tl-primary-50);
	color: var(--tl-primary-700);
	font-weight: var(--tl-weight-semi);
}

/* ==========================================================================
   16. TOASTS
   ========================================================================== */

.lspms-toasts {
	position: fixed;
	/* Bottom-right, clear of the sidebar and clear of the primary action row
	   at the bottom of a form. */
	right: var(--tl-space-5);
	bottom: var(--tl-space-5);
	z-index: 100000;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-2);
	max-width: min(380px, calc(100vw - 2rem));
	pointer-events: none;
}

.lspms-toast {
	display: flex;
	align-items: flex-start;
	gap: var(--tl-space-3);
	padding: var(--tl-space-3) var(--tl-space-4);
	border-radius: var(--tl-radius);
	background: var(--tl-neutral-900);
	color: #fff;
	font-size: var(--tl-size-sm);
	line-height: var(--tl-lh-snug);
	box-shadow: var(--tl-shadow-lg);
	pointer-events: auto;
	animation: lspms-toast-in 180ms cubic-bezier(0.16, 1, 0.3, 1);
	border-left: 3px solid var(--tl-neutral-500);
}

.lspms-toast--success { border-left-color: #46c07f; }
.lspms-toast--error   { border-left-color: #ef6a5a; }
.lspms-toast--warning { border-left-color: #e5a83a; }
.lspms-toast--info    { border-left-color: #5b9be0; }

.lspms-toast__close {
	margin-left: auto;
	background: none;
	border: 0;
	color: rgba(255, 255, 255, 0.6);
	cursor: pointer;
	font-size: 16px;
	line-height: 1;
	padding: 0 2px;
}

.lspms-toast__close:hover { color: #fff; }

@keyframes lspms-toast-in {
	from { opacity: 0; transform: translateY(8px); }
	to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   17. LEGEND
   ========================================================================== */

/* ==========================================================================
   18. RESPONSIVE
   ========================================================================== */

@media (max-width: 1280px) {
	.lspms__search { flex-basis: 300px; }
	.lspms__user-text { display: none; }
	.lspms__user { padding-right: 3px; }
}

@media (max-width: 1024px) {
	.lspms__content { padding: var(--tl-space-4) var(--tl-space-4) var(--tl-space-10); }
	.lspms-stats { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
	.lspms__quick-text { display: none; }
	.lspms__quick { padding: 0; width: 36px; justify-content: center; }
	.lspms__search-go { display: none; }
}

@media (max-width: 782px) {
	/* WordPress's admin bar grows from 32px to 46px at this width, and three
	   sticky offsets depend on it. */
	.lspms { top: 46px; min-height: calc(100vh - 46px); }
	.lspms__side, .lspms__top { top: 46px; }
	.lspms__side { height: calc(100vh - 46px); }
}

/* The sidebar becomes an off-canvas drawer rather than a squeezed column. */
@media (max-width: 900px) {

	.lspms__side {
		position: fixed;
		inset: 0 auto 0 0;
		z-index: 100001;
		height: 100vh;
		max-height: 100vh;
		transform: translateX(-100%);
		transition: transform var(--tl-transition);
		box-shadow: var(--tl-shadow-lg);
	}

	.lspms.is-menu-open .lspms__side { transform: none; }

	.lspms.is-menu-open::after {
		content: "";
		position: fixed;
		inset: 0;
		background: rgba(20, 24, 31, 0.45);
		z-index: 100000;
	}

	.lspms__menu-toggle { display: inline-flex; }

	.lspms__top {
		flex-wrap: wrap;
		height: auto;
		flex-basis: auto;
		padding: var(--tl-space-3) var(--tl-space-4);
		gap: var(--tl-space-3);
	}

	.lspms__top-title { order: 1; }
	.lspms__top-actions { order: 2; margin-left: auto; }
	.lspms__search { order: 3; flex: 1 1 100%; }
	.lspms__today { display: none; }
}

@media (max-width: 640px) {
	.lspms__content { padding: var(--tl-space-3); }
	.lspms-panel { padding: var(--tl-space-4); border-radius: var(--tl-radius); }
	.lspms-stats { grid-template-columns: repeat(2, 1fr); gap: var(--tl-space-2); }
	.lspms-stat__value { font-size: var(--tl-size-xl); }
	.lspms-board__rooms { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }

	.lspms-alloc { grid-template-columns: 1fr; }
	.lspms-alloc__actions { justify-content: stretch; }
	.lspms-alloc__actions .button { flex: 1 1 auto; }

	.lspms-form,
	.lspms-form--grid { grid-template-columns: 1fr; }
	.lspms-form__grow { grid-column: 1 / -1; }

	.lspms-toasts { left: var(--tl-space-3); right: var(--tl-space-3); bottom: var(--tl-space-3); max-width: none; }
}

/*
 * Tables on a phone. Rather than a horizontal scroll nobody discovers, each row
 * becomes a card and each cell is labelled from its column header, which the
 * views supply as a data-label attribute. Tables that have not been given
 * labels keep the scroll container, so nothing breaks while they are converted.
 */
@media (max-width: 640px) {
	.lspms-table.is-stacked thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }

	.lspms-table.is-stacked tbody tr {
		display: block;
		margin-bottom: var(--tl-space-3);
		border: 1px solid var(--tl-border);
		border-radius: var(--tl-radius);
		padding: var(--tl-space-2);
		background: var(--tl-surface);
		box-shadow: var(--tl-shadow-xs);
	}

	.lspms-table.is-stacked tbody td {
		display: flex;
		justify-content: space-between;
		gap: var(--tl-space-4);
		padding: 6px var(--tl-space-2);
		border-bottom: 1px dashed var(--tl-border);
		text-align: right;
	}

	.lspms-table.is-stacked tbody td:last-child { border-bottom: 0; }

	.lspms-table.is-stacked tbody td::before {
		content: attr(data-label);
		font-size: var(--tl-size-xs);
		font-weight: var(--tl-weight-semi);
		color: var(--tl-text-muted);
		text-transform: uppercase;
		letter-spacing: 0.04em;
		text-align: left;
		flex: 0 0 auto;
	}
}

/* ==========================================================================
   19. PRINT
   ========================================================================== */

@media print {
	.lspms__side,
	.lspms__top,
	.lspms-no-print,
	.lspms-toasts { display: none !important; }

	.lspms { margin: 0; display: block; }
	.lspms__content { padding: 0; max-width: none; }
	.lspms-panel { border: 0; box-shadow: none; padding: 0; page-break-inside: avoid; }
	.lspms-table thead th { position: static; background: none; }
}

/* ==========================================================================
   20. FOCUS AND MOTION SAFETY
   ========================================================================== */

.lspms :focus-visible {
	outline: 2px solid var(--tl-primary-500);
	outline-offset: 2px;
}

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

/* A skip link so keyboard users are not walked through the whole sidebar on
   every page load. */
.lspms__skip {
	position: absolute;
	left: -9999px;
	top: 0;
	z-index: 100002;
	padding: var(--tl-space-3) var(--tl-space-4);
	background: var(--tl-primary-700);
	color: #fff;
	border-radius: 0 0 var(--tl-radius) 0;
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
}

.lspms__skip:focus {
	left: 0;
	color: #fff;
}

/* ==========================================================================
   21. BRIEFING, KPIs, CHARTS AND REPORTS
   ==========================================================================
   Added for the analytics and daily-reporting work. Same tokens as everything
   above — no new colours were introduced for charts, because status colours
   measured as a categorical palette failed colour-blindness separation and a
   single-hue series has no such problem. */

/* -- The dashboard briefing ------------------------------------------------ */

/*
 * Two columns: the shift on the left as prose and facts, the queue of things
 * that need a decision on the right. The split is the point — reading what is
 * happening and deciding what to do about it are different jobs, and putting
 * them side by side is what makes this a console rather than a headline.
 */
.lspms-briefing {
	background: linear-gradient(135deg, var(--tl-primary-800), var(--tl-primary-900));
	color: rgba(255, 255, 255, 0.9);
	border-radius: var(--tl-radius-md);
	padding: var(--tl-space-6);
	display: grid;
	grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
	gap: var(--tl-space-6);
	align-items: start;
	box-shadow: var(--tl-shadow-sm);
}

.lspms-briefing__head { min-width: 0; }

.lspms-briefing__eyebrow {
	margin: 0 0 var(--tl-space-2);
	font-size: 0.6875rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--tl-primary-200);
}

/* The panel is dark, so the greeting and its link must beat the generic
   `.lspms h2` / `.lspms a` ink rules in section 3 — those are (0,1,1) and would
   otherwise paint dark text on a dark ground. Scoping under .lspms matches that
   specificity and wins on source order. */
.lspms .lspms-briefing__greeting {
	margin: 0;
	color: #fff;
	font-size: 1.5rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: -0.018em;
	line-height: 1.2;
}

.lspms-briefing__line {
	margin: 3px 0 0;
	font-size: var(--tl-size-sm);
	color: rgba(255, 255, 255, 0.62);
}

.lspms-briefing__headline {
	margin: var(--tl-space-4) 0 0;
	font-size: var(--tl-size-md);
	line-height: var(--tl-lh-snug);
	color: #fff;
	max-width: 46ch;
}

/* The facts list. Two columns of short sentences reads faster than one long
   column, and each line is one number somebody can go and check. */
.lspms-briefing__facts {
	list-style: none;
	margin: var(--tl-space-4) 0 0;
	padding: 0;
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
	gap: 6px var(--tl-space-5);
}

.lspms-briefing__facts li {
	position: relative;
	padding-left: var(--tl-space-4);
	font-size: var(--tl-size-sm);
	color: rgba(255, 255, 255, 0.8);
	line-height: var(--tl-lh-snug);
}

.lspms-briefing__facts li::before {
	content: "";
	position: absolute;
	left: 0;
	top: 0.62em;
	width: 5px;
	height: 5px;
	border-radius: 50%;
	background: var(--tl-primary-300);
}

.lspms-briefing__side {
	min-width: 0;
	border-left: 1px solid rgba(255, 255, 255, 0.12);
	padding-left: var(--tl-space-6);
	align-self: stretch;
}

.lspms-briefing__sidetitle {
	margin: 0 0 var(--tl-space-3);
	font-size: 0.6875rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.45);
}

.lspms-briefing__alerts {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.lspms-alertline {
	display: flex;
	align-items: center;
	gap: var(--tl-space-3);
	padding: var(--tl-space-2) var(--tl-space-3);
	border-radius: var(--tl-radius-sm);
	background: rgba(255, 255, 255, 0.08);
	font-size: var(--tl-size-sm);
	border-left: 3px solid rgba(255, 255, 255, 0.3);
}

/* Tone is carried by the bar AND by the wording of the sentence itself, so a
   reader who cannot separate the hues still knows which line is the urgent one. */
.lspms-alertline--critical { border-left-color: #ef6a5a; }
.lspms-alertline--danger   { border-left-color: #ef6a5a; }
.lspms-alertline--warning  { border-left-color: #e5a83a; }
.lspms-alertline--info     { border-left-color: #6fb2b4; }

.lspms-alertline__text {
	flex: 1 1 auto;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 1px;
}

/*
 * The severity, in words, above the sentence.
 *
 * Tinted to match its own bar so the two agree, but the word carries the
 * meaning on its own — which is the point. This sits on the dark briefing
 * panel, so the colours are the light ends of the ramps rather than the
 * mid-tones used on white.
 */
.lspms-alertline__level {
	font-size: 10px;
	font-weight: var(--tl-weight-bold);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: rgba(255, 255, 255, 0.55);
}

.lspms-alertline--critical .lspms-alertline__level { color: #ef8b7e; }
.lspms-alertline--danger   .lspms-alertline__level { color: #ef8b7e; }
.lspms-alertline--warning  .lspms-alertline__level { color: #e9bd6b; }
.lspms-alertline--info     .lspms-alertline__level { color: #9ccbcc; }

.lspms-alertline__meta {
	font-size: var(--tl-size-xs);
	color: rgba(255, 255, 255, 0.55);
}

.lspms .lspms-alertline__go {
	flex: 0 0 auto;
	color: #fff;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	text-decoration: none;
	padding: 5px 10px;
	border-radius: var(--tl-radius-full);
	background: rgba(255, 255, 255, 0.14);
	transition: background var(--tl-transition);
	white-space: nowrap;
}

.lspms .lspms-alertline__go:hover { background: rgba(255, 255, 255, 0.24); color: #fff; }

.lspms-briefing__clear {
	margin: 0;
	font-size: var(--tl-size-sm);
	color: rgba(255, 255, 255, 0.6);
	line-height: var(--tl-lh-snug);
}

/* -- KPI cards ------------------------------------------------------------- */

.lspms-kpis {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
	gap: var(--tl-space-3);
	margin-bottom: var(--tl-space-5);
}

.lspms-kpi {
	position: relative;
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	padding: var(--tl-space-4);
	box-shadow: var(--tl-shadow-xs);
	overflow: hidden;
}

.lspms-kpi::before {
	content: "";
	position: absolute;
	inset: 0 auto 0 0;
	width: 3px;
	background: var(--tl-neutral-200);
}

.lspms-kpi--good::before { background: var(--tl-success-500); }
.lspms-kpi--warn::before { background: var(--tl-warning-500); }
.lspms-kpi--bad::before  { background: var(--tl-danger-500); }

.lspms-kpi__label {
	margin: 0;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
}

.lspms-kpi__value {
	margin: 4px 0 0;
	font-size: var(--tl-size-2xl);
	font-weight: var(--tl-weight-semi);
	line-height: 1.1;
	letter-spacing: -0.02em;
	font-variant-numeric: tabular-nums;
}

.lspms-kpi__note,
.lspms-kpi__delta {
	margin: 6px 0 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-subtle);
	display: flex;
	align-items: center;
	gap: 4px;
}

.lspms-kpi__delta.is-good { color: var(--tl-success-700); }
.lspms-kpi__delta.is-bad  { color: var(--tl-danger-700); }

/* -- Charts ---------------------------------------------------------------- */

.lspms-chart { margin: 0; }

.lspms-chart figcaption {
	display: flex;
	flex-direction: column;
	gap: 2px;
	margin-bottom: var(--tl-space-4);
}

.lspms-chart__title {
	font-size: var(--tl-size-md);
	font-weight: var(--tl-weight-semi);
	color: var(--tl-text);
}

.lspms-chart__hint {
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

.lspms-chart__plot {
	width: 100%;
	/* A fixed block height with a stretching viewBox: the chart fills whatever
	   column it is given without the caller doing arithmetic. */
	height: 190px;
}

.lspms-chart__svg { width: 100%; height: 100%; display: block; overflow: visible; }

.lspms-chart__empty {
	padding: var(--tl-space-8) var(--tl-space-4);
	text-align: center;
	border: 1px dashed var(--tl-border-strong);
	border-radius: var(--tl-radius);
	color: var(--tl-text-muted);
	font-size: var(--tl-size-sm);
}

.lspms-chart__data { margin-top: var(--tl-space-4); }

.lspms-chart__data summary {
	cursor: pointer;
	font-size: var(--tl-size-xs);
	color: var(--tl-primary-600);
	font-weight: var(--tl-weight-medium);
	padding: 4px 0;
}

.lspms-chart__data[open] summary { margin-bottom: var(--tl-space-3); }

/* -- Meter (ranked room revenue) ------------------------------------------ */

.lspms-meter {
	display: block;
	width: 100%;
	min-width: 60px;
	height: 6px;
	border-radius: var(--tl-radius-full);
	background: var(--tl-neutral-100);
	overflow: hidden;
}

.lspms-meter__fill {
	display: block;
	height: 100%;
	border-radius: var(--tl-radius-full);
	background: var(--tl-primary-500);
}

/* -- Range bar ------------------------------------------------------------- */

.lspms-rangebar {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-3);
	align-items: center;
	justify-content: space-between;
}

.lspms-rangebar__custom {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	flex-wrap: wrap;
}

.lspms-rangebar__custom input[type="date"] { width: auto; min-width: 150px; }
.lspms-rangebar__custom label { font-size: var(--tl-size-xs); color: var(--tl-text-muted); }

.lspms-rangebar__actions { display: flex; gap: var(--tl-space-2); flex-wrap: wrap; }

/* -- Insights -------------------------------------------------------------- */

.lspms-insights {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-2);
}

.lspms-insight {
	padding: var(--tl-space-3) var(--tl-space-4);
	border: 1px solid var(--tl-border);
	border-left-width: 3px;
	border-radius: var(--tl-radius-sm);
	background: var(--tl-surface-2);
	font-size: var(--tl-size-sm);
	line-height: var(--tl-lh-snug);
}

.lspms-insight--good    { border-left-color: var(--tl-success-500); }
.lspms-insight--warning { border-left-color: var(--tl-warning-500); }
.lspms-insight--neutral { border-left-color: var(--tl-neutral-300); }

/* -- A finding about one room, with its reasoning ------------------------- */

/*
 * An insight is one sentence. A finding is three, and they do different jobs:
 * WHY is the evidence, IMPACT is arithmetic over what happened, ACTION is the
 * thing to go and do. They are laid out as three labelled lines rather than a
 * paragraph so a reader can take the action without reading the reasoning, and
 * check the reasoning without hunting for it.
 *
 * The label sits inline at the start of its line rather than in a column, so
 * the text wraps under itself on a narrow screen instead of into a two-column
 * layout that needs a horizontal scrollbar to read one sentence.
 */
.lspms-findings {
	list-style: none;
	margin: var(--tl-space-3) 0 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-3);
}

.lspms-finding {
	padding: var(--tl-space-4);
	border: 1px solid var(--tl-border);
	border-left: 3px solid var(--tl-warning-500);
	border-radius: var(--tl-radius-sm);
	background: var(--tl-surface-2);
}

.lspms-finding__room {
	margin: 0 0 var(--tl-space-2);
	font-weight: 600;
	font-size: var(--tl-size-base);
	color: var(--tl-text);
}

.lspms-finding__why,
.lspms-finding__impact,
.lspms-finding__action {
	margin: 0 0 var(--tl-space-2);
	font-size: var(--tl-size-sm);
	line-height: var(--tl-lh-snug);
	color: var(--tl-text-muted);
}

.lspms-finding__action { margin-bottom: 0; color: var(--tl-text); }

.lspms-finding__tag {
	display: inline-block;
	margin-right: var(--tl-space-2);
	padding: 1px 6px;
	border-radius: var(--tl-radius-sm);
	background: var(--tl-neutral-100);
	color: var(--tl-text-muted);
	font-size: var(--tl-size-xs);
	font-weight: 600;
	text-transform: uppercase;
	letter-spacing: 0.04em;
	vertical-align: baseline;
}

/* -- The printed report ---------------------------------------------------- */

.lspms-report__head {
	margin-bottom: var(--tl-space-5);
	padding-bottom: var(--tl-space-4);
	border-bottom: 2px solid var(--tl-neutral-200);
}

.lspms-report__head h2 { margin: 0; font-size: var(--tl-size-xl); }
.lspms-report__head p { margin: 2px 0 0; color: var(--tl-text-muted); font-size: var(--tl-size-sm); }

@media (max-width: 900px) {
	.lspms-briefing { grid-template-columns: minmax(0, 1fr); gap: var(--tl-space-5); padding: var(--tl-space-4); }
	.lspms-briefing__side { border-left: 0; border-top: 1px solid rgba(255, 255, 255, 0.12); padding-left: 0; padding-top: var(--tl-space-4); }
	.lspms-briefing__facts { grid-template-columns: minmax(0, 1fr); }
	.lspms-briefing__headline { margin-top: var(--tl-space-3); font-size: var(--tl-size-base); }
}

@media (max-width: 640px) {
	.lspms-kpis { grid-template-columns: repeat(2, 1fr); gap: var(--tl-space-2); }
	.lspms-kpi { padding: var(--tl-space-3); }
	.lspms-kpi__value { font-size: var(--tl-size-xl); }
	.lspms-chart__plot { height: 168px; }
	.lspms-rangebar { flex-direction: column; align-items: stretch; }
}

@media print {
	.lspms-briefing { background: none; color: #000; border: 1px solid #ccc; }
	.lspms .lspms-briefing__greeting, .lspms .lspms-briefing__headline { color: #000; }
	.lspms .lspms-alertline__go { color: #000; }
	.lspms-chart__data { display: none; }
}

/* A second-level heading inside a panel that already has its own heading —
   replaces a style="margin-top" attribute that was doing this inline. */
.lspms-subheading { margin-top: var(--tl-space-5); }

/* ==========================================================================
   22. LAYOUT PRIMITIVES
   ==========================================================================

   A twelve-column grid and a small set of section wrappers, so a screen is
   composed from named regions instead of each view inventing its own widths.
   Everything below uses `gap`; nothing uses per-element margins.
   ========================================================================== */

.lspms-grid {
	display: grid;
	grid-template-columns: repeat(12, minmax(0, 1fr));
	gap: var(--tl-space-5);
	align-items: start;
}

.lspms-col--3  { grid-column: span 3; min-width: 0; }
.lspms-col--4  { grid-column: span 4; min-width: 0; }
.lspms-col--5  { grid-column: span 5; min-width: 0; }
.lspms-col--6  { grid-column: span 6; min-width: 0; }
.lspms-col--7  { grid-column: span 7; min-width: 0; }
.lspms-col--8  { grid-column: span 8; min-width: 0; }
.lspms-col--9  { grid-column: span 9; min-width: 0; }
.lspms-col--12 { grid-column: 1 / -1; min-width: 0; }

@media (max-width: 1180px) {
	.lspms-col--3 { grid-column: span 6; }
	.lspms-col--4 { grid-column: span 6; }
	.lspms-col--5 { grid-column: span 12; }
	.lspms-col--7 { grid-column: span 12; }
	.lspms-col--8 { grid-column: span 12; }
	.lspms-col--9 { grid-column: span 12; }
}

@media (max-width: 720px) {
	.lspms-grid { gap: var(--tl-space-4); }
	.lspms-col--3,
	.lspms-col--4,
	.lspms-col--6 { grid-column: 1 / -1; }
}

/* A titled region of a screen: a heading rail, then its content. */
.lspms-section {
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-4);
}

.lspms-rail {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--tl-space-4);
	flex-wrap: wrap;
}

.lspms-rail__text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.lspms .lspms-rail h2 {
	margin: 0;
	font-size: 1.0625rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: -0.008em;
}

.lspms-rail p {
	margin: 0;
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

.lspms-rail__actions {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	flex: 0 0 auto;
	flex-wrap: wrap;
}

/* An eyebrow above a region — the section's kind, not its title. */
.lspms-eyebrow {
	font-size: 0.6875rem;
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--tl-text-subtle);
	margin: 0;
}

/* ==========================================================================
   23. METRIC CARDS
   ==========================================================================

   Compact by design. The screenshots showed KPI cards nearly as tall as a
   panel, which made eight of them a full screen of almost no information. A
   metric is a label, a number, one line of context and an optional delta —
   nothing else earns the vertical space.
   ========================================================================== */

/*
 * The KPI grid.
 *
 * `container-type: inline-size` is what lets the cards inside react to how much
 * room the GRID has rather than how wide the window is. That distinction is the
 * whole problem here: on a 1024px tablet the WordPress menu and the application
 * sidebar take 420px between them, so a "desktop" viewport leaves a 560px
 * content column — and a media query, which only ever sees 1024, has no way to
 * know that.
 *
 * 200px rather than 178px as the minimum: at 178 a 560px column made three
 * tracks of 180px, and "TSh 175,000" at 1.5rem does not fit in 148px of usable
 * width. It broke across two lines and the four cards came out 3 + 1 with
 * mismatched heights.
 */
.lspms-metrics {
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
	gap: var(--tl-space-3);
	container-type: inline-size;
}

/* Four across at desktop widths, whatever the count, so two rows of four line
   up as a block rather than as a ragged 5 + 3. */
@media (min-width: 1100px) {
	.lspms-metrics--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
	.lspms-metrics--6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

.lspms-metric {
	background: var(--tl-surface);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	padding: var(--tl-space-3) var(--tl-space-4) var(--tl-space-4);
	display: flex;
	flex-direction: column;
	gap: 2px;
	position: relative;
	min-width: 0;
	box-shadow: var(--tl-shadow-xs);
}

.lspms-metric__top {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--tl-space-2);
}

.lspms-metric__label {
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text-muted);
	letter-spacing: 0.01em;
	min-width: 0;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.lspms-metric__icon {
	flex: 0 0 22px;
	width: 22px;
	height: 22px;
	display: grid;
	place-items: center;
	border-radius: var(--tl-radius-sm);
	background: var(--tl-neutral-100);
	color: var(--tl-text-subtle);
}

.lspms-metric__icon .dashicons { font-size: 14px; width: 14px; height: 14px; }

.lspms-metric__value {
	font-size: 1.5rem;
	font-weight: var(--tl-weight-semi);
	line-height: 1.15;
	letter-spacing: -0.022em;
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
	word-break: break-word;
}

/*
 * A narrow grid gets a smaller figure.
 *
 * Stepping the type down is the right answer rather than letting the figure
 * wrap: a currency amount broken across two lines is harder to read than the
 * same amount one step smaller, and it drags the card taller than its
 * neighbours so a row of four stops being a row. Two steps, because a lodge
 * with millions in a month needs the second one.
 *
 * A browser without container queries keeps 1.5rem and wraps as it did before —
 * degraded, not broken.
 */
@container ( max-width: 700px ) {
	.lspms-metric__value { font-size: 1.3rem; }
}

@container ( max-width: 460px ) {
	.lspms-metric__value { font-size: 1.15rem; }
	.lspms-metric__note { font-size: 11px; }
}

.lspms-metric__note {
	font-size: var(--tl-size-xs);
	color: var(--tl-text-subtle);
	line-height: var(--tl-lh-snug);
}

/* Tone is a left rule plus the note's ink — never the number's colour, which
   would make the figure itself hard to read at a glance. */
.lspms-metric--good    { border-left: 3px solid var(--tl-success-500); }
.lspms-metric--warn    { border-left: 3px solid var(--tl-warning-500); }
.lspms-metric--bad     { border-left: 3px solid var(--tl-danger-500); }
.lspms-metric--primary { border-left: 3px solid var(--tl-primary-500); }

.lspms-metric__delta {
	display: inline-flex;
	align-items: center;
	gap: 4px;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	margin-top: 2px;
}

.lspms-metric__delta--up   { color: var(--tl-success-700); }
.lspms-metric__delta--down { color: var(--tl-danger-700); }
.lspms-metric__delta--flat { color: var(--tl-text-subtle); }
.lspms-metric__delta .dashicons { font-size: 13px; width: 13px; height: 13px; }

/* ==========================================================================
   24. ATTENTION LIST
   ==========================================================================

   The dashboard's "needs attention" queue. Each row is a severity mark, a
   sentence, the record it concerns, and the one action that resolves it. A row
   with no action would be a notification, not a task, so it does not appear.
   ========================================================================== */

.lspms-attention {
	list-style: none;
	margin: 0;
	padding: 0;
	display: flex;
	flex-direction: column;
	gap: 1px;
	background: var(--tl-border);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	overflow: hidden;
}

.lspms-attention__item {
	background: var(--tl-surface);
	display: grid;
	grid-template-columns: 4px minmax(0, 1fr) auto;
	align-items: center;
	gap: 0 var(--tl-space-4);
	padding: 0;
}

.lspms-attention__bar { align-self: stretch; background: var(--tl-neutral-300); }
.lspms-attention__item--critical .lspms-attention__bar { background: var(--tl-danger-500); }
.lspms-attention__item--warning  .lspms-attention__bar { background: var(--tl-warning-500); }
.lspms-attention__item--info     .lspms-attention__bar { background: var(--tl-primary-400); }

.lspms-attention__body {
	padding: var(--tl-space-3) 0;
	display: flex;
	flex-direction: column;
	gap: 2px;
	min-width: 0;
}

.lspms-attention__text {
	font-size: var(--tl-size-sm);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-text);
}

.lspms-attention__meta {
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
	display: flex;
	flex-wrap: wrap;
	gap: 4px var(--tl-space-3);
}

.lspms-attention__meta .lspms-ref {
	font-family: var(--tl-font-num);
	font-size: 0.75rem;
	letter-spacing: -0.01em;
}

.lspms-attention__go { padding-right: var(--tl-space-4); flex: 0 0 auto; }

@media (max-width: 640px) {
	.lspms-attention__item {
		grid-template-columns: 4px minmax(0, 1fr);
		gap: 0 var(--tl-space-3);
	}
	.lspms-attention__go {
		grid-column: 2;
		padding: 0 var(--tl-space-3) var(--tl-space-3) 0;
	}
}

/* ==========================================================================
   25. DONUTS AND LEGENDS
   ==========================================================================

   The four slice steps are far enough apart to be told apart (measured: worst
   adjacent pair ΔE 17.0 normal, 15.2 protan), but the palest two sit under 3:1
   against the panel. So a legend is not decoration here — it is the relief that
   makes the identity readable, and it always ships.
   ========================================================================== */

.lspms-chart__donut {
	display: flex;
	align-items: center;
	gap: var(--tl-space-5);
	flex-wrap: wrap;
}

.lspms-chart__ring {
	flex: 0 0 auto;
	width: 168px;
	max-width: 100%;
}

.lspms-chart__ring svg { width: 100%; height: auto; display: block; }

.lspms-legend {
	list-style: none;
	margin: 0;
	padding: 0;
	flex: 1 1 190px;
	min-width: 0;
	display: flex;
	flex-direction: column;
	gap: 6px;
}

.lspms-legend li {
	display: flex;
	align-items: baseline;
	gap: var(--tl-space-2);
	font-size: var(--tl-size-sm);
	min-width: 0;
}

.lspms-legend__swatch {
	flex: 0 0 10px;
	width: 10px;
	height: 10px;
	border-radius: 2px;
	/* A hairline, so the palest step still has an edge against the panel. */
	box-shadow: inset 0 0 0 1px rgba(20, 24, 31, 0.14);
	align-self: center;
}

.lspms-legend__label {
	flex: 1 1 auto;
	min-width: 0;
	color: var(--tl-text-muted);
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.lspms-legend__value {
	flex: 0 0 auto;
	font-weight: var(--tl-weight-semi);
	font-variant-numeric: tabular-nums;
	color: var(--tl-text);
}

.lspms-legend__value small {
	font-weight: var(--tl-weight-regular);
	color: var(--tl-text-subtle);
	margin-left: 5px;
}

.lspms-legend--inline {
	flex-direction: row;
	flex-wrap: wrap;
	gap: var(--tl-space-4);
	margin-bottom: var(--tl-space-3);
}

.lspms-legend--inline li { flex: 0 0 auto; }
.lspms-legend--inline .lspms-legend__label { flex: 0 0 auto; color: var(--tl-text); }

@media (max-width: 520px) {
	.lspms-chart__donut { flex-direction: column; align-items: stretch; }
	.lspms-chart__ring { width: 148px; margin-inline: auto; }
}

/* ==========================================================================
   26. ROW ACTIONS AND SMALL PIECES
   ========================================================================== */

.lspms-rowactions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: 6px;
}

/*
 * A row of controls that belongs on one line when there is room for one line.
 *
 * `inline-flex` with no wrap was the whole rule, and a flex row that cannot
 * wrap has no narrow layout at all: the seven weekday checkboxes and the Save
 * button on the Rates screen measured 642px inside a 336px panel and scrolled
 * the entire document sideways by 311px at 375px wide — the worst overflow in
 * the system, and invisible on a desktop where the row happens to fit.
 *
 * Wrapping is the fix, and it costs nothing at full width: the row still sits
 * on one line whenever one line is wide enough. `align-items: center` keeps the
 * button on the same baseline as the boxes once they wrap onto two rows.
 */
.lspms-form--inline {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	margin: 0;
	gap: var(--tl-space-2) var(--tl-space-3);
}

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

.lspms-ref {
	font-family: var(--tl-font-num);
	font-size: 0.8125rem;
	letter-spacing: -0.01em;
}

/* ==========================================================================
   27. THE CALENDAR GRID
   ==========================================================================

   Rooms down, dates across. The table is wide by construction — thirteen rooms
   against thirty days is not something that fits a phone — so it scrolls inside
   its own box. Without this it pushed the whole application sideways at 375px,
   which the table-wrapping check missed because this table is .lspms-cal rather
   than .lspms-table.
   ========================================================================== */

.lspms-calwrap {
	/* Containing block for its own absolutely positioned descendants, so they
	   are clipped by this scroller rather than pushing the page. See the note
	   on `.lspms-scroll`. */
	position: relative;
	overflow-x: auto;
	overscroll-behavior-x: contain;
	-webkit-overflow-scrolling: touch;
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	background: var(--tl-surface);
}

.lspms-cal {
	min-width: 860px;
	width: 100%;
	border-collapse: collapse;
	font-size: var(--tl-size-sm);
	table-layout: fixed;
}

/* The room column stays put while the dates scroll under it — otherwise you
   lose track of which row you are reading two weeks in. */
.lspms-cal__room {
	position: sticky;
	left: 0;
	z-index: 2;
	background: var(--tl-surface);
	box-shadow: 1px 0 0 var(--tl-border);
	/* Fixed, so fourteen date columns divide the rest evenly rather than being
	   squeezed by whichever room has the longest name. */
	width: 132px;
	text-align: left;
	padding: var(--tl-space-2) var(--tl-space-3);
	font-weight: var(--tl-weight-medium);
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

/*
 * The date header.
 *
 * Three lines — weekday, number, month — stacked and centred. Written as a run
 * of inline spans it came out as one unbroken sentence across the top of the
 * table ("Tue 25 Aug Wed 26 Aug Thu 27 Aug…") with nothing tying a date to the
 * column beneath it.
 */
.lspms-cal thead th {
	padding: var(--tl-space-2) 4px;
	border-bottom: 1px solid var(--tl-border);
	background: var(--tl-surface-2);
	font-weight: var(--tl-weight-medium);
	line-height: 1.15;
	text-align: center;
	vertical-align: bottom;
}

.lspms-cal thead th span,
.lspms-cal thead th small {
	display: block;
	font-size: 10px;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--tl-text-subtle);
}

.lspms-cal thead th strong {
	display: block;
	font-size: var(--tl-size-base);
	font-weight: var(--tl-weight-semi);
	color: var(--tl-text);
}

.lspms-cal thead th.is-today {
	background: var(--tl-primary-50);
	box-shadow: inset 0 -2px 0 var(--tl-primary-500);
}

.lspms-cal thead th.is-today strong { color: var(--tl-primary-700); }

/* Every night is a cell of the same height, so a row reads as a strip. */
.lspms-cal__cell {
	height: 34px;
	padding: 3px 0;
	border-bottom: 1px solid var(--tl-neutral-100);
	/* A hairline between nights, drawn inside the cell so it does not interrupt
	   a stay bar that runs across several of them. */
	box-shadow: inset -1px 0 0 var(--tl-neutral-100);
	vertical-align: middle;
}

.lspms-cal__cell.is-free { background: var(--tl-surface); }
.lspms-cal__cell.is-today { background: var(--tl-primary-50); }

/*
 * A stay.
 *
 * The fill is on the CELL and the label is on the first night only, so several
 * consecutive nights read as one continuous band rather than as a row of
 * repeated two-letter stubs. The rounded ends mark where the stay actually
 * starts and finishes inside the window; a stay that runs past either edge
 * stays square, which is how you can tell it continues.
 */
.lspms-cal__cell.is-booked { background: var(--tl-primary-200); }
.lspms-cal__cell.is-booked.is-pending { background: var(--tl-warning-100); }
.lspms-cal__cell.is-booked.is-confirmed { background: var(--tl-primary-200); }
.lspms-cal__cell.is-booked.is-checked_in { background: var(--tl-info-100); }

.lspms-cal__cell.is-booked.is-start { border-radius: var(--tl-radius-sm) 0 0 var(--tl-radius-sm); }
.lspms-cal__cell.is-booked.is-end { border-radius: 0 var(--tl-radius-sm) var(--tl-radius-sm) 0; }
.lspms-cal__cell.is-booked.is-start.is-end { border-radius: var(--tl-radius-sm); }

/* No inner hairline inside a stay — it would cut the band into nights again. */
.lspms-cal__cell.is-booked { box-shadow: none; }

/*
 * The guest's name, allowed to overflow its own cell.
 *
 * A one-night stay is about 50px wide and no useful name fits in that, so the
 * label is left to run across the nights that follow it. `overflow: visible` on
 * a table cell is the only way to do that without absolute positioning and a
 * measured pixel offset, which would need JavaScript and would be wrong the
 * moment somebody resized the window.
 */
.lspms-cal__stay {
	display: block;
	padding-left: 6px;
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-medium);
	color: var(--tl-primary-800);
	text-decoration: none;
	white-space: nowrap;
	overflow: visible;
	line-height: 28px;
}

.lspms-cal__cell.is-booked { overflow: visible; }
.lspms-cal__stay:hover { color: var(--tl-primary-900); text-decoration: underline; }

/*
 * The calendar key.
 *
 * Named `callegend`, not `legend`. `.lspms-legend` is the chart legend — a
 * stacked column — and the two rules collided: this key rendered as four lines
 * down the page instead of one row of four swatches. Both were `display: flex`,
 * so only the direction gave it away.
 */
.lspms-callegend {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--tl-space-2) var(--tl-space-5);
	margin-top: var(--tl-space-3);
	font-size: var(--tl-size-xs);
	color: var(--tl-text-muted);
}

.lspms-callegend__key {
	display: inline-block;
	width: 14px;
	height: 14px;
	border-radius: 3px;
	border: 1px solid var(--tl-border);
	background: var(--tl-surface);
	vertical-align: -3px;
	margin-right: 6px;
}

.lspms-callegend__key.is-free { background: var(--tl-surface); }
.lspms-callegend__key.is-pending { background: var(--tl-warning-100); border-color: var(--tl-warning-500); }
.lspms-callegend__key.is-confirmed { background: var(--tl-primary-200); border-color: var(--tl-primary-500); }
.lspms-callegend__key.is-checked_in { background: var(--tl-info-100); border-color: var(--tl-info-500); }

/* The Earlier / Today / Later row. */
.lspms-calnav {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin: 0 0 var(--tl-space-4);
}

/* ==========================================================================
   THE WEBSITE CONTENT EDITOR

   Thirty-odd controls generated from the block schema, grouped into six
   fieldsets. The styling here is doing one job: making a long form legible,
   because a long form that is not grouped is a form nobody finishes.
   ========================================================================== */

/*
 * A FIELDSET IS A ROW OF THE FORM, NOT A CELL OF IT
 *
 * .lspms-form is itself an auto-fit grid, which is right for the short
 * two-and-three-field forms everywhere else in this system. Dropping six
 * fieldsets into it laid them out as six grid ITEMS in two ragged columns of
 * wildly unequal height — measured on the live screen, "How it looks" started
 * 1,179px below "Size on each screen" beside it — and the form read as a pile
 * rather than a sequence. Each fieldset spans the whole row so the six groups
 * are read in the order they are written.
 *
 * `1 / -1` and not `span 6`: -1 is the end of the explicit grid however many
 * tracks auto-fit generated, whereas a span longer than the track count
 * invents implicit columns, which is a bug this project has already shipped
 * once.
 */
.lspms-form .lspms-fieldset { grid-column: 1 / -1; }

.lspms-fieldset {
	margin: 0 0 var(--tl-space-6);
	padding: var(--tl-space-5);
	border: 1px solid var(--tl-border);
	border-radius: var(--tl-radius);
	background: var(--tl-surface);
	/*
	 * And a grid of its own, so short controls sit side by side instead of
	 * each taking a line. The min() floor is what stops a 220px track
	 * overflowing a container narrower than 220px — the same fix the rest of
	 * this stylesheet uses, for the same reason.
	 */
	display: grid;
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
	gap: var(--tl-space-4);
	align-items: start;
	/* A fieldset's default min-width is its content, which is what lets one
	   long select push a form wider than its column. */
	min-width: 0;
}

/* Anything that needs the full width of its fieldset: the long text areas and
   the three-across responsive rows. */
.lspms-fieldset legend,
.lspms-fieldset .lspms-hint,
.lspms-fieldset .lspms-field--responsive { grid-column: 1 / -1; }

.lspms-fieldset label:has( textarea ) { grid-column: 1 / -1; }

.lspms-fieldset legend {
	padding: 0 var(--tl-space-2);
	font-size: 0.72rem;
	font-weight: 700;
	letter-spacing: 0.09em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-field { min-width: 0; }

.lspms-field small { display: block; margin-bottom: 4px; font-size: 0.78rem; line-height: 1.4; }

/*
 * A field the chosen block type does not render.
 *
 * Marked rather than hidden. Hiding it would make the form change shape as
 * somebody switches type, and removing it would throw away what they had
 * already typed — so it stays, marked as not in use, and is still saved.
 *
 * NOT dimmed with opacity, which is where this started. These are working
 * inputs somebody can still type into, and putting a functional control at 45%
 * opacity puts its label and its own text below the contrast floor — an
 * accessibility fault introduced to convey something a word conveys better.
 * The note says it instead, and the tinted panel carries it at a glance.
 */
.lspms-field--unused {
	padding: var(--tl-space-3);
	border-radius: var(--tl-radius-sm);
	background: var(--tl-neutral-50);
	border: 1px dashed var(--tl-border);
}

/* The words themselves are printed by the view, not by `content:` — a CSS
   string cannot go through the translation layer, and every other word in this
   interface does. */
.lspms-field__unused {
	display: block;
	margin-top: 4px;
	font-size: 0.74rem;
	font-style: italic;
	color: var(--tl-text-muted);
}

.lspms-field--responsive {
	padding-top: var(--tl-space-3);
	border-top: 1px dashed var(--tl-border);
}

.lspms-field__label {
	display: block;
	font-size: 0.82rem;
	font-weight: 600;
	margin-bottom: 4px;
}

.lspms-field__devices {
	display: grid;
	/* min(100%, …) so the three boxes stack rather than overflow on a phone. */
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 90px), 1fr));
	gap: var(--tl-space-3);
	margin-top: var(--tl-space-2);
}

.lspms-field__device {
	display: flex;
	flex-direction: column;
	gap: 3px;
	min-width: 0;
}

.lspms-field__device span {
	font-size: 0.7rem;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-field__device input[type="number"] { width: 100%; min-width: 0; }
.lspms-field__device input[type="checkbox"] { align-self: flex-start; }

.lspms-subhead {
	margin: var(--tl-space-6) 0 var(--tl-space-3);
	font-size: 0.78rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-subhead:first-of-type { margin-top: 0; }

/* ==========================================================================
   COMPONENTS THAT WERE SHIPPING WITHOUT RULES

   Every block below is markup this application has been printing for some time
   with nothing styling it. They were found by pms-test/css-check.py, which was
   written after the homepage hero's rules were deleted by accident and nothing
   in the suite noticed, because every check was about text or behaviour and
   none of them asked whether a component had any appearance at all.

   Nothing here changes what any screen says or does. It gives markup that was
   rendering as browser defaults the same treatment as the rest of the system.
   ========================================================================== */

/* -- Detail tables: a label column and a value column ---------------------- */

/*
 * The two-column shape used by the booking summary, the bill, the receipt and
 * the invoice. Without this the row header takes half the table and the value
 * floats in the middle of the panel, which is why every one of these screens
 * looked loose next to the list tables beside them.
 */
/*
 * Fixed layout is what makes the 38% below actually hold. With the automatic
 * algorithm the browser sizes the label column from its longest word, so
 * "Check-out" and "Received by" produced two different layouts on two screens
 * that are meant to read as the same document.
 */
.lspms-table--detail { table-layout: fixed; }

.lspms-table--detail th[scope="row"],
.lspms-table--detail tbody th {
	width: 38%;
	max-width: 15rem;
	font-weight: 500;
	color: var(--tl-text-muted);
	text-align: left;
	white-space: normal;
}

.lspms-table--detail tbody td {
	font-variant-numeric: tabular-nums;
}

.lspms-table--detail tr.is-total th,
.lspms-table--detail tr.is-total td {
	font-weight: 700;
	color: var(--tl-text);
	border-top: 2px solid var(--tl-neutral-300);
}

/* -- The capability matrix, and grid tables generally ---------------------- */

/*
 * Yes and No are read down a column, so the column has to be narrow and the
 * mark has to be legible without reading the word. Shape as well as colour:
 * the tick and the dash are what a receptionist with colour-vision deficiency
 * actually sees.
 */
/*
 * A wide table of narrow columns. It is given a floor rather than being allowed
 * to compress, because .lspms-scroll around it can scroll and an unreadable
 * column cannot.
 */
.lspms-table--grid,
.lspms-matrix { min-width: 42rem; }

.lspms-table--grid td,
.lspms-matrix td {
	text-align: center;
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
}

.lspms-matrix th[scope="row"] {
	text-align: left;
	font-weight: 500;
	min-width: 16rem;
}

.lspms-matrix td.is-yes,
.lspms-matrix td.is-no {
	font-size: 0;
}

.lspms-matrix td.is-yes::before,
.lspms-matrix td.is-no::before {
	display: inline-block;
	font-size: 0.9rem;
	font-weight: 700;
	line-height: 1;
}

.lspms-matrix td.is-yes::before {
	content: "\2713";
	color: var(--tl-ok-600, #1c6b41);
}

.lspms-matrix td.is-no::before {
	content: "\2013";
	color: var(--tl-neutral-400);
}

/* -- Needs attention, and the application-level alert banner --------------- */

.lspms-alerts {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-2);
}

/*
 * The severity stripe is the point. A list of four sentences where the urgent
 * one looks exactly like the routine one is a list nobody triages.
 */
.lspms-alerts__item {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--tl-space-4);
	padding: var(--tl-space-3) var(--tl-space-4);
	border: 1px solid var(--tl-neutral-200);
	border-left: 3px solid var(--tl-neutral-400);
	border-radius: var(--tl-radius-sm, 6px);
	background: var(--tl-neutral-25);
	font-size: 0.88rem;
}

.lspms-alerts__item a { flex: none; white-space: nowrap; }

.lspms-alerts__item--info     { border-left-color: var(--tl-primary-500, #3d7f86); }
.lspms-alerts__item--warning  { border-left-color: #b8860b; }
.lspms-alerts__item--danger,
.lspms-alerts__item--critical { border-left-color: #a8342a; }

.lspms-alert--info     { border-left: 3px solid var(--tl-primary-500, #3d7f86); }
.lspms-alert--warning  { border-left: 3px solid #b8860b; }
.lspms-alert--danger,
.lspms-alert--critical { border-left: 3px solid #a8342a; }

/* -- Notifications --------------------------------------------------------- */

.lspms-notes {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
}

.lspms-notes li {
	display: grid;
	grid-template-columns: minmax(0, 9.5rem) minmax(0, 1fr);
	gap: var(--tl-space-4);
	align-items: baseline;
	padding: var(--tl-space-3) 0;
	border-bottom: 1px solid var(--tl-neutral-100);
	font-size: 0.88rem;
}

.lspms-notes li:last-child { border-bottom: 0; }

/* Unread carries weight and a mark, not colour on its own. */
.lspms-notes li.is-unread { font-weight: 600; }

.lspms-notes li.is-unread .lspms-notes__time::before {
	content: "\2022";
	margin-right: 0.4em;
	color: var(--tl-primary-600, #2c6b71);
}

.lspms-notes__time {
	color: var(--tl-text-muted);
	font-weight: 400;
	font-variant-numeric: tabular-nums;
	font-size: 0.8rem;
}

@media (max-width: 782px) {
	.lspms-notes li { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* -- Plain lists inside panels --------------------------------------------- */

.lspms-list {
	margin: 0;
	padding: 0;
	list-style: none;
	display: flex;
	flex-direction: column;
}

.lspms-list li {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	gap: var(--tl-space-2);
	padding: var(--tl-space-3) 0;
	border-bottom: 1px solid var(--tl-neutral-100);
	font-size: 0.9rem;
}

.lspms-list li:last-child { border-bottom: 0; }

/* -- A row of status badges ------------------------------------------------ */

.lspms-badges {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin: 0 0 var(--tl-space-4);
}

/* -- A guest's own words, quoted back --------------------------------------- */

/*
 * A special request is the one piece of text on the booking screen that a
 * person wrote rather than the system generating, so it is set apart rather
 * than run in with the generated rows above it.
 */
.lspms-note {
	margin: var(--tl-space-4) 0 0;
	padding: var(--tl-space-3) var(--tl-space-4);
	border-left: 3px solid var(--tl-neutral-300);
	background: var(--tl-neutral-25);
	border-radius: 0 var(--tl-radius-sm, 6px) var(--tl-radius-sm, 6px) 0;
	font-size: 0.9rem;
	color: var(--tl-text-2, var(--tl-neutral-700));
}

/* -- Wide two-column layouts ----------------------------------------------- */

/*
 * .lspms-cols fits as many equal columns as will go. On a booking screen that
 * is wrong: the summary and the bill are two things, and a third column would
 * only ever be a gap. This pins it to two, and to one below the wp-admin
 * breakpoint where the sidebar collapses.
 */
.lspms-cols--wide {
	grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 960px) {
	.lspms-cols--wide { grid-template-columns: minmax(0, 1fr); }
}

/* -- Receipts and invoices ------------------------------------------------- */

/*
 * These two are documents, not screens: a person prints them and hands them to
 * a guest. So they get a measure, centred, and a header that reads as a
 * letterhead rather than as another panel heading.
 */
.lspms-receipt,
.lspms-document {
	max-width: 46rem;
	margin-inline: auto;
}

.lspms-receipt__head {
	padding-bottom: var(--tl-space-4);
	margin-bottom: var(--tl-space-5);
	border-bottom: 1px solid var(--tl-neutral-200);
	text-align: center;
}

.lspms-receipt__head h2 { margin: 0 0 var(--tl-space-2); }
.lspms-receipt__head p { margin: 0 0 2px; font-size: 0.88rem; color: var(--tl-text-muted); }

.lspms-receipt__no {
	margin-top: var(--tl-space-3) !important;
	font-size: 0.78rem !important;
	letter-spacing: 0.08em;
	text-transform: uppercase;
}

.lspms-receipt__no strong {
	display: block;
	margin-top: 2px;
	font-size: 1rem;
	letter-spacing: 0;
	text-transform: none;
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
}

/*
 * A voided receipt has to be unmistakable at arm's length, because the failure
 * mode is somebody filing it as though it settled a bill.
 */
.lspms-receipt__void {
	margin: var(--tl-space-5) 0 0;
	padding: var(--tl-space-3);
	border: 2px solid #a8342a;
	border-radius: var(--tl-radius-sm, 6px);
	color: #a8342a;
	font-weight: 700;
	letter-spacing: 0.18em;
	text-align: center;
}

.lspms-receipt__footer {
	margin: var(--tl-space-5) 0 0;
	padding-top: var(--tl-space-4);
	border-top: 1px solid var(--tl-neutral-200);
	font-size: 0.85rem;
	color: var(--tl-text-muted);
	text-align: center;
}

.lspms-document__head {
	display: flex;
	flex-wrap: wrap;
	justify-content: space-between;
	gap: var(--tl-space-6);
	padding-bottom: var(--tl-space-5);
	margin-bottom: var(--tl-space-5);
	border-bottom: 1px solid var(--tl-neutral-200);
}

.lspms-document__head h2 { margin: 0 0 var(--tl-space-2); }
.lspms-document__head p { margin: 0 0 2px; font-size: 0.88rem; color: var(--tl-text-muted); }

.lspms-document__meta { text-align: right; min-width: 0; }

.lspms-document__meta h3 {
	margin: 0 0 var(--tl-space-2);
	font-size: 0.78rem;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-document__meta p strong {
	font-size: 1.05rem;
	color: var(--tl-text);
	font-variant-numeric: tabular-nums;
}

.lspms-document__party { margin-bottom: var(--tl-space-5); }

.lspms-document__party h4 {
	margin: 0 0 var(--tl-space-2);
	font-size: 0.78rem;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-document__party p { margin: 0 0 2px; font-size: 0.92rem; }

.lspms-document__note {
	margin: var(--tl-space-5) 0 0;
	padding: var(--tl-space-4);
	background: var(--tl-neutral-25);
	border-radius: var(--tl-radius-sm, 6px);
	font-size: 0.9rem;
	color: var(--tl-text-2, var(--tl-neutral-700));
}

.lspms-document__actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-3);
	margin: var(--tl-space-6) 0 0;
	padding-top: var(--tl-space-5);
	border-top: 1px solid var(--tl-neutral-200);
}

@media (max-width: 782px) {
	.lspms-document__head { flex-direction: column; }
	.lspms-document__meta { text-align: left; }
}

/* -- Settings: the tab shell and the feature switches ---------------------- */

.lspms-tabs { display: block; }

.lspms-features {
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-2);
}

.lspms-features__row {
	display: grid;
	grid-template-columns: auto minmax(0, 1fr);
	gap: var(--tl-space-3);
	align-items: start;
	padding: var(--tl-space-3) var(--tl-space-4);
	border: 1px solid var(--tl-neutral-200);
	border-radius: var(--tl-radius-sm, 6px);
	background: var(--tl-neutral-0);
	cursor: pointer;
	transition: border-color var(--tl-transition), background var(--tl-transition);
}

.lspms-features__row:hover { border-color: var(--tl-neutral-300); background: var(--tl-neutral-25); }
.lspms-features__row:focus-within { outline: 2px solid var(--tl-primary-600, #2c6b71); outline-offset: 2px; }
.lspms-features__row input[type="checkbox"] { margin: 2px 0 0; }

.lspms-features__row span { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lspms-features__row strong { font-size: 0.92rem; }

.lspms-features__row em {
	font-style: normal;
	font-size: 0.82rem;
	color: var(--tl-text-muted);
}

/*
 * The save button on a long tabbed form. It is the last thing in the document
 * and the tab you are reading may be the first, so it follows you down rather
 * than making you scroll back to a control you already knew you wanted.
 */
.lspms-form__sticky {
	position: sticky;
	bottom: 0;
	z-index: 5;
	margin-top: var(--tl-space-6);
	padding: var(--tl-space-4);
	background: var(--tl-neutral-0);
	border-top: 1px solid var(--tl-neutral-200);
	box-shadow: 0 -6px 18px -12px rgba(20, 24, 31, 0.35);
}

@media (prefers-reduced-motion: reduce) {
	.lspms-form__sticky { position: static; box-shadow: none; }
}

/* -- A group heading inside a form ----------------------------------------- */

.lspms-form__legend {
	margin: var(--tl-space-5) 0 0;
	padding-bottom: var(--tl-space-2);
	border-bottom: 1px solid var(--tl-neutral-200);
	font-size: 0.78rem;
	font-weight: 700;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
}

.lspms-form__legend:first-of-type { margin-top: 0; }

/* -- The report body ------------------------------------------------------- */

.lspms-report {
	display: flex;
	flex-direction: column;
	gap: var(--tl-space-6);
}

/* -- How long is left of a stay -------------------------------------------- */

.lspms-room__left {
	margin: 2px 0 0;
	font-size: 0.78rem;
	color: var(--tl-text-muted);
	font-variant-numeric: tabular-nums;
}

/* -- Clearing the operational records --------------------------------------- */

/*
 * The one destructive control in this system. It sits inside .lspms-panel--alert
 * already, so it inherits that panel's warning treatment; what this adds is
 * width. The confirmation phrase and the reason belong on one line each rather
 * than side by side in the two-column data-entry grid, because reading them is
 * the whole point of asking for them.
 */
.lspms-reset { max-width: 46rem; }

.lspms-reset .lspms-form { grid-template-columns: minmax(0, 1fr); }

.lspms-reset input[type="text"] { font-family: var(--tl-mono, ui-monospace, SFMono-Regular, Menlo, monospace); }

.lspms-reset .lspms-table--detail td.is-num { font-weight: 600; }


/* -- The daily report's schedule ------------------------------------------ */

/*
 * A status block rather than an alert. It is usually good news and it is always
 * on the screen, so it is a bordered panel with a coloured left edge rather
 * than something that shouts. The tone comes from the state, and the state is
 * always written out in words above it — nothing here depends on telling amber
 * from green.
 */
.lspms-schedule {
	background: var(--tl-surface-2);
	border: 1px solid var(--tl-border);
	border-left: 3px solid var(--tl-neutral-300);
	border-radius: var(--tl-radius-sm);
	margin: var(--tl-space-4) 0 0;
	padding: var(--tl-space-4);
}

.lspms-schedule--good    { border-left-color: var(--tl-success-500); }
.lspms-schedule--warning { border-left-color: var(--tl-warning-500); }
.lspms-schedule--info    { border-left-color: var(--tl-info-500); }

.lspms-schedule__headline {
	font-size: var(--tl-size-base);
	font-weight: var(--tl-weight-semi);
	margin: 0;
}

.lspms-schedule__detail {
	color: var(--tl-text-muted);
	font-size: var(--tl-size-sm);
	line-height: var(--tl-lh-snug);
	margin: var(--tl-space-2) 0 0;
	max-width: 74ch;
}

.lspms-schedule__facts {
	display: grid;
	gap: var(--tl-space-3);
	grid-template-columns: repeat(auto-fit, minmax(min(100%, 200px), 1fr));
	margin: var(--tl-space-4) 0;
}

.lspms-schedule__facts dt {
	color: var(--tl-text-muted);
	font-size: var(--tl-size-xs);
	font-weight: var(--tl-weight-semi);
	letter-spacing: 0.04em;
	text-transform: uppercase;
}

.lspms-schedule__facts dd {
	font-size: var(--tl-size-sm);
	margin: 2px 0 0;
}

/* -- The homepage slider --------------------------------------------------- */

/*
 * WHY THIS IS A STACK OF FIELDSETS AND NOT A GRID OF CARDS
 * -------------------------------------------------------
 * The slider is an ORDER. A card grid reads as a set — three across, two down —
 * and the reader has to work out that the order runs left-to-right then wraps.
 * A single column reads top to bottom, which is the order the photographs
 * actually appear in, so the arrangement on the screen IS the arrangement on
 * the website. The up and down arrows then mean exactly what they say.
 *
 * Inside each slide the photograph and its words sit side by side once there is
 * room, and stack below that. `minmax(0, 1fr)` on the text column and not `1fr`
 * for the usual reason: a long unbroken button URL would otherwise widen the
 * track past its share and push the whole admin screen sideways.
 */

.lspms-lede {
	margin: 0 0 var(--tl-space-5);
	max-width: 68ch;
	color: var(--tl-text-2, var(--tl-neutral-700));
	font-size: 0.95rem;
	line-height: 1.6;
}

.lspms-actions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	align-items: center;
	margin: var(--tl-space-4) 0 0;
}

.lspms-actions .dashicons {
	margin-right: 4px;
	vertical-align: text-bottom;
}

/*
 * The warning tone on a note. The left bar carries the colour and the dashicon
 * inside carries the meaning, so the notice still reads as a warning to
 * somebody who cannot see the amber.
 */
.lspms-note--warn {
	border-left-color: var(--tl-warning-500);
	background: var(--tl-warning-50);
	color: var(--tl-text);
}

.lspms-note--warn p { margin: 0 0 var(--tl-space-2); }
.lspms-note--warn p:last-child { margin-bottom: 0; }
.lspms-note--warn .dashicons { color: var(--tl-warning-700); }

.lspms-hero { margin-top: var(--tl-space-5); }

.lspms-hero__slide {
	border: 1px solid var(--tl-neutral-200);
	border-radius: var(--tl-radius-md);
	background: var(--tl-surface);
	padding: var(--tl-space-4);
	margin: 0 0 var(--tl-space-4);
}

.lspms-hero__slide legend {
	font-weight: 600;
	font-size: 0.85rem;
	letter-spacing: 0.04em;
	text-transform: uppercase;
	color: var(--tl-text-muted);
	padding: 0 var(--tl-space-2);
}

/*
 * A switched-off slide is dimmed AND says so in its own legend, because
 * "slightly paler" is not a state anybody can read on its own. The words come
 * from the checkbox label, which stays fully legible: the dimming is on the
 * media and the text fields, never on the control that turns it back on.
 */
.lspms-hero__slide.is-off { background: var(--tl-neutral-25); }
.lspms-hero__slide.is-off .lspms-hero__media,
.lspms-hero__slide.is-off .lspms-hero__fields .lspms-field:not(.lspms-field--check) { opacity: 0.6; }

.lspms-hero__grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: var(--tl-space-4);
	align-items: start;
}

@media (min-width: 782px) {
	.lspms-hero__grid { grid-template-columns: 220px minmax(0, 1fr); }
}

.lspms-hero__media { min-width: 0; }

.lspms-hero__thumb {
	display: block;
	width: 100%;
	max-width: 220px;
	height: auto;
	aspect-ratio: 16 / 9;
	object-fit: cover;
	border-radius: var(--tl-radius-sm);
	border: 1px solid var(--tl-neutral-200);
	background: var(--tl-neutral-50);
}

.lspms-hero__thumb--empty {
	display: flex;
	align-items: center;
	justify-content: center;
	border-style: dashed;
	color: var(--tl-text-muted);
	font-size: 0.85rem;
	text-align: center;
	padding: var(--tl-space-3);
}

.lspms-hero__pick { margin: var(--tl-space-2) 0 0; }
.lspms-hero__pick .button { width: 100%; max-width: 220px; }

.lspms-hero__fields { min-width: 0; }

.lspms-hero__fields .lspms-field { margin: 0 0 var(--tl-space-3); }
.lspms-hero__fields .lspms-field input[type="text"] { width: 100%; }
.lspms-hero__fields label { display: block; font-weight: 600; font-size: 0.85rem; margin-bottom: 4px; }

.lspms-hero__pair {
	display: grid;
	grid-template-columns: minmax(0, 1fr);
	gap: 0 var(--tl-space-4);
}

@media (min-width: 600px) {
	.lspms-hero__pair { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/*
 * A checkbox reads as one line — box, then the thing it switches — so its label
 * is not the block label the text fields get.
 */
.lspms-field--check label {
	display: flex;
	align-items: center;
	gap: var(--tl-space-2);
	font-weight: 500;
}

.lspms-field--check input[type="checkbox"] { margin: 0; }

.lspms-hero__problems {
	list-style: none;
	margin: var(--tl-space-2) 0 0;
	padding: var(--tl-space-3);
	border-left: 3px solid var(--tl-warning-500);
	background: var(--tl-warning-50);
	border-radius: 0 var(--tl-radius-sm) var(--tl-radius-sm) 0;
	font-size: 0.88rem;
}

.lspms-hero__problems li {
	display: flex;
	align-items: flex-start;
	gap: var(--tl-space-2);
	margin: 0 0 4px;
}

.lspms-hero__problems li:last-child { margin-bottom: 0; }
.lspms-hero__problems .dashicons { flex: none; color: var(--tl-warning-700); }

.lspms-hero__rowactions {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin: var(--tl-space-4) 0 0;
	padding-top: var(--tl-space-3);
	border-top: 1px solid var(--tl-neutral-100);
}

.lspms-hero__save {
	display: flex;
	flex-wrap: wrap;
	gap: var(--tl-space-2);
	margin: var(--tl-space-4) 0 0;
}

.lspms-hero__restore {
	margin: var(--tl-space-6) 0 0;
	padding: var(--tl-space-4);
	border: 1px dashed var(--tl-neutral-300);
	border-radius: var(--tl-radius-md);
	background: var(--tl-neutral-25);
}

.lspms-hero__restore h3 { margin: 0 0 4px; font-size: 0.95rem; }
.lspms-hero__restore .lspms-hint { display: block; margin-bottom: var(--tl-space-3); max-width: 60ch; }

/* -- The slider's switch and its upload ------------------------------------ */

/*
 * The switch reads as a statement of fact with the way to change it beside it,
 * rather than as a checkbox. "The slider is on" is what somebody arriving at
 * this screen wants to know first, and a checkbox makes them infer it from
 * whether a box is ticked.
 */
.lspms-hero__switch {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	justify-content: space-between;
	gap: var(--tl-space-3);
	padding: var(--tl-space-4);
	margin: 0 0 var(--tl-space-5);
	background: var(--tl-surface);
	border: 1px solid var(--tl-neutral-200);
	border-radius: var(--tl-radius-md);
}

.lspms-hero__switchtext { min-width: 0; flex: 1 1 260px; }
.lspms-hero__switchtext h3 { margin: 0 0 2px; font-size: 1rem; }
.lspms-hero__switchtext .lspms-hint { display: block; margin: 0; }

.lspms-hero__upload {
	padding: var(--tl-space-4);
	margin: 0 0 var(--tl-space-5);
	background: var(--tl-neutral-25);
	border: 1px dashed var(--tl-neutral-300);
	border-radius: var(--tl-radius-md);
}

.lspms-hero__upload h3 { margin: 0 0 var(--tl-space-3); font-size: 0.95rem; }

.lspms-form--upload {
	display: flex;
	flex-wrap: wrap;
	align-items: flex-end;
	gap: var(--tl-space-3);
}

.lspms-form--upload .lspms-field { margin: 0; flex: 1 1 320px; min-width: 0; }
.lspms-form--upload input[type="file"] { width: 100%; max-width: 100%; }

/*
 * A disabled control still has to be readable. Greying it to the point of
 * illegibility means somebody cannot tell what it was they cannot use.
 */
.lspms-form--upload input[type="file"]:disabled,
.lspms-form--upload .button:disabled {
	opacity: 0.55;
	cursor: not-allowed;
}

/* ------------------------------------------------------- room photographs */

/*
 * A grid rather than a table row per room.
 *
 * The picture is the thing being judged here — "is that the right room, is it
 * the right way up, is it dark" — and a thumbnail squeezed into a table cell
 * answers none of those. The cards wrap, so this reads the same on a phone at
 * the desk as on the office screen.
 */
.lspms-rooms-photos {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
	gap: var(--tl-space-4);
	margin-top: var(--tl-space-4);
}

.lspms-roomphoto {
	border: 1px solid var(--tl-neutral-200);
	border-radius: var(--tl-radius-md);
	background: var(--tl-surface);
	padding: var(--tl-space-4);
}

.lspms-roomphoto__name {
	margin: 0 0 var(--tl-space-3);
	font-size: 0.95rem;
}

/*
 * A fixed frame with a neutral ground, so a room with no photograph is an
 * obvious empty space of the same shape rather than a card that has silently
 * collapsed to nothing.
 */
.lspms-roomphoto__frame {
	display: flex;
	align-items: center;
	justify-content: center;
	min-height: 130px;
	margin-bottom: var(--tl-space-3);
	border-radius: var(--tl-radius-sm);
	background: var(--tl-neutral-100);
	overflow: hidden;
}

.lspms-roomphoto__img {
	display: block;
	max-width: 100%;
	height: auto;
}

.lspms-roomphoto__none {
	margin: 0;
	padding: var(--tl-space-3);
	text-align: center;
	font-size: 0.85rem;
	color: var(--tl-muted);
}

.lspms-roomphoto input[type="file"] {
	max-width: 100%;
}
