/* NDSH Manufacturing - Industrial Enterprise
 *
 * Served statically from /app.css. The source of truth is Files/app.css, which
 * EnsureStaticAssets materialises into Objects/www on every start, because
 * Objects is build output and is not tracked.
 *
 * Tokens come from UI Examples/DESIGN.md. The shell rules below were ported
 * from MainView.ShellStyles at identical values, so moving the stylesheet out
 * of the B4J string changed nothing visually. New in this pass: the type scale
 * and the table layer.
 */

/* ---- Tokens -------------------------------------------------------------- */

:root {
	/* Surfaces */
	--surface-bg: #f4f6f8;
	--surface: #ffffff;
	--surface-container-low: #f1f3ff;
	/* The Journey popup's fact panels, step tiles, empty state and close-button
	 * hover all read this token, and it was never defined -- so all six sat
	 * transparent from the day the popup shipped. An undefined var() does not
	 * fall back; it invalidates the whole declaration. Aliased to the lightest
	 * tinted container rather than given a seventh colour. Found by the
	 * var(--name) sweep on 11 Sep 2026. */
	--surface-soft: var(--surface-container-low);
	--surface-container: #e9edff;
	--surface-container-high: #e1e8ff;
	--surface-variant: #d9e2fc;

	/* Ink */
	--on-surface: #121b2e;
	--on-surface-variant: #44474e;
	--ink-body: #172033;
	--ink-muted: #566176;
	--ink-faint: #687386;

	/* Structure */
	--border-standard: #d9dee7;
	--border-hairline: #edf0f4;
	--outline-variant: #c4c6cf;

	/* Navy */
	--primary: #001634;
	--primary-container: #122b4e;
	--sidebar-bg: #122b4e;
	--on-sidebar: #eaf0f8;
	--on-sidebar-dim: #bac6d8;
	--on-sidebar-faint: #9eabc0;

	/* Accent and status */
	--secondary-accent: #c8961c;
	--status-good: #1f7a5a;
	--status-watch: #a97612;
	--status-problem: #9b2c1c;
	--status-neutral: #122b4e;
	--error-action: #b42318;
	--error-ink: #912018;

	/* Shape */
	--radius-input: .45rem;
	--radius-nav: .55rem;
	--radius-card: .7rem;
	--radius-sm: .25rem;

	/* Layout */
	--sidebar-width: 17rem;
	--container-max: 90rem;
	--gutter: 1.5rem;

	/* Type scale */
	--font-body: 'Work Sans', system-ui, -apple-system, 'Segoe UI', Roboto, Arial, sans-serif;
	--display-lg: 800 27px/32px var(--font-body);
	--headline-lg: 800 21px/28px var(--font-body);
	--headline-md: 700 18px/24px var(--font-body);
	--body-lg: 400 16px/24px var(--font-body);
	--body-md: 400 14px/20px var(--font-body);
	--label-md: 700 12px/16px var(--font-body);
	--label-sm: 400 11px/14px var(--font-body);
}

/* Work Sans is a variable font: Google serves one file for the whole
 * 400-800 range, so this is a single face with a weight range rather than three
 * static faces. Self-hosted from /fonts so the app renders correctly with no
 * internet. Every rule below still states its own weight and size, so the scale
 * survives a fallback to system-ui. Licence: Files/worksans-OFL.txt */
@font-face {
	font-family: 'Work Sans';
	font-style: normal;
	font-weight: 400 800;
	font-display: swap;
	src: url('/fonts/worksans-latin.woff2') format('woff2');
}

/* ---- Base ---------------------------------------------------------------- */

* { box-sizing: border-box }

body {
	margin: 0;
	background: var(--surface-bg);
	color: var(--ink-body);
	font: var(--body-lg);
}

/* Headings carry the scale rather than browser defaults. */
h1 { font: var(--headline-lg); color: var(--on-surface); margin: 0 0 .75rem }
h2 { font: var(--headline-md); color: var(--on-surface); margin: 0 0 .6rem }
h3 { font: var(--headline-md); font-size: 16px; color: var(--on-surface); margin: 0 0 .5rem }

/* ---- Shell: layout ------------------------------------------------------- */

.erp-layout {
	min-height: 100vh;
	display: grid;
	grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
}

.erp-sidebar {
	position: sticky;
	top: 0;
	height: 100vh;
	display: flex;
	flex-direction: column;
	background: var(--sidebar-bg);
	color: #fff;
	padding: 1.25rem 1rem;
	overflow-y: auto;
}

.erp-brand {
	display: flex;
	flex-direction: column;
	align-items: center;
	text-align: center;
	/* No horizontal padding: the mark is centred and wants every pixel the
	 * sidebar can give it. Vertical padding is tight because the space above and
	 * below the mark was doing nothing except pushing the navigation down. */
	padding: .5rem 0 .6rem;
	border-bottom: 1px solid rgba(255, 255, 255, .18);
}

/* 15rem is the CEILING, not a preference: 17rem of sidebar less its own 1rem
 * padding either side leaves exactly 240px, and the brand block gives up its
 * horizontal padding above so the mark can have all of it. A true doubling of
 * the old 9.5rem would be 304px and would overflow the column by 88px, so this
 * is 1.58x rather than 2x. Going further means widening --sidebar-width, which
 * DESIGN.md fixes at 17rem and every page is laid out against.
 *
 * Comfortably clear of the floor either way: the silver wordmark stops
 * resolving against #122b4e below about 150px, checked at 160 and 96. */
/* BACK TO 9.5rem, and the floor above is why it does not go smaller. 15rem was
 * the widest the column allows, but the sidebar's scarce dimension is vertical,
 * not horizontal: at 15rem the mark plus its padding pushed the last nav entries
 * below the fold on a laptop, which is a real cost for a logo nobody navigates
 * by. 9.5rem is 152px, just clear of the ~150px at which the silver wordmark
 * stops resolving against #122b4e. */
.erp-brand-logo {
	display: block;
	width: 9.5rem;
	max-width: 100%;
	height: auto;
	margin-bottom: 0;
}


.erp-nav-list {
	list-style: none;
	margin: 1rem 0;
	padding: 0;
	display: grid;
	gap: .3rem;
}

.erp-nav-item {
	min-height: 2.75rem;
	display: flex;
	align-items: center;
	/* flex-start, not space-between: the glyph and the label are one thing and
	 * must sit together. The "Planned" tag on a disabled entry is pushed to the
	 * far edge by its own margin below instead. */
	justify-content: flex-start;
	gap: .7rem;
	padding: .7rem .8rem;
	border-radius: var(--radius-nav);
	color: var(--on-sidebar);
	text-decoration: none;
}

.erp-nav-item.is-active { background: #fff; color: var(--sidebar-bg); font-weight: 700 }
.erp-nav-item > span { white-space: nowrap }
.erp-nav-item.is-disabled { color: var(--on-sidebar-faint); cursor: not-allowed }
.erp-nav-item small { margin-left: auto; font: var(--label-md); font-size: .68rem; text-transform: uppercase; letter-spacing: .05em }

.erp-project-mark {
	flex: none;
	display: grid;
	justify-items: center;
	gap: .45rem;
	margin: .15rem 0 0;
	padding: .7rem .5rem 0;
	border-top: 1px solid rgba(255, 255, 255, .18);
}

.erp-project-logo {
	display: block;
	width: 7rem;
	max-width: 60%;
	height: auto;
	border-radius: .4rem;
}

.erp-project-caption {
	margin: 0;
	font: var(--label-sm);
	font-size: .72rem;
	color: var(--on-sidebar-dim);
	text-align: center;
	white-space: nowrap;
}

/* stroke: currentColor, so a glyph turns navy with its label when the entry goes
 * active against white and needs no second rule to do it. */
.erp-nav-icon {
	flex: none;
	display: grid;
	place-items: center;
	width: 1.25rem;
	height: 1.25rem;
}

.erp-nav-icon svg {
	width: 1.25rem;
	height: 1.25rem;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.erp-sidebar-footer {
	margin-top: auto;
	padding: .9rem .75rem 0;
	border-top: 1px solid rgba(255, 255, 255, .18);
	display: grid;
	gap: .25rem;
	overflow-wrap: anywhere;
}

.erp-sidebar-label,
.erp-sidebar-roles { font: var(--label-sm); font-size: .76rem; color: var(--on-sidebar-dim) }

/* ---- Shell: page chrome -------------------------------------------------- */

.erp-page { min-width: 0; display: flex; min-height: 100vh; flex-direction: column }

.erp-topbar {
	min-height: 4.25rem;
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 1rem;
	padding: .75rem var(--gutter);
	background: var(--surface);
	border-bottom: 1px solid var(--border-standard);
}

.erp-topbar-left,
.erp-user-menu { display: flex; align-items: center; gap: 1rem }
.erp-user-menu { text-align: right }
.erp-user-name,
.erp-user-roles { display: block }
.erp-user-name { font-weight: 700 }
.erp-user-roles { max-width: 28rem; color: var(--ink-muted); font-size: .78rem }

.erp-nav-toggle {
	display: none;
	border: 1px solid #aeb7c6;
	background: var(--surface);
	border-radius: var(--radius-input);
	padding: .5rem .65rem;
	font: inherit;
}

/* The hamburger, and the initials disc. These two are the whole of the top bar
 * icon set the reference draws - its bell needs a notification system and its
 * cog needs a Settings page, and this application has neither, so neither is
 * here. A control that cannot do what its picture says is worse than none. */
.erp-nav-toggle svg {
	width: 1.25rem;
	height: 1.25rem;
	display: block;
	fill: none;
	stroke: currentColor;
	stroke-width: 1.75;
	stroke-linecap: round;
}

.erp-user-avatar {
	flex: none;
	display: grid;
	place-items: center;
	width: 2.25rem;
	height: 2.25rem;
	border-radius: 50%;
	background: var(--primary-container);
	color: #fff;
	font-size: 12px;
	font-weight: 800;
	letter-spacing: .04em;
}

/* Quiet beside Log out: Log out is bordered red because it ends the session,
 * and Help should not compete with it. */
.erp-help {
	border: 1px solid var(--border-standard);
	background: var(--surface);
	color: var(--on-surface);
	border-radius: var(--radius-input);
	padding: .55rem .75rem;
	font: inherit;
	font-weight: 700;
	text-decoration: none;
	white-space: nowrap;
}

.erp-help:hover { background: var(--surface-container-low) }

/* Full screen. Styled as Help is, because they are the same kind of thing: a
 * secondary control beside the one action on this bar that matters. Log out
 * keeps its own weight.
 *
 * The button ships hidden and the script reveals it, so a browser without the
 * Fullscreen API never shows a control that does nothing. The hiding is written
 * out below rather than left to the browser's own [hidden] default: any author
 * display: added to this class later would silently beat that default, and the
 * button would reappear on exactly the browsers it was hidden from. */
.erp-fullscreen {
	border: 1px solid var(--border-standard);
	background: var(--surface);
	color: var(--on-surface);
	border-radius: var(--radius-input);
	padding: .55rem .75rem;
	font: inherit;
	font-weight: 700;
	cursor: pointer;
	white-space: nowrap;
}

.erp-fullscreen[hidden] { display: none }

.erp-fullscreen:hover { background: var(--surface-container-low) }

.erp-logout {
	border: 1px solid var(--error-action);
	background: var(--surface);
	color: var(--error-ink);
	border-radius: var(--radius-input);
	padding: .55rem .75rem;
	font: inherit;
	font-weight: 700;
	cursor: pointer;
}

.erp-main {
	width: 100%;
	max-width: var(--container-max);
	margin: 0 auto;
	padding: var(--gutter);
	flex: 1;
}

.erp-footer {
	padding: 1rem var(--gutter);
	color: var(--ink-faint);
	font: var(--label-sm);
	font-size: .8rem;
	border-top: 1px solid var(--border-standard);
	background: var(--surface);
}

#toast-container { position: fixed; right: 1rem; bottom: 1rem; z-index: 1200 }
#dialog-container { position: relative; z-index: 1100 }

/* ---- Summary cards ------------------------------------------------------- */

/* Read off home_operational_kpis_queue_ndsh_manufacturing/code.html in the
 * generator output: white card, 1px border, 0.75rem radius, a small shadow, an
 * 11px uppercase label top-left and a 28px tinted tile top-right holding a 16px
 * icon, a 24px extrabold figure, and the change as a tinted chip ahead of its
 * caption.
 *
 * This replaces the full-bleed colour these cards had. DESIGN.md and the first
 * seven reference screens both call for a coloured card; the generator's home
 * screen does not, and the product owner chose the white one after seeing both.
 * Applied to every card in the estate rather than Home alone, because one
 * component that looks like two things is worse than either.
 *
 * pb-tone-* no longer paints the card. It now tints the icon tile, and colours
 * the figure on watch and problem only - which is how the mockup marks "3
 * Materials" and "320 Units" without shouting on the calm ones.
 */

.pb-summary {
	display: grid;
	grid-template-columns: 1fr;
	gap: .875rem;
	margin-bottom: 1.75rem;
}

/* The mockup's own breakpoints: one card, then two, then four. Four is fixed
 * rather than auto-fit, which is what keeps eight cards in the 4x2 the product
 * owner asked for. A row of three therefore leaves a gap on the right - the
 * recipe cards are the only place that shows, and it is a deliberate
 * consequence of a fixed grid rather than an oversight. */
@media (min-width: 640px) {
	.pb-summary { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

@media (min-width: 1024px) {
	.pb-summary { grid-template-columns: repeat(4, minmax(0, 1fr)) }
}

.pb-summary-card {
	display: flex;
	flex-direction: column;
	justify-content: space-between;
	gap: .75rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: .75rem;
	padding: 1rem;
	box-shadow: 0 1px 2px rgba(23, 32, 51, .06);
	text-decoration: none;
	color: var(--on-surface);
	transition: box-shadow .15s ease, border-color .15s ease;
}

.pb-summary-card:hover {
	box-shadow: 0 6px 18px rgba(23, 32, 51, .1);
	border-color: var(--outline-variant);
}

/* A card that filters the list under it and is the filter showing (Orders
 * from shops, 25 September 2026). */
.pb-summary-card[aria-current="true"] { border-color: var(--primary-container); box-shadow: 0 0 0 2px var(--primary-container) }

.pb-summary-head {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: .5rem;
}

.pb-summary-label {
	font-size: 14px;
	line-height: 20px;
	font-weight: 700;
	letter-spacing: normal;
	text-transform: none;
	color: var(--on-surface);
}

.pb-summary-note { font-size: 13px; line-height: 18px; color: var(--ink-muted); margin: 0 }

.pb-summary-icon {
	flex: none;
	width: 1.75rem;
	height: 1.75rem;
	border-radius: .5rem;
	display: grid;
	place-items: center;
}

/* stroke: currentColor on the glyph, so the tile's colour carries the icon and
 * one rule per tone paints both. */
.pb-summary-icon svg {
	width: 1rem;
	height: 1rem;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.pb-summary-value {
	display: block;
	font-size: 24px;
	line-height: 30px;
	font-weight: 800;
	letter-spacing: -.02em;
	color: var(--on-surface);
}

.pb-summary-trend {
	display: flex;
	align-items: center;
	gap: .4rem;
	margin-top: .3rem;
	font-size: 11px;
	line-height: 16px;
	color: var(--ink-muted);
}

.pb-summary-change {
	display: inline-flex;
	align-items: center;
	padding: .1rem .35rem;
	border-radius: .25rem;
	font-size: 11px;
	font-weight: 700;
	white-space: nowrap;
}

/* Icon tile, one per tone. */
.pb-tone-neutral .pb-summary-icon { background: #eff4ff; color: var(--primary-container) }
.pb-tone-good .pb-summary-icon { background: #ecfdf5; color: var(--status-good) }
.pb-tone-watch .pb-summary-icon { background: #fffbeb; color: var(--status-watch) }
.pb-tone-problem .pb-summary-icon { background: #fef2f2; color: var(--status-problem) }

/* The figure is coloured only where the tone is a warning. A green figure on a
 * healthy card would make every screen shout at once. */
.pb-tone-watch .pb-summary-value { color: var(--status-watch) }
.pb-tone-problem .pb-summary-value { color: var(--status-problem) }

/* The change chip. Direction is the model's - MainView already decides whether
 * up is good for each figure - so these say favourable and unfavourable, not up
 * and down. */
.pb-trend-good { color: var(--status-good); background: #ecfdf5 }
.pb-trend-bad { color: var(--status-problem); background: #fef2f2 }
.pb-trend-flat { color: var(--ink-muted); background: var(--surface-container-low) }

/* ---- Tables -------------------------------------------------------------- */

/* Matched to UI Examples/batches_ndsh_manufacturing.png. Note the header is
 * Title Case on white with a single bottom rule - not uppercase on a tint,
 * which is what pass 1 guessed before the reference was read properly.
 *
 * These rules used to live in five views' own PageStyles, byte-identical in
 * each. They are owned here now, so the stylesheet actually wins. The one
 * table rule still in a view is ProductBomView's display:none inside its
 * mobile media query, which belongs with the card fallback it switches to. */

.pb-desktop-table {
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-card);
	box-shadow: 0 5px 16px rgba(23, 32, 51, .05);
	overflow-x: auto;
	margin-bottom: 1.25rem;
}

/* The Orders list is one table per stage group. Left to size themselves,
 * their columns landed in different places block by block (16 September
 * 2026); one column plan lines every group up down the page. */
.pb-short { color: var(--error-ink) }
.pb-stage-table { table-layout: fixed }
/* What they want takes the room (Justice, 30 September 2026): one product
 * a line, and Order and Shop give up the space they did not use. */
.pb-stage-table th:nth-child(1) { width: 11% }
.pb-stage-table th:nth-child(2) { width: 21% }
.pb-stage-table th:nth-child(3) { width: 12% }
.pb-stage-table th:nth-child(4) { width: 34% }
.pb-stage-table th:nth-child(5) { width: 22% }
.pb-desktop-table table {
	width: 100%;
	border-collapse: collapse;
	font: var(--body-md);
}

.pb-desktop-table caption {
	text-align: left;
	padding: 1rem 1.25rem;
	font: var(--headline-md);
	color: var(--on-surface);
}

/* Bottom-aligned, so a header that wraps to two lines still sits on the same
 * baseline as its neighbours. */
.pb-desktop-table th {
	font: var(--body-md);
	font-weight: 700;
	color: var(--on-surface);
	background: var(--surface);
	text-align: left;
	vertical-align: bottom;
	padding: 1rem 1.25rem;
	border-bottom: 1px solid var(--border-standard);
	white-space: nowrap;
}

.pb-desktop-table td {
	padding: 1rem 1.25rem;
	border-bottom: 1px solid var(--border-hairline);
	vertical-align: top;
	color: var(--ink-body);
}

.pb-desktop-table tbody tr:last-child td { border-bottom: 0 }
.pb-desktop-table tbody tr:hover td { background: var(--surface-container-low) }

/* The product or batch name is the thing an operator scans for, so it carries
 * real weight and size rather than sitting at body text. */
.pb-cell-main {
	display: block;
	font-size: 17px;
	line-height: 22px;
	font-weight: 700;
	color: var(--on-surface);
}

.pb-cell-sub {
	display: block;
	margin-top: .2rem;
	font-size: 12.5px;
	line-height: 17px;
	font-weight: 400;
	color: var(--ink-muted);
}

/* A best before that needs attention (Material stock, 25 Sep 2026): red once
 * it has passed, the watch colour of the Materials near best before card while
 * it is near. The date and the words under it take the colour together. */
.pb-date-past .pb-cell-main, .pb-date-past .pb-cell-sub { color: var(--error-ink) }
.pb-date-near .pb-cell-main, .pb-date-near .pb-cell-sub { color: var(--status-watch) }
.pb-date-past .pb-cell-sub, .pb-date-near .pb-cell-sub { font-weight: 700 }

/* Figures read down the decimal point, not ragged. */
.pb-desktop-table td[data-numeric],
.pb-desktop-table th[data-numeric] { text-align: right; font-variant-numeric: tabular-nums }

.pb-total-row td {
	font-weight: 800;
	background: var(--surface-container-low);
	border-top: 2px solid var(--primary-container);
	color: var(--on-surface);
}

/* Sortable headers and the find box (tables.js, 18 September 2026). A header
 * that sorts looks like one: a pointer, a muted glyph that becomes an arrow
 * when the column is sorted. The find box sits above the table card, not in
 * it, so a person sees it before the rows. */
.pb-desktop-table th.pb-sort, th.pb-sort { cursor: pointer; user-select: none }
th.pb-sort:hover, th.pb-sort:focus-visible { color: var(--primary-container) }
th.pb-sort:focus-visible { outline: 2px solid var(--primary-container); outline-offset: -2px }
.pb-sort-glyph { color: var(--ink-muted); font-weight: 400 }
th[aria-sort] .pb-sort-glyph { color: inherit; font-weight: 700 }
/* Breathing room above and below: it sat hard against the note above it and
 * the table card below (Justice, 18 September 2026: "add some line spacing"). */
.pb-table-find { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin: 1.1rem 0 .85rem }
.pb-table-find label { display: flex; align-items: center; gap: .5rem; font: var(--body-md); color: var(--ink-body) }
.pb-table-find input { min-width: 16rem; padding: .45rem .7rem; border: 1px solid var(--border-standard); border-radius: 8px; font: var(--body-md) }
.pb-table-find input:focus-visible { outline: 2px solid var(--primary-container); outline-offset: 1px }
.pb-table-count { color: var(--ink-muted); font: var(--body-md) }

/* The lots behind a split material subtotal, on the batch cost card. Indented
 * and quieter than the material row above it: it is a breakdown of that row's
 * number, not a new fact at the same level. */
.pb-sub td { padding-left: 1.6rem; color: var(--ink-muted); font-size: 13px }

/* An incomplete batch's cost is a floor, not a settled fact yet -- amber, the
 * same amber Ready to make uses for "watch this". */
.pb-warn {
	display: block;
	background: #fffbeb;
	border: 1px solid #f0dca8;
	border-left: 4px solid var(--status-watch);
	border-radius: .5rem;
	padding: .7rem .9rem;
	margin-top: .75rem;
	color: var(--ink-muted);
}

/* Every unpriced delivery, named, because the batch page is where you act on
 * one. Plain list markup: the content carries the message, not the styling. */
.pb-unpriced { margin: .5rem 0 0; padding-left: 1.25rem; color: var(--ink-muted) }

/* In a list the batch code is a link to the run: same size as everywhere else
 * (see .pb-batch-code), plus the dotted underline that marks it as a link. */
.pb-desktop-table .pb-batch-code {
	display: block;
	margin: 0;
	text-decoration-line: underline;
	text-decoration-style: dotted;
	text-underline-offset: 3px;
}

.pb-desktop-table .pb-actions { margin-top: 0 }

/* ---- Content layer ------------------------------------------------------- */

/* These rules were consolidated out of seven per-view PageStyles blocks. That
 * arrangement was deliberate once - each view carried only the classes it
 * emitted - but it cost a specificity war against this stylesheet, 18.6KB
 * re-sent with every page, and the same button written seven times. One
 * stylesheet also removes the defect a past review caught, where a block copied
 * between views left seven classes with no rule at all: here every class is
 * styled on every page that emits it.
 *
 * Two selectors genuinely disagreed between views and are resolved rather than
 * merged blindly - see .pb-lines .pb-line and .pb-check input below. */

/* Page header */
.pb-content { display: grid; gap: 1rem }
.bs-content { min-width: 0 }
.bs-content > h2, .bs-content > p { margin: 0 }
.bs-content > .pb-product-tabs { margin-bottom: .25rem }
.bs-content .pb-card, .bs-content .pb-form { min-width: 0; overflow-wrap: anywhere }
.bs-content .pb-form { box-sizing: border-box; width: 100% }
.bs-recipe summary { cursor: pointer; font-size: 1.05rem; font-weight: 700; padding: .4rem 0 }
.bs-recipe summary:focus-visible { outline: 2px solid var(--secondary-accent); outline-offset: 3px }
.bs-recipe > .pb-form { margin-top: 1rem }
.bs-table { width: 100%; border-collapse: collapse; margin: 1rem 0; text-align: left }
.bs-table th, .bs-table td { padding: .65rem .4rem; border-bottom: 1px solid var(--border-standard); vertical-align: top }
.bs-table .pb-button { padding: .45rem .65rem; font-size: .9rem }
/* The header is a column so the back link can be appended last by the view and
 * still draw first: MainView.BackLink puts one link in every header of a page
 * that was reached from another page, and order:-1 lifts it above the title
 * where a breadcrumb sits. Nothing else in a header cares about the order. */
.pb-page-header { display: flex; flex-direction: column; align-items: flex-start }
.pb-page-header > * { max-width: 100% }
.pb-back-link {
	order: -1;
	display: inline-block;
	margin: 0 0 .6rem;
	font-size: .86rem;
	font-weight: 700;
	color: #174f8a;
	text-decoration: none;
}
.pb-back-link:hover { text-decoration: underline }
.pb-page-header h1 { margin: 0; font: var(--display-lg); letter-spacing: -.02em; color: var(--on-surface) }
.pb-page-header p { margin: .45rem 0 0; color: var(--ink-muted); max-width: 48rem }
.pb-eyebrow { margin: 0 0 .35rem; font: var(--label-md); text-transform: uppercase; color: var(--ink-faint) }
.pb-note { color: var(--ink-faint); font-size: .86rem; max-width: 48rem }

/* Cards.
 *
 * .pb-choice (the Orders page's availability choice) does not share this rule:
 * it keeps the larger padding and lighter resting shadow the setup tiles once
 * had, in its own block below. */
.pb-card {
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-card);
	padding: 1.1rem;
	box-shadow: 0 5px 16px rgba(23, 32, 51, .05);
}

.pb-card h2 { margin: 0; font: var(--headline-md); color: var(--on-surface) }
.pb-card p { margin: .5rem 0 0; color: var(--ink-muted) }
.pb-card-list { display: grid; gap: 1rem }
.pb-card-heading { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start }
.pb-card-heading small { color: var(--ink-faint) }

/* Buttons. DESIGN.md puts buttons and inputs on the tighter 0.45rem radius for
 * a tool-like feel; these were on 0.55rem. Secondary becomes white with a
 * border rather than a blue tint, which is what the reference shows for
 * Filter, Sort and "Record one already made". */
/* Finished products uses ordinary links as tabs, with a visible current view. */
.pb-product-tabs { display: flex; overflow-x: auto; border-bottom: 1px solid var(--border-standard); margin-bottom: 1.25rem }
.pb-product-tabs a { flex: 1 0 auto; min-width: max-content; max-width: 13rem; padding: .8rem .75rem; text-align: center; white-space: nowrap; color: var(--ink-body); text-decoration: none; border-bottom: 3px solid transparent }
.pb-product-tabs a:hover { background: var(--surface-container-low) }
.pb-product-tabs a[aria-current="page"] { color: var(--primary-container); border-bottom-color: var(--primary-container); font-weight: 700 }
.pb-product-tabs a:focus-visible { outline: 2px solid var(--primary-container); outline-offset: -2px }
.pb-product-intro { margin-bottom: 1.25rem }

/* Section accents (Justice, 17 September 2026). Every tab of a tabbed page is
 * a section with its own colour: a bold bar down the left of the content, the
 * same colour on the active tab's underline and on the active pill. No red -
 * red already means stop, refuse or short everywhere in this app - and no
 * tinted page, which would fight the cards and alerts sitting on it. One
 * class per tab; a page with no section class looks as it always did. */
.pb-section-balance { --section-accent: #122b4e }
.pb-section-history { --section-accent: #0f6b66 }
.pb-section-checks { --section-accent: #8a5a19 }
.pb-section-withdrawals { --section-accent: #5b2a6b }
/* The rest of the app, 18 September 2026. The same kind of view carries the
 * same colour wherever it appears: a balance is navy, a history is teal, a
 * check is ochre, a stop is plum. */
/* Material stock */
.pb-section-material-balance { --section-accent: #122b4e }
.pb-section-came-in { --section-accent: #0f6b66 }
/* Production batches */
.pb-section-batch-list { --section-accent: #122b4e }
.pb-section-bulk-batches { --section-accent: #6b3f1e }
.pb-section-packaging-runs { --section-accent: #5b2a6b }
/* Check products: Product checks keeps the ochre; Packaging checks is its own tab */
.pb-section-packaging-checks { --section-accent: #7a3e0a }
/* Manufacturing setup, 18 September 2026: the same three colours as the views they parallel */
.pb-section-materials { --section-accent: #0f6b66 }
.pb-section-products { --section-accent: #122b4e }
.pb-section-recipes { --section-accent: #8a5a19 }
.pb-section-business { --section-accent: #4a3b7a }
.pb-content[class*="pb-section-"] {
	border-left: 6px solid var(--section-accent);
	padding-left: 1.25rem;
}
.pb-content[class*="pb-section-"] .pb-product-tabs a[aria-current="page"] {
	color: var(--section-accent);
	border-bottom-color: var(--section-accent);
}
.pb-content[class*="pb-section-"] .pb-product-intro h2 { color: var(--section-accent) }
.pb-content[class*="pb-section-"] .pb-pill-tabs a[aria-current="page"] {
	background: var(--section-accent);
	border-color: var(--section-accent);
}
.pb-product-intro p { margin: 0; color: var(--ink-muted) }
.pb-material-tabs { overflow-x: auto }
.pb-material-tabs a { flex: 0 0 auto; padding-inline: 1rem }

.pb-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin-top: .9rem }

.pb-button {
	display: inline-block;
	border: 1px solid transparent;
	border-radius: var(--radius-input);
	padding: .7rem 1.1rem;
	font: inherit;
	font-weight: 700;
	text-decoration: none;
	cursor: pointer;
	transition: background-color .15s ease, border-color .15s ease;
}

/* A button sitting straight in the page's grid was stretched to the full
 * width and read as a bar, not a button (Add something you make, 25 Sep 2026
 * re-test). It keeps its own width; a phone still gets 100% below. */
.pb-content > .pb-button { justify-self: start }

.pb-primary { background: var(--primary-container); color: #fff }
.pb-primary:hover { background: var(--primary) }
.pb-next { background: var(--status-good); border-color: var(--status-good); color: #fff }
.pb-next:hover { background: #17604a; border-color: #17604a; color: #fff }

.pb-secondary { background: var(--surface); border-color: var(--border-standard); color: var(--on-surface) }
.pb-secondary:hover { background: var(--surface-container-low) }

.pb-danger { background: var(--surface); color: var(--error-ink); border-color: #e7aaa5 }
.pb-danger:hover { background: #fff1f0 }

.pb-button-quiet { font-weight: 400 }
.pb-link { color: #174f8a; font-weight: 700 }

/* Nothing had a focus ring before. Keyboard operation on a factory floor is
 * the case where that matters most. */
.pb-button:focus-visible,
.pb-link:focus-visible,
.pb-field input:focus-visible,
.pb-field select:focus-visible,
.pb-field textarea:focus-visible,
.pb-desktop-table td input[type="text"]:focus-visible,
.pb-desktop-table td input[type="date"]:focus-visible,
.pb-desktop-table td select:focus-visible {
	outline: 2px solid var(--secondary-accent);
	outline-offset: 2px;
}

/* Forms */
.pb-form {
	display: grid;
	gap: 1rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-card);
	padding: 1.1rem;
	max-width: 48rem;
}

.pb-inline-action {
	display: flex;
	flex-wrap: wrap;
	/* start, not end. These fields have different heights - Product carries no
	 * helper line where the yield field does - and bottom-aligning them dropped
	 * the Product label below its neighbour. Top-aligning puts every label on one
	 * line, which is what the reference shows. */
	align-items: start;
	gap: .8rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-card);
	padding: 1rem;
}

.pb-compact-form { display: inline-flex; gap: .5rem; align-items: center }

/* What came in's Cost per unit: the box and its Save on one line inside the
 * cell. It was a boxed form with Save underneath, which made every delivery
 * row about 200px tall (25 Sep 2026 usability pass). */
.pb-price-form { flex-wrap: nowrap }
.pb-price-form input { width: 7rem; padding: .35rem .5rem }

/* A form whose fields come in pairs (Receive materials): two columns on a PC,
 * one on a phone. Whatever is not a field - the buttons, the error list -
 * takes the full width. */
@media (min-width: 801px) {
	.pb-form-pairs { grid-template-columns: 1fr 1fr; column-gap: 1.25rem; max-width: 60rem }
	.pb-form-pairs > :not(.pb-field) { grid-column: 1 / -1 }
	.pb-form-pairs > .pb-field { align-content: start }
}

.pb-nowrap { white-space: nowrap }

/* The submit sits beside the controls rather than beside the labels, so it is
 * pushed down by one label line plus the field gap. In em so it tracks the
 * font rather than a fixed pixel guess. */
/* Bounded so the helper text wraps inside its column instead of stretching the
 * row until the submit is pushed onto a line of its own. The reference wraps
 * that sentence over three lines in a column about this wide. */
.pb-inline-action .pb-field { flex: 1 1 15rem; max-width: 20rem }

.pb-inline-action > .pb-button { margin-top: calc(1.5em + .35rem) }

/* An explanatory band under a form. Quieter than .pb-errors, louder than the
 * grey .pb-note, for a fact the person needs before they commit. */
.pb-note-panel {
	display: block;
	background: var(--surface-container-low);
	border: 1px solid var(--border-standard);
	border-left: 4px solid var(--primary-container);
	border-radius: .5rem;
	padding: .85rem 1rem;
	margin-top: .75rem;
	font-size: 13px;
	line-height: 19px;
	color: var(--ink-muted);
	max-width: 60rem;
}

.pb-note-panel strong { color: var(--on-surface) }
.pb-field { display: grid; gap: .35rem }
.pb-field label,
.pb-fieldset legend { font-weight: 700; color: var(--on-surface) }
.pb-field small { color: var(--ink-faint) }
/* The live "1 000 L x R85.00 = R85 000.00 for this delivery" line under the price
 * box on Receive materials (shell script, 18 September 2026). Louder than the
 * help text because it is the number that catches a typo. */
.pb-live-total { margin: .35rem 0 0; font: var(--body-md); font-weight: 700; color: var(--primary-container); min-height: 1.4em }
.pb-live-total:empty { display: none }

/* The setup wizard (spec 2026-09-18-setup-wizard-design.md §4). The progress
 * bar heads every step: the four names in order, the current one bold in the
 * section's accent, the others muted links whenever the draft exists. */
/* The recipe steps as chevron blocks joined point to notch (story note 7, option A, 30 September 2026):
 * green with a tick for done, navy for the step you are on, grey for still to come. On a phone they stack. */
.pb-wizard-steps { display: flex; gap: 4px; list-style: none; margin: 0 0 1.25rem; padding: 0 }
.pb-wizard-step { flex: 1 1 0; min-width: 0; clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%, 14px 50%) }
.pb-wizard-step:first-child { clip-path: polygon(0 0, calc(100% - 14px) 0, 100% 50%, calc(100% - 14px) 100%, 0 100%) }
.pb-wizard-step-body { display: block; height: 100%; padding: .65rem 1.4rem .7rem 1.65rem; color: inherit; text-decoration: none; line-height: 1.3 }
.pb-wizard-step:first-child .pb-wizard-step-body { padding-left: .9rem }
.pb-wizard-step-number { display: block; font-size: .72rem; opacity: .85; margin-bottom: .1rem }
.pb-wizard-step strong { display: block; font-size: .9rem; font-weight: 700; margin-bottom: .15rem }
.pb-wizard-step-what { display: block; font-size: .74rem; line-height: 1.35; opacity: .9 }
.pb-wizard-step.is-done { background: var(--status-good); color: #fff }
.pb-wizard-step.is-current { background: var(--primary-container); color: #fff }
.pb-wizard-step.is-todo { background: #e8ebf0; color: #3f4757 }
a.pb-wizard-step-body:hover strong, a.pb-wizard-step-body:focus-visible strong { text-decoration: underline; text-underline-offset: .2em }
a.pb-wizard-step-body:focus-visible { outline: 3px solid #f0b429; outline-offset: -3px }
@media (max-width: 800px) {
	.pb-wizard-steps { flex-direction: column }
	.pb-wizard-step, .pb-wizard-step:first-child { clip-path: none; border-radius: .4rem }
	.pb-wizard-step-body, .pb-wizard-step:first-child .pb-wizard-step-body { padding: .6rem .9rem }
}

/* Batch steps (Justice 30 September 2026): the recipe wizard's chevrons, one
   bar per batch, a row of small ones per packaging run. */
.pb-wizard-step.is-blocked { background: #fbe3e3; color: #8f1d1d }
.pb-batch-map { margin: 0 0 1.25rem }
/* Pinned while the page scrolls, with "You are here" over the active block
   (Justice 1 October 2026). Sections scroll in below it, not under it. */
.pb-steps-pin { position: sticky; top: 0; z-index: 20; background: var(--surface-bg); padding: 1rem 0 .5rem; margin: 0 0 .5rem }
.pb-steps-pin .pb-wizard-steps { margin: 0 }
.pb-steps-title { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; margin: 0 0 .35rem; font-size: .85rem; color: var(--ink-muted) }
.pb-steps-title a { color: var(--primary-container); font-weight: 700; text-decoration: none }
.pb-steps-title a:hover, .pb-steps-title a:focus-visible { text-decoration: underline }
.pb-steps-title .pb-steps-code { color: var(--on-surface); font-weight: 800 }
.pb-steps-markers { display: flex; gap: 4px; margin: 0 0 .3rem; min-height: 1.25rem }
.pb-steps-markers span { flex: 1 1 0; min-width: 0; text-align: center; font-size: 1rem; font-weight: 800; line-height: 1.2; color: #b42318 }
.pb-setup-steps { margin: .25rem 0 1rem }
.pb-setup-steps .pb-wizard-steps { margin-bottom: 0 }
.pb-setup-next { margin: .6rem 0 0 }
.pb-step-tags { margin: 0 0 .5rem }
.pb-step-runs { border-top: 1px solid #eceef3; padding-top: .5rem }
.pb-step-runs h2 { font-size: 1rem; margin: 0 0 .35rem }
.pb-step-run { display: grid; grid-template-columns: 15rem minmax(0, 1fr); gap: 1rem; align-items: center; padding: .45rem .75rem; border-radius: 10px }
.pb-step-run.is-here-run { background: #fdecea; outline: 3px solid #b42318; outline-offset: 0 }
.pb-step-run.is-here-run .pb-step-run-name strong { color: #b42318 }
.pb-step-run-name { display: flex; flex-direction: column; font-weight: 700 }
.pb-step-run-name span { font-weight: 400; font-size: .8rem; color: #4a5468 }
.pb-mini-steps { margin: 0 !important }
.pb-mini-steps .pb-wizard-step-body { padding: .4rem 1.1rem .45rem 1.3rem }
.pb-mini-steps .pb-wizard-step:first-child .pb-wizard-step-body { padding-left: .75rem }
.pb-mini-steps .pb-wizard-step strong { font-size: .82rem; margin-bottom: .05rem }
.pb-step-section { margin: 1.5rem 0 0; scroll-margin-top: 12rem }
.pb-step-section > h2 { font-size: 1.05rem; margin: 0 0 .5rem }
/* A way out that is not the next step: quiet, on the right of the step's own
   button, opening a box that asks why (Justice 1 October 2026). */
.pb-step-actions { align-items: center }
.pb-stop-choice { margin-left: auto; display: flex; flex-wrap: wrap; align-items: center; gap: .6rem }
.pb-stop-question { color: var(--ink-muted); font-weight: 600 }
.pb-check-summary .pb-check-lead { font-weight: 700; margin: 0 0 .5rem }
.pb-check-summary ul { margin: 0 0 .75rem 1.1rem; padding: 0 }
@media (max-width: 800px) {
	.pb-step-run { grid-template-columns: 1fr }
	.pb-steps-pin { position: static }
	.pb-steps-markers { display: none }
	.pb-step-section { scroll-margin-top: 1rem }
}

/* Step 1's two large choices: bordered boxes, the chosen one outlined in the
 * section's accent. The radio stays visible so the choice reads as one. */
.pb-kind-choice { display: grid; gap: .75rem }
.pb-kind-choice label {
	display: grid;
	grid-template-columns: auto 1fr;
	gap: .2rem .8rem;
	align-items: start;
	align-content: start;
	padding: 1rem 1.1rem;
	border: 1px solid var(--border-standard);
	border-radius: .6rem;
	cursor: pointer;
}
.pb-kind-choice label:has(input:checked) { border-color: var(--section-accent, var(--primary-container)); outline: 2px solid var(--section-accent, var(--primary-container)); outline-offset: -1px }
.pb-kind-choice label:focus-within { outline: 2px solid var(--section-accent, var(--primary-container)); outline-offset: 2px }
.pb-kind-choice label:has(input:disabled) { cursor: default; color: var(--ink-muted) }
.pb-kind-choice input { grid-row: 1 / span 2; width: 1.2rem; height: 1.2rem; min-height: 0; margin-top: .15rem }
.pb-kind-choice strong { display: block; color: var(--on-surface) }
.pb-kind-choice span { color: var(--ink-muted) }
.pb-kind-choice small { color: var(--ink-faint) }
@media (min-width: 40rem) {
	.pb-kind-choice { grid-template-columns: 1fr 1fr }
	.pb-kind-choice small { grid-column: 1 / -1 }
}
.pb-fixed-label { font-weight: 700; color: var(--on-surface) }
.pb-fixed-value { margin: 0; font: var(--body-md); color: var(--ink-body) }

/* Step 2's lines table: a select and an amount box per row, a unit beside the
 * amount that follows the material, and a red x that removes the row. */
.pb-lines-table select, .pb-lines-table input { width: 100% }
.pb-lines-table td select {
	min-height: 2.8rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-input);
	padding: .65rem;
	font: inherit;
	color: var(--ink-body);
}
.pb-lines-table td select:focus-visible { outline: 2px solid var(--secondary-accent); outline-offset: 2px }
.pb-lines-table th:nth-child(1) { width: 55% }
.pb-lines-table th:nth-child(2) { width: 35% }
.pb-lines-table .pb-line-amount { display: flex; align-items: center; gap: .5rem }
.pb-line-unit { color: var(--ink-muted); white-space: nowrap }
.pb-line-unit:empty { display: none }
.pb-line-remove { color: var(--error-ink); background: transparent; border: 0; padding: .5rem; font: inherit; cursor: pointer; line-height: 1 }
.pb-lines-note { margin: 0; color: var(--ink-muted); font: var(--body-md) }

/* Step 4's read-only blocks, each with its Change link. */
.pb-wizard-check-head { display: flex; justify-content: space-between; align-items: baseline; gap: 1rem }
.pb-wizard-check-head h3 { margin: 0; font: var(--headline-md); color: var(--on-surface) }
.pb-wizard-lines { margin: .5rem 0 0; padding-left: 1.25rem; color: var(--ink-body) }
/* Beside its What goes in label, the list starts level with the label. */
.pb-definition .pb-wizard-lines { margin: 0 }

.pb-field input::placeholder,
.pb-field textarea::placeholder,
.pb-desktop-table td input[type="text"]::placeholder { color: var(--ink-faint); opacity: 1 }

/* Table-cell inputs are form controls too. The count page puts one in every
 * row, and ProductionView does the same on its two quantity tables, but they
 * are written straight into a td rather than into a .pb-field - so they had no
 * rule at all and rendered as browser defaults: square, hairline-bordered and
 * about half this height. Scoped to type="text" so a checkbox in a cell is not
 * stretched to the full column. A select or date box in a cell (Receive materials, 26
 * September 2026) rendered small and square beside its text boxes, so they share it. */
.pb-field input,
.pb-field select,
.pb-field textarea,
.pb-desktop-table td input[type="text"],
.pb-desktop-table td input[type="date"],
.pb-desktop-table td select {
	width: 100%;
	min-height: 2.8rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-input);
	padding: .65rem;
	font: inherit;
	color: var(--ink-body);
}

.pb-field input:hover,
.pb-field select:hover,
.pb-field textarea:hover,
.pb-desktop-table td input[type="text"]:hover,
.pb-desktop-table td input[type="date"]:hover,
.pb-desktop-table td select:hover { border-color: var(--outline-variant) }

.pb-fieldset { border: 1px solid var(--border-standard); border-radius: .6rem; padding: 1rem }

/* A form already inside a card drops the card chrome, so a panel does not
 * appear nested inside a panel. */
.pb-form-plain {
	background: none;
	border: 0;
	border-radius: 0;
	padding: 0;
	max-width: 44rem;
}

/* A unit label sitting inside the box, as the reference draws it. The word is
 * the product's own output label - packs, loaves - not a fixed one, and it is
 * absent when the product has none. */
.pb-input-suffix { position: relative; display: block }
.pb-input-suffix input { padding-right: 7rem }

.pb-suffix {
	position: absolute;
	right: .75rem;
	top: 50%;
	transform: translateY(-50%);
	max-width: 6rem;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
	font-size: 11px;
	font-weight: 700;
	letter-spacing: .06em;
	text-transform: uppercase;
	color: var(--ink-muted);
	pointer-events: none;
}

/* The destructive action sits below a rule, away from the two that move the
 * draft forward. */
.pb-card-footer {
	/* Full width, because .pb-compact-form is inline-flex and the rule would
	 * otherwise be only as wide as the button sitting on it. */
	display: flex;
	width: 100%;
	margin-top: 1.1rem;
	padding-top: 1rem;
	border-top: 1px solid var(--border-standard);
}
.pb-check { display: flex; align-items: center; gap: .55rem; min-height: 2.5rem }
.pb-check-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(13rem, 1fr)); gap: .65rem }

/* Resolved conflict. ProductionView cancelled the field min-height so a
 * checkbox would not stretch; ProductBomView sized the box. Both intents are
 * needed, so both declarations are kept. */
.pb-check input { width: 1.2rem; height: 1.2rem; min-height: 0 }

/* Status and badges */
.pb-status {
	display: inline-block;
	border-radius: 2rem;
	padding: .3rem .65rem;
	font-size: .78rem;
	line-height: 1.3;
	white-space: nowrap;
	border: 1px solid transparent;
}

.pb-status-active { background: #e6f4ec; color: var(--status-good); border-color: #b7e0cb }
.pb-status-inactive { background: #fdecea; color: var(--status-problem); border-color: #f2c2bb }

/* A third tone, for a state that is neither settled nor refused. */
.pb-status-waiting { background: #fffbeb; color: var(--status-watch); border-color: #f0dca8 }

/* A figure that carries its own meaning: good is a good number and spoilt is
 * not. A ZERO IS LEFT PLAIN in both - nothing spoilt is not a warning, and a
 * table that colours every zero shouts its way through a clean month. */
.pb-figure-good { color: var(--status-good); font-weight: 700 }
.pb-figure-bad { color: var(--status-problem); font-weight: 700 }

.pb-badge { display: inline-block; border-radius: .4rem; padding: .15rem .5rem; font-size: .75rem; font-weight: 700; line-height: 1.5 }
.pb-badge-danger { background: #fff1f0; color: var(--error-ink); border: 1px solid #e7aaa5 }
/* Past its date on batch lists and pages: red and bold, like the Product balance badge (story note 22). */
.pb-past-date { color: var(--error-ink) !important; font-weight: 700 }
/* Had no rule until 17 September 2026: "Shop told" rendered as small plain
 * text and the person who had just recorded it thought nothing had happened. */
.pb-badge-good { background: #ecfdf5; color: var(--status-good); border: 1px solid #a7dcc4 }

/* Withdraw products, 17 September 2026. The stopped-batch page was a stack of
 * cards, two per order and one per pack, each carrying its own form; Justice
 * said the users would be lost. Every repeated block is now a table. A
 * record the signed-in person may act on gets a second row under it holding
 * its forms side by side; the caption carries the one sentence the old card
 * put above its rows. */
.pb-caption-note { display: block; margin-top: .25rem; font: var(--body-md); font-weight: 400; color: var(--ink-muted) }
.pb-table-danger caption { color: var(--error-ink) }
.pb-table-danger .pb-caption-note { color: var(--error-ink); font-weight: 700 }
.pb-cell-line { display: block }
.pb-cell-line + .pb-cell-line { margin-top: .2rem }
.pb-desktop-table tbody tr.pb-action-row td { padding-top: 0; background: var(--surface-container-low) }
.pb-desktop-table tbody tr.pb-action-row:hover td { background: var(--surface-container-low) }
.pb-action-forms { display: flex; flex-wrap: wrap; gap: .75rem; padding: .75rem 0 .25rem }
.pb-action-forms .pb-inline-action { padding: .75rem 1rem }
.pb-action-forms .pb-inline-action .pb-field { flex: 0 1 13rem }
.pb-action-forms .pb-inline-action .pb-actions { margin-top: calc(1.5em + .35rem) }
.pb-inline-title { flex: 1 1 100%; color: var(--on-surface) }

/* The stopped batch's second navigation: pills, so it cannot be mistaken for
 * the Finished products bar above it. The active pill is filled, the rest are
 * outlined; hover only tints. */
.pb-pill-tabs { display: flex; flex-wrap: wrap; gap: .5rem; margin: 0 0 1.25rem }
.pb-pill-tabs a {
	display: inline-block;
	padding: .45rem 1rem;
	border: 1px solid var(--border-standard);
	border-radius: 999px;
	background: var(--surface);
	color: var(--ink-body);
	text-decoration: none;
	font-weight: 600;
	white-space: nowrap;
}
.pb-pill-tabs a:hover { background: var(--surface-container-low) }
.pb-pill-tabs a[aria-current="page"] { background: var(--primary-container); border-color: var(--primary-container); color: #fff; font-weight: 700 }
.pb-pill-tabs a:focus-visible { outline: 2px solid var(--primary-container); outline-offset: 2px }
/* Actions column and form dialogs (17 September 2026). A row is one line
 * however many things can be done to it; each button opens a dialog the size
 * of the form block it replaced. The dialog is rendered closed after the
 * tables and opened by the shell script; one the server rendered open (a
 * refused save) shows as a block without script and as a modal with it. */
.pb-actions-cell { white-space: nowrap }
.pb-actions-cell .pb-button + .pb-button { margin-left: .4rem }
.pb-button-small { padding: .35rem .7rem; font-size: .85rem; line-height: 1.3 }
.pb-dialog {
	width: min(38rem, calc(100vw - 2rem));
	padding: 0;
	border: 0;
	border-radius: 1rem;
	box-shadow: 0 1.25rem 4rem rgba(15, 23, 42, .28);
	color: var(--ink-body);
	background: var(--surface);
}
.pb-dialog::backdrop { background: rgba(15, 23, 42, .62) }
.pb-dialog-body { padding: 1.25rem }
.pb-dialog-body h3 { margin: 0 0 .9rem; font: var(--headline-md); color: var(--on-surface) }
.pb-dialog-body .pb-alert { margin: 0 0 .9rem }
.pb-dialog .pb-inline-action { border: 0; padding: 0; background: transparent }
.pb-dialog .pb-inline-action .pb-field { flex: 1 1 12rem; max-width: none }
.pb-dialog .pb-inline-action .pb-actions { flex: 1 1 100%; margin-top: .35rem }
.pb-stopped-facts { margin: .35rem 0 0 }
.pb-stopped-notes { margin: .35rem 0 0; color: var(--ink-muted) }

/* A batch number is ONE size everywhere it is printed as a code -- a list row, a
 * pick row, the line under a detail page's title, a card. Bold, letter-spaced
 * and in the primary colour so it reads as a code, and no larger than a
 * subheading, so it never swamps the figures beside it. The number that gets
 * written on the tray in marker pen is the detail page's h1, which is the
 * display size already; this class is never the headline. It used to be, at up
 * to 3rem, and every place that reused the class had to scope it back down. */
.pb-batch-code {
	font-size: 1.05rem;
	font-weight: 700;
	letter-spacing: .04em;
	margin: 0 0 .75rem;
	color: var(--primary-container);
}

/* The user guide, rendered into one card.
 *
 * A measure is set because this is the only screen in the app that is read
 * rather than scanned. Every other page is figures and controls, where full
 * width helps; 662 lines of prose at full width on a desktop is a wall nobody
 * finishes a paragraph of.
 *
 * The headings carry scroll-margin so that following a link from the contents
 * does not leave the heading jammed against the top edge with its first line
 * hidden behind nothing. */
.pb-help { max-width: 46rem }

.pb-help h2 {
	margin: 2rem 0 .5rem;
	padding-top: .75rem;
	border-top: 1px solid var(--border-standard);
	scroll-margin-top: 1rem;
}

.pb-help h2:first-of-type { border-top: 0; margin-top: .5rem }

.pb-help h3 { margin: 1.25rem 0 .35rem; scroll-margin-top: 1rem }
.pb-help p { margin: 0 0 .75rem; color: var(--ink-body) }
.pb-help ul, .pb-help ol { margin: 0 0 .75rem 1.25rem; display: grid; gap: .35rem }
.pb-help li { color: var(--ink-body) }
.pb-help a { color: var(--primary); text-underline-offset: .2em }

/* Block quotes in the guide are asides, not warnings. Toned like a note so a
 * reader does not read every one of them as something going wrong. */
.pb-help blockquote {
	margin: 0 0 .75rem;
	padding: .6rem .9rem;
	border-left: 3px solid var(--secondary-accent);
	background: var(--surface-container-low);
	border-radius: var(--radius-input);
}

.pb-help blockquote p:last-child { margin-bottom: 0 }

/* Packing: one row per batch a line can draw on.
 * The batch code keeps the one size every code has (see .pb-batch-code); this
 * rule only places it in the grid. */
.pb-pick-row {
	display: grid;
	grid-template-columns: 1fr auto;
	align-items: center;
	gap: .25rem 1rem;
	padding: .75rem 0;
	border-top: 1px solid var(--border-standard);
}

.pb-pick-row:first-of-type { border-top: 0 }

.pb-pick-row .pb-batch-code {
	grid-column: 1;
	margin: 0;
}

.pb-pick-row .pb-cell-sub {
	grid-column: 1;
	grid-row: 2;
	color: var(--ink-faint);
}

/* The quantity box sits beside the code rather than under it, spanning both
 * rows, so a thumb reaches it without scrolling past the batch it belongs to. */
.pb-pick-row .pb-field {
	grid-column: 2;
	grid-row: 1 / span 2;
	width: 7rem;
}

.pb-pick-row .pb-field label {
	font-size: .8rem;
	font-weight: 600;
	color: var(--ink-faint);
}

/* Dashboard: the Getting started cards.
 *
 * Six steps as six cards in two rows of three, one column on a phone. A card
 * is its number, its title, one line on what the step is for, and the state
 * line with a tick once done. Done cards are navy with light text; the next
 * card carries the gold accent and a "Next" tag; the rest are plain paper.
 * The whole card is the link when the role may act. Justice's design,
 * 16 September 2026, replacing the six sentences that read as a to-do list
 * and told a new owner nothing about what each step was for. */
.pb-step-cards { list-style: none; margin: .9rem 0 0; padding: 0; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: .8rem }
.pb-step-card-body { display: grid; gap: .45rem; align-content: start; height: 100%; padding: .9rem 1rem 1rem; border: 1px solid var(--outline-variant); border-radius: 10px; background: var(--surface); color: var(--on-surface); text-decoration: none }
a.pb-step-card-body:hover { border-color: var(--primary-container); box-shadow: 0 2px 10px rgba(18, 43, 78, .12) }
a.pb-step-card-body:focus-visible { outline: 3px solid var(--status-watch); outline-offset: 2px }
.pb-step-card-head { display: flex; align-items: center; justify-content: space-between; gap: .5rem }
.pb-step-title { font-size: 1rem; font-weight: 700; line-height: 1.25; text-wrap: balance }
.pb-step-explanation { margin: 0; font-size: .86rem; color: var(--ink-muted); line-height: 1.4 }
.pb-step-state { margin-top: auto; padding-top: .35rem; font-size: .84rem; font-weight: 600; color: var(--ink-faint); display: inline-flex; align-items: center; gap: .35rem }
.pb-step-tick { color: var(--status-good); font-weight: 800 }
.pb-step-next { color: var(--status-watch); font-size: .74rem; font-weight: 700; text-transform: uppercase; letter-spacing: .06em }
.pb-step-card.is-next .pb-step-card-body { border: 2px solid var(--status-watch) }
.pb-step-card.is-done .pb-step-card-body { background: var(--primary-container); border-color: var(--primary-container); color: #ffffff }
.pb-step-card.is-done .pb-step-explanation { color: rgba(255, 255, 255, .78) }
.pb-step-card.is-done .pb-step-state { color: #ffffff }
.pb-step-card.is-done .pb-step-tick { color: #9fe0b8 }
.pb-step-card.is-done .pb-step-number { background: rgba(255, 255, 255, .16); color: #ffffff }
/* The step number on Home's Getting started card (DashboardView), at the
 * right-hand end of the card's head. It began on the Manufacturing setup tiles,
 * which went with the setup wizard on 19 September 2026.
 *
 * Gold-on-navy became navy-on-pale-blue: the gold is this estate's accent for
 * something acted on - the current step in the wizard's bar, a primary button -
 * and three tiles all wearing it made every one of them look like the next
 * thing to do. The number is an ordinal, not a call to action. */
.pb-step-number {
	flex: none;
	display: grid;
	place-items: center;
	width: 2.25rem;
	height: 2.25rem;
	border-radius: 50%;
	background: #e1e8ff;
	color: var(--primary-container);
	font: var(--headline-md);
}

/* Dashboard: attention rows.
 *
 * Read off the generator's home screen: each row is its own white card with a
 * 6px tone-coloured bar down the left, a bold label in a fixed column, the names
 * as tinted chips, a tone-coloured action, and the count in a tinted badge.
 *
 * Still scoped to .pb-lines. ProductBomView used to emit a bare .pb-line for its
 * recipe lines, a different row entirely - flat, with a bottom rule - and the
 * scoping kept these rules off it. That page went with the setup wizard (the
 * wizard's lines are .pb-lines-table rows, above) and the bare rule with it;
 * the scoping stays, so nothing else named pb-line ever inherits a card.
 *
 * --line-tone carries the colour, set by .pb-line-<tone> below, and is read here
 * by the bar, the action and the count. A custom property rather than four
 * repeated selectors, and it survives specificity: the base rule is 0,2,0 and
 * would otherwise outrank a 0,1,0 tone class, which is the bug this arrangement
 * was introduced to fix. */

.pb-lines {
	list-style: none;
	margin: 1rem 0 0;
	padding: 0;
	display: grid;
	gap: .6rem;
}

.pb-lines .pb-line {
	position: relative;
	display: grid;
	grid-template-columns: 14rem minmax(0, 1fr) auto auto;
	align-items: center;
	gap: 1.25rem;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: .5rem;
	box-shadow: 0 1px 2px rgba(23, 32, 51, .06);
	padding: .9rem 1rem .9rem 1.5rem;
	overflow: hidden;
	transition: box-shadow .15s ease;
}

.pb-lines .pb-line:hover { box-shadow: 0 6px 18px rgba(23, 32, 51, .1) }

.pb-line-bar {
	position: absolute;
	left: 0;
	top: 0;
	bottom: 0;
	width: .375rem;
	background: var(--line-tone, var(--status-neutral));
}

.pb-line-label {
	font-size: 15px;
	line-height: 20px;
	font-weight: 700;
	color: var(--on-surface);
}

.pb-line-names {
	display: flex;
	flex-wrap: wrap;
	align-items: center;
	gap: .4rem;
	min-width: 0;
}

/* Monospaced, because these are batch codes and material names read against a
 * tray or a label. A digit that changes width is harder to check. */
.pb-line-chip {
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: 12px;
	line-height: 18px;
	padding: .1rem .45rem;
	border-radius: .25rem;
	white-space: nowrap;
	color: var(--line-tone, var(--on-surface));
	background: var(--surface-container-low);
	border: 1px solid var(--border-standard);
}

.pb-line-more {
	font-size: 12px;
	color: var(--ink-muted);
}

.pb-line-action {
	display: inline-flex;
	align-items: center;
	gap: .25rem;
	font-size: 12px;
	font-weight: 700;
	text-decoration: none;
	white-space: nowrap;
	color: var(--line-tone, var(--primary-container));
}

.pb-line-action:hover { text-decoration: underline }

.pb-line-chev svg {
	display: block;
	width: .875rem;
	height: .875rem;
	fill: none;
	stroke: currentColor;
	stroke-width: 2;
	stroke-linecap: round;
	stroke-linejoin: round;
}

.pb-line-count {
	min-width: 2.25rem;
	height: 2.25rem;
	padding: 0 .5rem;
	display: grid;
	place-items: center;
	border-radius: .5rem;
	font-size: 17px;
	font-weight: 700;
	font-variant-numeric: tabular-nums;
	color: var(--line-tone, var(--status-neutral));
	background: var(--surface-container-low);
	border: 1px solid var(--border-standard);
}

/* Set as a custom property, not a colour on each part. The base rule above is
 * .pb-lines .pb-line at specificity 0,2,0 and would outrank a 0,1,0 tone class,
 * silently discarding the accent - which it did on first attempt. */
.pb-line-neutral { --line-tone: var(--status-neutral) }
.pb-line-good { --line-tone: var(--status-good) }
.pb-line-watch { --line-tone: var(--status-watch) }
.pb-line-problem { --line-tone: var(--status-problem) }

/* The chip and badge grounds, tinted to match. Kept as literals rather than
 * tokens because they are the -50 and -200 tints the mockup uses and exist
 * nowhere else in the palette. */
.pb-line-neutral .pb-line-chip,
.pb-line-neutral .pb-line-count { background: #eff4ff; border-color: #c9d7f0 }
.pb-line-good .pb-line-chip,
.pb-line-good .pb-line-count { background: #ecfdf5; border-color: #b7e0cb }
.pb-line-watch .pb-line-chip,
.pb-line-watch .pb-line-count { background: #fffbeb; border-color: #f0dca8 }
.pb-line-problem .pb-line-chip,
.pb-line-problem .pb-line-count { background: #fef2f2; border-color: #f2c2bb }

.pb-line-label { color: var(--on-surface) }
a.pb-line-label { color: #174f8a }
a.pb-line-label:hover { text-decoration: underline }

/* The Orders page's availability choice (OrdersView.AvailabilityChoice and
 * UnrecognisedChoice): the radio group for one ordered line, drawn as its own
 * card. The rule began life as the Manufacturing setup tiles', which went with
 * the setup wizard on 19 September 2026; the grid, glyph, step number and
 * action rules that only the tiles used went with them. */
.pb-choice {
	display: flex;
	flex-direction: column;
	background: var(--surface);
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-card);
	padding: 1.5rem;
	box-shadow: 0 1px 2px rgba(23, 32, 51, .06);
	color: var(--on-surface);
	text-decoration: none;
	transition: box-shadow .15s ease, border-color .15s ease;
}

.pb-choice:hover {
	box-shadow: 0 6px 18px rgba(23, 32, 51, .1);
	border-color: var(--outline-variant);
}

/* Reports hub. The cards take the same surface, border, radius and resting
 * shadow as .pb-choice above and add only a two-button footer. Three across at
 * desktop, and an 11rem floor so a section of two or three stays abreast. */
/* View reports: one table, one row per report (25 September 2026). */
.pb-report-table td { vertical-align: middle; padding-top: .45rem; padding-bottom: .45rem }
.pb-report-table td:first-child { white-space: nowrap }
.pb-report-table td:nth-child(n+4) { white-space: nowrap; width: 1% }
.pb-report-table .pb-report-what { color: var(--ink-muted); font-size: .85rem; line-height: 1.4; min-width: 20rem }
.pb-report-table .pb-button { padding: .35rem .8rem; min-height: 0 }
.pb-report-batch label { display: block; font-size: .78rem; color: var(--ink-muted); margin-bottom: .2rem }
.pb-report-batch input { width: 10rem; max-width: 100% }
.pb-report-range { display: flex; flex-wrap: wrap; align-items: flex-end; gap: 1rem }
.pb-report-range p { flex: 1 1 18rem; margin: 0; color: var(--ink-muted); font-size: .82rem }

/* Definition lists */
.pb-definition { display: grid; grid-template-columns: minmax(8rem, .35fr) 1fr; gap: .75rem; padding: .55rem 0; border-bottom: 1px solid var(--border-hairline) }
.pb-definitions { list-style: none; margin: 0; padding: 0; display: grid; gap: .65rem }
.pb-definitions li { padding: .55rem 0; border-bottom: 1px solid var(--border-hairline) }
.pb-definitions strong { display: block; color: var(--on-surface) }
.pb-definitions span { display: block; margin-top: .15rem; color: var(--ink-muted) }

/* The same list as cards, on "Made so far" only.
 *
 * A modifier rather than a change to .pb-definitions, because the Help page
 * (DashboardView.HelpPage) renders the plain list from the same class and has
 * four items, which do not fall into a 3x2.
 *
 * Every selector here is doubled - .pb-definitions.pb-definition-cards - on
 * purpose. .pb-definition-cards li alone is (0,1,1), exactly what .pb-definitions
 * li scores, so it would win or lose on source order alone. That is the trap that
 * silently dropped the .pb-line accents; two classes settle it outright. */
.pb-definitions.pb-definition-cards { grid-template-columns: 1fr; gap: .875rem }

/* What you made (25 September 2026): What these numbers mean is folded under
 * the results - a heading-sized line to click, the six explanations inside.
 * And the This month / Last month buttons beside the dates, the one showing
 * marked as current. */
.pb-explain > summary {
	cursor: pointer;
	font: var(--headline-md);
	color: var(--on-surface);
	list-style-position: inside;
}
.pb-explain[open] > summary { margin-bottom: .9rem }
.pb-explain > summary:focus-visible { outline: 2px solid var(--primary-container); outline-offset: 2px }
.pb-period[aria-current="true"] { background: var(--primary-container); border-color: var(--primary-container); color: #fff }
/* Each product's batches, opened from its row on What you made. */
.pb-batches-dialog { width: min(48rem, calc(100vw - 2rem)) }
.pb-batches-dialog .pb-desktop-table { box-shadow: none; margin-bottom: .9rem }
.pb-batches-dialog .pb-table-find { margin-top: 0 }


/* Fixed counts, not auto-fit: the content column is wide enough that
 * minmax(16rem, 1fr) lands four across, and six items would break 4 + 2. Three
 * is the 3x2 that was asked for. Same reasoning, and the same breakpoints, as
 * .pb-summary. */
@media (min-width: 640px) {
	.pb-definitions.pb-definition-cards { grid-template-columns: repeat(2, minmax(0, 1fr)) }
}

@media (min-width: 1024px) {
	.pb-definitions.pb-definition-cards { grid-template-columns: repeat(3, minmax(0, 1fr)) }
}

/* Tinted, not white. These cards sit INSIDE a white .pb-card, so a white fill
 * would leave only the border to separate them from their own container. */
.pb-definitions.pb-definition-cards li {
	padding: 1rem;
	border: 1px solid var(--border-standard);
	border-radius: var(--radius-input);
	background: var(--surface-bg);
}

/* Empty and error states. .pb-line-bad is a modifier on .pb-card despite its
 * name - ReportView line 156 emits "pb-card pb-line-bad". */
.pb-empty p { color: var(--ink-faint) }
/* Messages a person must not miss: refusals, status lines, notices.
 *
 * .pb-alert had NO rules until 16 September 2026 - every refusal and status
 * line on the bulk, packaging, check and withdrawal pages rendered as a plain
 * grey sentence, and a take-out refusal in the walkthrough went unseen. One
 * family now, with the meaning in the colour: red for a refusal or a
 * rejection, green for approved, amber for on hold, navy for plain
 * information. Bold, an icon, a left band, and an entrance that pulses TWICE
 * and then holds still - motion once catches the eye; a flash fails
 * accessibility guidance and is exhausting on a screen used all day
 * (Justice's ruling, 16 September 2026: "no flash"). Reduced motion gets no
 * animation at all. .pb-errors, the setup and stock pages' "Please check the
 * form" box, is the same thing under an older name and shares the look. */
@keyframes pb-alert-in {
	0% { transform: translateY(-4px); opacity: .5; box-shadow: 0 0 0 0 rgba(180, 35, 24, .35) }
	50% { transform: none; opacity: 1; box-shadow: 0 0 0 8px rgba(180, 35, 24, 0) }
	100% { box-shadow: 0 0 0 0 rgba(180, 35, 24, 0) }
}
.pb-alert, .pb-errors {
	margin: .75rem 0 1rem;
	padding: .85rem 1rem .85rem 2.6rem;
	border: 1px solid #bcd0ee;
	border-left: 6px solid var(--primary-container);
	border-radius: .6rem;
	background: #eef3fb;
	color: var(--on-surface);
	font-size: 1rem;
	font-weight: 600;
	line-height: 1.4;
	position: relative;
	max-width: none;
}
.pb-alert::before, .pb-errors::before {
	content: "i";
	position: absolute;
	left: .85rem;
	top: .85rem;
	width: 1.3rem;
	height: 1.3rem;
	border-radius: 50%;
	display: grid;
	place-items: center;
	font-size: .85rem;
	font-weight: 800;
	font-style: italic;
	background: var(--primary-container);
	color: #ffffff;
}
.pb-alert p { margin: 0 }
.pb-alert p + p { margin-top: .35rem }
.pb-alert-error, .pb-errors {
	background: #fff1f0;
	border-color: #e7aaa5;
	border-left-color: var(--error-action);
	color: var(--error-ink);
	animation: pb-alert-in .9s ease-out 2;
}
.pb-alert-error::before, .pb-errors::before { content: "!"; font-style: normal; background: var(--error-action) }
.pb-alert-good { background: #e6f4ec; border-color: #b7e0cb; border-left-color: var(--status-good); color: var(--status-good) }
.pb-alert-good::before { content: "✓"; font-style: normal; background: var(--status-good) }
.pb-alert-watch { background: #fffbeb; border-color: #f0dca8; border-left-color: var(--status-watch); color: var(--status-watch) }
.pb-alert-watch::before { content: "!"; font-style: normal; background: var(--status-watch) }
.pb-alert-info { background: #eef3fb }
.pb-errors h2, .pb-errors h4 { margin: 0; font-size: 1rem; color: var(--error-ink) }
.pb-errors p, .pb-errors li { color: var(--error-ink) }
@media (prefers-reduced-motion: reduce) {
	.pb-alert-error, .pb-errors { animation: none }
}
.pb-line-bad { background: #fff1f0; border-color: #e7aaa5; border-left: 4px solid var(--error-action) }
.pb-line-bad p { color: var(--error-ink) }

/* ---- Planning ------------------------------------------------------------ */

/* Read off what_can_i_make_ndsh_manufacturing: the products; the
 * shopping list opens from a button.
 *
 * Two things the reference draws are NOT here, because there is nothing behind
 * them. "Line capacity - Available / Scheduling Conflict" with its progress bar
 * needs a production line, a capacity model and a schedule, and this application
 * has none of the three; the bar could only ever say the same thing. "Export
 * Requisition Report" needs an export, and there is no export anywhere in the
 * estate. A control that cannot do what it says is worse than an absent one.
 *
 * The product cards became a table on 25 September 2026, and the shopping
 * list a dialog (below). */

/* Ready to make is one table since 25 September 2026: the product cards, the
 * potential tile and the bottleneck panel went with it. The two tones stay -
 * red where nothing can be made, amber where more of one thing would buy
 * another batch - on the words themselves rather than on a panel. */
.pb-plan-table td { vertical-align: middle }
.pb-plan-table td:nth-child(2) { white-space: nowrap }
.pb-plan-none .pb-cell-main,
.pb-plan-none .pb-cell-sub { color: var(--status-problem) }
.pb-plan-runs-out .pb-cell-main { color: var(--status-watch) }
.pb-plan-runs-out.is-blocking .pb-cell-main { color: var(--status-problem) }

/* What must I buy? is a dialog the red button above the products opens
 * (Justice, 25 September 2026): the same on every screen, where a list under
 * the products drifted to the bottom and a rail beside them only fitted a
 * wide monitor. */
.pb-buy-jump {
	background: var(--error-action);
	border-color: var(--error-action);
	color: #fff;
	font-size: 1.05rem;
	padding: .8rem 1.3rem;
}
.pb-buy-jump:hover { background: var(--error-ink); border-color: var(--error-ink) }
.pb-buy-dialog { width: min(44rem, calc(100vw - 2rem)) }
.pb-buy-dialog .pb-desktop-table { box-shadow: none; margin-bottom: .9rem }
.pb-buy-dialog .pb-table-find { margin-top: 0 }

/* What must I buy?: the material in bold, its shortfall in the "this is
 * stopping you" red. */
.pb-buy-name { font-weight: 700; color: var(--on-surface); overflow-wrap: anywhere }
.pb-desktop-table td.pb-buy-short {
	font-weight: 800;
	white-space: nowrap;
	font-variant-numeric: tabular-nums;
	color: var(--status-problem);
}


/* ---- Responsive ---------------------------------------------------------- */

@media (max-width: 800px) {
	.erp-layout { display: block }
	.erp-sidebar {
		position: fixed;
		left: 0;
		top: 0;
		width: min(84vw, var(--sidebar-width));
		z-index: 1000;
		transform: translateX(-105%);
		transition: transform .2s ease;
		box-shadow: 0 0 2rem rgba(0, 0, 0, .25);
	}
	.erp-layout[data-open='true'] .erp-sidebar { transform: translateX(0) }
	.erp-nav-toggle { display: inline-block }
	.erp-topbar { padding: .7rem 1rem }
	.erp-topbar-left { gap: .65rem }
	.erp-user-menu { gap: 0 }
	.erp-user-menu > div { display: none }
	/* Four controls now share this bar, and "Exit full screen" is the longest
	 * label in the estate. Tighter padding rather than hiding the button: a
	 * phone or tablet is precisely where there is no F11 to fall back on. */
	.erp-fullscreen,
	.erp-help { padding: .5rem .5rem; font-size: 13px }
	.erp-main { padding: 1rem }
	.pb-desktop-table th,
	.pb-desktop-table td { padding: .75rem .8rem }
	.pb-cell-main { font-size: 16px }

	/* The seven per-view queries were all max-width:800px, so their rules merge
	 * here. No desktop table is hidden on a phone: the one page that shipped a
	 * mobile card list to take over (ProductBomView's has-mobile-list marker)
	 * went with the setup wizard on 19 September 2026, and every other table
	 * has no fallback, so hiding it would leave a phone with nothing to read. */
	.pb-button { width: 100%; text-align: center }
	.pb-inline-action { display: grid }
	.pb-inline-action .pb-field { width: 100% }
	.pb-step-cards { grid-template-columns: 1fr }

	/* The row stacks: label beside its count, then the chips, then the action.
	 * The fixed 14rem label column cannot hold on a phone. */
	.pb-lines .pb-line { grid-template-columns: minmax(0, 1fr) auto; row-gap: .6rem }
	.pb-line-label { grid-column: 1 }
	.pb-line-count { grid-column: 2; grid-row: 1 }
	.pb-line-names { grid-column: 1 / -1 }
	.pb-line-action { grid-column: 1 / -1; justify-self: start }
	.pb-definition { grid-template-columns: 1fr; gap: .2rem }
	.pb-card-heading { display: grid }
}

/* ---- Sign in ------------------------------------------------------------- */

/* The one page outside the shell, because a visitor who is not signed in has no
 * navigation to render. It used to carry every style as an inline sty()
 * attribute and link no stylesheet at all, which is why it was the last page
 * still wearing Arial and the pre-consolidation input border. It links this
 * sheet now and borrows the same components as every other page. */

/* TWO-TONE. The brand sits on the sidebar's navy - the ground this mark has
 * everywhere else in the application, and the one its silver wordmark was drawn
 * to need - and the form keeps the light ground it shares with every other card
 * in the estate. Rows rather than a centred single column, so the band reaches
 * the full width of the window. */
.erp-login {
	min-height: 100vh;
	display: grid;
	grid-template-rows: auto 1fr;
	background: var(--surface-bg);
	color: var(--ink-body);
	font: var(--body-lg);
}

.erp-login-band {
	background: var(--sidebar-bg);
	display: grid;
	place-items: center;
	padding: 2.75rem 1.5rem 2.25rem;
}

.erp-login-body {
	display: grid;
	place-items: center;
	padding: 2rem 1.5rem;
}

.erp-login-main { width: min(92vw, 27rem) }

/* Every other page carries the name in the sidebar. This one has no sidebar, so
 * it says it here. */
.erp-login-brand {
	display: grid;
	gap: .05rem;
	justify-items: center;
	text-align: center;
}

/* Larger here than in the sidebar: this sits on white, where the silver in the
 * wordmark has the contrast it was drawn for. */
.erp-login-logo {
	display: block;
	width: 11rem;
	max-width: 100%;
	height: auto;
	margin-bottom: .35rem;
}

.erp-login-name { font: var(--headline-md); color: #fff }

.erp-login-tag {
	margin-top: .2rem;
	font: var(--label-md);
	text-transform: uppercase;
	color: var(--on-sidebar-dim);
}

.erp-login .pb-card { padding: 1.75rem }
.erp-login h1 { font: var(--headline-lg); margin: 0 0 .3rem }
.erp-login .pb-form { gap: 1.1rem }
.erp-login .pb-errors { margin-bottom: 1.1rem }
.erp-login-submit { width: 100%; text-align: center; margin-top: .3rem }

/* ---- Product journey dialog --------------------------------------------- */

[data-journey] { white-space: nowrap }

/* 84rem, from 64rem (28 September 2026): the packs column was about 590px on a
 * laptop and clipped a pack's badge and its Stock takes table on the right. */
.journey-dialog {
	width: min(84rem, calc(100vw - 2rem));
	max-height: calc(100vh - 2rem);
	padding: 0;
	border: 0;
	border-radius: 1rem;
	box-shadow: 0 1.25rem 4rem rgba(15, 23, 42, .28);
	color: var(--ink-body);
	background: var(--surface-bg);
}

.journey-dialog::backdrop { background: rgba(15, 23, 42, .62) }
.journey-shell { padding: 1.25rem; overflow: auto; max-height: calc(100vh - 2rem) }
.journey-page { max-width: 84rem; margin-inline: auto }

.journey-header {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 1rem;
	margin-bottom: 1rem;
}

.journey-header h1 { margin: .1rem 0 0; font: var(--headline-lg) }
.journey-eyebrow { display: block; color: var(--ink-muted); font: var(--label-md) }

.journey-close {
	display: grid;
	place-items: center;
	flex: 0 0 2.75rem;
	width: 2.75rem;
	height: 2.75rem;
	border: 1px solid var(--border-standard);
	border-radius: 999px;
	background: #fff;
	color: var(--ink-body);
	font-size: 1.8rem;
	line-height: 1;
	cursor: pointer;
}

.journey-close:hover { background: var(--surface-soft) }
.journey-close:focus-visible,
[data-journey]:focus-visible,
.journey-child-action:focus-visible { outline: 3px solid var(--secondary-accent); outline-offset: 3px }

.journey-identity {
	display: grid;
	grid-template-columns: minmax(0, 1fr) auto;
	gap: .2rem 1rem;
	align-items: center;
	padding: 1rem 1.1rem;
	border: 1px solid var(--border-standard);
	border-radius: .85rem;
	background: #fff;
}

.journey-identity h2 { grid-column: 1; grid-row: 1; margin: 0; font: var(--headline-md) }
.journey-product-code { grid-column: 1; grid-row: 2; margin: 0; color: var(--ink-muted) }
.journey-identifiers { grid-column: 2; grid-row: 1 / span 2; display: grid; gap: .35rem; justify-items: end }
.journey-batch { display: grid; justify-items: end; margin: 0 }
.journey-batch span { color: var(--ink-muted); font: var(--label-md) }
.journey-batch strong { font-size: 1.05rem; letter-spacing: .04em; color: var(--primary-container); white-space: nowrap }

.journey-current {
	display: flex;
	align-items: center;
	gap: .85rem;
	margin: .8rem 0;
	padding: .9rem 1rem;
	border: 1px solid var(--border-standard);
	border-left-width: .4rem;
	border-radius: .75rem;
	background: #fff;
}

.journey-current strong { display: block; font-size: 1.1rem }
.journey-current p { margin: .25rem 0 0 }
.journey-state-icon,
.journey-step-marker {
	position: relative;
	display: grid;
	place-items: center;
	flex: 0 0 1.8rem;
	width: 1.8rem;
	height: 1.8rem;
	border: 1px solid currentColor;
	border-radius: 999px;
	font-weight: 900;
	background: var(--surface-soft);
}

.journey-tone-good { border-color: #15803d }
.journey-tone-waiting { border-color: #b45309 }
.journey-tone-blocked { border-color: #b91c1c }
.journey-tone-neutral { border-color: var(--border-standard) }

.journey-current.journey-tone-good .journey-state-icon,
.journey-step.is-done .journey-step-marker {
	color: #fff;
	border-color: #059669;
	background: #059669;
}

.journey-current.journey-tone-good .journey-state-icon::before,
.journey-step.is-done .journey-step-marker::before {
	content: '';
	width: .42rem;
	height: .72rem;
	border: solid #fff;
	border-width: 0 2px 2px 0;
	transform: translateY(-.08rem) rotate(45deg);
}

.journey-layout {
	display: grid;
	grid-template-columns: minmax(17rem, .8fr) minmax(22rem, 1.2fr);
	gap: .9rem;
	align-items: start;
}
/* A grid item keeps its content's width unless told otherwise, so a wide table
 * inside a card pushed past the section's edge and was clipped. */
.journey-layout > *, .journey-child, .journey-stock-takes { min-width: 0 }

.journey-section {
	overflow: hidden;
	border: 1px solid var(--border-standard);
	border-radius: .9rem;
	background: #fff;
	box-shadow: 0 .35rem 1.15rem rgba(15, 23, 42, .06);
}

.journey-section-heading {
	display: grid;
	grid-template-columns: .34rem minmax(0, 1fr);
	gap: .65rem;
	align-items: stretch;
	padding: .9rem 1rem;
	border-bottom: 1px solid #e5e7eb;
	background: linear-gradient(180deg, #fff, #fbfcff);
}

.journey-section-accent {
	min-height: 2rem;
	border-radius: 999px;
	background: #4f46e5;
}

.journey-packaging .journey-section-accent,
.journey-finished .journey-section-accent { background: #0891b2 }
.journey-section-heading h2 { margin: 0; font: var(--headline-md) }
.journey-section-intro { margin: .28rem 0 0; color: var(--ink-muted); line-height: 1.45 }
.journey-section-body { padding: 1rem }

.journey-metrics {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: .65rem;
	margin-bottom: .8rem;
}

.journey-metric {
	min-width: 0;
	padding: .78rem .8rem;
	border: 1px solid var(--border-standard);
	border-radius: .7rem;
	background: var(--surface-soft);
}

.journey-metric span {
	display: block;
	color: var(--ink-muted);
	font: var(--label-md);
	text-transform: uppercase;
}

.journey-metric strong { display: block; margin-top: .18rem; font-size: 1.08rem }
.journey-metric.is-produced { border-color: #a7f3d0; background: #ecfdf5 }
.journey-metric.is-produced strong { color: #047857 }

.journey-facts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .55rem; margin: 0 0 1rem }
.journey-facts > div { min-width: 0; padding: .55rem .65rem; border: 1px solid #e5e7eb; border-radius: .55rem; background: #fff }
.journey-facts dt { color: var(--ink-muted); font: var(--label-md) }
.journey-facts dd { margin: .15rem 0 0; font-weight: 700 }
.journey-source-details { margin-bottom: .85rem }
.journey-subheading { margin: 0 0 .55rem; color: var(--ink-muted); font: var(--label-md); text-transform: uppercase }

.journey-steps { display: grid; gap: .55rem; margin: 0; padding: 0; list-style: none }
/* The recall question on the journey page, both directions (16 September
 * 2026): materials and the delivery each came from, under the source run and
 * under each pack; what left each pack and to whom. Plain lists, indented
 * lots under their material, small type: it is reference, not headline. */
.journey-materials, .journey-departures { margin: 0 0 .8rem; padding: 0; list-style: none; display: grid; gap: .45rem; font-size: .88rem }
.journey-materials > li > strong { display: block; color: var(--on-surface) }
.journey-lots { margin: .15rem 0 0; padding-left: 1rem; list-style: disc; color: var(--ink-muted); display: grid; gap: .15rem }
.journey-departures li { color: var(--on-surface) }
.journey-departures small { color: var(--ink-faint) }
.journey-subheading + .journey-materials, .journey-subheading + .journey-departures { margin-top: 0 }
.journey-child .journey-subheading { margin-top: .7rem }
/* Room between the last step card and Materials and where they came from
 * (after-story N23, Justice 30 September 2026). */
.journey-steps ~ .journey-subheading { margin-top: 1.25rem }
.journey-step { position: relative; display: flex; gap: .7rem; min-height: 3rem; padding: .7rem; border: 1px solid #e5e7eb; border-radius: .65rem; background: #fff }
.journey-step:not(:last-child)::after { content: ''; position: absolute; z-index: 0; left: 1.56rem; top: 100%; height: .58rem; border-left: 2px solid #cbd5e1 }
.journey-step-marker { z-index: 1 }
.journey-step div { min-width: 0; padding-top: .25rem }
.journey-step div strong,
.journey-step div span { display: block }
.journey-step div span { margin-top: .12rem; color: var(--ink-muted); font-size: .9rem }
.journey-step.is-blocked .journey-step-marker { color: #fff; background: #b91c1c }
.journey-step.is-waiting .journey-step-marker { color: #713f12; background: #fef3c7 }

.journey-children { display: grid; gap: .7rem }
.journey-child { padding: .85rem; border: 1px solid var(--border-standard); border-radius: .75rem; background: #fff; box-shadow: 0 .2rem .7rem rgba(15, 23, 42, .04) }
.journey-child > header { display: flex; align-items: flex-start; justify-content: space-between; gap: .8rem }
.journey-child h3 { margin: 0; font: var(--headline-md); font-size: 16px }
.journey-run-code { margin: .15rem 0 0; font-size: 1.05rem; letter-spacing: .04em; color: var(--primary-container); font-weight: 700; white-space: nowrap }
.journey-badge { flex: 0 0 auto; padding: .3rem .55rem; border: 1px solid; border-radius: 999px; font: var(--label-md) }
.journey-child-facts { grid-template-columns: repeat(4, minmax(0, 1fr)); margin-top: .7rem; padding-top: .7rem; border-top: 1px solid #e5e7eb }
.journey-child-facts > div { padding: .5rem .55rem; background: var(--surface-soft) }
.journey-direct-facts { grid-template-columns: repeat(2, minmax(0, 1fr)); margin-bottom: 0 }
.journey-child-steps { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: .4rem }
.journey-child-steps .journey-step { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: .45rem; align-content: start; min-height: 0; padding: .55rem; background: var(--surface-soft) }
.journey-child-steps .journey-step strong { font-size: .82rem; line-height: 1.35 }
.journey-child-steps .journey-step div span { font-size: .76rem; line-height: 1.45 }
.journey-child-steps .journey-step.is-done { border-color: #a7f3d0; background: #f0fdf4 }
.journey-child-steps .journey-step::after { display: none }
.journey-child-action { display: inline-block; margin-top: .75rem; font-weight: 800 }
.journey-empty { margin: 0; padding: .8rem; border-radius: .6rem; background: var(--surface-soft) }

.journey-next { margin-top: .8rem; padding: 1rem; border-radius: .85rem; background: #eef6ff; border: 1px solid #bfdbfe }
.journey-next h2 { margin: 0 0 .25rem; font: var(--headline-md) }
.journey-next p { margin: 0 0 .75rem }
.journey-next-action { width: auto }
.journey-no-action { display: block; margin-top: .4rem }
.journey-error { margin: 1rem 0 }

@media (max-width: 800px) {
	.journey-dialog { width: calc(100vw - 1rem); max-height: calc(100vh - 1rem) }
	.journey-shell { max-height: calc(100vh - 1rem); padding: 1rem }
	.journey-layout { grid-template-columns: 1fr }
	.journey-identity { grid-template-columns: 1fr }
	.journey-identity h2,
	.journey-product-code,
	.journey-identifiers { grid-column: 1; grid-row: auto }
	.journey-identifiers { justify-items: start; margin-top: .4rem }
	.journey-batch { justify-items: start }
	.journey-child-facts,
	.journey-child-steps { grid-template-columns: 1fr 1fr }
	.journey-next-action { width: 100%; text-align: center }
}

@media (max-width: 480px) {
	.journey-metrics,
	.journey-facts,
	.journey-child-steps { grid-template-columns: 1fr }
	.journey-child-steps .journey-step strong { font-size: .92rem }
	.journey-child-steps .journey-step div span { font-size: .84rem }
	.journey-child > header { display: grid }
	.journey-badge { justify-self: start }
}

/* ---- Money ---------------------------------------------------------------
 * Five tiles on the summary grid, then a card per chart with its table under
 * it. The SVG is width-bound and scales with the card; Chart sets a viewBox.
 * A withheld figure -- "nothing yet" or a dash -- is set lighter and smaller
 * than a real one so the eye cannot read it as a number. */
/* A card may be narrower than the table it holds: the table scrolls inside
 * its own box. Without this, a card holding a wide table (Financial
 * performance, 25 September 2026) widened the whole page past a laptop's
 * screen, and the fifth tile went off the edge. */
.pb-content > .pb-card { min-width: 0 }
.pb-money-tabs { margin-top: .25rem }
.money-tiles { grid-template-columns: repeat(auto-fit, minmax(11.5rem, 1fr)) }
.money-tile .pb-summary-note { margin-top: .35rem }
.money-tile-secondary {
	display: block;
	font: var(--body-md);
	color: var(--ink-muted);
	font-variant-numeric: tabular-nums;
}
.money-tile-withheld { font-size: 16px; font-weight: 600; color: var(--ink-faint) }
.money-tile-excluded { color: var(--secondary-accent) }

.money-chart-card h2 { margin: 0 0 .25rem; font: var(--headline-md); color: var(--on-surface) }
.money-chart-intro { margin: 0 0 1rem; color: var(--ink-muted); font-size: .9rem; line-height: 1.45 }
.money-figure { margin: 0 0 1.25rem }
.money-chart {
	display: block;
	width: 100%;
	height: auto;
	max-width: 42rem;
	margin: 0 0 1.25rem;
	font: var(--body-md);
}
.money-chart-card .pb-desktop-table { margin-bottom: 0 }
.money-totals {
	margin: .75rem 0 0;
	font: var(--body-md);
	font-weight: 700;
	color: var(--on-surface);
	font-variant-numeric: tabular-nums;
}

/* Shared with Withdraw products, which had the class and no rule. */
.pb-empty-state {
	background: var(--surface);
	border: 1px dashed var(--outline-variant);
	border-radius: var(--radius-card);
	padding: 2rem 1.5rem;
	margin: 1.25rem 0;
	text-align: center;
}
.pb-empty-state h2 { margin: 0 0 .5rem; font: var(--headline-md); color: var(--on-surface) }
.pb-empty-state p { margin: 0 auto; max-width: 36rem; color: var(--ink-muted) }
/* Receive materials, one delivery (25 September 2026): the supplier, date and
 * notes on one line, then a row per material. */
.pb-delivery-form { max-width: none }
.pb-delivery-form > * { min-width: 0 }
.pb-delivery-head { display: grid; grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); align-items: start; gap: 1rem; margin-bottom: 1rem }
.pb-delivery-lines th { white-space: normal }
.pb-delivery-lines th, .pb-delivery-lines td { padding-left: .45rem; padding-right: .45rem }
.pb-delivery-lines th:first-child, .pb-delivery-lines th:nth-child(4) { width: 1% }
.pb-delivery-lines td { vertical-align: top; padding-top: .45rem; padding-bottom: .45rem }
.pb-delivery-lines select, .pb-delivery-lines input { width: 100%; min-width: 5rem; box-sizing: border-box }
.pb-delivery-lines select { min-width: 15rem }
/* The material gets the room; the amount and price boxes need a few digits. */
.pb-delivery-lines td:nth-child(3), .pb-delivery-lines td:nth-child(7) { width: 7rem }
.pb-delivery-lines td:nth-child(5) { width: 9rem }
.pb-delivery-lines td:nth-child(6) { width: 9.5rem }
/* The wizard hides its empty unit span; an empty cell here must stay, or the
 * row slides one column left until a material is chosen. */
.pb-delivery-lines td.pb-line-unit:empty { display: table-cell }
.pb-delivery-lines .pb-line-no { color: var(--ink-muted); width: 1%; text-align: center; padding-top: .95rem }
.pb-delivery-lines .pb-line-unit { width: 1%; white-space: nowrap; color: var(--ink-muted); padding-top: .95rem }
.pb-delivery-lines .pb-line-total { display: block; margin-top: .25rem; font-weight: 700; color: var(--on-surface); font-size: .8rem }
.pb-delivery-lines tr.pb-line-bad td { background: #fff1f0 }
.pb-delivery-lines tr.pb-line-bad .pb-line-no { color: var(--error-ink); font-weight: 800 }
.pb-line-remove { border: 1px solid var(--border-standard); background: var(--surface); border-radius: .4rem; width: 2rem; height: 2rem; cursor: pointer; font-size: 1.1rem; line-height: 1; margin-top: .2rem }
.pb-line-remove:hover { border-color: var(--error-action); color: var(--error-action) }
.pb-hint { color: var(--ink-muted); font-size: .85rem; max-width: 60rem }
.pb-visually-hidden { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap }
/* Material stock, 26 September 2026: two tabs that are lists, each with a
 * line under its name, and the two forms as buttons above them opening as
 * dialogs over the list. */
.pb-stock-actions { margin: .25rem 0 1rem }
.pb-material-tabs a { display: flex; flex-direction: column; align-items: center; gap: .15rem; white-space: normal; max-width: 18rem }
.pb-material-tabs .pb-tab-name { white-space: nowrap }
.pb-material-tabs .pb-tab-words { font-size: .78rem; font-weight: 400; color: var(--ink-muted); line-height: 1.3 }
.pb-stock-dialog { max-height: calc(100vh - 2rem); overflow: auto }
.pb-receive-dialog { width: min(72rem, calc(100vw - 2rem)) }
/* Wider, and a batch number or product name on one line (after-story N25,
 * Justice 30 September 2026: BLK-260928-02/P1 wrapped over three lines). */
.pb-count-dialog { width: min(76rem, calc(100vw - 2rem)) }
.pb-count-dialog th, .pb-count-dialog td { white-space: nowrap }
.pb-count-form { max-width: none }
.pb-stock-dialog .pb-desktop-table { box-shadow: none }
.pb-dialog-intro { margin: -.4rem 0 1rem; color: var(--ink-muted) }
/* Deliveries received, 28 September 2026: the price is a figure with Change
 * beside it; the box opens only when asked for. */
.pb-price-edit summary { list-style: none; cursor: pointer; display: inline-flex; gap: .6rem; align-items: baseline; white-space: nowrap }
.pb-price-edit summary::-webkit-details-marker { display: none }
.pb-price-edit .pb-price-shown { font-weight: 600; color: var(--ink-body) }
.pb-price-edit .pb-price-change { color: var(--primary-container); text-decoration: underline; font-size: .85rem }
.pb-price-edit[open] .pb-price-change { display: none }
.pb-price-edit .pb-price-form { margin-top: .4rem }
/* Product stock taking, 28 September 2026: Variance red when short, green
 * when more was found, and a warning line when promised stock runs short. */
.pb-variance { white-space: nowrap; font-variant-numeric: tabular-nums }
.pb-variance-down, .pb-desktop-table td.pb-variance-down { color: var(--error-action); font-weight: 800 }
.pb-variance-up, .pb-desktop-table td.pb-variance-up { color: var(--status-good); font-weight: 800 }
.pb-count-warning { margin: .75rem 0; padding: .6rem .9rem; border-left: 4px solid var(--status-watch); background: #fffbeb; color: var(--status-watch); font-weight: 600; border-radius: .4rem }
.pb-count-warning p { margin: 0 }
/* Product journey, 28 September 2026: a batch's stock takes in a block of
 * their own - the latest as a sentence, every count in a short table. */
.journey-stock-takes { margin-top: 1rem }
.journey-count-summary { margin: .25rem 0 .6rem; padding: .6rem .8rem; border-radius: .5rem; background: var(--surface-container-low); color: var(--ink-body); line-height: 1.45 }
.journey-counts { box-shadow: none; margin: 0; overflow-x: auto }
.journey-counts td:first-child, .journey-counts td:nth-child(2), .journey-counts td:nth-child(3), .journey-counts td:nth-child(4) { white-space: nowrap }
.journey-counts td, .journey-counts th { padding: .45rem .5rem; font-size: .85rem }
.pb-count-warning:empty, .pb-count-warning[hidden] { display: none }

/* Size declared on the label (30 September 2026): a number box and its unit list on one line. */
.pb-size-field { display: flex; gap: .5rem; max-width: 20rem }
.pb-size-field input { flex: 1 1 8rem }
.pb-size-field select { flex: 0 0 6.5rem; width: auto }

/* The hover card on a batch number (after-story batch 6, N33), placed by
 * tables.js beside the number, and the Open button column that replaced the
 * second link in the batch cell. */
.pb-hover-card-host { position: absolute; z-index: 1000; width: 19rem; max-width: calc(100vw - 16px) }
.pb-hover-card-host[hidden] { display: none }
.pb-hover-card { box-sizing: border-box; padding: .9rem 1.05rem; background: var(--surface); color: var(--ink-body);
	border: 1px solid var(--outline-variant); border-radius: 10px; box-shadow: 0 10px 30px rgba(18, 27, 46, .18);
	font-size: .875rem; line-height: 1.4 }
.pb-hover-card p { margin: 0 0 .3rem }
.pb-hover-card-product { font-weight: 700; font-size: .95rem }
.pb-hover-card-where { font-weight: 600 }
.pb-hover-card-where.journey-tone-good { color: var(--status-good) }
.pb-hover-card-where.journey-tone-waiting { color: var(--status-watch) }
.pb-hover-card-where.journey-tone-blocked { color: var(--status-problem) }
.pb-hover-card .pb-hover-card-hint { margin: .5rem 0 0; padding-top: .45rem; border-top: 1px solid var(--border-hairline);
	color: var(--primary-container); font-weight: 600 }
.pb-open-cell { width: 1%; white-space: nowrap; text-align: right }
.pb-batch-code { white-space: nowrap }
.pb-check-history { margin: .6rem 0 0; font-weight: 600 }
.journey-steps + .pb-check-history { margin: .5rem 0 0 }
