/*
 * BSBC — site-level styles.
 *
 * Loaded FIRST by every page on the site, each of which then loads exactly one
 * feature stylesheet after it:
 *
 *   /index.html, /404.html   →  /styles.css  then  /home.css
 *   /dashboard/**            →  /styles.css  then  /dashboard/dashboard.css
 *   /tickets/**  (planned)   →  /styles.css  then  /tickets/tickets.css
 *
 * LOAD ORDER IS PART OF THE DESIGN. The site file comes first so a feature
 * file can override it with a plain rule of the same specificity; put the
 * <link>s the other way round on a page and those overrides silently stop
 * working. Every new feature follows the same pattern: link this file, then
 * one file of its own.
 *
 * What lives here is what every feature shares — the tokens, the reset and
 * page frame, the logo lockup, the passphrase lock screen, the top bar and
 * page tabs, the generic table look, the footer, and the loading/error
 * states. Anything about one feature's DATA rather than the site's chrome
 * belongs in that feature's file instead.
 *
 * The section numbers are the ones these sections had in dashboard.css, which
 * they were split out of, so the two files still read as one sequence and the
 * gaps on each side line up.
 *
 *   1. Tokens          colours, fonts — change these, not the rules below
 *   2. Base            resets and page frame
 *   3. Logo
 *   4. Lock screen
 *   5. Header          top bar, page tabs, section headings
 *   9. Tables          the generic table look, and click-to-sort
 *  11. Notes and footer
 *  12. States (loading, error, banner)
 *  13. Responsive and motion
 *
 * The gaps are deliberate: sections 6 KPI cards, 7 Goal rails, 8 Charts,
 * 10 Rankings, 14 Print export and 15 Admin panel are dashboard-only and live
 * in /dashboard/dashboard.css, as do the dashboard-specific parts of 5, 9
 * and 13.
 */

/* ── 1. Tokens ─────────────────────────────────────────────────────────── */
/* Every colour on the site comes from here. To restyle it, change these
   values rather than hunting through the rules in this file or in a
   feature's own stylesheet. */
:root {
	--ink: #14171c;
	--paper: #ecedea;
	--panel: #ffffff;
	--rule: #d2d5d0;
	--rule-soft: #e4e6e2;
	--muted: #6c737b;
	--faint: #98a0a6;

	--good: #146b4f;
	--good-bg: #e4f0ea;
	--warn: #9c5f00;
	--warn-bg: #f6eedd;
	--bad: #ad2a22;
	--bad-bg: #f7e5e3;

	--brand: #c8102e;
	--slate: #3a4250;

	/* Series colours, named by what they mean rather than what they look like */
	--series-cogs: #c9a227;
	--series-labor: #5d7a9b;
	--series-variance: #8a5a9b;

	--mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	--sans: "Archivo", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

	/* Collapse/expand motion, shared by the trend section's CSS transition
	   (.collapsible, dashboard.css) and every <details> panel's
	   animation (/js/disclosure.js, which reads these at click time), so the
	   two can never move differently. Height takes the full duration; the
	   fade is a little shorter so content is gone before the box finishes. */
	--collapse-duration: 220ms;
	--collapse-fade: 180ms;
	--collapse-easing: ease;
}

/* ── 2. Base ───────────────────────────────────────────────────────────── */
* {
	box-sizing: border-box;
}
html,
body {
	margin: 0;
	padding: 0;
}
body {
	background: var(--paper);
	color: var(--ink);
	font-family: var(--sans);
	font-size: 15px;
	line-height: 1.45;
	-webkit-font-smoothing: antialiased;
}
.wrap {
	max-width: 95vw;
	margin: 0 auto;
	padding: 0 20px 80px;
}
.eyebrow {
	font-family: var(--mono);
	font-size: 10.5px;
	font-weight: 600;
	letter-spacing: 0.14em;
	text-transform: uppercase;
	color: var(--faint);
}

/* ── 3. Logo ───────────────────────────────────────────────────────────── */
/* --logoh sets the height; the text fallback scales off it, so one variable
   controls the whole lockup. If the image fails to load, JS adds .nologo and
   the wordmark takes over. */
.logo {
	display: inline-flex;
	align-items: center;
	line-height: 1;
}
.logo img {
	display: block;
	height: var(--logoh, 38px);
	width: auto;
}
.logo .fb {
	display: none;
	align-items: baseline;
	gap: 8px;
}
.logo.nologo img {
	display: none;
}
.logo.nologo .fb {
	display: inline-flex;
}
.logo .fb b {
	font-family: var(--sans);
	font-weight: 800;
	letter-spacing: -0.03em;
	color: var(--brand);
	font-size: calc(var(--logoh, 38px) * 0.82);
	line-height: 1;
}
.logo .fb i {
	font-style: normal;
	font-family: var(--mono);
	font-weight: 600;
	font-size: calc(var(--logoh, 38px) * 0.26);
	letter-spacing: 0.11em;
	text-transform: uppercase;
	color: var(--slate);
	border-left: 1px solid var(--rule);
	padding-left: 8px;
}

/* ── 4. Lock screen ────────────────────────────────────────────────────── */
#gate {
	min-height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 24px;
}
.lockbox {
	width: 100%;
	max-width: 420px;
	background: var(--panel);
	border: 1px solid var(--rule);
	padding: 34px 30px 28px;
}
.lockbox .brandrule {
	height: 4px;
	background: var(--brand);
	margin: -34px -30px 26px;
}
.lockbox h1 {
	font-size: 19px;
	font-weight: 800;
	letter-spacing: -0.01em;
	margin: 0 0 4px;
}
.lockbox .sub {
	font-size: 12.5px;
	color: var(--muted);
	margin: 0 0 24px;
}
.lockbox label {
	display: block;
	margin: 0 0 7px;
}
.lockbox input {
	width: 100%;
	padding: 11px 12px;
	font-family: var(--mono);
	font-size: 14px;
	border: 1px solid var(--rule);
	background: #fafaf9;
	color: var(--ink);
	border-radius: 0;
}
.lockbox input:focus {
	outline: 2px solid var(--slate);
	outline-offset: -1px;
	background: #fff;
}
.lockbox button {
	width: 100%;
	margin-top: 12px;
	padding: 11px;
	background: var(--ink);
	color: #fff;
	border: 0;
	font-family: var(--sans);
	font-size: 13.5px;
	font-weight: 700;
	letter-spacing: 0.02em;
	cursor: pointer;
}
.lockbox button:hover {
	background: var(--slate);
}
.lockbox button:disabled {
	background: var(--faint);
	cursor: wait;
}
/*
 * Off-screen but still in the layout tree. Used for the fixed hidden
 * username inside each passphrase form (#pwuser, #adminpwuser): password
 * managers ignore display:none / [hidden] fields, and without a username the
 * dashboard and admin passphrases are saved as one login and overwrite each
 * other. !important because `.lockbox input` above is more specific.
 */
.visually-hidden {
	position: absolute !important;
	width: 1px !important;
	height: 1px !important;
	padding: 0 !important;
	margin: -1px !important;
	border: 0 !important;
	overflow: hidden !important;
	clip: rect(0 0 0 0) !important;
	clip-path: inset(50%) !important;
	white-space: nowrap !important;
}
#gatemsg {
	min-height: 18px;
	margin-top: 11px;
	font-family: var(--mono);
	font-size: 12px;
	color: var(--bad);
}
.lockfoot {
	margin-top: 22px;
	padding-top: 16px;
	border-top: 1px solid var(--rule-soft);
	font-size: 11.5px;
	color: var(--muted);
	line-height: 1.5;
}

/* ── 5. Header ─────────────────────────────────────────────────────────── */
#app {
	display: none;
}
/* A dashboard tab that was signed in on its previous page opens on the page
   frame (top bar, tabs, "Loading…") instead of the gate, so moving between
   pages no longer flashes the lock screen. `signed-in` is set by
   /dashboard/js/boot.js before the page is drawn; it is only a guess, and the
   shell's inline `display` (showGate/showApp) overrides it the moment the
   real session check answers. */
html.signed-in #gate {
	display: none;
}
html.signed-in #app {
	display: block;
}
.topbar {
	border-top: 4px solid var(--brand);
	background: var(--panel);
	border-bottom: 1px solid var(--rule);
}
.topbar .inner {
	max-width: 1180px;
	margin: 0 auto;
	padding: 20px 20px 18px;
	display: flex;
	flex-wrap: wrap;
	gap: 16px;
	align-items: flex-end;
	justify-content: space-between;
}
.brandblock {
	display: flex;
	align-items: center;
	gap: 18px;
}
.brandblock .logo {
	flex: 0 0 auto;
	padding-right: 18px;
	border-right: 1px solid var(--rule);
	align-self: stretch;
}
h1.title {
	font-size: 24px;
	font-weight: 800;
	letter-spacing: -0.02em;
	margin: 6px 0 3px;
	line-height: 1.15;
}
.srcline {
	font-family: var(--mono);
	font-size: 11.5px;
	color: var(--muted);
}
.topactions {
	display: flex;
	gap: 8px;
	align-items: center;
}
.lockbtn {
	font-family: var(--mono);
	font-size: 11px;
	font-weight: 600;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	background: none;
	border: 1px solid var(--rule);
	padding: 7px 12px;
	cursor: pointer;
	color: var(--muted);
}
.lockbtn:hover:not(:disabled) {
	border-color: var(--ink);
	color: var(--ink);
}
.lockbtn:disabled {
	opacity: 0.5;
	cursor: wait;
}

/* Page tabs, sitting under the title inside the same top bar. */
.pagenav {
	border-top: 1px solid var(--rule-soft);
}
/* The tabs are drawn by script (nav.js), which on a signed-in tab now runs
   after the frame has already painted (see `html.signed-in` below) — so the
   empty row keeps a tab's height, 11 + 17 + 11 padding/line plus the 2px
   underline, and the page does not jump down when they arrive. Keep
   min-height in step with the tab's padding, line-height and border. */
.pagenav .inner {
	max-width: 1180px;
	margin: 0 auto;
	padding: 0 20px;
	display: flex;
	gap: 4px;
	min-height: 41px;
}
.pagenav a {
	display: block;
	padding: 11px 16px;
	font-family: var(--mono);
	font-size: 11.5px;
	line-height: 17px;
	font-weight: 600;
	letter-spacing: 0.06em;
	text-transform: uppercase;
	color: var(--muted);
	text-decoration: none;
	/* Reserve the underline on every tab so the row does not shift by 2px
	   when the current page changes. */
	border-bottom: 2px solid transparent;
}
.pagenav a:hover {
	color: var(--ink);
	background: #f6f6f4;
}
.pagenav a[aria-current="page"] {
	color: var(--ink);
	border-bottom-color: var(--brand);
}
.pagenav a:focus-visible {
	outline: 2px solid var(--slate);
	outline-offset: -2px;
}

section {
	margin-top: 34px;
}
h2.sec {
	font-size: 12px;
	font-weight: 700;
	letter-spacing: 0.13em;
	text-transform: uppercase;
	font-family: var(--mono);
	color: var(--slate);
	margin: 0 0 3px;
	padding-bottom: 8px;
	border-bottom: 1px solid var(--rule);
	display: flex;
	justify-content: space-between;
	align-items: baseline;
	gap: 12px;
	flex-wrap: wrap;
}
h2.sec .hint {
	font-weight: 400;
	letter-spacing: 0.02em;
	text-transform: none;
	color: var(--faint);
	font-size: 11.5px;
}

/* ── 9. Tables ─────────────────────────────────────────────────────────── */
.tablewrap {
	margin-top: 14px;
	margin-bottom: 14px;
	border: 1px solid var(--rule);
	background: var(--panel);
	overflow-x: auto;
}
table {
	border-collapse: collapse;
	width: 100%;
	min-width: 900px;
}
th,
td {
	padding: 8px 11px;
	text-align: center;
	font-family: var(--mono);
	font-size: 12.5px;
	font-variant-numeric: tabular-nums;
	white-space: nowrap;
}
th {
	font-size: 9.5px;
	font-weight: 600;
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--faint);
	border-bottom: 1px solid var(--rule);
	cursor: pointer;
	user-select: none;
	position: sticky;
	top: 0;
	background: var(--panel);
	z-index: 1;
}
th:hover,
th.sorted {
	color: var(--ink);
}
th .arr {
	font-size: 8px;
	margin-left: 3px;
}
td.name,
th.name {
	text-align: left;
	font-weight: 600;
	font-size: 12.5px;
}
tbody tr {
	border-bottom: 1px solid var(--rule-soft);
}
tbody tr:last-child {
	border-bottom: 0;
}
tbody tr:hover {
	background: #f7f8f6;
}
tfoot td {
	border-top: 2px solid var(--ink);
	font-weight: 600;
	background: #f7f8f6;
}
tfoot td.name {
	font-weight: 700;
}

/* The pinned company/total row inside a tbody (sort.js keeps tr.totalrow
   last through every sort — see applySort()). Same treatment as tfoot's
   totals, just living in tbody because sort.js only reorders tbody rows. */
tr.totalrow td {
	border-top: 2px solid var(--ink);
	font-weight: 600;
	background: #f7f8f6;
}
tr.totalrow td.name {
	font-weight: 700;
}

.na {
	color: var(--faint);
}

/* Click-to-sort (sort.js). Scoped to th[role="button"] — the attribute
   sort.js sets on every header it binds — so a th[data-nosort] header, which
   sort.js skips, stays a plain label with no pointer or hover affordance. */
th[role="button"] {
	cursor: pointer;
	user-select: none;
}
th[role="button"]:focus-visible {
	outline: 2px solid var(--brand);
	outline-offset: -2px;
}
th[aria-sort="ascending"]::after {
	content: " ▲";
	color: var(--faint);
}
th[aria-sort="descending"]::after {
	content: " ▼";
	color: var(--faint);
}

/* ── 11. Notes and footer ──────────────────────────────────────────────── */
.notes {
	background: var(--panel);
	border: 1px solid var(--rule);
	border-left: 4px solid var(--warn);
	padding: 16px 20px;
	margin-top: 18px;
}
.notes h4 {
	margin: 0 0 8px;
	font-size: 12px;
	font-family: var(--mono);
	letter-spacing: 0.1em;
	text-transform: uppercase;
	color: var(--warn);
}
.notes ul {
	margin: 0;
	padding-left: 18px;
	font-size: 13.5px;
	line-height: 1.6;
}
.notes li {
	margin-bottom: 7px;
}
.notes li:last-child {
	margin-bottom: 0;
}
.notes code {
	font-family: var(--mono);
	font-size: 12px;
	background: #f2f2ef;
	padding: 1px 5px;
}

footer {
	margin-top: 44px;
	padding-top: 18px;
	border-top: 1px solid var(--rule);
	font-family: var(--mono);
	font-size: 11px;
	color: var(--faint);
	line-height: 1.7;
}
.footbrand {
	display: flex;
	align-items: center;
	gap: 16px;
	margin-bottom: 14px;
	padding-bottom: 14px;
	border-bottom: 1px solid var(--rule-soft);
	color: var(--muted);
}
.footbrand .logo {
	flex: 0 0 auto;
	opacity: 0.85;
}
/* The plain footer link to the admin page — see panels.js's drawFooter(). */
.adminlink {
	color: var(--faint);
	text-decoration: none;
}
.adminlink:hover {
	color: var(--muted);
	text-decoration: underline;
}

/* ── 12. States ─────────────────────────────────────────────────────────── */
/* Shown while the Worker is fetching from SharePoint, which can take a couple
   of seconds on a cache miss. */
.loading {
	padding: 60px 20px;
	text-align: center;
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
}

/*
 * Progress indicator for the long waits (a cold Location Report rebuild runs
 * past a minute). Movement is the point: a static "Loading…" for that long
 * reads as a hang, and the user's next move is a reload that discards the work
 * already in flight.
 */
.progress {
	max-width: 460px;
	margin: 0 auto;
}

.progressbar {
	height: 3px;
	border-radius: 2px;
	background: var(--rule);
	overflow: hidden;
}

.progressbar i {
	display: block;
	width: 40%;
	height: 100%;
	border-radius: 2px;
	background: var(--brand, var(--ink));
	animation: progressslide 1.4s ease-in-out infinite;
}

/*
 * An indeterminate bar, deliberately. We cannot know how far along the Worker
 * is — one fetch reports nothing until it resolves — and a bar that crept to
 * 90% on a timer would be inventing a number the page does not have.
 */
@keyframes progressslide {
	0% {
		transform: translateX(-100%);
	}
	100% {
		transform: translateX(250%);
	}
}

.progresstitle {
	margin-top: 16px;
	font-family: var(--mono);
	font-size: 12px;
	letter-spacing: 0.08em;
	text-transform: uppercase;
	color: var(--faint);
}

/* Sentence case and untracked: this is prose to read, not a label to scan. */
.progressnote {
	margin-top: 10px;
	font-size: 13px;
	line-height: 1.5;
	letter-spacing: 0;
	text-transform: none;
	color: var(--faint);
}

.progresstime {
	margin-top: 8px;
	font-family: var(--mono);
	font-size: 11px;
	letter-spacing: 0.06em;
	color: var(--faint);
	opacity: 0.75;
}

/*
 * Respect a reduced-motion preference: the sliding bar is decorative, and the
 * elapsed counter already carries the "still working" signal on its own.
 */
@media (prefers-reduced-motion: reduce) {
	.progressbar i {
		animation: none;
		width: 100%;
		opacity: 0.4;
	}
}

.banner {
	margin-top: 18px;
	padding: 12px 16px;
	background: var(--warn-bg);
	border-left: 4px solid var(--warn);
	font-size: 13px;
	color: var(--ink);
}
.banner.error {
	background: var(--bad-bg);
	border-left-color: var(--bad);
}
.banner b {
	font-weight: 700;
}
.banner code {
	font-family: var(--mono);
	font-size: 12px;
	background: rgba(0, 0, 0, 0.05);
	padding: 1px 5px;
}
/*
 * Failed-lookup lines. Monospaced and one per row because each carries a
 * person, a store, an HTTP status and an API path — run together they are
 * unreadable, and nobody acts on a warning they cannot parse.
 */
.banner .faillist {
	margin: 8px 0 0;
	padding-left: 18px;
	font-family: var(--mono);
	font-size: 11.5px;
	line-height: 1.7;
}
.banner .faillist li {
	/* The URL is the long part; let it wrap rather than widen the banner. */
	overflow-wrap: anywhere;
}

/* ── 13. Responsive and motion ─────────────────────────────────────────── */
/* Only the site-level selectors are here. Each feature file carries its own
   @media (max-width: 640px) block for its own selectors — see the matching
   block near the end of /dashboard/dashboard.css. */
@media (max-width: 640px) {
	h1.title {
		font-size: 20px;
	}
	.wrap {
		padding: 0 14px 60px;
	}
	.topbar .inner {
		padding: 16px 14px;
	}
	.brandblock {
		flex-direction: column;
		align-items: flex-start;
		gap: 12px;
	}
	.brandblock .logo {
		border-right: 0;
		padding-right: 0;
		--logoh: 34px;
	}
	.footbrand {
		flex-direction: column;
		align-items: flex-start;
		gap: 10px;
	}
}

/* This one is global on purpose and stays in the site file: it is `!important`
   on `*`, so it beats every feature file's transitions and animations no
   matter which file they are in or which loads first. */
@media (prefers-reduced-motion: reduce) {
	* {
		animation: none !important;
		transition: none !important;
	}
}

/* Cross-fade between pages on the same origin instead of cutting (2026-10-13),
   where the browser supports cross-document view transitions — others ignore
   this and cut as before. Off for reduced motion. */
@media (prefers-reduced-motion: no-preference) {
	@view-transition {
		navigation: auto;
	}
}
