/* ====================================================================
	base.css - the shell and every rule more than one page group needs:
	design tokens, top bar, avatar menu, content column, buttons, forms,
	flash messages, cards, tabs, tables, status chips, the combobox, the modal,
	presence, and the shared filter bar. Loaded by every page, signed in or out.
	==================================================================== */

/* ====================================================================
	Beacon Consultants - customer web app
	Built from the V1-68 demo's design tokens: every value the demo also
	uses is a variable here, so the app is written in tokens, not hex.
	==================================================================== */

:root {
	/* Brand and status */
	--color-primary: #EF233C;
	--color-primary-light: #FFF3F4;
	--color-ink: #171725;
	--color-info: #0058FF;
	--color-info-hover: #0047CC;
	/* AI actions (the "AI prompts" button): violet to blue, as Explorer's AI Insights button. White
	   text on every stop meets WCAG AA. */
	--color-ai-violet: #7C3AED;
	--color-ai-blue: #2563EB;
	--color-ai-violet-dark: #6D28D9;
	--color-ai-blue-dark: #1D4ED8;
	/* #0077C2, not the demo's #00A0FF - white initials on #00A0FF are ~2.6:1, below AA's 4.5:1.
	   Demo colours are for decoration; anything that carries text meets WCAG AA. */
	--color-avatar: #0077C2;
	--color-success: #5FBF8B;
	--color-success-bg: #E8F5E9;
	--color-danger: #F0142F;
	--color-danger-bg: #FFEBEE;

	/* Surfaces */
	--color-bg-app: #FAFAFA;
	--color-bg-card: #FFFFFF;
	--color-bg-section: #F3F3F3;
	--color-bg-muted: #F0F0F0;

	/* Text */
	--color-text-primary: #131523;
	--color-text-secondary: #666666;
	--color-text-muted: #999999;
	--color-text-header: #5A607F;
	--color-text-inverse: #FFFFFF;

	/* Borders */
	--color-border: #E5E5E5;
	--color-border-dark: #CCCCCC;
	--color-border-light: #F0F0F0;

	/* Spacing */
	--spacing-xs: 4px;
	--spacing-sm: 6px;
	--spacing-md: 8px;
	--spacing-lg: 12px;
	--spacing-xl: 16px;
	--spacing-2xl: 20px;
	--spacing-3xl: 24px;
	--spacing-4xl: 30px;
	--spacing-5xl: 40px;
	--spacing-6xl: 48px;

	/* Radii */
	--radius-sm: 4px;
	--radius-md: 6px;
	--radius-lg: 8px;
	--radius-xl: 10px;
	--radius-pill: 16px;
	--radius-round: 50%;

	/* Type */
	--font-family: 'Lexend Deca', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
	/* The export preview table and its column pills only - both stand for a CSV file's own text. */
	--font-mono: ui-monospace, 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
	--font-size-2xs: 10px;
	--font-size-xs: 11px;
	--font-size-sm: 12px;
	--font-size-base: 13px;
	--font-size-md: 14px;
	--font-size-lg: 15px;
	--font-size-xl: 16px;
	--font-size-2xl: 18px;
	--font-size-3xl: 20px;
	--font-size-4xl: 24px;
	--font-size-5xl: 28px;

	/* Icons, shadows, motion */
	--icon-md: 16px;
	--icon-lg: 18px;
	--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.05);
	--shadow-base: 0 2px 8px rgba(0, 0, 0, 0.1);
	--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
	--transition-fast: 0.2s;

	/* Layout */
	/* The demo's own .main-content max-width (dashboard-styles.css), so the content column and the
	   app bar stop widening at the same point the demo does. */
	--content-max-width: 1600px;
	--app-bar-height: 56px;
}

/* ====================================================================
	BASE
	==================================================================== */

*,
*::before,
*::after {
	box-sizing: border-box;
}

html,
body {
	margin: 0;
	padding: 0;
}

body {
	background: var(--color-bg-app);
	color: var(--color-text-primary);
	font-family: var(--font-family);
	font-size: var(--font-size-md);
	font-weight: 400;
	line-height: 1.5;
	-webkit-font-smoothing: antialiased;
}

a {
	color: var(--color-info);
	text-decoration: none;
}

a:hover {
	text-decoration: underline;
}

.material-symbols-outlined {
	font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 20;
}

/* top/left: 0 pins this at the containing block's origin: an absolutely positioned element with no
	offsets keeps the x/y it would have had in normal flow, so one sitting inside a wide,
	horizontally-scrolling table cell stretched the whole document's scrollWidth out to that cell's
	un-clipped position and made the page itself scroll sideways, even though the table's own
	overflow was already contained by its .card. */
.visually-hidden {
	position: absolute;
	top: 0;
	left: 0;
	width: 1px;
	height: 1px;
	margin: -1px;
	padding: 0;
	overflow: hidden;
	clip-path: inset(50%);
	white-space: nowrap;
	border: 0;
}

/* ====================================================================
	TOP BAR
	==================================================================== */

.app-bar {
	position: sticky;
	top: 0;
	z-index: 1000;
	background: var(--color-bg-card);
	box-shadow: var(--shadow-sm);
}

.app-bar-inner {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-3xl);
	min-height: var(--app-bar-height);
	padding: var(--spacing-md) var(--spacing-5xl);
}

.app-bar-left {
	display: flex;
	align-items: center;
	gap: var(--spacing-3xl);
	min-width: 0;
}

.app-bar-right {
	display: flex;
	align-items: center;
	/* 20px between the credits pill and the avatar, as the demo's own bar has it. */
	gap: var(--spacing-2xl);
	flex: 0 0 auto;
}

/* The real Beacon mark, same rendered height as the demo (dashboard-styles.css .beacon-logo:
	height: var(--spacing-3xl), width left to the SVG's own aspect ratio). */
.beacon-logo {
	height: var(--spacing-3xl);
}

/* Tablet portrait and below - swap the wide wordmark for the compact mark, same technique and
	breakpoint as the demo (dashboard-styles.css ~line 8974). Separate rule from the customer
	.app-bar/.auth-brand context so the admin bar's dark background gets the white-on-transparent
	mark instead.

	These two url()s are plain paths, not run through UI_View::AssetUrl(), so they carry no content-hash
	query string of their own. A new base.css URL does NOT re-fetch them - a sub-resource keeps its
	own cached URL - which is why /images/ is served with a one-day Cache-Control rather than this
	file's immutable year (see rootfs/etc/apache2/conf-available/zz-consultants-hardening.conf): a
	changed mark is at worst a day late, never stuck for a year. */
@media (max-width: 768px) {
	.app-bar .beacon-logo,
	.auth-brand .beacon-logo {
		content: url('/images/beacon-mark.svg');
		height: 32px;
		width: auto;
	}

	.admin-bar .beacon-logo {
		content: url('/images/beacon-mark-white.svg');
		height: 32px;
		width: auto;
	}
}

.company-name {
	padding-left: var(--spacing-xl);
	border-left: 1px solid var(--color-border);
	color: var(--color-text-primary);
	font-size: var(--font-size-lg);
	font-weight: 600;
	line-height: 1.4;
	white-space: nowrap;
	overflow: hidden;
	text-overflow: ellipsis;
}

.credit-chip {
	display: inline-flex;
	align-items: center;
	gap: var(--spacing-xs);
	padding: var(--spacing-md) var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-pill);
	background: var(--color-bg-card);
	color: var(--color-text-primary);
	font-size: var(--font-size-md);
	font-weight: 600;
	/* body sets line-height: 1.5 for readable paragraphs; a pill sized to its own text needs the
	   font's own natural line-height instead, or the inherited 1.5 adds several extra pixels of
	   height on top of the padding below. */
	line-height: normal;
	white-space: nowrap;
	transition: all var(--transition-fast);
}

.credit-chip:hover {
	border-color: var(--color-border-dark);
	background: var(--color-bg-app);
	text-decoration: none;
}

/* The balance is a <strong> for its semantic weight, not for the browser's default 'bolder',
	which inside this 600-weight pill resolves to 900. The demo's own version is a plain span at
	600, so this pins the same value explicitly. */
.credit-chip strong {
	font-weight: 600;
}

.credit-word {
	color: var(--color-text-secondary);
	font-weight: 400;
}

/* ====================================================================
	AVATAR MENU
	==================================================================== */

.user-menu {
	position: relative;
}

.user-avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	width: var(--spacing-5xl);
	height: var(--spacing-5xl);
	padding: 0;
	border: none;
	border-radius: var(--radius-round);
	background: var(--color-avatar);
	color: var(--color-text-inverse);
	font-family: var(--font-family);
	font-size: var(--font-size-md);
	font-weight: 600;
	cursor: pointer;
}

.user-avatar:focus-visible {
	outline: 2px solid var(--color-info);
	outline-offset: 2px;
}

.user-menu-dropdown {
	position: absolute;
	top: calc(100% + var(--spacing-md));
	right: 0;
	z-index: 1000;
	min-width: 200px;
	overflow: hidden;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-lg);
	background: var(--color-bg-card);
	box-shadow: var(--shadow-lg);
}

.user-menu-dropdown[hidden] {
	/* display: block, not the [hidden] attribute's UA-stylesheet default of none - a display:none
	   box is skipped when the browser decides which @font-face resources to fetch, so the Material
	   Symbols webfont was only ever requested on first open, and the four ligature words rendered at
	   full width for a moment before snapping to icon width once it arrived. visibility: hidden
	   keeps the panel out of paint and out of the tab order (same as display: none did) while still
	   generating real boxes, so the font is discovered and fetched at page load instead. */
	display: block;
	visibility: hidden;
	pointer-events: none;
}

.user-menu-form {
	margin: 0;
}

.user-menu-item {
	display: flex;
	align-items: center;
	gap: var(--spacing-lg);
	width: 100%;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: none;
	background: transparent;
	color: var(--color-text-primary);
	font-family: var(--font-family);
	font-size: var(--font-size-base);
	font-weight: 500;
	text-align: left;
	cursor: pointer;
	transition: background var(--transition-fast);
}

.user-menu-item:hover {
	background: var(--color-bg-app);
	text-decoration: none;
}

.user-menu-item .material-symbols-outlined {
	display: inline-flex;
	width: var(--icon-md);
	height: var(--icon-md);
	overflow: hidden;
	color: var(--color-text-secondary);
	font-size: var(--icon-md);
}

/* #C4102A, not --color-danger: the token is 4.34:1 on the white panel, and "Log out" is 13px
	normal text, so it misses AA's 4.5:1. Same override and same reason as .admin-badge-stale and
	.property-remove further down. */
.user-menu-item-danger {
	color: #C4102A;
}

.user-menu-item-danger .material-symbols-outlined {
	color: #C4102A;
}

/* ====================================================================
	CONTENT COLUMN
	==================================================================== */

.main-content {
	max-width: var(--content-max-width);
	margin: 0 auto;
	padding: 25px var(--spacing-5xl) var(--spacing-5xl) var(--spacing-5xl);
}

/* The demo's own .page-description sits 15px under the title and leaves 39px before whatever
	comes next - .page-subtitle below carries the first figure, this one the second. */
.page-header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-3xl);
	margin-bottom: 39px;
}

/* A list page's header (h1, description, primary action) as a grid rather than a title-block
	div plus a button: the demo's own mobile layout puts the button between the two lines of text,
	which only a visual reorder (grid-template-areas here) can do without changing what a screen
	reader meets first - the DOM stays h1, then description, then action (the right reading order
	at any width), and only the visual position of the button moves. */
.page-header-list {
	display: grid;
	grid-template-columns: 1fr auto;
	grid-template-areas:
		"title  button"
		"desc   button";
	align-items: center;
	/* .page-header's own gap would otherwise open a 24px ROW gap between the title and
	   description rows on top of .page-subtitle's 15px margin - only the column gap is wanted. */
	gap: 0 var(--spacing-3xl);
}

.page-header-list h1 {
	grid-area: title;
}

.page-header-list .page-subtitle {
	grid-area: desc;
}

.page-header-list .btn {
	grid-area: button;
	/* Level with the h1, as the demo has it, not centred on the whole two-line block. */
	align-self: start;
	justify-self: end;
}

.page-header h1 {
	margin: 0;
	color: var(--color-text-primary);
	font-size: var(--font-size-5xl);
	font-weight: 600;
	/* The demo sets no line-height on a heading, so its h1 is 35px tall at 28px; the body's
	   inherited 1.5 stood ours at 42px, which pushed every page's first card down with it. */
	line-height: 1.25;
}

/* Every card in the demo is a white box with a shadow and NO border, at 24px padding:
	.settings-card, .co-search-card, .co-wallet-card and .data-table-container are all
	`box-shadow: var(--shadow-base)` (dashboard-styles.css:6593, consultants-styles.css:1081, :893,
	dashboard-styles.css:3703). */
.card {
	padding: var(--spacing-3xl);
	border-radius: var(--radius-lg);
	background: var(--color-bg-card);
	box-shadow: var(--shadow-base);
	/* Unconditional, not just under the 720px breakpoint below: a wide table (7+ admin columns)
	   can outgrow a card anywhere from about 375px up to roughly 900px, and a card with room to
	   spare never shows a scrollbar anyway - this is what keeps a table's own horizontal scroll
	   inside its card instead of the whole page scrolling sideways at, say, 768px. */
	overflow-x: auto;
}

/* A card whose content is a table. The demo's .data-table-container (dashboard-styles.css:3703)
	has no padding at all: the shaded header band runs edge to edge and the card's own radius clips
	it, where a 30px-padded card insets the band and leaves white above the header and below the
	last row. Used together with .card, so the .card + .card spacing rules still see it. */
.table-card {
	padding: 0;
}

/* The two things that can sit above a table inside that card still need the card's own padding
	back - and, being flush to the card's edges, they draw the demo's full-width divider for free.
	The `.card.table-card` prefix is what beats the equally specific full-bleed rule further down
	(a card with padding has to pull its heading band out with negative margins; this one does not
	have to, because the card has no padding to cancel). */
.card.table-card > .card-title:first-child,
.card.table-card > .card-header-row:first-child {
	margin: 0;
	padding: var(--spacing-3xl);
}

/* The empty state replaces the table in the same card (an unfiltered list with no rows). */
.table-card > .empty-state {
	padding: var(--spacing-3xl);
}

.card-message {
	margin: 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-md);
}

/* A link sitting inside a paragraph of ordinary text needs more than colour to mark it as a
	link (axe's link-in-text-block rule) - the close-account card's support address is the case
	this exists for. */
.card-message a {
	text-decoration: underline;
}

/* No bottom margin is right where .card-message is the last thing in its card (the credits page's
	Paddle note) and wrong where a form follows it: on the admin company detail page's Free re-run
	card the "Report" label sat flush against the sentence above it. */
.card-message + form,
.card-message + .form-hint {
	margin-top: var(--spacing-3xl);
}

/* The Edit user page's email line: a plain sentence above the form, not a card-message (which
	has no bottom margin and would sit flush against the Name label below it). */
.card-subtext {
	margin: 0 0 var(--spacing-3xl) 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
}

/* Forms sit in a column roughly this wide inside their card - the demo never lets an input or a
	select stretch the full width of the card. */
.form-column {
	max-width: 600px;
}

/* Multi-field forms (settings and limits, new company, invites, the API key form, About) use the
	whole card instead of the 600px column above: the fields sit in a grid of up to three columns
	that each stretch to their cell, and the grid drops to two and then one as the card narrows.
	The max() keeps a column from being thinner than 260px, and the calc() is a third of the width
	less the two gaps, so a wide card stops at three columns rather than packing in a fourth.
	auto-fill rather than auto-fit, so a form with two fields keeps the same column width as the
	three-field grids around it instead of stretching to half the card. Each .form-group loses its
	own bottom margin to the gap, and whatever follows the grid (the buttons) is spaced by the
	grid's bottom margin. */
.form-grid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(max(260px, calc((100% - 2 * var(--spacing-3xl)) / 3)), 1fr));
	gap: var(--spacing-3xl);
	margin-bottom: var(--spacing-3xl);
}

.form-grid > .form-group {
	margin: 0;
}

/* ====================================================================
	BUTTONS
	==================================================================== */

.btn {
	display: inline-flex;
	align-items: center;
	justify-content: center;
	gap: var(--spacing-sm);
	padding: var(--spacing-md) var(--spacing-2xl);
	border: 1px solid transparent;
	border-radius: var(--radius-md);
	font-family: var(--font-family);
	font-size: var(--font-size-md);
	font-weight: 500;
	/* `normal`, as the demo's own .co-btn: the body's inherited 1.5 made the content box 21px
	   against the demo's 18px, which stood every button 39px tall next to the demo's 36px. */
	line-height: normal;
	white-space: nowrap;
	cursor: pointer;
	transition: all var(--transition-fast);
}

/* "Copy prompt for AI" (the first .btn to carry a leading icon) stood 42px against every icon-less
	button's 36px - Material Symbols' own glyph metrics make line-height: normal (the rule just
	above) compute a much taller line box for the icon than for the button's own text, and a flex
	item's line box sets its height like any other. Pinning the icon's own line-height to 1 stops it
	contributing more height than its font-size, so the row's height goes back to being set by the
	text, exactly as it is on every other .btn. */
.btn .material-symbols-outlined {
	font-size: var(--icon-md);
	line-height: 1;
}

.btn-primary {
	border-color: var(--color-ink);
	background: var(--color-ink);
	color: var(--color-text-inverse);
}

.btn-primary:hover {
	border-color: #2B2D42;
	background: #2B2D42;
	text-decoration: none;
}

.btn-primary:focus-visible {
	outline: 2px solid var(--color-info);
	outline-offset: 2px;
}

.btn-ai {
	border-color: transparent;
	background: linear-gradient(135deg, var(--color-ai-violet), var(--color-ai-blue));
	box-shadow: 0 2px 8px rgba(124, 58, 237, 0.3);
	color: var(--color-text-inverse);
}

.btn-ai:hover {
	background: linear-gradient(135deg, var(--color-ai-violet-dark), var(--color-ai-blue-dark));
	box-shadow: 0 4px 12px rgba(124, 58, 237, 0.4);
	text-decoration: none;
	transform: translateY(-1px);
}

.btn-ai:active {
	transform: translateY(0);
}

.btn-ai:focus-visible {
	outline: 2px solid var(--color-ai-violet);
	outline-offset: 2px;
}

/* The demo's 18px sparkle (the .btn icon rule above is --icon-md, 16px). */
.btn-ai .material-symbols-outlined {
	font-size: 18px;
}

@media (prefers-reduced-motion: reduce) {
	.btn-ai:hover {
		transform: none;
	}
}

.btn-block {
	width: 100%;
}

/* ====================================================================
	FORMS
	==================================================================== */

.form-group {
	margin-bottom: var(--spacing-3xl);
}

.form-group label {
	display: block;
	margin-bottom: var(--spacing-md);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	font-weight: 500;
	/* Tighter than the body's inherited 1.5, which made every label 19.5px tall against the demo's
	   16 and opened the gap between a label and its field by 3.5px on every form in the app. */
	line-height: 1.3;
}

/* The height is stated rather than left to each control's own intrinsic one: an input worked out
	at 42px and a select at 44px from identical padding and font size, which is 2px of drift in
	every filter bar that has both - and, because .filter-grid aligns its columns on their bottom
	edge, 2px of drift in the labels above them too. The demo has one field height (42px) for both,
	so this says so in both places rather than relying on two different intrinsic line boxes
	agreeing. */
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"],
.form-group input[type="date"],
.form-group input[type="number"] {
	width: 100%;
	height: 42px;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-bg-card);
	color: var(--color-text-primary);
	font-family: var(--font-family);
	font-size: var(--font-size-base);
	transition: border-color var(--transition-fast);
}

.form-group input:hover {
	border-color: var(--color-border-dark);
}

.form-group input:focus {
	outline: none;
	border-color: var(--color-info);
	box-shadow: 0 0 0 2px rgba(0, 88, 255, 0.1);
}

/* Secondary rather than muted: #999 on white is 2.85:1, which fails AA outright, and this line
	carries a rule the visitor has to be able to read (the minimum password length). */
.form-hint {
	margin: var(--spacing-sm) 0 0 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

.form-errors {
	margin: 0 0 var(--spacing-3xl) 0;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-danger);
	border-radius: var(--radius-md);
	background: var(--color-danger-bg);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
}

.form-errors ul {
	margin: 0;
	padding-left: var(--spacing-2xl);
}

.form-errors li + li {
	margin-top: var(--spacing-xs);
}

/* ====================================================================
	FLASH MESSAGES
	==================================================================== */

.flash {
	margin: 0 0 var(--spacing-3xl) 0;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid transparent;
	border-radius: var(--radius-md);
	font-size: var(--font-size-base);
}

.flash-success {
	border-color: var(--color-success);
	background: var(--color-success-bg);
	color: var(--color-text-primary);
}

.flash-error {
	border-color: var(--color-danger);
	background: var(--color-danger-bg);
	color: var(--color-text-primary);
}

/* Every flash is .main-content's own direct child, and .main-content's own padding already lines
	it up with whatever it precedes - EXCEPT the New/Edit report page, whose $content narrows itself
	to .report-form-page's own 800px centred column (below), leaving the flash a visibly wider,
	left-shifted banner above the h1/card it introduces. :has() picks out exactly that one case - a
	flash immediately before a .report-form-page - so it matches that column instead; every other
	page's flash is untouched. */
.flash:has(+ .report-form-page) {
	max-width: 800px;
	margin-right: auto;
	margin-left: auto;
}

/* The "Viewing as admin: <company>" strip on report-form.php and report-detail.php when AdminPages
	renders them for a company's report rather than ReportPages rendering them for the logged-in
	customer. Same tint --color-info already gets at 10% opacity elsewhere on this page (the search
	input's own focus ring), rather than a new --color-info-bg variable for one banner. */
.admin-viewing-strip {
	margin: 0 0 var(--spacing-3xl) 0;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-info);
	border-radius: var(--radius-md);
	background: rgba(0, 88, 255, 0.06);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
}

.admin-viewing-strip a {
	color: var(--color-info);
	font-weight: 500;
}

.admin-viewing-strip a:hover {
	color: var(--color-info-hover);
	text-decoration: underline;
}

/* --------------------------------------------------------------------
	SIGNED-OUT PAGES (app.css 678-757) - not signed-out only.
	error.php carries .auth-title/.auth-sub/.auth-footer but renders through
	layout.php, the signed-in shell, so it never loads auth.css.
	-------------------------------------------------------------------- */
.auth-title {
	margin: 0 0 var(--spacing-sm) 0;
	color: var(--color-text-primary);
	font-size: var(--font-size-3xl);
	font-weight: 600;
	text-align: center;
}

.auth-sub {
	margin: 0 0 var(--spacing-4xl) 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-md);
	line-height: 1.5;
	text-align: center;
}

.auth-footer {
	margin: var(--spacing-3xl) 0 0 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
	text-align: center;
}

.auth-footer a {
	color: var(--color-text-secondary);
	text-decoration: underline;
}

.auth-footer a:hover {
	color: var(--color-text-primary);
}

/* ====================================================================
	PAGE HEADER SUBTITLE, STACKED CARDS
	==================================================================== */

.page-subtitle {
	/* The demo's own .page-description margin-bottom, matched here as margin-top since ours sits
	   under the h1 rather than the h1 sitting above it - see the .page-header comment above. */
	margin: 15px 0 0 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-md);
}

/* The line under a report or run title is the demo's .co-title-meta, which is a size smaller than
	a page description and sits tight under the heading (consultants-styles.css). */
.page-byline {
	margin: var(--spacing-xs) 0 0 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
	line-height: 1.3;
}

.card + .card {
	margin-top: var(--spacing-3xl);
}

/* The sibling margin above is for cards stacked down a page. Inside a grid or flex
	row the container's gap spaces them, and the margin would push every card but
	the first down a row's worth - which is exactly what the credits page and the
	admin dashboard did (first card visibly higher than its neighbours). */
:is(.admin-stat-grid, .admin-action-grid) > .card + .card {
	margin-top: 0;
}

/* The other half of that rule: .card + .card only ever spaces two CARDS, and a grid of cards
	following a card is a <div>, not a .card, so it matched nothing and the admin company detail
	page's Adjust credits / Free re-run pair sat flush against the bottom edge of the Ledger card
	above it. The customer side never showed it because its own first card opens the page. Written
	out per grid rather than as a bare `.card + div` so it says which layouts it is for. */
.card + .admin-stat-grid,
.card + .admin-action-grid,
.table-card + .admin-action-grid,
.card + .form-errors,
.table-card + .form-errors {
	margin-top: var(--spacing-3xl);
}

.card-header-row {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-xl);
	margin-bottom: var(--spacing-3xl);
	padding-bottom: var(--spacing-lg);
	border-bottom: 1px solid var(--color-border);
}

.card-title {
	margin: 0 0 var(--spacing-3xl) 0;
	padding-bottom: var(--spacing-lg);
	border-bottom: 1px solid var(--color-border);
	color: var(--color-text-primary);
	font-size: var(--font-size-2xl);
	font-weight: 600;
}

/* The row already carries the divider under both the title and the Invite button together, so
	the title's own copy of it would draw the rule twice. */
.card-header-row .card-title {
	margin-bottom: 0;
	padding-bottom: 0;
	border-bottom: none;
}

/* The demo's card heading is a band, not an inset line: .settings-card is `overflow: hidden` with
	a .card-header of its own 24px padding and a bottom border, so the rule reaches both edges of
	the card (dashboard-styles.css:6593-6607). Negative margins give the same band without
	restructuring every card template; the card's `overflow-x: auto` clips them to the radius and
	adds no scrollbar (the left overflow is exactly the padding it cancels). */
.card > .card-title:first-child,
.card > .card-header-row:first-child {
	margin: calc(-1 * var(--spacing-3xl)) calc(-1 * var(--spacing-3xl)) var(--spacing-3xl) calc(-1 * var(--spacing-3xl));
	padding: var(--spacing-3xl);
}

.form-static-label {
	display: block;
	margin-bottom: var(--spacing-md);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	font-weight: 500;
}

.form-static-value {
	margin: 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
}

/* 42px, the same as .form-group's inputs above and for the reason given there.
	The chevron is the demo's own .co-select (consultants-styles.css:1354-1369): appearance is
	turned off and the same inline SVG sits 12px from the right edge, with 36px of padding kept
	clear for it, rather than the browser's native arrow in its own shape and place. */
.form-group select,
.run-time-zone-fallback select {
	width: 100%;
	height: 42px;
	padding: var(--spacing-lg) 36px var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background-color: var(--color-bg-card);
	background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23666666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
	background-repeat: no-repeat;
	background-position: right var(--spacing-lg) center;
	appearance: none;
	-webkit-appearance: none;
	color: var(--color-text-primary);
	font-family: var(--font-family);
	font-size: var(--font-size-base);
	cursor: pointer;
	transition: border-color var(--transition-fast);
}

.form-group select:hover,
.run-time-zone-fallback select:hover {
	border-color: var(--color-border-dark);
}

.form-group select:focus,
.run-time-zone-fallback select:focus {
	outline: none;
	border-color: var(--color-info);
	box-shadow: 0 0 0 2px rgba(0, 88, 255, 0.1);
}

.btn-secondary {
	border-color: var(--color-border);
	background: var(--color-bg-card);
	color: var(--color-text-primary);
}

.btn-secondary:hover {
	border-color: var(--color-border-dark);
	background: var(--color-bg-app);
	text-decoration: none;
}

/* ====================================================================
	TABS (demo's underline style: plain text, active tab in the brand red
	with a red underline sitting on the shared border - see 09-company.jpg)
	==================================================================== */

.tabs {
	display: flex;
	gap: var(--spacing-3xl);
	margin-bottom: var(--spacing-3xl);
	border-bottom: 1px solid var(--color-border);
}

.tab {
	margin-bottom: -1px;
	padding: var(--spacing-lg) 0;
	border: none;
	border-bottom: 2px solid transparent;
	background: transparent;
	color: var(--color-text-secondary);
	font-size: var(--font-size-md);
	font-weight: 500;
	line-height: 1;
}

.tab:hover {
	color: var(--color-text-primary);
	text-decoration: none;
	border-bottom-color: var(--color-border-dark);
}

/* The underline carries the brand red; the text stays dark rather than red-on-white, which is
	only ~3.9:1 - below AA's 4.5:1 for text. Decoration and text follow different rules here. */
.tab-active,
.tab-active:hover {
	border-bottom-color: var(--color-primary);
	color: var(--color-text-primary);
	font-weight: 600;
}

/* ====================================================================
	TABLES
	==================================================================== */

.table {
	width: 100%;
	border-collapse: collapse;
}

/* Cell metrics are the demo's .data-table exactly (dashboard-styles.css:3719 and :3757): a header
	row of 18px/20px and body rows of 10px/20px, so a row is 34px of text in a 50px header. */
.table th,
.table td {
	text-align: left;
}

.table th {
	padding: 18px var(--spacing-2xl);
	border-bottom: 1px solid var(--color-border);
	/* The demo leaves headings and cells at `normal` (~1.32 for Lexend Deca); the body's inherited
		   1.5 made every row about 3.5px taller than the demo's and a long list visibly looser. */
	line-height: 1.3;
	background: var(--color-bg-section);
	color: var(--color-text-header);
	font-size: var(--font-size-xs);
	font-weight: 600;
	text-transform: uppercase;
	/* 0.5px, as .data-table th - 0.02em computes to 0.22px at 11px and read as no tracking. */
	letter-spacing: 0.5px;
	white-space: nowrap;
}

.table td {
	padding: 10px var(--spacing-2xl);
	font-size: var(--font-size-md);
	line-height: 1.3;
}

/* 0.5px, one rule for the eyebrow/micro-label role instead of three copies of it: the demo's own
	.co-stat-label and .table th above both carry that figure, where these three each declared the
	tighter 0.02em (0.22px at 11px) and read as no tracking at all. */
.report-meta-label,
.card-eyebrow,
.admin-stat-label {
	letter-spacing: 0.5px;
}

/* The demo's tables are a light grid, and the two rules are not the same weight: column rules are
	--color-border (#E5E5E5, `border-right` on every cell but the last), row rules the lighter
	--color-border-light (#F0F0F0, on the row) - ours had the two colours the other way round, so
	the grid read as rows-first where the demo reads as columns-first. */
.table th + th,
.table td + td {
	border-left: 1px solid var(--color-border);
}

.table tbody tr {
	border-bottom: 1px solid var(--color-border-light);
}

.table tbody tr:last-child {
	border-bottom: none;
}

/* The demo runs two table styles: its reports table is .data-table, which .table above already
	matches; its Users table (both the customer Company > Users tab and the admin company detail
	page - the same class on both) is the roomier one, 12px/16px padding against a 57px row. A
	modifier on `.table` rather than changing the cell metrics generally, which would move every
	other table (reports, credits, admin logs) off the figures they were just matched to. */
.table-users th,
.table-users td {
	padding: var(--spacing-lg) var(--spacing-xl);
}

/* The reports list's own column widths, as .co-table-reports (consultants-styles.css:1798-1830).
	Without them the table is laid out from its content, so the same list looks different from one
	company to the next - here the report-name column came out 237px against the demo's 313px
	because a wider Status cell took the room. The two count columns keep tabular figures for the
	same reason the demo gives them. */
.reports-table th:nth-child(1), .reports-table td:nth-child(1) { width: 23%; }
.reports-table th:nth-child(2), .reports-table td:nth-child(2) { width: 8%; }
.reports-table th:nth-child(3),
.reports-table td:nth-child(3) {
	width: 9%;
	font-variant-numeric: tabular-nums;
}
.reports-table th:nth-child(4), .reports-table td:nth-child(4) { width: 9%; }
.reports-table th:nth-child(5), .reports-table td:nth-child(5) { width: 12%; }
.reports-table th:nth-child(6),
.reports-table td:nth-child(6) {
	width: 12%;
	font-variant-numeric: tabular-nums;
}
.reports-table th:nth-child(7), .reports-table td:nth-child(7) { width: 11%; }
.reports-table th:nth-child(8), .reports-table td:nth-child(8) { width: 10%; }
.reports-table th:nth-child(9), .reports-table td:nth-child(9) { width: 6%; }

/* "Capturing 1,234 of 2,190" under an In progress chip, and the note that appears when a report
	changed elsewhere could not be shown in place. Both are the same quiet secondary line
	.export-progress already is on the report page - the same size, the same colour - so a customer
	meets one voice for "this is still happening", not two. */
.reports-table-progress {
	display: block;
	margin-top: var(--spacing-xs);
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	font-variant-numeric: tabular-nums;
}

.table-refresh-note {
	margin: 0 0 var(--spacing-lg);
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

/* Money columns: the demo right-aligns the credits and balance columns of the credit activity
	table, header and values together, and leaves every other column left. */
.table th.table-number,
.table td.table-number {
	text-align: right;
}

.table-actions-heading {
	text-align: right;
}

/* `.table td.table-actions` so it outranks `.table td` above, which otherwise wins the text-align
	and leaves the action links sitting on the left of a right-hand column. */
.table td.table-actions,
.table-actions {
	display: table-cell;
	text-align: right;
	white-space: nowrap;
}

/* --color-text-muted (#999) is 2.85:1 on white - below AA. Secondary is the same grey family at
	5.7:1, same reasoning as form-hint above. */
.table-actions-you {
	color: var(--color-text-secondary);
}

.table-name-cell {
	display: flex;
	align-items: center;
	gap: var(--spacing-lg);
}

.table-avatar {
	display: flex;
	align-items: center;
	justify-content: center;
	flex: 0 0 auto;
	width: 32px;
	height: 32px;
	border-radius: var(--radius-round);
	background: var(--color-avatar);
	color: var(--color-text-inverse);
	font-size: var(--font-size-sm);
	font-weight: 600;
}

.action-link {
	display: inline-block;
	margin-left: var(--spacing-xl);
	border: none;
	background: transparent;
	padding: 0;
	color: var(--color-info);
	font-family: var(--font-family);
	font-size: var(--font-size-base);
	font-weight: 500;
	cursor: pointer;
}

.action-link:first-child {
	margin-left: 0;
}

.action-link:hover {
	color: var(--color-info-hover);
	text-decoration: underline;
}

.action-inline-form {
	display: inline;
	margin: 0;
}

/* An .action-link that is a plain reference inside an ordinary body cell - the credit ledger's
	report name, an admin company/report/run link - is not an action, and the demo's own cell text
	for these is unstyled dark text with no underline until hover: no blue, no button look. Scoped
	to anchors so it never touches the .action-link-button elements below, and to `:not(.table-
	actions)` so the ghost-button treatment further down keeps the actions column separate. */
.table td:not(.table-actions) a.action-link {
	color: var(--color-text-primary);
	text-decoration: none;
}

.table td:not(.table-actions) a.action-link:hover,
.table td:not(.table-actions) a.action-link:focus {
	text-decoration: underline;
}

/* Inside a table's actions column the demo does not use a blue link at all: .co-action-btn is a
	quiet grey ghost button - 13px/500 #666666, 6px/8px padding, 6px radius, and the row's
	background on hover (consultants-styles.css:272-293) - rather than putting the loudest colour on
	the page in the least important column of every list. */
.table-actions .action-link {
	display: inline-flex;
	align-items: center;
	gap: var(--spacing-xs);
	margin-left: var(--spacing-xs);
	padding: var(--spacing-sm) var(--spacing-md);
	border-radius: var(--radius-md);
	color: var(--color-text-secondary);
	transition: background var(--transition-fast), color var(--transition-fast);
}

.table-actions .action-link:hover {
	background: var(--color-bg-section);
	color: var(--color-text-primary);
	text-decoration: none;
}

/* The negative margin puts the last action's text-not-padding on the cell's right edge, so a
	column of them lines up with the header above and with the cells that hold plain text. */
.table-actions .action-link:last-child {
	margin-right: calc(-1 * var(--spacing-md));
}

/* The one destructive action in a table's actions column: Delete on /admin/admins. #C4102A, not
	--color-danger, for the reason .admin-badge-stale gives - 6.08:1 on the card's white, where the
	token itself is 4.33:1 and misses AA. */
.table-actions .action-link-danger {
	color: #C4102A;
}

.table-actions .action-link-danger:hover,
.table-actions .action-link-danger:focus {
	background: var(--color-danger-bg);
	color: #C4102A;
}

/* ====================================================================
	STATUS CHIPS
	==================================================================== */

.chip {
	display: inline-flex;
	align-items: center;
	padding: var(--spacing-xs) var(--spacing-lg);
	border-radius: var(--radius-pill);
	font-size: var(--font-size-sm);
	font-weight: 500;
	white-space: nowrap;
}

/* Chip text is darkened past the demo's own token for each colour - #5FBF8B and #F57C00 on
	their pastel backgrounds are 2.0:1 and 2.5:1, both well under AA's 4.5:1. The backgrounds
	stay exactly as the demo has them; only the text darkens, which is what a chip actually needs
	to meet AA for the word inside it. */
.chip-active {
	background: var(--color-success-bg);
	color: #1B5E20;
}

.chip-invited {
	background: #FFF8E1;
	color: #8A5300;
}

.chip-disabled {
	background: var(--color-bg-muted);
	color: var(--color-text-secondary);
}

/* The demo's role badges are 11px/600 rather than the 12px/500 a status badge carries
	(.role-badge, dashboard-styles.css:6859), and it gives every role its own colour - Owner and
	Admin painted the same red left the two most consequential roles indistinguishable in the Users
	table. */
.chip-owner,
.chip-admin,
.chip-member {
	font-size: var(--font-size-xs);
	font-weight: 600;
}

/* .role-badge.role-owner exactly: #7B1FA2 on #F3E5F5 is 6.77:1, comfortably AA. */
.chip-owner {
	background: #F3E5F5;
	color: #7B1FA2;
}

/* The demo's .role-admin pair (#D32F2F on #FFEBEE) is 4.36:1, under AA at this size, so the red
	stays the one already proven here: #C41E3A on --color-primary-light is 5.39:1. Same hue as the
	demo's, one step darker - the house rule that AA beats demo fidelity for anything carrying
	text. --color-primary itself is only 3.9:1 on the same background. */
.chip-admin {
	background: var(--color-primary-light);
	color: #C41E3A;
}

.chip-member {
	background: var(--color-bg-muted);
	color: var(--color-text-secondary);
}

/* ====================================================================
	NARROW SCREENS
	==================================================================== */

@media (max-width: 720px) {
	.app-bar-inner {
		gap: var(--spacing-lg);
		padding: var(--spacing-md) var(--spacing-xl);
	}

	.app-bar-left {
		gap: var(--spacing-lg);
	}

	.company-name {
		padding-left: var(--spacing-lg);
		font-size: var(--font-size-md);
	}

	/* The credits pill is NOT shrunk here. It was (6px 12px, a 32px pill), and it is the one
	   thing on a phone header that has to stay tappable and legible - the demo keeps it at 36px
	   with its 8px/16px padding at every width, and the bar has room for it once the company
	   name truncates. */

	.main-content {
		padding: var(--spacing-2xl) var(--spacing-xl) var(--spacing-4xl) var(--spacing-xl);
	}

	.page-header h1 {
		font-size: var(--font-size-4xl);
	}

	/* A long report/run name wraps to 2-3 lines at this width; align-items: center then floats the
	   action button somewhere in the middle of that block instead of level with anything. Stacking
	   is the same treatment .report-detail-header already gives its own, more specific header. */
	.page-header {
		flex-direction: column;
		align-items: stretch;
	}

	/* The demo's own stacking at this width: h1, then the full-width primary button, then the
	   description - see the .page-header-list comment above for why this is a CSS reorder rather
	   than a template change. */
	.page-header-list {
		grid-template-columns: 1fr;
		grid-template-areas:
			"title"
			"button"
			"desc";
	}

	.page-header-list .btn {
		justify-self: stretch;
		/* The demo's 12px between the h1 and the stacked button (the description's own 15px
		   margin-top provides the gap below it). */
		margin-top: 12px;
	}

	.table {
		min-width: 560px;
	}
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - not reports only.
	.section-heading-row/-actions/-note: reports, report detail, credits and
	admin dashboard/companies/runs/run detail/events/log/admins/company detail.
	-------------------------------------------------------------------- */
/* The demo's own section heading is a plain h2: `font-size: 20px; margin: 24px 0 16px` with the
	note beside it (dashboard-styles.css:172-177, consultants-styles.css:237). */
.section-heading-row {
	display: flex;
	flex-wrap: wrap;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--spacing-xl);
	margin: var(--spacing-3xl) 0 var(--spacing-xl) 0;
}

.section-heading-row .card-title {
	font-size: var(--font-size-3xl);
	line-height: 1.25;
}

/* A section heading that follows a card (admin run detail: "By property" card, then
	"Failed requests") needs its own breathing room, or the heading sits on the card. */
.card + .section-heading-row,
.table-card + .section-heading-row {
	margin-top: var(--spacing-5xl);
}

/* .card-title's own divider (drawn for it sitting alone atop a card, e.g. company-about.php)
	would otherwise show as a stray underline the width of the text - .section-heading-row is a
	flex row outside any card, which shrinks the heading to its content width. */
.section-heading-row .card-title {
	margin-bottom: 0;
	padding-bottom: 0;
	border-bottom: none;
}

/* The right-hand end of a .section-heading-row when it carries more than one thing - the admin run
	detail page's "N requests." note beside its Retry button. Without it the row's own space-between
	would strand the note in the middle of the heading. */
.section-heading-actions {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--spacing-xl);
}

.section-heading-note {
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
	/* .co-reports-note is `white-space: nowrap`; ours wrapped "Your 4 reports, most recent first."
	   onto a second line as soon as the heading beside it grew. */
	white-space: nowrap;
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - the filter bar, shared (C2).
	www/rate-capture/index.php, www/credits.php,
	www/admin/companies/index.php, www/admin/events.php,
	www/admin/log.php. Each bar's own column weights follow, and stay here too -
	-------------------------------------------------------------------- */
/* .co-search-card: 20px/24px of padding and 16px under it, not the 24px/24px a plain card has. */
.filter-card {
	padding: var(--spacing-2xl) var(--spacing-3xl);
	margin-bottom: var(--spacing-xl);
}

/* .card + .card's own 24px would be added to the 16px above, opening a 40px gap between a filter
	bar and the table it filters where the demo has 16px. */
.filter-card + .card {
	margin-top: 0;
}

.filter-grid {
	display: grid;
	grid-template-columns: repeat(6, 1fr) auto;
	gap: var(--spacing-xl);
	align-items: end;
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - .reports-filter-grid lives in base.css
	for CASCADE ORDER, not because base needs it: /reports is its only user.
	It has to stay ABOVE the narrow-screen .filter-grid blocks further down,
	which collapse the bar to two columns at 900px and one at 720px. Tidied
	into report.css it would sit AFTER them at equal specificity and win at
	every width - the /reports filter bar would silently stop collapsing on a
	phone. Leave it here.
	-------------------------------------------------------------------- */
/* The reports bar's own column weights, as .co-search-grid-reports (consultants-styles.css:1781):
	the name search is the widest field and Currency the narrowest. Six equal tracks truncated the
	search placeholder ("Search by report nam…") while leaving the Currency select half empty. */
.reports-filter-grid {
	grid-template-columns: 1.6fr 0.6fr 1.1fr 1.1fr 0.9fr 1.1fr auto;
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - .admin-log-filter-grid lives in
	base.css for CASCADE ORDER only; www/admin/log.php is its only user. Same trap
	as .reports-filter-grid above, and the rule's own comment below spells it
	out: it is declared above the narrow-screen block so that block still
	collapses this bar. Tidied into admin.css it would stop collapsing.
	-------------------------------------------------------------------- */
/* The admin log's own column weights - the same kind of per-bar override .reports-filter-grid
	above is, for the two things this bar has that no other one does:

	  - its Admin select's options are whole email addresses, and a native <select> clips with no
	    ellipsis at all, so an equal 1fr track showed the first part of an address and simply
	    stopped - the bar no longer said which admin it was filtered to. 2fr fits the longest address
	    from about 1200px up, and the select carries the selected address as its title for the widths
	    below that, since there is no width at which a 255px address fits a 375px phone.
	  - it is the only bar with TWO date inputs. A native date input's min-content is 156px, and
	    `1fr` is `minmax(auto, 1fr)`, so between about 901 and 1350px those two tracks outgrew their
	    fr share and squeezed the three fields beside them to 93px each. minmax() states that 156px
	    floor explicitly instead of letting it fight the other tracks for the space.

	minmax(0, ...) on the first three is what stops a long option or placeholder pushing its own
	track past its share. Six tracks, not .filter-grid's seven: five fields and the buttons, with no
	empty track to leave. Declared here, above the narrow-screen block, so that block's own
	.filter-grid rules still collapse this bar to two columns at 900 and one at 720 - the ordering
	trap .credits-filter-grid had to re-declare its way out of, avoided rather than repeated. */
.admin-log-filter-grid {
	grid-template-columns: minmax(0, 2fr) minmax(0, 1.4fr) minmax(0, 1fr) minmax(156px, 1fr) minmax(156px, 1fr) auto;
}

.filter-grid .form-group {
	margin-bottom: 0;
}

/* The demo's own filter-bar buttons (.co-search-buttons): 12px between Clear and Search, and both
	44px tall against the 42px fields beside them, so the pair is one shape on every bar. The
	grid-column is for /admin/events above all: its two-field form leaves four of .filter-grid's six
	1fr tracks empty, so without being placed in the last track the buttons sat where the third field
	would be, with most of the card empty to their right. Below 900px the grid has no such track and
	the buttons take a row of their own - see the narrow-screens block further down. */
.filter-search-button {
	display: flex;
	gap: var(--spacing-lg);
}

.filter-grid .filter-search-button {
	grid-column: -2 / -1;
}

.filter-search-button .btn {
	width: 100%;
	height: 44px;
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - .empty-state, shared.
	www/rate-capture/index.php + reports.js, www/credits.php, and
	nine admin templates.
	-------------------------------------------------------------------- */
.empty-state {
	display: flex;
	flex-direction: column;
	align-items: flex-start;
	gap: var(--spacing-xl);
	padding: var(--spacing-3xl) 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
}

.empty-state p {
	margin: 0;
}

/* --------------------------------------------------------------------
	REPORTS LIST (app.css 1301-1470) - run status chips, shared (C6).
	Never written as a literal class= value: PHP concatenates them in
	report-detail.php and AdminPages.php, and admin.js/reports.js set them.
	-------------------------------------------------------------------- */
/* Report status chips - separate from the Users tab's chip-active/chip-invited/chip-disabled
	even where the colour matches, since the two chip families describe different things. */
/* .co-badge-draft's own background is --color-bg-section, a shade lighter than the muted grey
	this used, which is what makes the demo's "Not run" read as a label rather than a button. */
.chip-not-run {
	background: var(--color-bg-section);
	color: var(--color-text-secondary);
}

.chip-in-progress {
	background: var(--color-bg-section);
	color: var(--color-text-primary);
}

/* --color-success on --color-success-bg is only ~2:1, well below WCAG AA - fine for a decorative
	dot but not for the text this chip carries. #1E7A44 on the same background is ~4.76:1. A new
	token rather than changing --color-success itself, which other chips still use decoratively. */
.chip-completed {
	background: var(--color-success-bg);
	color: #1E7A44;
}

/* --------------------------------------------------------------------
	NEW / EDIT REPORT FORM (app.css 1471-1631) - .form-error-inline, shared.
	report-form.php, credits.php and partials/modal.php (written by shared.js).
	-------------------------------------------------------------------- */
/* #C4102A, not --color-danger: this line is 12px text on the white card, and #F0142F is 4.33:1
	there - below the 4.5:1 AA floor. The demo's own field error is #F0142F, and the project's AA
	rule wins over matching it, exactly as .credits-amount-negative and .admin-badge-stale already
	decide the same question the same way. */
.form-error-inline {
	margin: var(--spacing-sm) 0 0 0;
	color: #C4102A;
	font-size: var(--font-size-sm);
}

.form-error-inline[hidden] {
	display: none;
}

/* --------------------------------------------------------------------
	NEW / EDIT REPORT FORM (app.css 1471-1631) - .quiet-link, shared.
	Every filter bar's Clear link: reports, credits, admin companies/events/log,
	plus report detail and report prompts.
	-------------------------------------------------------------------- */
.quiet-link {
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
	text-decoration: underline;
}

.quiet-link:hover {
	color: var(--color-text-primary);
}

/* Clear on a filter bar is the demo's .co-quiet-link - grey, underlined, no border and no box
	(consultants-styles.css:1262). It has to be a submit here rather than the demo's <span>, since
	clearing means posting the empty search back to the session, so the button element is stripped
	back to the same appearance. */
button.quiet-link {
	border: none;
	background: transparent;
	padding: 0;
	font-family: var(--font-family);
	font-weight: 400;
	cursor: pointer;
}

/* --------------------------------------------------------------------
	NEW / EDIT REPORT FORM (app.css 1471-1631) - disabled buttons, shared.
	Every page that renders a .btn-primary or .btn-secondary.
	-------------------------------------------------------------------- */
/* The demo fades a disabled button's own fill rather than repainting it grey - .co-btn-primary
	disabled stays --color-ink at opacity .4, .co-btn-secondary disabled stays white at .5.
	Repainting every disabled button to #F0F0F0/#999 read as a different button, and put the muted
	grey where .pagination-link-disabled's own "#666 not the muted #999" AA reasoning rules it
	out. */
.btn-primary:disabled,
.btn-primary:disabled:hover {
	opacity: 0.4;
	cursor: not-allowed;
}

.btn-secondary:disabled,
.btn-secondary:disabled:hover {
	opacity: 0.5;
	cursor: not-allowed;
}

/* ====================================================================
	CURRENCY COMBOBOX (searchable currency picker)
	==================================================================== */

.combobox {
	position: relative;
}

.combobox-input {
	width: 100%;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-bg-card);
	color: var(--color-text-primary);
	font-family: var(--font-family);
	font-size: var(--font-size-base);
}

.combobox-input:focus {
	outline: none;
	border-color: var(--color-info);
	box-shadow: 0 0 0 2px rgba(0, 88, 255, 0.1);
}

.combobox-list {
	position: absolute;
	top: calc(100% + var(--spacing-xs));
	left: 0;
	right: 0;
	z-index: 1000;
	max-height: 260px;
	margin: 0;
	padding: var(--spacing-sm) 0;
	overflow-y: auto;
	list-style: none;
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-bg-card);
	box-shadow: var(--shadow-lg);
}

.combobox-list[hidden] {
	display: none;
}

.combobox-option {
	padding: var(--spacing-lg) var(--spacing-xl);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	cursor: pointer;
}

.combobox-option:hover,
.combobox-option-active {
	background: var(--color-bg-app);
}

/* The time zone field (ledger D19). It is a picker, so it carries the chevron of the other pickers
	(.form-group select above, the same inline SVG, the same 36px kept clear for it) where the
	currency and country boxes, which are for typing a search, do not. [type="text"] lifts it over
	.form-group input[type="text"]'s padding shorthand. */
.combobox-time-zone .combobox-input[type="text"] {
	padding-right: 36px;
	background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='%23666666' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
	background-repeat: no-repeat;
	background-position: right var(--spacing-lg) center;
	text-overflow: ellipsis;
	cursor: pointer;
}

/* Each option is its UTC offset, then its cities, so the offsets read down one column; at 360px a
	long list of cities wraps inside its own column and the offset keeps its line. The zone the
	field holds is marked by the inset bar and a heavier name. */
.combobox-time-zone .combobox-option {
	display: flex;
	align-items: baseline;
	gap: var(--spacing-lg);
}

.combobox-option-offset {
	flex-shrink: 0;
	min-width: 72px;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}

.combobox-time-zone .combobox-option[aria-selected="true"] {
	box-shadow: inset 3px 0 0 var(--color-info);
}

.combobox-time-zone .combobox-option[aria-selected="true"] .combobox-option-name {
	font-weight: 500;
}

/* The line a search with no match shows (ledger P7), in the options' own padding. */
.combobox-empty {
	padding: var(--spacing-lg) var(--spacing-xl);
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
	cursor: default;
}

/* ====================================================================
	MODAL (the demo's .co-modal, as a native <dialog> so the focus trap,
	Escape and the return of focus are the browser's own. The dialog
	itself is the overlay: transparent, padded, and closing on a click
	that lands on it rather than on the card, so .modal-card carries
	every visible pixel.)
	==================================================================== */

.modal {
	width: 100%;
	/* 480 = the demo's 440px card plus this overlay's own 20px padding on each side. Capping the
	   overlay at 440 instead left a 400px card - the padding came out of the card. */
	max-width: 480px;
	margin: auto;
	padding: var(--spacing-2xl);
	border: none;
	background: transparent;
}

.modal::backdrop {
	background: rgba(19, 21, 35, 0.5);
}

/* A dialog that has never been shown is display:none by the browser's own rule; saying it again
	here is what keeps the Invite user modal's form out of reach in a browser with no <dialog>
	support at all, where an unknown element would otherwise render inline in the page. */
.modal:not([open]) {
	display: none;
}

.modal-card {
	padding: var(--spacing-3xl);
	border-radius: var(--radius-lg);
	background: var(--color-bg-card);
	box-shadow: 0 12px 32px rgba(0, 0, 0, 0.2);
}

.modal-title {
	margin: 0 0 var(--spacing-lg) 0;
	color: var(--color-text-primary);
	font-size: var(--font-size-2xl);
	font-weight: 600;
}

.modal-body {
	margin: 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-md);
	line-height: 1.5;
}

.modal-summary {
	display: flex;
	align-items: baseline;
	justify-content: space-between;
	gap: var(--spacing-xl);
	margin: var(--spacing-2xl) 0;
	padding: var(--spacing-lg) 0;
	border-top: 1px solid var(--color-border);
	color: var(--color-text-primary);
	font-size: var(--font-size-md);
	font-weight: 600;
}

/* Grey, but not lighter: the whole total row is 600 in the demo (.co-summary-row.total), label
	included, so this sets the colour and inherits the weight from .modal-summary above. */
.modal-summary-label {
	color: var(--color-text-secondary);
}

.modal-warning {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: var(--spacing-xl);
	margin: var(--spacing-2xl) 0;
	padding: var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-bg-section);
	color: var(--color-text-primary);
	font-size: var(--font-size-md);
	line-height: 1.5;
}

/* The shortfall top-up's two lines under the warning: the note Paddle's own checkout.completed
	writes ("Payment received - adding your credits...") and the error a checkout that could not be
	opened leaves. Both sit where the warning's own bottom margin has already put the space, so
	neither carries a top margin of its own. */
.modal-note {
	margin: calc(-1 * var(--spacing-lg)) 0 var(--spacing-2xl) 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
	line-height: 1.5;
}

.modal-note[hidden] {
	display: none;
}

/* .form-error-inline sets the colour, the size and the [hidden] rule; this only closes the gap the
	warning above left, the same way .modal-note does. */
.modal-error {
	margin: calc(-1 * var(--spacing-lg)) 0 var(--spacing-2xl) 0;
}

.modal-warning[hidden] {
	display: none;
}

/* flex-wrap: a confirm label too long to sit beside Cancel on a phone (an unlimited company's "Run
	report · Included (unlimited)", ledger W2) drops to its own line rather than pushing Cancel out
	of the card. Rows that fit are unchanged. */
.modal-actions {
	display: flex;
	flex-wrap: wrap;
	justify-content: flex-end;
	gap: var(--spacing-lg);
	margin-top: var(--spacing-3xl);
}

/* .form-errors carries a bottom margin for the form under it but no top one, since everywhere else
	it is the first thing in its card. In the Invite user modal it follows the body sentence and was
	sitting flush against it. */
.modal-body + .form-errors {
	margin-top: var(--spacing-2xl);
}

/* The Invite user modal's body is a form where every other modal's is a sentence, so it needs the
	gap under .modal-body that a following .modal-summary would otherwise have carried. The last
	field's own bottom margin is dropped: .modal-actions already spaces itself. */
.modal-form {
	margin-top: var(--spacing-2xl);
}

.modal-form .form-group:last-of-type {
	margin-bottom: 0;
}

/* The run confirmation's time zone field (ledger D13) sits between the sentence and the Cost row, so
	it carries the gap the sentence's bottom edge would otherwise give a following .modal-summary. */
.modal-time-zone {
	margin: var(--spacing-2xl) 0 0 0;
}

/* The time zone list floats over the page, as wan's combobox list does (ledger P7): it is the
	absolute .combobox-list above, and opening it never pushes the fields below or makes its card or
	modal taller. A <dialog> scrolls its own overflow and a .card clips (overflow-x: auto), either of
	which would cut a floating list off at its edge, so while a time zone list is open - and only
	then, so a long dialog on a short screen still scrolls - the card or dialog holding it lets it
	show. The run confirmation and the About tab keep the shorter list they had. */
.card:has(.combobox-time-zone .combobox-input[aria-expanded="true"]),
.modal:has(.combobox-time-zone .combobox-input[aria-expanded="true"]) {
	overflow: visible;
}

.modal-time-zone .combobox-list,
.company-time-zone .combobox-list {
	max-height: 200px;
}

/* The Run button's own form, for a browser with scripting off: the plain time zone select, labelled,
	sits above the button, styled as the app's other selects. */
.run-time-zone-fallback {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-md);
	margin: 0 0 var(--spacing-lg) 0;
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	font-weight: 500;
}

.run-time-zone-fallback select {
	min-width: 0;
	max-width: 100%;
	font-weight: 400;
}

/* --------------------------------------------------------------------
	NARROW SCREENS (FILTER BARS) (app.css 1993-2060) - C2's narrow half.
	The filter-bar rules only; the report-form rules from the same block stay
	in report.css. Same five pages as the filter bar above.
	-------------------------------------------------------------------- */
@media (max-width: 900px) {
	.filter-grid {
		grid-template-columns: repeat(2, 1fr);
	}

	/* No auto track to sit in once the bar has collapsed, so the pair takes a row of its own
	   rather than half of one. This is also what .credits-filter-actions' own "span 2" / "span 1"
	   rules below already say for the credits bar, in the two column counts that bar uses. */
	.filter-grid .filter-search-button {
		grid-column: 1 / -1;
	}
}

@media (max-width: 720px) {
	.filter-grid {
		grid-template-columns: 1fr;
	}

	/* The note is nowrap so it never breaks mid-sentence beside its heading, but at 375 there is
	   no room for both on one line - the wrapping row above puts it on its own line instead of
	   squeezing "Your reports" into two. */
	.section-heading-note {
		white-space: normal;
	}
}

/* --------------------------------------------------------------------
	CREDITS PAGE (app.css 2391-2693) - .card-eyebrow, shared (C4).
	www/credits.php and www/admin/companies/detail.php's credit
	ledger card.
	-------------------------------------------------------------------- */
.card-eyebrow {
	display: block;
	margin-bottom: var(--spacing-lg);
	color: var(--color-text-header);
	font-size: var(--font-size-xs);
	font-weight: 600;
	text-transform: uppercase;
}

/* --------------------------------------------------------------------
	CREDITS PAGE (app.css 2391-2693) - ledger amounts, shared (C4).
	www/credits.php and www/admin/companies/detail.php's credit
	ledger.
	-------------------------------------------------------------------- */
/* Both colours are chosen for AA on plain white, not on a tinted background - #C4102A already
	proven at 5.5:1 (see .property-remove above); #1E7A44 already proven at ~5.35:1 on white
	(chip-completed's own comment computes it against its pastel background, which is lighter still
	than white, so the ratio here can only be higher). */
.credits-amount-negative {
	color: #C4102A;
}

.credits-amount-positive {
	color: #1E7A44;
}

/* ====================================================================
	PRESENCE
	==================================================================== */

/* #1E7A44, not --color-success (#5FBF8B): the demo's pale green is 1.8:1 on white, and a dot that
	MEANS something - this person has a tab open - has to meet the 3:1 WCAG asks of a non-text
	indicator. This one is 5.3:1, which also lets .presence-note use it as text. The word "Online"
	is in the markup beside it, visually hidden, because a colour on its own says nothing to a
	screen reader. */
.presence-dot {
	display: inline-block;
	/* The name cell is a flex row, and without this the dot is the item that gives way when a long
	   name meets a narrow phone - it flattens into an oval rather than staying a dot. */
	flex-shrink: 0;
	width: 8px;
	height: 8px;
	border-radius: var(--radius-round);
	background: #1E7A44;
}

/* The [hidden] attribute is how both of these start life and how they go away again; without this
	the display rules above and below would out-specify the browser's own [hidden] rule and they
	would never hide at all. */
.presence-dot[hidden],
.presence-note[hidden] {
	display: none;
}

.presence-note {
	color: #1E7A44;
	font-size: var(--font-size-sm);
	font-weight: 500;
}

.presence-list {
	display: flex;
	flex-direction: column;
	gap: var(--spacing-sm);
	margin: var(--spacing-sm) 0 0 0;
	padding: 0;
	list-style: none;
}

/* Wraps rather than truncates: on a phone the page path and the time move onto their own line
	instead of one of them disappearing. */
.presence-row {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: var(--spacing-md);
	font-size: var(--font-size-base);
}

.presence-name {
	color: var(--color-text-primary);
	font-weight: 500;
}

.presence-page {
	color: var(--color-text-secondary);
	word-break: break-word;
}

.presence-since {
	margin-left: auto;
	color: var(--color-text-secondary);
	font-size: var(--font-size-sm);
}

/* ====================================================================
	API ACCESS (Company > API tab, www/company/index.php)
	==================================================================== */

/* The one-time token panel rides on .flash.flash-success for its box and colours; these only lay
	out the label and the read-only field inside it, in the monospace the token is easier to check
	character by character in. */
.api-key-token label {
	display: block;
	margin-bottom: var(--spacing-md);
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	font-weight: 500;
}

.api-key-token input[type="text"] {
	width: 100%;
	height: 42px;
	padding: var(--spacing-lg) var(--spacing-xl);
	border: 1px solid var(--color-border);
	border-radius: var(--radius-md);
	background: var(--color-bg-card);
	color: var(--color-text-primary);
	font-family: var(--font-mono);
	font-size: var(--font-size-sm);
}

/* Base URL, reference link and curl example: a label column beside its value, stacked under the
	720px breakpoint below. */
.api-access-details {
	display: grid;
	gap: var(--spacing-xl);
	margin: var(--spacing-3xl) 0 0 0;
}

.api-access-detail {
	display: grid;
	grid-template-columns: 120px minmax(0, 1fr);
	gap: var(--spacing-xl);
	align-items: baseline;
}

.api-access-detail dt {
	color: var(--color-text-primary);
	font-size: var(--font-size-base);
	font-weight: 500;
}

.api-access-detail dd {
	margin: 0;
	color: var(--color-text-secondary);
	font-size: var(--font-size-base);
}

.api-access-detail a {
	text-decoration: underline;
}

.api-access-details code,
.card-message code,
.api-key-prefix {
	font-family: var(--font-mono);
	font-size: var(--font-size-sm);
	color: var(--color-text-primary);
}

.api-access-example {
	margin: 0;
	padding: var(--spacing-lg) var(--spacing-xl);
	border-radius: var(--radius-md);
	background: var(--color-bg-section);
	overflow-x: auto;
	white-space: pre;
}

/* A list of ranges can run to 500 characters; it wraps inside its cell rather than widening the
	table. */
.api-key-cidrs {
	max-width: 220px;
	word-break: break-word;
}

@media (max-width: 720px) {
	.api-access-detail {
		grid-template-columns: minmax(0, 1fr);
		gap: var(--spacing-sm);
	}
}
