/* ===========================================
   Z-Index Hierarchy (matches CortexConnectTenant)
   =========================================== */
:root {
    --z-notification: 30001;
    --z-loading-screen: 9999;
    --z-modal: 1050;
    --z-dropdown: 1060;
    --z-drawer-mobile: 900;
    --z-sticky: 50;
}

html, body {
    font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
}

html, body, #app, .page {
    height: 100%;
    margin: 0;
}

.page {
    display: flex;
    flex-direction: column;
}

main {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden; /* prevent page-level scroll */
}

article.content {
    flex: 1;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

h1:focus {
    outline: none;
}

a, .btn-link {
    color: #0071c1;
}

.btn-primary {
    color: #fff;
    background-color: #1b6ec2;
    border-color: #1861ac;
}

.dxbl-image {
    vertical-align: middle;
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}

/* Form controls should use theme text color */
.form-control,
.form-select,
input.form-control,
textarea.form-control,
select.form-control {
    color: var(--bs-light-text-emphasis);
}

/* Content gutter: a slim 1rem on desktop so grids and tab panels take the width
   the viewport offers (the page container below is uncapped), narrower on
   tablet/phone. */
.content {
    padding: 1.1rem 1rem 0;
}

/* Reduce horizontal padding on small screens so content uses the full viewport */
@media (max-width: 767px) {
    .content {
        padding-top: 0.5rem;
        padding-left: 0.5rem;
        padding-right: 0.5rem;
    }

    .list-container {
        padding: 0.5rem;
    }
}

@media (min-width: 768px) and (max-width: 991px) {
    .content {
        padding-left: 0.75rem;
        padding-right: 0.75rem;
    }

    .list-container {
        padding: 0.5rem;
    }
}

/* Touch-friendly tap targets on phones (min 44px per Apple/WCAG guidance) */
@media (max-width: 767px) {
    .dxbs-btn {
        min-height: 44px;
        padding: 0.5rem 1rem;
    }

    .btn {
        min-height: 44px;
    }

    .clickable,
    button,
    a,
    .dxbl-grid-data-row {
        min-height: 44px;
    }

    /* Stack consecutive buttons vertically by default */
    button + button,
    .btn + .btn {
        margin-top: 0.5rem;
    }

    /* Keep horizontal button groups horizontal */
    .action-buttons button + button,
    .action-buttons .btn + .btn,
    .dxbl-btn-group button + button,
    .dxbl-btn-group .btn + .btn,
    .dxbl-modal-footer button + button,
    .dxbl-modal-footer .btn + .btn {
        margin-top: 0;
    }
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid red;
}

.validation-message {
    color: red;
}

#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: var(--z-notification);
}

    #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."
    }

.loading-progress {
    position: relative;
    display: block;
    width: 8rem;
    height: 8rem;
    margin: 20vh auto 1rem auto;
}

    .loading-progress circle {
        fill: none;
        stroke: #e0e0e0;
        stroke-width: 0.6rem;
        transform-origin: 50% 50%;
        transform: rotate(-90deg);
    }

        .loading-progress circle:last-child {
            stroke: #1b6ec2;
            stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
            transition: stroke-dasharray 0.05s ease-in-out;
        }

.loading-progress-text {
    position: absolute;
    text-align: center;
    font-weight: bold;
    inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
}

    .loading-progress-text:after {
        content: var(--blazor-load-percentage-text, "Loading");
    }

code {
    color: #c02d76;
}

.filled-icon {
    font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24
}

#loading-screen {
    position: fixed;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background-color: white;
    z-index: var(--z-loading-screen);
}

    #loading-screen .loading-logo {
        width: 160px;
        margin-bottom: 1.5rem;
    }

    #loading-screen .bar-container {
        width: 60%;
        max-width: 360px;
        height: 6px;
        background: rgba(0,0,0,0.1);
        border-radius: 3px;
        overflow: hidden;
    }

    /* paddle is 50% wide, slides full-width of itself (i.e. half the container) */
    #loading-screen .bar {
        width: 50%;
        height: 100%;
        background: var(--bs-primary);
        /* start at left edge */
        transform: translateX(0);
        /* slide to 100% of its own width, then back, forever */
        animation: slide 0.6s ease-in-out infinite alternate;
    }

@keyframes slide {
    to {
        transform: translateX(100%);
    }
}

/* ===========================================
   Styling Alignment with CortexConnectTenant
   =========================================== */

/* Grid Cell Text Truncation */
.dxbl-grid-data-cell,
.dxbl-grid-header-cell {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

.dxbl-grid-data-cell *,
.dxbl-grid-header-cell *,
.dxbl-grid-data-cell > *,
.dxbl-grid-header-cell > *,
.dxbl-grid-data-cell div,
.dxbl-grid-data-cell span {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    max-width: 100% !important;
}

/* Badges in grid should also truncate */
.dxbl-grid-data-cell .badge {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    max-width: 100% !important;
}

/* Disable hover effect on DxGrid empty filler rows */
.dxbl-grid .dxbl-grid-table > tbody > .dxbl-grid-empty-row:hover,
.dxbl-grid .dxbl-grid-table > tbody > .dxbl-grid-empty-row:hover > td {
    background-color: var(--dxbl-grid-row-bg) !important;
    cursor: default !important;
}

/* Target table cells directly in DxGrid */
.dxbl-grid td,
.dxbl-grid th {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

/* Target table cells content */
.dxbl-grid td *,
.dxbl-grid th * {
    white-space: nowrap !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
}

/* Mobile Grid Handling */
@media (max-width: 767px) {
    .dxbl-grid {
        overflow-x: auto;
        -webkit-overflow-scrolling: touch;
    }

    .dxbl-grid-data-cell,
    .dxbl-grid-header-cell {
        padding: 0.5rem 0.25rem !important;
    }

    .dxbl-grid-data-cell:first-child,
    .dxbl-grid-header-cell:first-child {
        position: sticky;
        left: 0;
        background-color: var(--bs-body-bg);
        z-index: var(--z-sticky);
        box-shadow: 2px 0 4px rgba(0, 0, 0, 0.1);
    }

    /* Action buttons - larger touch targets */
    .action-buttons-mobile {
        display: flex;
        flex-direction: column;
        gap: 0.25rem;
        align-items: center;
    }

    .action-buttons-mobile .dxbs-btn,
    .action-buttons-mobile .dxbl-btn {
        min-width: 44px;
        min-height: 44px;
        padding: 0.5rem;
    }

    /* Hide pager text on mobile */
    .dxbl-pager-info {
        display: none;
    }
}

/* Page shell: layout-owned tab strip (MainTabs) + routed page below. The chain is
   overflow-hidden on purpose — DxGrids scroll internally (see .tab-scroll for the
   opt-out). Full-width on purpose: this is a grid-heavy staff tool, so the content
   spans the viewport minus the .content gutter instead of sitting in a capped,
   centred column. */
.page-container {
    padding: 12px 0 24px;
    width: 100%;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    overflow: hidden;
}

.main-tabs-strip {
    flex: 0 0 auto;
}

.page-body {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

@media (max-width: 767px) {
    .page-container {
        padding: 8px 0;
    }
}

/* Entity-page header: breadcrumb trail + ownership line above the content */
.page-head {
    flex: 0 0 auto;
    margin: 0.25rem 0 0.75rem;
}

/* The head's ⋯ menu — the verbs that are not the visible one (NAVIGATION.md D15) */
.page-head .head-more .material-symbols-outlined {
    font-size: 22px;
}

/* Tabbed pages (Users, Access, tenant drill-down) continue the height chain
   through the DxTabs internals so the grid inside a tab still scrolls itself.

   Every selector here is qualified with .dxbl-tabs, and each one must outrank
   its DevExpress counterpart: the theme stylesheet is registered through
   DxResourceManager (App.razor <HeadContent>), so it lands in <head> AFTER
   app.css and wins every specificity tie. Unqualified, `.nested-tabs` loses
   `display` to the theme's `.dxbl-tabs { display: var(--dxbl-tabs-display) }`
   (block for top-positioned tabs), the chain collapses, and a long grid grows
   past the viewport where the overflow:hidden above it clips the rows away —
   unreachable, with no scrollbar anywhere. */
.nested-tabs.dxbl-tabs {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Tab content panel — container for all tab pages */
.nested-tabs.dxbl-tabs .dxbl-tabs-content-panel {
    flex: 1;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
}

/* Individual tab content — the grid handles its own scrolling. Flex column so a
   tab can stack info banner + toolbar + grid + count strip. The basis must beat
   the theme's `flex: 1 0 100%`, which cannot shrink to the panel's height. */
.nested-tabs.dxbl-tabs .dxbl-tabs-content-panel .dxbl-tabs-content {
    flex: 1 1 0;
    min-height: 0;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    -webkit-overflow-scrolling: touch;
}

.list-container {
    display: flex;
    flex-direction: column;
    flex: 1;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    padding: 0.5rem;
    -webkit-overflow-scrolling: touch;
}

/* DxGrid with constrained height for internal scrolling */
.scrollable-grid {
    flex: 1;
    min-height: 200px;
    height: 100%;
    overflow: hidden;
}

/* Entity link (P10 — "every entity is a door"): entity names in grid cells and
   detail views render as visible links that scope into the entity. Deliberately
   underlined text, not clickable rows — the affordance must look like one.
   Avutec primary orange (#FF5C00, avutec.tokens.css) in both themes — the
   Bootstrap link blue was hard to read in dark mode. */
.entity-link {
    cursor: pointer;
    text-decoration: none;
    color: #FF5C00;
}

.entity-link:hover {
    text-decoration: underline;
    color: #d94e00;
}

[data-bs-theme="dark"] .entity-link:hover {
    color: #ff7a2e;
}

/* Breadcrumb ancestors are doors too — same link treatment as .entity-link
   (consumed by SharedUI's Breadcrumb component). */
:root {
    --breadcrumb-link-color: #FF5C00;
    --breadcrumb-link-hover-color: #d94e00;
}

[data-bs-theme="dark"] {
    --breadcrumb-link-hover-color: #ff7a2e;
}

/* Copyable value cell (serials, emails, permission names): the VALUE truncates
   with an ellipsis, the copy button is a fixed flex item that never leaves view —
   same pattern as CortexConnectTenant's serial cells. Pair with title="" on the
   text so the full value survives truncation as a tooltip. */
.cell-copy {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    min-width: 0;
    max-width: 100%;
}

.cell-copy .cell-copy-text {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.cell-copy .copy-button {
    flex: 0 0 auto;
}

/* Two-line grid cell: the main value over a quieter second line — a ticket's subject over its
   problem, a camera's name over its serial (the serial being the camera's door). Both lines
   truncate; pair with title="" so the full value survives as a tooltip. */
.two-line {
    display: flex;
    flex-direction: column;
    line-height: 1.25;
    min-width: 0;
}

.two-line-main {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.two-line-sub {
    font-size: 12px;
    color: var(--bs-secondary-color);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Release facts strip (the camera release page and the Portal release page, PORTAL_RELEASES.md P11):
   label-over-value cells that wrap to the width available; a wide cell for channels and doors. */
.release-facts {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.5rem;
    margin: 0.5rem 0 0;
}

.release-facts > div {
    min-width: 0;
    max-width: 16rem;
}

.release-facts .release-facts-wide {
    max-width: 24rem;
}

.release-facts dt {
    font-size: 0.72rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--bs-secondary-color, #6c757d);
    margin: 0;
}

.release-facts dd {
    margin: 0;
    font-size: 0.9rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Scrollable tab content for non-grid surfaces (diagnostics cards, detail
   drill-downs). The height chain above is overflow-hidden on purpose — DxGrids
   scroll internally — so anything that is NOT an internally-scrolling grid must
   opt into its own scroll area or it gets clipped at the viewport edge. */
.tab-scroll {
    flex: 1;
    min-height: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
}

/* Grid wrapper: keeps the flex chain intact and hosts the reload overlay */
.grid-host {
    position: relative;
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* Shading overlay while a non-empty list reloads a page */
.grid-busy {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba(var(--bs-body-bg-rgb), 0.6);
    z-index: 5;
}

/* Count strip under each list grid */
.pagination-info {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid var(--bs-border-color);
}

.pagination-info p {
    margin: 0;
    color: var(--bs-secondary-color);
    font-size: 0.85rem;
}

/* Load-failure state with retry button */
.loading-indicator,
.error-message {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 3rem;
    text-align: center;
    gap: 0.75rem;
}

/* Portal version pills (tenant list). Environment identity is carried by three
   redundant channels — a text prefix, a tinted container and a coloured label —
   so it survives colour-blindness, dark mode and scrolling the header away. The
   value itself stays in body colour for contrast; only the CONTAINER is tinted.
   Hues match the menu-bar API toggle (dev orange, prod red) so the whole app
   shares one environment vocabulary. */
:root {
    --version-dev-rgb: 230, 126, 34;    /* #e67e22 */
    --version-prod-rgb: 192, 57, 43;    /* #c0392b */
    --version-dev-label: #b35f0e;
    --version-prod-label: #a52a1f;
    --version-tint-alpha: 0.12;
}

[data-bs-theme="dark"] {
    --version-dev-label: #ffb266;
    --version-prod-label: #ff8f85;
    --version-tint-alpha: 0.22;
}

.version-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    max-width: 100%;
    padding: 0.1rem 0.45rem 0.1rem 0.4rem;
    border-radius: 999px;
    border: 1px solid transparent;
    background-color: rgba(var(--version-rgb), var(--version-tint-alpha));
    color: var(--bs-body-color);
    line-height: 1.35;
    vertical-align: middle;
}

.version-pill--prod { --version-rgb: var(--version-prod-rgb); --version-label: var(--version-prod-label); }
.version-pill--dev  { --version-rgb: var(--version-dev-rgb);  --version-label: var(--version-dev-label); }

/* Dev differs from prod: the exception gets a dashed edge, matching rows stay quiet. */
.version-pill--differs {
    border-style: dashed;
    border-color: rgba(var(--version-rgb), 0.75);
}

.version-pill__env {
    flex: 0 0 auto;
    font-size: 0.62rem;
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--version-label);
}

/* The value is NEVER ellipsised: the tail of a stamp is the git hash. The column is
   sized to fit a full stamp; should a longer one ever arrive, it wraps inside the pill
   rather than losing its end (overrides the grid-wide nowrap/ellipsis rules above). */
.dxbl-grid .version-pill__value,
.version-pill__value {
    flex: 0 1 auto;
    min-width: 0;
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: normal !important;
    overflow-wrap: anywhere;
    font-size: 0.8rem;
    color: inherit;   /* beat the global magenta `code` rule */
}

.dxbl-grid .version-pill {
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: normal !important;
}

.version-pill-empty {
    color: var(--bs-secondary-color);
}

/* The channel a production version matches, worn inside its pill (Portal releases): small, after
   the version, the Updater's channel colours. */
.version-pill .version-pill__channel {
    font-size: 0.6rem;
    padding: 0.1rem 0.35rem;
    line-height: 1.2;
    font-family: var(--bs-body-font-family);
}

/* The spin of a "running" glyph: the Last push cell and the "n running" chip (Portal releases). */
@keyframes running-spin {
    to { transform: rotate(360deg); }
}

.running-spin {
    animation: running-spin 1.2s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
    .running-spin { animation: none; }
}

/* A finder's selection column (the Portal API's Tenants view, the Updater's Cameras grid): a
   checkbox inside a cell that stops the row click (rows are doors, NAVIGATION.md D5). */
.select-cell {
    display: flex;
    align-items: center;
    justify-content: center;
}

/* Fluent's unchecked box is transparent (surface-neutral-muted): on the grey header the select-all
   box read washed out. Give it the default surface so header and row boxes look the same in both
   modes; the hover keeps it too (only the border changes). On the checkbox itself, where Fluent
   declares these. */
.select-cell .dxbl-checkbox {
    --dxbl-checkbox-primary-unchecked-check-element-bg: var(--DS-color-surface-neutral-default-rest);
    --dxbl-checkbox-check-element-primary-unchecked-hover-bg: var(--DS-color-surface-neutral-default-rest);
}

/* "2 pushes running · refreshing" — the finder's live note while pushes run (N1). */
.portal-running-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    color: var(--bs-primary);
    white-space: nowrap;
    text-decoration: none;
}

.portal-running-chip:hover {
    text-decoration: underline;
}

.portal-running-chip .material-symbols-outlined {
    font-size: 16px;
}

.selection-summary {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
    white-space: nowrap;
}

/* Material Icon Sizing Utilities */
.material-symbols-outlined {
    vertical-align: middle;
}

.dxbl-grid .material-symbols-outlined {
    font-size: 18px !important;
}

.icon-xxs { font-size: 0.75em; }
.icon-xs { font-size: 1.25em; }
.icon-sm { font-size: 1.375em; }
.icon-md { font-size: 1.5em; }
.icon-lg { font-size: 1.75em; }
.icon-xl { font-size: 1.8125em; }
.icon-2xl { font-size: 1.875em; }

/* ========================================
   Help Drawer & Floating Button
   ======================================== */
.help-floating-button {
    position: fixed;
    top: 70px;
    right: 20px;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    background-color: #17A2B8;
    color: white;
    border: none;
    cursor: pointer;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
    z-index: 1000;
    font-size: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: transform 0.2s, box-shadow 0.2s;
}

.help-floating-button:hover {
    transform: scale(1.1);
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
}

.help-floating-button:focus {
    outline: 2px solid #17A2B8;
    outline-offset: 2px;
}

.help-floating-button::before {
    content: "?";
    font-size: 20px;
    font-weight: 700;
}

/* Fix DxDrawer breaking flex height chain */
main > .dxbl-drawer {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

main > .dxbl-drawer > * {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

main > .dxbl-drawer .dxbl-drawer-target {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
}

main > .dxbl-drawer .dxbl-drawer-panel {
    flex: none;
}

/* Help Drawer Panel */
.help-drawer .dxbl-drawer-panel {
    max-width: 90vw;
    background-color: var(--bs-body-bg);
    box-shadow: -2px 0 8px rgba(0, 0, 0, 0.15);
}

.help-drawer-header {
    padding: 1rem 1.25rem;
}

.help-drawer-header h5 {
    margin: 0;
    color: var(--bs-heading-color, var(--bs-body-color));
    font-weight: 600;
}

.help-drawer-body {
    padding: 1.25rem;
    line-height: 1.6;
    color: var(--bs-body-color);
}

.help-drawer-body h5 {
    margin-top: 1rem;
    margin-bottom: 0.5rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--bs-heading-color, var(--bs-body-color));
}

.help-drawer-body h5:first-child {
    margin-top: 0;
}

.help-drawer-body p {
    margin-bottom: 0.75rem;
    color: var(--bs-light-text-emphasis);
}

.help-drawer-body ul {
    margin: 0.5rem 0;
    padding-left: 1.25rem;
}

.help-drawer-body li {
    margin-bottom: 0.25rem;
    color: var(--bs-light-text-emphasis);
}

.help-drawer-body code {
    background-color: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    padding: 0.125rem 0.375rem;
    border-radius: 3px;
    font-size: 0.875em;
    border: 1px solid var(--bs-border-color);
}

/* Mobile adjustments for help button */
@media (max-width: 575px) {
    .help-floating-button {
        top: auto;
        bottom: 20px;
        right: 20px;
    }
}

/* Action Buttons in Grids */
/* An empty action slot: the same space as a real button, invisible and inert, so the icons of
   state-dependent rows stay in their columns (CLAUDE.md, Actions column). */
.action-buttons .action-button-hidden {
    visibility: hidden;
    pointer-events: none;
}

.action-buttons {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    justify-content: flex-start;
}

/* Action button containers should not force wrapping */
.dxbl-grid-data-cell .action-buttons,
.dxbl-grid-data-cell .action-buttons-mobile {
    white-space: nowrap !important;
    display: inline-flex !important;
    align-items: center !important;
}

/* Prevent action buttons from being truncated in grid cells */
.dxbl-grid-data-cell .action-buttons {
    overflow: visible !important;
    text-overflow: unset !important;
}

.action-buttons .dxbl-btn,
.action-buttons .dxbs-btn {
    flex-shrink: 0;
    overflow: visible !important;
}

/* Ensure icon span doesn't clip wide icons */
.action-buttons .dxbl-btn .material-symbols-outlined,
.action-buttons .dxbs-btn .material-symbols-outlined {
    overflow: visible;
}

/* A badge-sized placeholder for a cell whose value is still being looked up from a
   second call after the grid rendered (PaymentBadge while Billing answers): the wait
   shows where the answer will land, not only in the footer. */
.value-pending {
    display: inline-block;
    width: 3.5em;
    height: 1.25em;
    border-radius: var(--bs-border-radius, 0.375rem);
    background-color: var(--bs-secondary-bg, #e9ecef);
    vertical-align: middle;
    animation: value-pending-pulse 1.4s ease-in-out infinite;
    cursor: progress;
}

@keyframes value-pending-pulse {
    0%, 100% { opacity: 0.45; }
    50% { opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
    .value-pending { animation: none; opacity: 0.7; }
}

/* A camera's boot report (ReportedVersion / ReportedLayout): the version in the
   serial's monospace, and the stale marker — a small clock — beside the first cell
   of a report the camera has connected long after. */
.reported-rev {
    font-family: var(--bs-font-monospace);
    font-size: 0.82rem;
}

.report-stale {
    font-size: 1rem;
    vertical-align: text-bottom;
    margin-left: 0.25rem;
    color: var(--bs-warning-text-emphasis, #997404);
    cursor: help;
}

/* ---- Navigation (NAVIGATION.md) ---- */

/* A finder whose rows are doors (D5): the cursor says so. */
.clickable-rows .dxbl-grid-data-row {
    cursor: pointer;
}

/* The row you left through (NAVIGATION.md D14): a standing mark on its leading edge until the
   list next reloads — recognition, not recall. A border, not a colour alone (WCAG 1.4.1); the row
   also carries an aria-description. Qualified with .dxbl-grid so it outranks the theme. */
.dxbl-grid tr.row-returned > td:first-child {
    box-shadow: inset 3px 0 0 #17A2B8;
}

/* Skip link (D11): off-screen until it has focus. */
.skip-link {
    position: absolute;
    top: -100px;
    left: 0.5rem;
    z-index: 2000;
    padding: 0.4rem 0.75rem;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    border: 1px solid var(--bs-border-color);
    border-radius: 4px;
    text-decoration: none;
}

.skip-link:focus {
    top: 0.5rem;
}

/* Arrival focus target (D11): FocusOnNavigate lands here so a screen reader announces the page,
   but the outline is never drawn — the strip's lit tab (finders) or the title (entity pages) is
   the "you are here". Not ":not(:focus-visible)": a browser treats programmatic focus straight
   after a fresh load (no pointer interaction yet) as keyboard focus, which drew the default ring
   around the finder's info banner on first visit. Same unconditional rule as the sibling apps' h1. */
[data-page-focus]:focus {
    outline: none;
}
