﻿
html, body
{
	font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
	height: 100dvh;
	overflow: hidden;
}

h1:focus
{
	outline: none;
}

.content
{
	padding-top: 1.1rem;
}

.valid.modified:not([type=checkbox])
{
	outline: 1px solid #26b050;
}

.invalid
{
	outline: 1px solid red;
}

.validation-message
{
	font-size: 0.75rem ;
	color: var(--pc-color-error) !important;
	font-weight: 400;
	line-height: 1.66;
	letter-spacing: 0.03333em;
}

#blazor-error-ui
{
	background: lightyellow;
	bottom: 0;
	box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
	display: none;
	left: 0;
	padding: 0.6rem 1.25rem 0.7rem 1.25rem;
	position: fixed;
	width: 100%;
	z-index: 1000;
}

	#blazor-error-ui .dismiss
	{
		cursor: pointer;
		position: absolute;
		right: 0.75rem;
		top: 0.5rem;
	}

.blazor-error-boundary
{
	background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
	padding: 1rem 1rem 1rem 3.7rem;
	color: white;
}

	.blazor-error-boundary::after
	{
		content: "An error has occurred."
	}

.sidebar
{
	background-image: linear-gradient(180deg, #d16b1c 0%, #d16b1c 70%);
}

tr.selected
{
	background-color: var(--pc-color-primary) !important;
	/*background-color: #1E88E5 !important;*/
}

.selected > td
{
	color: white !important;
}

.column-filter-popup
{
	max-width: min(100%, 400px) !important;
}

/* ProfileButton's menu surface (PlatformMenu SurfaceClass). */
.profile-menu-popover
{
	max-width: 90%;
}

.filter-button.filtered {
	color: var(--pc-color-primary);
}

/* A grid column that only shows an icon (ColumnSizes.IconFixed): the density's cell padding would
   leave too little room at Comfortable and squeeze the icon, so it gets the smallest padding and is
   centred in the column. */
.pc-data-grid__cell.grid-icon-cell
{
	justify-content: center;
	padding-inline: var(--pc-density-space-xs);
}

.grid-group-filter-popover
{
	/* The grid group filters sit in a 20rem slot in the grid header, and the dropdown
	   panel would otherwise inherit that width. Group names are long and often share a
	   leading prefix, so at 20rem the ellipsis truncation above collapses distinct
	   options into identical-looking rows. Let the panel be wider than its anchor.
	   min-width is used deliberately: it overrides the inline width the select puts on
	   the popover to match the anchor. */
	/* Clamp the minimum as well as the maximum: CSS resolves min-width over
	   max-width when they conflict, so an unconditional 28rem minimum overflowed
	   narrow mobile viewports despite the 90vw maximum below. */
	min-width: min(28rem, 90vw);
	max-width: min(90vw, 40rem);
}

/* Side-pane accordions (asset dashboard, asset map, package dashboard): PlatformExpansionPanels with
   FillContainer and the asset-panels class. PC sizes them: closed headers keep their full height, open
   panels keep their natural height while they fit and otherwise share the side pane and scroll. Do not
   override the group's height or the panels' sizing here: the group needs PC's height: 100% (capped at
   its content) for that, and a height: auto override let open panels push closed headers out of the
   side pane. The pager footers bring their own divider and padding. */
.pc-expansion-panels.asset-panels .pc-expansion-panel__footer
{
	padding: 0;
	background: transparent;
	border-top: 0;
}

/* Scheduler date filter: the schedulers show the long date (SchedulerConventions.ControlAttributes), which
   needs the 300px the earlier scheduler's date picker had. */
.pc-scheduler .pc-scheduler__date-filter
{
	width: min(18.75rem, 100%);
}

/* Knowledge-base markdown links (KnowledgeBaseArticleRenderer, wherever it is used): pc-link styles on plain
   anchors and on command links (KbLinkButton), kept inline, unlike pc-link's inline-flex, so a long link
   wraps with the sentence it sits in. */
.kb-markdown-body .kb-markdown-link,
.kb-markdown-body .kb-link-button
{
	display: inline;
}

/* Group membership pickers (GroupMembershipGroup and the dialogs that inline it) use dense expansion
   panels, but keep the full header padding (the dense SelectList rows inside bring their own spacing
   and truncation, SelectList.razor.css). The panel class lifts this above the PC dense rule, which
   loads later. */
.group-membership-container .pc-expansion-panel.pc-expansion-panel--dense > .pc-expansion-panel__summary
{
	padding: var(--pc-density-space-lg);
}

/* Membership pattern (MembershipSelect, GroupMembershipItemList): the pickers of a membership section
   stack with field spacing; a notice sits above its picker; an option's supporting line keeps its icon
   (the key of an API key login) beside the text. */
.bh-group-membership-group__fields
{
	display: flex;
	flex-direction: column;
	gap: var(--pc-density-space-lg);
}

.bh-membership-select
{
	display: flex;
	flex-direction: column;
	gap: var(--pc-density-space-sm);
	min-width: 0;
}

.bh-membership-select__support
{
	display: inline-flex;
	align-items: center;
	gap: var(--pc-density-space-xs);
}

/* CreateEditWrapper_Dialog and SimpleWrapper_Dialog are PlatformDialog side sheets (full height, at the
   end edge, full width on a phone), so only their tint is the app's. SimpleWrapper's sticky title row
   (.simple-wrapper-dialog__header, scoped CSS) uses the same surface-container-low: keep the two in step. */
.create-edit-wrapper-dialog
{
	--pc-dialog-background: var(--pc-color-surface-container-low);
	color: var(--pc-color-on-surface-variant);
}

/* Confirmation_Dialog is a Material 3 basic dialog: at most 560px wide (whatever size the opener asks
   for) and 24px between the text and the actions (the content's bottom padding alone). Below 600px
   PlatformDialog's full-width bottom sheet applies instead. */
@media (min-width: 37.5rem)
{
	.pc-dialog.confirmation-dialog
	{
		max-width: min(100%, calc(35rem * var(--pc-font-scale, 1)));
	}
}

.pc-dialog.confirmation-dialog > .pc-dialog__actions
{
	padding-top: 0;
}

/* QR dialogs (QRCodeScan_Dialog, QRScannerAssetAssignment_Dialog; more in their scoped CSS): the
   PlatformDialog content loses its padding so the camera view fills the dialog edge to edge, and lays
   its child out as a column. It still scrolls (PC's overflow-y: auto) when the content is taller. */
.fullscreen-scrollable-dialog > .pc-dialog__content
{
	display: flex;
	flex-direction: column;
	padding: 0;
}

/* ############ Form Dense CSS ############## */

/* ==========================================
   GENERAL FORM STYLES
   ========================================== */

/* The FormFieldWrapper is a PlatformCard (.pc-card__header / .pc-card__content), and every field below
   adds its form-field-*-dense class to that card in Dense mode (form runner, FormEdit preview). An
   unexpected-response answer (FormFieldFailure) is a nested PlatformCard inside the wrapper's content.
   site.css loads before platform-components.css, so each rule here outranks the PC rule it overrides. */

/* The image fields' photo grid sits in an outlined PlatformFormField (FormFieldImage, FormFieldFailure). */
.form-asset-wrapper .pc-form-field__control:has(> .image-grid) {
	padding: 5px;
}

/* ==========================================
   IMAGE GRID STYLES (Multiple Field Types)
   ========================================== */

.form-asset-wrapper .form-field-boolean-dense .image-grid,
.form-asset-wrapper .form-field-image-dense .image-grid,
.form-asset-wrapper .form-field-multichoice-dense .image-grid,
.form-asset-wrapper .form-field-multiselect-dense .image-grid,
.form-asset-wrapper .form-field-numeric-dense .image-grid {
	grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

	.form-asset-wrapper .form-field-boolean-dense .image-grid img,
	.form-asset-wrapper .form-field-image-dense .image-grid img,
	.form-asset-wrapper .form-field-multichoice-dense .image-grid img,
	.form-asset-wrapper .form-field-multiselect-dense .image-grid img,
	.form-asset-wrapper .form-field-numeric-dense .image-grid img {
		width: 150px !important;
		height: 150px !important;
	}

	.form-asset-wrapper .form-field-boolean-dense .image-grid > div,
	.form-asset-wrapper .form-field-image-dense .image-grid > div,
	.form-asset-wrapper .form-field-multichoice-dense .image-grid > div,
	.form-asset-wrapper .form-field-multiselect-dense .image-grid > div,
	.form-asset-wrapper .form-field-numeric-dense .image-grid > div {
		width: 150px !important;
	}

/* ==========================================
   SHARED DENSE CARD STYLES (form-field-dense is added by FormFieldWrapper)
   ========================================== */

.form-field-dense .pc-card__header {
	padding: 5px 7px;
}

.form-field-dense > .pc-card__header h6 {
	font-size: 16px;
	font-weight: 400;
}

.form-field-dense .pc-card__content {
	padding: 7px;
}

/* ==========================================
   SHARED INPUT STYLES
   ========================================== */

/* The answer input (PlatformTextField / PlatformNumberField). */
.form-field-freetext-dense .pc-field__input,
.form-field-numeric-dense .pc-field__input {
	padding: 5px;
	margin: 5px;
}

.form-field-contact-dense .pc-field__input {
	padding: 11.5px 14px;
}

/* The unexpected-response notes (FormFieldFailure's multiline PlatformTextField). */
.form-field-hourmeter-dense .pc-field__input--textarea,
.form-field-numeric-dense .pc-field__input--textarea,
.form-field-multichoice-dense .pc-card__content .pc-card .pc-field__input--textarea,
.form-field-multiselect-dense .pc-card__content .pc-card .pc-field__input--textarea {
	padding: 5px;
	margin: 5px;
}

/* ==========================================
   SHARED FAILURE CARD & PARAGRAPH STYLES
   ========================================== */

/* The unexpected-response card (FormFieldFailure) sits flush in the dense wrapper; its pc-mt-4 is
   !important, hence this one. */
.form-field-boolean-dense .pc-card__content > .pc-card,
.form-field-multichoice-dense .pc-card__content > .pc-card,
.form-field-multiselect-dense .pc-card__content > .pc-card,
.form-field-numeric-dense .pc-card__content > .pc-card {
	border: 0;
	margin-top: 0px !important;
}

.form-field-boolean-dense .pc-flex-column > p:nth-of-type(1),
.form-field-multichoice-dense .pc-flex-column > p:nth-of-type(1),
.form-field-multiselect-dense .pc-flex-column > p:nth-of-type(1),
.form-field-numeric-dense .pc-flex-column > p:nth-of-type(1) {
	margin-top: 5px !important;
}

/* ==========================================
   BOOLEAN FIELD SPECIFIC STYLES
   ========================================== */

.form-field-boolean-dense.grouping {
	flex: 1;
}

/* The Yes/No PlatformButtonGroup, below the title like other dense answers; its pc-ml-1 is !important, hence this one. */
.form-field-boolean-dense form > .pc-button-group {
	height: 35px;
	margin-left: 2px !important;
}

	.form-field-boolean-dense .pc-card__content {
		padding: 5px;
	}

		.form-field-boolean-dense .pc-card__content .pc-card .pc-card__header {
			padding: 5px;
			padding-top: 10px;
		}

			.form-field-boolean-dense .pc-card__content .pc-card .pc-card__header h5 {
				font-size: 16px;
			}

	.form-field-boolean-dense .pc-field__input--textarea {
		padding: 10px;
		margin-top: 5px;
	}

/* ==========================================
   DATE FIELD SPECIFIC STYLES
   ========================================== */

/* The inline PlatformDatePicker has no date toolbar to compact (the previous picker's header with
   the selected date and year buttons), so the date field has no dense rules beyond the shared ones. */

.form-field-servicelog-dense .date-time,
.form-field-unplanned-servicelog-dense .date-time {
	display: flex;
	gap: 15px
}

/* ==========================================
   ASSET LOCATION FIELD SPECIFIC STYLES
   ========================================== */

.form-field-assetlocation-dense {
	position: relative;
}

	/* AddressSearch's 'Use my location' button moves to the card's top-right corner. */
	.form-field-assetlocation-dense .pc-card__content form > button {
		position: absolute;
		top: 0;
		right: 0;
	}

	/* AddressSearch's map surface (an inline 350px height, hence !important). */
	.form-field-assetlocation-dense .pc-surface:has(.mapComponent) {
		height: 270px !important;
	}

/* ########################## */


/* The grid's list view (the same query as PlatformDataGridBreakpoints.CompactQuery): small icon-only
   PlatformFabs in a row's actions (a table row's action cell, a list item's inline actions or its own
   template) shrink to 28px, so several fit beside the row's text. Only rows: the toolbar's actions and
   the selection bar's keep their 48px targets (grid design 4.4). */
@media (max-width: 43.75rem), (max-height: 30rem) and (orientation: landscape) and (pointer: coarse) {
	:is(.pc-data-grid__row-actions, .pc-data-grid__mobile-row) .pc-fab.pc-fab--small:not(.pc-fab--extended)
	{
		min-height: 28px;
		min-width: 28px;
		height: 28px;
		width: 28px;
		padding: 0;
		align-self: center;
	}

	:is(.pc-data-grid__row-actions, .pc-data-grid__mobile-row) .pc-fab.pc-fab--small:not(.pc-fab--extended) > .pc-icon
	{
		width: 1.2rem;
		height: 1.2rem;
	}
}

@media (max-width: 480px) {
	/* NotificationButton's menu surface (PlatformMenu SurfaceClass) on a narrow phone. */
	.notification-menu-popover
	{
		width: 290px !important;
	}

	.notification-menu-popover .notification-item__title
	{
		font-size: 0.8em;
	}

	.notification-menu-popover .notification-menu__content
	{
		min-width: auto !important;
		width: 100% !important;
	}
} @media (max-width: 355px) {
	.notification-menu-popover
	{
		width: 210px !important;
	}

	.notification-menu-popover .notification-item__title
	{
		font-size: 0.6em;
	}
}

.no-highlight {
	-webkit-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

/* Offline mode — disable navigation menu */
.nav-disabled {
        pointer-events: none;
        opacity: 0.4;
}

/* Offline mode — greyed-out nav items (PlatformNavLink Disabled: no href, aria-disabled) */
.nav-offline-disabled .pc-nav-link.pc-nav-link--disabled,
.nav-offline-disabled .pc-nav-group.pc-nav-group--disabled {
    opacity: 0.4;
}
/* Offline mode — mute parent groups whose children are all unavailable offline */
.nav-group-offline-muted > .pc-nav-group__summary {
    opacity: 0.2;
}
/* Offline inspector panel */
/* Height of the dev Offline DB Inspector. Declared on :root, not on .offline-inspector-active, so the
   inspector's own resize control can override it by setting the variable on documentElement -- a value
   set there cannot beat a class-scoped declaration, which is why the panel used to ignore the control. */
:root {
    --offline-inspector-h: 220px;
}

/* The inspector is fixed to the bottom: the shell (app bar, docked drawer, page) ends above it, and so
   does the overlay drawer. .offline-inspector-active is on the ClientPortal layout's theme root. */
.offline-inspector-active > .bh-shell {
    height: calc(100% - var(--offline-inspector-h));
}

@media (max-width: 959.98px) {
    .offline-inspector-active .bh-drawer.pc-drawer--responsive {
        bottom: var(--offline-inspector-h);
    }
}

.offline-inspector-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: var(--offline-inspector-h, 220px);
    overflow-y: auto;
    background-color: #1a1a2e;
    border-top: 2px solid #e94560;
    z-index: var(--bh-z-appbar, 1300);
}

/* Drag strip along the inspector's top edge. touch-action:none is required, not cosmetic: without it
   the WebView claims the gesture as a scroll and pointermove never reaches the handler. The hit area is
   deliberately taller than the visible grabber so it stays catchable with a thumb. */
.offline-inspector-resize {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 14px;
    cursor: ns-resize;
    touch-action: none;
    z-index: 2;
}

.offline-inspector-resize::after {
    content: '';
    position: absolute;
    top: 4px;
    left: 50%;
    transform: translateX(-50%);
    width: 48px;
    height: 4px;
    border-radius: 2px;
    background-color: #e94560;
    opacity: 0.5;
    transition: opacity 0.12s ease-out;
}

.offline-inspector-resize:hover::after,
.offline-inspector-resize:focus-visible::after,
.offline-inspector-resize.resizing::after {
    opacity: 1;
}

.offline-inspector-resize:focus-visible {
    outline: 2px solid #e94560;
    outline-offset: -2px;
}

/* Floating action button container that hosts the offline status + sync indicator
   together, replacing the previous app-bar placement.
   z-index must sit ABOVE everything that could otherwise hide a persistent status icon -- notably the
   dev Offline DB Inspector panel (.offline-inspector-panel, z 1300, fixed to the bottom), the staging
   banner, MudBlazor dialogs/snackbars -- so the user (and a dev with the inspector open) always sees the
   offline/sync state. It stays BELOW the full-screen "Page Not Available Offline" overlay (z 9999), which
   is meant to cover the whole screen. */
.offline-fab-container {
    position: fixed;
    bottom: 16px;
    right: 16px;
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    gap: 8px;
    z-index: 9000;
    pointer-events: none;
}

.offline-fab-container > * {
    pointer-events: auto;
    background-color: var(--pc-color-surface);
    border-radius: 50%;
    box-shadow: var(--pc-elevation-level-3);
}

/* Cross-tab logout overlay. It is created by app.js so it remains available
   while the Blazor authentication state and local databases are being reset. */
.auth-logout-overlay {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background-color: rgba(0, 0, 0, 0.55);
    cursor: wait;
}

.auth-logout-overlay__panel {
    display: flex;
    align-items: center;
    gap: 16px;
    min-width: min(320px, 100%);
    padding: 24px;
    color: var(--pc-color-on-surface, #222);
    background-color: var(--pc-color-surface, #fff);
    border-radius: 8px;
    box-shadow: var(--pc-elevation-level-3, 0 8px 24px rgba(0, 0, 0, 0.3));
    font-size: 1rem;
    line-height: 1.5;
}

.auth-logout-overlay__spinner {
    width: 28px;
    height: 28px;
    flex: 0 0 28px;
    border: 3px solid rgba(89, 74, 226, 0.25);
    border-top-color: var(--pc-color-primary, #594ae2);
    border-radius: 50%;
    animation: auth-logout-spin 0.8s linear infinite;
}

@keyframes auth-logout-spin {
    to {
        transform: rotate(360deg);
    }
}

/*
    Portal shell
    ============
    The three portal layouts (ClientPortal, AdminPortal and InstallPortal MainLayout) render:

        PortalTheme (.pc-theme.bh-app-theme)
          .bh-shell                 column, fills the theme root
            PlatformAppBar .bh-app-bar
            .bh-body                row, takes the remaining height
              PlatformDrawer .bh-drawer   responsive: overlay below 960px, docked sidebar at 960px and up
              main.bh-main                the page; .bh-main__content scrolls and is height-bounded, so
                                          pages that fill their container (the schedulers) keep working
            StagingEnvironmentBar
          PlatformOverlayHosts (dialogs and snackbars)

    The app bar keeps MudBlazor's height at the Standard density (56px, 48px on a landscape phone, 64px from
    600px), which app CSS reads as --bh-toolbar-height / --bh-appbar-height and Platform.Components as
    --pc-app-bar-height (the clipped drawer's top edge). The heights are built from the density tokens
    (control-large plus a space step), so the Compact and Comfortable densities from the account menu move
    them a step down or up (48/44/56px and 64/52/72px), and the text size scales them with the rem. A narrow
    phone (400px and below) also shrinks the root font by 0.8 (ProfileButton); the bar divides that step back
    out (--bh-narrow-text-factor), so Standard stays 56px there and the bar does not jump when the interactive
    ProfileButton mounts over the prerendered page. The font is the app font (Rubik) through --pc-brand-font-family, which
    wins over every design language's own font, in nested themes too.

    The theme-root rules use .pc-theme.bh-app-theme, not .bh-app-theme alone: platform-components.css loads
    after this file and declares its own --pc-app-bar-height on ":root, .pc-theme", which would otherwise win
    on the same element. The same applies to every rule below that adds to a single-class PC rule.
*/
.pc-theme.bh-app-theme {
    --bh-toolbar-height: calc((var(--pc-density-control-large) + var(--pc-density-space-sm)) / var(--bh-narrow-text-factor, 1));
    --bh-appbar-height: var(--bh-toolbar-height);
    --pc-app-bar-height: var(--bh-toolbar-height);
    --pc-brand-font-family: var(--bh-font-family);
    /* The background of full-page authorisation screens (it was MudBlazor's grey page background). */
    --bh-page-background: var(--pc-color-surface-container);
    /* Canvas and paper (see "Canvas, paper and page placement" below). */
    --bh-page-gutter: var(--pc-density-space-lg);       /* 16px: 700px to 959px */
    --bh-page-gutter-wide: var(--pc-density-space-xl);  /* 24px: 960px and up (docked drawer) */
    --bh-canvas: var(--pc-color-surface-container-low);
    --bh-paper: var(--pc-color-surface-container-lowest);
    --bh-paper-radius: var(--pc-shape-corner-extra-small);  /* 4px: the radius every app paper uses */
    --pc-data-grid-radius: var(--bh-paper-radius);          /* every outlined data grid in the app */
    height: 100%;
}

/* Dark values. They use the selectors PlatformTheme's own dark tokens use (the mode class and the
   data-theme attribute, and "system" under the OS preference), and are declared after the light
   block at the same specificity so source order wins. Tonal elevation: the paper is lighter than the
   canvas. light-dark() is deliberately not used: custom properties are not validated at parse time, so
   an engine without it (iOS WebView before 17.5, Chromium before 123, both possible in MAUI) would
   accept a light-dark() declaration, let it replace the plain value above it, and then compute it to
   transparent. */
.pc-theme--dark.bh-app-theme,
[data-theme="dark"].bh-app-theme {
    --bh-canvas: var(--pc-color-surface);
    --bh-paper: var(--pc-color-surface-container-low);
    /* The dark paper is surface-container-low, the grid's own hover colour, so rows hover one step up. */
    --pc-data-grid-row-hover-background: var(--pc-color-surface-container);
}

@media (prefers-color-scheme: dark) {
    .pc-theme--system.bh-app-theme,
    [data-theme="system"].bh-app-theme {
        --bh-canvas: var(--pc-color-surface);
        --bh-paper: var(--pc-color-surface-container-low);
        --pc-data-grid-row-hover-background: var(--pc-color-surface-container);
    }
}

/* The offline status button floats over the bottom end corner (.offline-fab-container, fixed). It is
   rendered next to .bh-shell inside the theme root, not inside the shell, so the token is set here;
   the phone list reads it as the space under its last item so the button never covers the pager,
   and its pinned pager keeps the same width clear at its end so the button never covers Next. */
.pc-theme.bh-app-theme:has(.offline-fab-container) {
    --bh-fab-clearance: 4.5rem;
    --pc-data-grid-mobile-pager-end-clearance: var(--bh-fab-clearance);
}

/* A page showing an action bar pinned to the bottom of a phone screen (the audit photos' Next photo, or Previous and
   Next) marks its root bh-page--bottom-bar; the offline status button then floats above the bar (about 5rem tall with
   its padding), not over it. */
@media (max-width: 959.98px) {
    .pc-theme.bh-app-theme:has(.bh-page--bottom-bar) .offline-fab-container {
        bottom: calc(16px + 5rem);
    }
}

@media (max-width: 599.98px) and (orientation: landscape) {
    .pc-theme.bh-app-theme {
        --bh-toolbar-height: calc(var(--pc-density-control-large) / var(--bh-narrow-text-factor, 1));
    }
}

@media (min-width: 600px) {
    .pc-theme.bh-app-theme {
        --bh-toolbar-height: calc((var(--pc-density-control-large) + var(--pc-density-space-lg)) / var(--bh-narrow-text-factor, 1));
    }
}

.bh-shell {
    display: flex;
    flex-direction: column;
    height: 100%;
}

/* MudAppBar Gutters=false: the toggle sits at the start edge and the profile menu at the end. */
.pc-app-bar.bh-app-bar {
    flex: 0 0 auto;
    height: var(--pc-app-bar-height);
    min-height: var(--pc-app-bar-height);
    padding: 0;
    gap: 0;
}

.pc-app-bar.bh-app-bar.bh-app-bar--gutters {
    padding-inline: var(--pc-density-space-lg);
}

/* The logo gives way on a phone so the actions, including the profile menu, stay on screen. */
.bh-app-bar .pc-app-bar__leading {
    flex-shrink: 1;
    min-width: 0;
}

/* The title had MudBlazor's h6 style. */
.bh-app-bar .pc-app-bar__title {
    font-size: 1.25rem;
    font-weight: 500;
    line-height: 1.6;
}

.bh-app-bar .title-on-appbar {
    margin-inline-start: var(--pc-density-space-sm);
}

.bh-app-bar .bh-app-bar__sign-in {
    flex-shrink: 0;
    margin-inline-start: var(--pc-density-space-sm);
}

/* An Install page's own bar (InstallPageBar): below 960px, where the drawer is an overlay, a page's back button,
   title and note replace the menu, logo and account buttons. From 960px the app bar is unchanged and the page shows
   its own heading. The account buttons stay mounted, only hidden, so the narrow-phone text step they apply does not
   jump. Two classes: platform-components.css loads later and sets the icon button's own display. */
.bh-app-bar .bh-app-bar__shell {
    display: contents;
}

.bh-app-bar .bh-app-bar__page-back,
.bh-app-bar .bh-app-bar__page-title,
.bh-app-bar .bh-app-bar__page-note {
    display: none;
}

@media (max-width: 959.98px) {
    .bh-app-bar--page .bh-app-bar__shell {
        display: none;
    }

    .bh-app-bar--page .bh-app-bar__page-back {
        display: inline-flex;
        margin-inline-start: var(--pc-density-space-xs);
    }

    .bh-app-bar--page .bh-app-bar__page-title {
        display: block;
        overflow: hidden;
        margin-inline-start: var(--pc-density-space-xs);
        font-weight: 600;
        text-overflow: ellipsis;
        white-space: nowrap;
    }

    .bh-app-bar--page .bh-app-bar__page-note {
        display: inline;
        margin-inline-end: var(--pc-density-space-lg);
        font-size: 0.875rem;
        white-space: nowrap;
        opacity: 0.8;
    }
}

.bh-body {
    position: relative;
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

/* Docked (960px and up, PlatformDrawerBreakpoint.Medium as MudDrawer's Md) the drawer is a column of the
   body below the app bar, not a sticky viewport-height sidebar. */
@media (min-width: 960px) {
    .bh-body > .bh-drawer.pc-drawer--responsive {
        position: relative;
        top: auto;
        height: auto;
        align-self: stretch;
    }
}

/* "Powered by Blackhawk" (PoweredByLogo) sits at the foot of the drawer content: sticky at top 100%
   keeps it at the bottom while the menu is short and after the last item once the menu scrolls. Its
   inline padding lines the logo up with the navigation labels (the drawer inset is space-sm and the
   items' inline padding space-md), and the block padding is one small step, so it stays close to the
   bottom edge at every density. */
.bh-powered-by {
    position: sticky;
    top: 100%;
    padding: var(--pc-density-space-sm) var(--pc-density-space-md) var(--pc-density-space-xs);
}

.bh-powered-by__logo {
    display: block;
    height: calc(1.5rem * var(--pc-font-scale, 1));
    fill: var(--pc-color-on-surface-variant);
}

.bh-powered-by .bh-powered-by__copyright {
    padding-top: var(--pc-density-space-xs);
    font-size: calc(0.53125rem * var(--pc-font-scale, 1));
    line-height: 1.3;
}

.bh-main {
    position: relative;
    flex: 1 1 auto;
    min-width: 0;
    height: 100%;
    overflow: hidden;
}

/* The scroll container of every page. Its background is the canvas the app's papers sit on, for every
   page and not only the grid pages, so moving between a list, a detail page and a form changes neither
   the canvas colour nor the paper radius. Phones drop it (see "Canvas, paper and page placement"). */
.bh-main__content {
    height: 100%;
    width: 100%;
    overflow: auto;
    background-color: var(--bh-canvas, var(--pc-color-surface));
}

.bh-main__content--page-background {
    background-color: var(--bh-page-background);
}

.bh-main__loading {
    display: flex;
    flex-direction: column;
    gap: 20px;
    align-items: center;
    justify-content: center;
    height: 100%;
    width: 100%;
}

/* The public asset page's thin progress bar, over the top edge of the content below the app bar (two classes:
   PC's later .pc-progress-linear sets position: relative). */
.pc-progress-linear.bh-main__progress {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    z-index: 1;
}

/*
    Canvas, paper and page placement
    ================================
    Full-page data grids fill the content area (grid design spec, sections 4.2 to 4.4). DataGridWrapper
    classes its root data-grid-wrapper--auto (the default), --page, --section or --dialog, and
    --virtualize for a virtualized grid. The grid itself only CONSUMES the tokens below, with fallbacks,
    and never declares them on .pc-data-grid, so an ancestor can set them and every grid under it
    inherits them:

        --pc-data-grid-background          the paper colour (--bh-paper here)
        --pc-data-grid-radius              the paper radius (--bh-paper-radius, on the theme root above)
        --pc-data-grid-host-block-size     the grid's block size; "fill" is 100%
        --pc-data-grid-host-viewport-flex  the rows' viewport flex; "fill" is 1 1 0
        --pc-data-grid-host-viewport-max-height  lifts the grid's MaxHeight cap; "fill" is none
        --pc-data-grid-row-hover-background      the desktop row hover layer (dark papers set it)
        --pc-data-grid-mobile-end-space    the space under the phone list's last item
        --pc-data-grid-mobile-pager-end-clearance  the space kept clear at the end of the pinned phone pager
        --pc-data-grid-mobile-inset        the phone list's inline inset (the grid's own default is 16px)

    An Auto wrapper gets the page treatment only where the height chain is known to be definite: when it
    is a DIRECT child of the content area or of .bh-page (a page with content above its grid). Any other
    host (a tab, a panel, a card, a dialog) keeps the grid's own outlined card at content height with no
    gutter, so a grid in a host without a definite height never collapses to zero rows. An explicit Page
    wrapper is for a container that is neither but does have a definite height. The structural selector
    cannot live in the wrapper's scoped CSS, which cannot see its parent, so it is global.

    Zero is written 0px, not 0, for the inset and the end space: the list's divider offset adds the inset
    to lengths inside calc(), where a unitless zero is invalid and would drop the whole declaration.
*/

/* Desktop (wider than 43.75rem, and not a landscape phone): the paper sits one gutter inside the content
   area on the canvas, fills the height under the app bar (and above the staging bar, which sits outside
   .bh-body), and its rows scroll inside it under the sticky header. No maximum width: grids use it all. */
:is(.bh-main__content, .bh-page) > :is(.data-grid-wrapper--auto, .data-grid-wrapper--page),
.data-grid-wrapper--page {
    box-sizing: border-box;
    height: 100%;
    display: flex;
    flex-direction: column;
    padding: var(--bh-page-gutter);
    --pc-data-grid-background: var(--bh-paper);
    --pc-data-grid-host-block-size: 100%;
    --pc-data-grid-host-viewport-flex: 1 1 0;
    --pc-data-grid-host-viewport-max-height: none;  /* the paper, not MaxHeight, bounds the rows */
    --pc-data-grid-mobile-end-space: var(--bh-fab-clearance, 0px);
}

@media (min-width: 60rem) {
    :is(.bh-main__content, .bh-page) > :is(.data-grid-wrapper--auto, .data-grid-wrapper--page),
    .data-grid-wrapper--page {
        padding: var(--bh-page-gutter-wide);
    }
}

/* A page with content above its grid (a back button, breadcrumbs, KPI cards, a select) wraps the content
   and the grid in .bh-page: one padded column whose grid takes the remaining height. The page padding is
   the gutter, so the wrapper carries none of its own here. This rule follows the structural rule above on
   purpose: both are one class pair strong, so source order lets this one win. */
.bh-page {
    display: flex;
    flex-direction: column;
    gap: var(--pc-density-space-lg);
    box-sizing: border-box;
    height: 100%;
    padding: var(--bh-page-gutter);
}

@media (min-width: 60rem) {
    .bh-page {
        padding: var(--bh-page-gutter-wide);
    }
}

.bh-page > :is(.data-grid-wrapper--auto, .data-grid-wrapper--page) {
    flex: 1 1 0;
    min-height: 0;
    height: auto;
    padding: 0;
}

/* A dialog's content padding already insets its content (a dialog is a full-screen sheet on a phone), so
   a grid in one adds no list inset of its own. */
.data-grid-wrapper--dialog {
    --pc-data-grid-mobile-inset: 0px;
}

/*
    Phones (the list view). The query is the grid's own compact query (PlatformDataGridBreakpoints.CompactQuery,
    the same string as platform-components.css and the layout watch): narrow windows, and landscape phones,
    which would otherwise get the desktop table with a few rows of room.

    No canvas and no paper: the list sits edge to edge on the page surface and scrolls WITH the page, in
    this content area, not in a viewport of its own. A fixed-height scroller under a pinned toolbar would
    cost a permanent 216px or more of chrome and, on a page with KPI cards above its grid, shrink the list
    to a strip while the cards never scroll away. The grid's toolbar is sticky against this scroller
    (platform-components.css), so nothing between it and .bh-main__content may clip: the wrapper and
    .bh-page set no overflow. A virtualized list keeps a scroller of its own, because Virtualize needs a
    bounded viewport. Content next to the grid inside .bh-page keeps the list's inset, so a card, a
    breadcrumb, the toolbar title and the item text share one left edge.
*/
@media (max-width: 43.75rem), (max-height: 30rem) and (orientation: landscape) and (pointer: coarse) {
    .bh-main__content:not(.bh-main__content--page-background) {
        background-color: var(--pc-color-surface);
    }

    :is(.bh-main__content, .bh-page) > :is(.data-grid-wrapper--auto, .data-grid-wrapper--page),
    .data-grid-wrapper--page {
        height: auto;
        min-height: 100%;  /* the list surface reaches the bottom of a short list */
        padding: 0;
        --pc-data-grid-host-block-size: auto;
        --pc-data-grid-host-viewport-flex: 0 0 auto;
    }

    :is(.bh-main__content, .bh-page) > .data-grid-wrapper--virtualize {
        height: 100%;
        --pc-data-grid-host-block-size: 100%;
        --pc-data-grid-host-viewport-flex: 1 1 0;
    }

    .bh-page {
        height: auto;
        min-height: 100%;
        padding: 0;
        gap: var(--pc-density-space-md);
    }

    .bh-page > :not(.data-grid-wrapper) {
        padding-inline: var(--pc-data-grid-mobile-inset, var(--pc-density-space-lg));
    }

    .bh-page > :first-child:not(.data-grid-wrapper) {
        padding-block-start: var(--pc-density-space-md);
    }

    .bh-page > .data-grid-wrapper {
        flex: 1 0 auto;
    }
}

/*
    Full-screen page mode
    =====================
    Set on <body> by a page that offers a full-screen toggle. The app bar and navigation drawer (and its scrim)
    are taken out so the page fills the window; the shell is a flex layout, so nothing else needs adjusting.
    Global rather than component-scoped because the chrome is rendered by the layout, outside any page's CSS
    isolation.
*/
body.bh-page-fullscreen .bh-app-bar,
body.bh-page-fullscreen .bh-drawer,
body.bh-page-fullscreen .bh-body > .pc-drawer-scrim {
    display: none !important;
}

/*
    Sign-in and account screens
    ===========================
    Shared by every screen inside SignInFrame and SignInCard (Components/SignIn): the sign-in steps, the account
    links in emails (activation, password reset, email change) and the authorisation pages. Global rather than
    component-scoped so each page composes the same pieces:

        .bh-signin-form      a form's fields and its submit button, one column
        .bh-signin-form__aside  a link under a field, at its end ("Forgot password?")
        .bh-signin-actions   stacked full-width actions (sign-in methods, a primary and its alternative)
        .bh-signin-links     a centred row of text buttons (Back to sign in, Send a new code)
        .bh-signin-or        the "or" rule between the sign-in methods and the email form
        .bh-signin-status    a centred state: progress, success or failure, with an icon and a line of text
        .bh-signin-chip      the account a step is for, which also goes back to choose another
        .bh-signin-microsoft the Microsoft sign-in button, with .bh-signin-microsoft__logo (and a provider's
                             own logo, .bh-signin-provider__logo) before its text
*/
.bh-signin-form,
.bh-signin-actions {
    display: flex;
    flex-direction: column;
    gap: var(--pc-density-space-md);
    margin: 0;
}

.bh-signin-form__aside {
    display: flex;
    justify-content: flex-end;
    margin-block: calc(-1 * var(--pc-density-space-sm)) calc(-1 * var(--pc-density-space-xs));
}

.bh-signin-links {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: var(--pc-density-space-xs) var(--pc-density-space-sm);
}

.bh-signin-or {
    display: flex;
    align-items: center;
    gap: var(--pc-density-space-md);
    color: var(--pc-color-on-surface-variant);
    font-size: calc(0.875rem * var(--pc-font-scale, 1));
    line-height: 1.4;
}

.bh-signin-or::before,
.bh-signin-or::after {
    content: "";
    flex: 1 1 0;
    border-top: 1px solid var(--pc-color-outline-variant);
}

.bh-signin-status {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--pc-density-space-md);
    padding-block: var(--pc-density-space-lg);
    text-align: center;
}

.bh-signin-status__icon {
    display: grid;
    place-items: center;
    width: calc(3.5rem * var(--pc-font-scale, 1));
    height: calc(3.5rem * var(--pc-font-scale, 1));
    border-radius: 50%;
    color: var(--pc-color-on-primary-container);
    background: var(--pc-color-primary-container);
}

.bh-signin-status__icon--success {
    color: var(--pc-color-on-success-container);
    background: var(--pc-color-success-container);
}

.bh-signin-status__icon--error {
    color: var(--pc-color-on-error-container);
    background: var(--pc-color-error-container);
}

.bh-signin-chip {
    display: inline-flex;
    align-items: center;
    gap: var(--pc-density-space-sm);
    align-self: center;
    max-width: 100%;
    min-height: calc(2.25rem * var(--pc-font-scale, 1));
    padding: var(--pc-density-space-xs) var(--pc-density-space-md) var(--pc-density-space-xs) var(--pc-density-space-xs);
    color: var(--pc-color-on-surface);
    background: transparent;
    border: 1px solid var(--pc-color-outline-variant);
    border-radius: var(--pc-shape-corner-full, 999px);
    font: inherit;
    font-size: calc(0.875rem * var(--pc-font-scale, 1));
    font-weight: 500;
    cursor: pointer;
}

.bh-signin-chip:hover:not(:disabled) {
    background: color-mix(in srgb, var(--pc-color-on-surface) 8%, transparent);
}

.bh-signin-chip:disabled {
    cursor: default;
}

.bh-signin-chip:focus-visible {
    outline: 3px solid var(--pc-color-primary);
    outline-offset: 2px;
}

.bh-signin-chip__name {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.bh-signin-chip > .pc-icon {
    flex: 0 0 auto;
    color: var(--pc-color-on-surface-variant);
}

/* Microsoft's sign-in button: its own colours, typeface and logo in light, dark and the system theme (whichever
   the OS prefers), on the sign-in page and wherever else it is offered. */
.bh-signin-actions .bh-signin-microsoft {
    --pc-action-color: #5e5e5e;
    color: #5e5e5e;
    background: #ffffff;
    border-color: #8c8c8c;
    font-family: "Segoe UI", system-ui, sans-serif;
    font-weight: 600;
}

.pc-theme[data-theme="dark"] .bh-signin-actions .bh-signin-microsoft {
    --pc-action-color: #ffffff;
    color: #ffffff;
    background: #2f2f2f;
    border-color: #2f2f2f;
}

@media (prefers-color-scheme: dark) {
    .pc-theme[data-theme="system"] .bh-signin-actions .bh-signin-microsoft {
        --pc-action-color: #ffffff;
        color: #ffffff;
        background: #2f2f2f;
        border-color: #2f2f2f;
    }
}

/* The logo shares the button's label with its text, so it brings its own gap (12px, as Microsoft's guidance asks). */
.bh-signin-actions .bh-signin-microsoft__logo,
.bh-signin-actions .bh-signin-provider__logo {
    flex: 0 0 auto;
    width: 1.25rem;
    height: 1.25rem;
    margin-inline-end: 0.75rem;
    object-fit: contain;
    vertical-align: middle;
}

/* Context shown above a sign-in card (why this sign-in is being asked for), as wide as the card. */
.bh-signin-context {
    display: flex;
    flex-direction: column;
    gap: var(--pc-density-space-md);
    width: min(100%, 27rem);
}
