/* Signed-in app chrome: sidebar polish and the layout variants
   (see Theme/AppLayoutStyle.cs — cycle layouts with the dev button in the app bar). */

/* ---------- Authenticator QR code (2FA setup) ---------- */

.pf-authenticator-qr svg {
    width: 200px;
    height: 200px;
    max-width: 100%;
    padding: 8px;
    background: #fff;
    border-radius: 8px;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12);
}

/* ---------- Account-management form action buttons ----------
   A comfortable button (not a full-width bar) on desktop, full-width on
   mobile. Put the class on the button's container (MudItem or a wrapping div). */

.pf-form-actions {
    /* Span the full row even below the md breakpoint, where these grid items
       (md="12" only) would otherwise shrink to their content width. */
    width: 100%;
    margin-top: 4px;
}

.pf-form-actions .mud-button-root {
    /* Comfortable 200px on desktop, but never wider than the container
       (min() keeps it from overflowing a squeezed card). */
    min-width: min(200px, 100%);
}

@media (max-width: 599.95px) {
    .pf-form-actions .mud-button-root {
        width: 100%;
    }
}

/* ---------- App page padding ----------
   Comfortable breathing room around every signed-in page (dashboard, grids,
   theme demo, account manage, …). Applied to MudMainContent in MainLayout;
   the variables also drive the pf-page-fill height calc below. */

.pf-main-pad {
    --pf-pad-x: 24px;
    /* The app bar's height plus a gap below it.
       Composed rather than declared as one number, for two reasons. A theme that picks a bar
       height publishes --pf-theme-bar on MudLayout (AppTheme.DimensionVariables), and a custom
       property declared on *this* element beats an inherited one whatever the ancestor says — so
       the theme's value has to arrive as a fallback lookup. And the compact density needs to trim
       only the gap: when it overrode --pf-pad-top outright it also discarded the bar height, so a
       compact theme with a tall bar tucked its content 3px underneath. Compact now sets
       --pf-pad-gap alone and this recomputes. pf-page-fill's height calc reads --pf-pad-top, so it
       follows either way. */
    --pf-pad-gap: 12px;
    --pf-pad-top: calc(var(--pf-theme-bar, 64px) + var(--pf-pad-gap));
    --pf-pad-bottom: 24px;
    padding: var(--pf-pad-top) var(--pf-pad-x) var(--pf-pad-bottom) !important;
}

@media (min-width: 960px) {
    .pf-main-pad {
        --pf-pad-x: 40px;
    }
}

@media (max-width: 599.95px) {
    .pf-main-pad {
        --pf-pad-x: 16px;
    }
}

/* ---------- Viewport-filling list pages (multi-select grid style) ----------
   The grid sizes itself to the space below the app bar and scrolls internally,
   so the page itself never scrolls. Height = viewport minus the main-content
   top offset and bottom padding (variables from .pf-main-pad above). */

.pf-page-fill {
    height: calc(100dvh - var(--pf-pad-top, 76px) - var(--pf-pad-bottom, 24px));
    display: flex;
    flex-direction: column;
}

.pf-grid-fill {
    flex: 1 1 0;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

/* Horizontal overflow is deliberately left to scroll. Desktop used to force
   overflow-x: hidden here, to suppress a scrollbar the vertical one provoked by
   stealing ~10px of width. It also hid overflow that was real: with the nav
   drawer open the columns want ~36px more than they get, and the right edge of
   the last one was simply unreachable. A scrollbar that occasionally appears
   when it needn't is the cheaper of the two faults. */
.pf-grid-fill .mud-table-container {
    flex: 1 1 0;
    min-height: 0;
}

/* Opt-in companion to .pf-grid-fill: drops the two rules MudBlazor draws around the
   grid's chrome, so the only lines left are the ones between rows. Kept as its own
   class because the two are different concerns — .pf-grid-fill decides how the grid
   is laid out, this decides how it looks — so a later fill grid can take one without
   the other. Safe on the sticky header: MudBlazor gives it an opaque surface
   background, so rows scrolling underneath do not bleed through where the border was. */
.pf-grid-flush .mud-table-pagination-toolbar {
    border-top: none;
}

.pf-grid-flush thead .mud-table-cell {
    border-bottom: none;
}

/* Below the card breakpoint (600px) MudBlazor stacks each row's cells but drops the
   row divider, so consecutive cards blur together. Restore a separator: make the row
   a block (tr borders don't paint under border-collapse: separate) and give it the
   same line colour the desktop rows use, so the two views match. Applies to the
   multi-select fill grid (.pf-grid-fill) and the standard list tables (.pf-list-table). */
@media (max-width: 599.95px) {
    .pf-grid-fill tbody tr.mud-table-row,
    .pf-list-table tbody tr.mud-table-row {
        display: block;
        border-bottom: 1px solid var(--mud-palette-table-lines);
        padding-bottom: 8px;
    }

    .pf-grid-fill tbody tr.mud-table-row:last-child,
    .pf-list-table tbody tr.mud-table-row:last-child {
        border-bottom: none;
        padding-bottom: 0;
    }
}

/* Dialogs opened from the fill grids: skip the enter animation so rapid
   open/close feels instant. */
.no-animation-dialog {
    animation: none !important;
    transition: none !important;
    transform: none !important;
}

.no-animation-dialog + .mud-dialog-container {
    transition: none !important;
}

/* ---------- Profile picture editor (account page + user dialog) ---------- */

.pf-avatar-preview {
    width: 132px;
    height: 132px;
    border-radius: 12px;
    object-fit: cover;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-background-gray);
}

.pf-avatar-placeholder {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.6rem;
    font-weight: 600;
    color: var(--mud-palette-text-secondary);
}


/* Centered avatar header used by the user dialog and the account profile page. */
.pf-user-hero {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 20px 0 4px;
}

.pf-user-hero .pf-avatar-preview {
    width: 112px;
    height: 112px;
    border-radius: 50%;
}

.pf-user-hero .pf-avatar-placeholder {
    font-size: 2.2rem;
}

/* ---------- Account management section ----------
   The section keeps one centred column so the sub-nav and the panel don't drift
   apart on a wide monitor, and every page inside the panel gets the same
   measure — no page brings its own card or its own width. */

.pf-manage {
    max-width: 1180px;
    margin-inline: auto;
}

.pf-manage-body {
    /* Comfortable reading/edit measure, centred inside the panel. */
    max-width: 720px;
    margin-inline: auto;
}

/* Panels a page draws inside the body (2FA, personal data) should span the measure. */
.pf-manage-body > .mud-paper {
    width: 100%;
}

/* ---------- Grid / list toolbars ----------
   As the toolbar runs out of width the flex items shrink; a squeezed button wraps its
   label onto a second line and grows tall ("enlarged"). Fix: buttons keep their natural
   size, the search/filter inputs give up width first, and the whole toolbar wraps onto
   another row when there genuinely isn't space.
   Scoped to .mud-table-toolbar so the app bar toolbar (.mud-toolbar-appbar) and the
   pager are untouched. */

.mud-table-toolbar {
    flex-wrap: wrap;
    row-gap: 8px;
}

/* MudBlazor pins the table toolbar to a fixed height, so once items wrap the extra
   row overflowed the box and sat on top of the grid header. Let it grow with its
   content, keep the normal single-row height as the floor, and add vertical padding
   so a wrapped row isn't flush against the edges. */
.mud-table-toolbar {
    height: auto;
    min-height: 56px;
    padding-top: 8px;
    padding-bottom: 8px;
}

.mud-table-toolbar .mud-button-root {
    white-space: nowrap;
    flex-shrink: 0;
}

.mud-table-toolbar .mud-input-control {
    min-width: 150px;
}

/* ---------- Tabs ----------
   A MudGrid opens with a negative gutter margin (-24px at the default spacing). As the
   first child of a tab panel that pulled the content up over the tab bar, which covered
   the bottom half of every tab and swallowed clicks there — only the label itself stayed
   hittable. Keep the bar above the panels, and stop the grid riding up in our dialogs. */

.mud-tabs-tabbar {
    z-index: 1;
}

.pf-dialog-tabs .mud-tab-panel > .mud-grid {
    margin-top: 0;
}

/* ---------- Show / hide password (statically rendered forms) ----------
   The toggle is the field's end adornment; js/ui.js flips the input type and swaps in
   the spare "eye off" icon rendered next to it. */

.pf-password-field .mud-input-adornment {
    cursor: pointer;
}

.pf-password-alt {
    display: none;
}

/* ---------- App-bar account menu ----------
   A <details> element rather than MudMenu: the account pages are statically rendered,
   where MudMenu never opens. js/ui.js only adds click-outside and Escape dismissal. */

.pf-user-menu {
    position: relative;
}

.pf-user-menu > summary {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border-radius: 50%;
    cursor: pointer;
    user-select: none;
    list-style: none;
}

.pf-user-menu > summary::-webkit-details-marker {
    display: none;
}

/* No hover tint — the avatar itself is the affordance. Keyboard focus still gets a ring. */
.pf-user-menu > summary:focus-visible {
    outline: 2px solid currentColor;
    outline-offset: 2px;
}

.pf-user-menu-panel {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 1300;
    min-width: 236px;
    max-width: calc(100vw - 24px);
    padding: 8px 0;
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
}

.pf-user-menu-head {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 16px 10px;
}

.pf-user-menu-name {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pf-user-menu-logout {
    padding: 4px 16px 6px;
}

.pf-user-menu-item {
    display: flex;
    align-items: center;
    /* Explicit start alignment: the item is a <button> on interactive pages and an <a> on
       static ones — without this the two rendered with different alignment. */
    justify-content: flex-start;
    gap: 12px;
    width: 100%;
    margin: 0;
    padding: 8px 16px;
    border: 0;
    box-sizing: border-box;
    background: none;
    font: inherit;
    font-size: 0.875rem;
    color: inherit;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.pf-user-menu-item:hover {
    background: var(--mud-palette-action-default-hover);
}

/* ---------- App bar on narrow screens ----------
   At 320px the bar's content measured 375px, pushing the theme toggle and account
   button off-screen. The wordmark alone is 143px of that, so drop it below sm and
   let the icon badge carry the branding. */

@media (max-width: 599.95px) {
    /* 12px on the left absorbs the drawer toggle's Edge.Start -12px margin, which would
       otherwise hang the button 4px off the left of the viewport. The right side is
       anchored by the spacer, so this costs nothing on the right. */
    .mud-appbar .mud-toolbar {
        padding-left: 12px;
        padding-right: 8px;
        /* MudBlazor pins its toolbar to 56px down here, which quietly cancelled a theme's app bar
           height on mobile: the bar came out 56 while .pf-main-pad still reserved the theme's own
           height plus the gap, and .pf-rail-brand (which already reads the -sm variable) stood 8 to
           16px taller than the bar it is supposed to line up with. The height is the theme's at
           every width now, and the toolbar is what carries it — the bar itself has no height of its
           own, it wraps this. */
        height: var(--pf-appbar-height-sm, 56px);
    }

    /* Only the logo badge — the landing bar's buttons also carry ml-3 and should keep it. */
    .mud-appbar .mud-toolbar > img.ml-3 {
        margin-left: 4px !important;
    }
}

/* ---------- Permissions manager (master list) ---------- */

.pf-perm-page {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 9px 16px;
    cursor: pointer;
    border-left: 3px solid transparent;
    transition: background .12s ease;
}

.pf-perm-page:hover {
    background: var(--mud-palette-action-default-hover);
}

.pf-perm-page-selected {
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
    border-left-color: var(--mud-palette-primary);
}

.pf-perm-page-selected .mud-icon-root,
.pf-perm-page-selected .pf-perm-page-title {
    color: var(--mud-palette-primary);
}

.pf-perm-page-title {
    flex-grow: 1;
    font-size: .9rem;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------- Dashboard stat cards ---------- */

.pf-stat-card {
    height: 100%;
    display: flex;
    flex-direction: column;
    gap: 2px;
    transition: box-shadow .15s ease, transform .15s ease;
}

.pf-stat-card:hover {
    transform: translateY(-2px);
    box-shadow: var(--mud-elevation-4);
}

.pf-stat-icon {
    width: 44px;
    height: 44px;
    border-radius: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    margin-bottom: 10px;

    /* The glyph is the tile's colour pulled 40% toward the theme's text colour; the tile keeps the
       hue undiluted at 12%. Mixing toward text-primary is what makes this safe in both modes without
       a light/dark hook in the CSS: text-primary is by definition readable on the surface, so the
       mix always moves *away* from the ground — darkening on white, lightening on near-black.

       Straight palette values are not enough on their own. MudBlazor's success (#00C853) and warning
       (#FF9800) are only 2.24:1 and 2.16:1 on a white card, under the 3:1 WCAG asks of a 24px
       graphical object; at 60% they reach 3.99 and 3.80, and on the dark card 9.8 and 11.5. */
    color: color-mix(in srgb, var(--pf-stat-tone) 60%, var(--mud-palette-text-primary));
    background: color-mix(in srgb, var(--pf-stat-tone) 12%, transparent);
}

/* One palette variable per tile. These were four hard-coded hexes inline on the markup, which meant
   they described the light palette and nothing else: the Invoices tile was the brand navy #101B2D,
   and PaletteDark.Surface is also #101B2D, so in dark mode it painted a navy icon on a navy card at
   1.00:1 and the tile looked empty. Palette variables flip with the mode and follow whatever theme
   is applied, which is the whole point of the theme system. */
.pf-stat-icon-primary { --pf-stat-tone: var(--mud-palette-primary); }
.pf-stat-icon-info    { --pf-stat-tone: var(--mud-palette-info); }
.pf-stat-icon-success { --pf-stat-tone: var(--mud-palette-success); }
.pf-stat-icon-warning { --pf-stat-tone: var(--mud-palette-warning); }

.pf-stat-label {
    color: var(--mud-palette-text-secondary);
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    font-weight: 600;
}

.pf-stat-value {
    font-size: 1.9rem;
    font-weight: 700;
    line-height: 1.15;
    font-variant-numeric: tabular-nums;
}

.pf-stat-value-sm {
    font-size: 1.4rem;
}

/* ---------- Invoice generator ---------- */

@media (min-width: 1280px) {
    /* Keep the preview/actions in view while scrolling the capture form. */
    .pf-invoice-preview-col {
        position: sticky;
        top: 88px;
    }
}

.pf-invoice-preview-frame {
    width: 100%;
    height: 72vh;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: #fff;
}

.pf-invoice-preview-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    height: 72vh;
    color: var(--mud-palette-text-secondary);
}

.pf-invoice-totals td {
    padding: 6px 4px;
}

.pf-invoice-totals .text-right {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.pf-invoice-total-row td {
    font-weight: 700;
    border-top: 2px solid var(--mud-palette-lines-default);
    font-size: 1.05rem;
}

/* ---------- Recovery codes ---------- */

.pf-recovery-codes {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
    margin-top: 8px;
}

@media (max-width: 599.95px) {
    .pf-recovery-codes {
        grid-template-columns: 1fr;
    }
}

.pf-recovery-codes code {
    display: block;
    text-align: center;
    font-family: "Roboto Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.95rem;
    letter-spacing: 0.05em;
    padding: 10px 8px;
    color: var(--mud-palette-text-primary);
    background: var(--mud-palette-background-gray);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 6px;
}

/* ---------- Chrome tones ----------
   The sidebar and the app bar are each toned independently of the light/dark theme (see
   Theme/ChromeColor.cs), so a light app can carry a dark sidebar and vice versa. MudBlazor's
   palette variables can't express that — only one palette is emitted at a time — so each skin
   declares its own set of --pf-chrome-* values and every piece of chrome is styled against those
   rather than against --mud-palette-*.

   The two skins are deliberately the *same* colours the matching theme uses, so "Light" in light
   mode and "Dark" in dark mode are indistinguishable from "Match theme" — the setting only shows
   itself in the mixed combinations. */

/* Each skin is one palette from Theme/AppTheme.cs, published by MainLayout as --pf-light-* and
   --pf-dark-* (AppTheme.ChromeVariables reads them straight off PaletteLight/PaletteDark). Nothing
   here is a hand-picked colour, so editing a palette — or a user choosing a custom brand colour —
   flows through both tones automatically, and pinning a tone is indistinguishable from the theme
   arriving at it: "Light" in dark mode renders the same pixels as "Match theme" in light mode.

   The one deliberate departure is the app bar's background: MudBlazor's appbar-background is navy
   in both palettes, but this chrome pairs the bar with the sidebar, so both use the drawer colour.
   That departure is identical for a pinned tone and a followed one, so the rule above still holds. */
.pf-chrome-light {
    --pf-chrome-bg: var(--pf-light-bg);
    --pf-chrome-text: var(--pf-light-text);
    --pf-chrome-text-secondary: var(--pf-light-text-secondary);
    --pf-chrome-text-muted: var(--pf-light-icon);
    --pf-chrome-hover: var(--pf-light-hover);
    --pf-chrome-line: var(--pf-light-line);
    --pf-chrome-divider: var(--pf-light-divider);
    --pf-chrome-accent: var(--pf-light-accent);
    --pf-chrome-on-accent: var(--pf-light-on-accent);
    --pf-chrome-active-text: var(--pf-chrome-accent);
    --pf-chrome-active-icon: var(--pf-chrome-accent);
}

.pf-chrome-dark {
    --pf-chrome-bg: var(--pf-dark-bg);
    --pf-chrome-text: var(--pf-dark-text);
    --pf-chrome-text-secondary: var(--pf-dark-text-secondary);
    --pf-chrome-text-muted: var(--pf-dark-icon);
    --pf-chrome-hover: var(--pf-dark-hover);
    --pf-chrome-line: var(--pf-dark-line);
    --pf-chrome-divider: var(--pf-dark-divider);
    --pf-chrome-accent: var(--pf-dark-accent);
    --pf-chrome-on-accent: var(--pf-dark-on-accent);
    --pf-chrome-active-text: var(--pf-chrome-accent);
    --pf-chrome-active-icon: var(--pf-chrome-accent);
}

/* Point MudBlazor's *own* chrome variables at the skin as well. Some of its rules are more
   specific than anything sensible we can write — nav-link icons are coloured by
   `.mud-nav-link:not(.mud-nav-link-disabled) .mud-nav-link-icon.mud-nav-link-icon-default`,
   four classes deep — so overriding the variable is the only fix that holds: custom properties
   inherit, so MudBlazor's rules resolve to the tone rather than to the theme. Without this the
   icons follow light/dark mode instead of the sidebar, and vanish on a light sidebar in dark mode.

   Only the drawer/appbar variables, deliberately: they are read by chrome rules alone. Redefining
   the general text/surface ones here would also repaint the account dropdown, which is a popover
   on the theme's own surface and must keep the theme's colours. */
.pf-chrome-light,
.pf-chrome-dark {
    --mud-palette-drawer-background: var(--pf-chrome-bg);
    --mud-palette-drawer-text: var(--pf-chrome-text-secondary);
    --mud-palette-drawer-icon: var(--pf-chrome-text-muted);
    --mud-palette-appbar-background: var(--pf-chrome-bg);
    --mud-palette-appbar-text: var(--pf-chrome-text);
}

/* The selected nav item is painted by `.mud-nav-link.active:not(.mud-nav-link-disabled)` out of
   `--mud-palette-primary` and `--mud-palette-action-default-hover` — both theme values, and both
   out of reach of any sane selector of ours. Retargeting them at the tone is what makes a pinned
   sidebar identical to a followed one; the drawer is a safe scope for it because everything inside
   is chrome. The app bar is *not*: the account dropdown renders inside it but is a panel on the
   theme's own surface, so there the hover tints stay per-element (see above). */
.app-drawer.pf-chrome-light,
.app-drawer.pf-chrome-dark {
    --mud-palette-primary: var(--pf-chrome-accent);
    --mud-palette-action-default-hover: var(--pf-chrome-hover);
}

/* Two classes deep so this beats MudBlazor's own .mud-appbar / .mud-drawer palette rules
   without reaching for !important. */
.mud-appbar.pf-chrome-light,
.mud-appbar.pf-chrome-dark,
.mud-drawer.pf-chrome-light,
.mud-drawer.pf-chrome-dark {
    background: var(--pf-chrome-bg);
    color: var(--pf-chrome-text);
}

/* A docked drawer is a panel beside the page, not a layer above it: it gets the same hairline the
   app bar draws on its own edges (all drawers render at Elevation 0), so the chrome reads as one
   flat surface. A box-shadow rather than border-right because it costs no layout width — a border
   would eat 1px of the 64px rail and knock every icon half a pixel off centre. */
.app-drawer {
    box-shadow: 1px 0 0 var(--pf-chrome-line);
}

/* The bar's own edge against the page. A dark bar over light content separates by colour alone,
   but the line costs nothing there and is the only separation a light bar gets. */
.mud-appbar.pf-chrome-light,
.mud-appbar.pf-chrome-dark {
    border-bottom: 1px solid var(--pf-chrome-line);
}

/* Every hover and focus tint inside the chrome. MudBlazor reaches for
   `--mud-palette-action-default-hover`, which is a *theme* value: on a dark bar in light mode it
   paints a dark wash over a dark bar, and on a light bar in dark mode a light wash over white —
   invisible either way. Spelled out per element rather than by overriding the variable, because
   the account dropdown lives inside the app bar but is a panel on the theme's own surface and
   must keep the theme's tints. */
.mud-appbar.pf-chrome-light .pf-user-menu > summary:hover,
.mud-appbar.pf-chrome-dark .pf-user-menu > summary:hover,
.mud-appbar.pf-chrome-light .mud-icon-button:hover,
.mud-appbar.pf-chrome-dark .mud-icon-button:hover,
.app-drawer .mud-nav-link:focus-visible,
.pf-nav-search:focus-visible {
    background: var(--pf-chrome-hover);
}

/* The initials avatar is filled with the primary colour, and the primary belongs to whichever
   palette the surface is wearing — a light-theme primary on a dark bar goes muddy.
   `> summary` on purpose: that is the trigger sitting *on* the bar. The panel below it is a
   dropdown on the theme's own surface — it must keep the theme's colours even when the bar
   disagrees, so the same avatar is deliberately a different blue in the two places. */
.pf-user-menu > summary .mud-avatar-filled-primary {
    background-color: var(--pf-chrome-accent);
    color: var(--pf-chrome-on-accent);
}

/* The Ctrl+K chip is a miniature surface, and a theme-coloured one lands as a dark tile on a light
   sidebar (or the reverse). Outline only — it belongs to the chrome it sits on. */
.app-drawer .pf-kbd,
.mud-appbar .pf-kbd {
    background: transparent;
    border-color: var(--pf-chrome-line);
    color: var(--pf-chrome-text-muted);
}

/* ---------- Sidebar polish (all drawer layouts) ---------- */

/* The first label sits directly under the search row, so it needs far less lead-in. */
.nav-section-label-first {
    padding-top: 8px !important;
}

.nav-section-label {
    padding: 18px 16px 4px;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--pf-chrome-text-muted);
}

/* Symmetric on purpose: 8px/12px left the version text 11px below the rule and 16px above the
   drawer's edge, which reads as misaligned. Same total height, centred. */
.nav-footer {
    padding: 10px 16px;
    text-align: center;
}

/* Sub-heading inside a module nav group (e.g. "Audience" under Marketing Mailer) */
.nav-subsection-label {
    padding: 12px 16px 2px;
    font-size: .64rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--pf-chrome-text-muted);
}

/* MudText's own .mud-text-secondary would pull this back to the theme's palette, which is the
   wrong tone whenever the sidebar disagrees with the theme. */
.app-drawer .nav-footer .mud-text-secondary {
    color: var(--pf-chrome-text-muted);
}

/* ---------- Global search (sidebar activator + Ctrl+K) ---------- */

/* Full-bleed like a nav link: same 16px gutter, no rounded box. */
.pf-nav-search {
    display: flex;
    align-items: center;
    gap: 0;
    width: 100%;
    padding: 10px 16px;
    border: 0;
    border-top: 1px solid var(--pf-chrome-divider);
    border-bottom: 1px solid var(--pf-chrome-divider);
    background: transparent;
    color: var(--pf-chrome-text-secondary);
    font: inherit;
    font-size: .8125rem;
    cursor: pointer;
    transition: background .15s ease, color .15s ease;
}

.pf-nav-search:hover {
    background: var(--pf-chrome-hover);
    color: var(--pf-chrome-text);
}

.pf-nav-search-text {
    flex: 1;
    text-align: left;
}

/* ---------- The search field (mini rail's app bar, top nav's app bar, classic's sidebar) ----------
   One look everywhere: a bordered box with the label reading as a placeholder and the Ctrl K chip at
   the trailing edge. Only the box model changes between the three (see .pf-sidebar-search below). */

/* MudTooltip's wrapper is inline-block, so a width:100% field inside it shrink-wraps to the
   label. Give the wrapper the sizing and let the field fill it. (Selected by element: MudTooltip's
   Class parameter lands on the popover bubble, not on this wrapper.) */
.mud-appbar .mud-tooltip-root:has(> .pf-search-field) {
    display: block;
    /* A fixed share, shrinking when the bar is tight but never *growing*: the two are flex children
       of the same bar, so anything the field claims is a nav entry the top-nav layout has to push
       into its More menu. 240px reads comfortably and leaves the navigation the rest. */
    flex: 0 1 240px;
    /* The gap either side belongs out here on the flex item, not on the field inside it: a
       width:100% field carrying its own margin measures 8px wider than this wrapper, and that
       overhang lands on top of the next control - covering the left edge of the theme button and
       the hover circle it draws there. */
    margin: 0 8px;
}

.pf-search-field {
    width: 100%;
    /* The height the magnifier used to give it (20px line + padding + border), held so the field
       stays the same size whether or not the chip is showing, and matches the controls beside it. */
    min-height: 38px;
    padding: 8px 12px;
    border: 1px solid var(--pf-chrome-line);
    border-radius: var(--mud-default-borderradius);
    background: transparent;
    /* The Ctrl K chip asks the field how wide it is (below). Safe to contain: the field's width
       always comes from outside - 100% of the tooltip wrapper, or the sidebar's width - never
       from its label. */
    container: pf-search / inline-size;
}

.pf-search-field:hover {
    background: var(--pf-chrome-hover);
}

/* The shortcut chip sits at the trailing edge (the label's flex:1 pushes it there), and goes
   wherever it would crowd the label: a field squeezed by a tight top-nav bar (the tooltip still
   says Ctrl+K there), and touch screens, which have no Ctrl key to press. */
.pf-search-field .pf-nav-search-kbd {
    margin-left: 8px;
    /* Keeps the chip (19px with its borders) inside the field's 20px line, so showing it does not
       make the field taller than min-height. */
    line-height: 14px;
}

/* Measured on the field's content box (inside its padding). Label and chip need about 103px, so
   this still shows the chip in the 180px field the phone-width app bar gives it. */
@container pf-search (max-width: 109.95px) {
    .pf-search-field .pf-nav-search-kbd {
        display: none;
    }
}

@media (pointer: coarse) {
    .pf-search-field .pf-nav-search-kbd {
        display: none;
    }
}

@media (max-width: 599.95px) {
    .mud-appbar .mud-tooltip-root:has(> .pf-search-field) {
        flex-basis: 180px;
    }
}

/* The same field boxed into the classic sidebar rather than sitting in the bar: inset from all
   four sides so it reads as a control on the panel, not another nav row. Only the box model
   differs, so it rides on .pf-search-field above and must stay after it in this file. */
.pf-sidebar-search {
    width: auto;
    margin: 12px;
}

.pf-kbd {
    padding: 1px 6px;
    border: 1px solid var(--mud-palette-lines-default);
    border-bottom-width: 2px;
    border-radius: 4px;
    background: var(--mud-palette-surface);
    font-family: inherit;
    font-size: .6875rem;
    color: var(--mud-palette-text-secondary);
    white-space: nowrap;
}

/* ---------- Nav links, in whichever tone the sidebar is wearing ----------
   Full-width (stock MudBlazor shape) with a clear active state. `button.mud-nav-link` is the
   nav-group header, which MudBlazor renders as a <button> and colours separately. */

.app-drawer .mud-nav-link,
.app-drawer button.mud-nav-link {
    color: var(--pf-chrome-text-secondary);
}

.app-drawer .mud-nav-link .mud-icon-root {
    color: var(--pf-chrome-text-muted);
}

.app-drawer .mud-nav-link:hover {
    background: var(--pf-chrome-hover);
    color: var(--pf-chrome-text);
}

/* Background deliberately absent: MudBlazor's own active rule is more specific and wins, and now
   that it reads the tone's variables (above) its tint is the right one. Setting it here as well
   would be dead CSS that looks like it works. */
.app-drawer .mud-nav-link.active {
    color: var(--pf-chrome-active-text);
    font-weight: 600;
}

.app-drawer .mud-nav-link.active .mud-icon-root {
    color: var(--pf-chrome-active-icon);
}

.app-drawer .mud-divider {
    border-color: var(--pf-chrome-divider);
}

/* ---------- Mini rail variant ---------- */

/* The rail's two widths, as literals rather than MudBlazor's --mud-drawer-width-* variables.
   Those variables are written by MudDrawer from the width it *measures*, so driving the width
   from them creates a feedback loop: one render at 0px and the variable sticks at 0 forever. */
:root {
    --pf-rail-collapsed-width: 64px;
    --pf-rail-expanded-width: 240px;
}

/* The rail carries the brand, so its header lines up with the app bar beside it — same height
   at both breakpoints (64px, 56px below sm), so the two read as one strip. No rule under it:
   the brand and the nav below belong to the same panel. */
.pf-rail-brand {
    display: flex;
    flex: none;
    align-items: center;
    gap: 10px;
    /* Follows the app bar beside it, whatever height a theme has chosen. */
    height: var(--pf-appbar-height, 64px);
    padding: 0 16px;
}

.pf-rail-brand-badge {
    height: 32px;
}

.pf-rail-brand-name {
    /* Full-strength against the sidebar's own tone: the drawer's inherited colour is the
       secondary one, which greys the wordmark out. */
    color: var(--pf-chrome-text);
    font-size: 1.5rem;
    font-weight: 400;
    line-height: 1.334;
    white-space: nowrap;
    overflow: hidden;
}

@media (max-width: 599.95px) {
    .pf-rail-brand {
        /* MudBlazor's bar drops to 56px down here; a theme that has chosen a height keeps it at
           every width, so --pf-appbar-height-sm carries that same value rather than 56. */
        height: var(--pf-appbar-height-sm, 56px);
    }
}

/* Collapsed: the wordmark goes and the badge centres in the rail. */
.pf-rail-collapsed .pf-rail-brand {
    padding: 0;
    justify-content: center;
}

.pf-rail-collapsed .pf-rail-brand-name {
    display: none;
}

/* The collapsed width is owned here, not left to MudBlazor: MudDrawer's internal open/closed
   state can disagree with the Open parameter we pass (it paints open on first render whatever
   we hand it), which left the rail 240px wide while the C#-driven tooltips already treated it
   as collapsed. .pf-rail-collapsed comes off the same flag as those tooltips (MainLayout
   DrawerClass), so width, labels and tooltips cannot drift apart. */
@media (min-width: 960px) {
    .app-drawer.pf-rail-collapsed {
        width: var(--pf-rail-collapsed-width, 64px) !important;
    }
}

/* The rail owns the top-left corner in this layout, so the app bar starts where the rail
   ends instead of spanning the full width — expanded by default, narrow once collapsed. */
.pf-appbar-inset {
    left: var(--pf-rail-expanded-width, 240px) !important;
    width: auto !important;
    transition: left 225ms cubic-bezier(0, 0, .2, 1);
    /* The bar sits above the drawer (z-index 1300 vs 1100) and its opaque background covers
       whatever the rail draws on its own right edge for the height of the bar. So the bar draws
       that top segment itself; the rail draws the rest (see .mud-drawer-mini.app-drawer below).
       One colour, one width, one continuous line from the top of the brand to the bottom. */
    border-left: 1px solid var(--pf-chrome-line);
}

body:has(.pf-rail-collapsed) .pf-appbar-inset {
    left: var(--pf-rail-collapsed-width, 64px) !important;
}

/* MudLayout indents the content by its own --mud-drawer-width-mini-left, which is not the width
   this layout gives the rail — the page slid under it. Same source of truth as the bar above. */
@media (min-width: 960px) {
    body:has(.pf-rail-collapsed) .mud-main-content {
        margin-left: var(--pf-rail-collapsed-width, 64px) !important;
    }
}

/* Below md the rail is hidden entirely (see the media query further down), so the bar
   reclaims the full width and the app bar's own menu button takes over. */
@media (max-width: 959.95px) {
    .pf-appbar-inset,
    body:has(.pf-rail-collapsed) .pf-appbar-inset {
        left: 0 !important;
        /* Nothing to the left of the bar down here — the divider would be a stray line at the
           window edge. */
        border-left: 0;
    }
}

/* MudBlazor sizes .mud-drawer-content from its own --mud-drawer-width-* variables, which know
   nothing about the widths this layout sets — the content came out 54px inside a 64px rail, so
   every row sat left of centre. Let it fill whatever width the rail actually has. */
.mud-drawer-mini.app-drawer .mud-drawer-content {
    width: 100% !important;
    box-sizing: border-box;
}

/* Only the nav list scrolls. With the drawer scrolling as a whole, its rule and version footer
   sat wherever the list happened to end — drifting up and down as groups were expanded — and in
   the rail the scrollbar also ate 10px of the width and carried the brand out of view. Pinning
   the scroll to the list keeps the header and the footer where they belong. */
.app-drawer .mud-drawer-content {
    overflow: hidden;
}

.app-drawer .mud-navmenu {
    overflow-y: auto;
    /* A flex child will not shrink below its content without this, so the list would push the
       footer off the bottom instead of scrolling. */
    min-height: 0;
    scrollbar-width: thin;
}

/* **A nested nav menu must never be a scroll container.**
   MudNavGroup renders each group's children inside their own MudNavMenu, so the rule above — which
   is about the *outer* list — also handed every expanded group an `overflow-y: auto` box with zero
   room to scroll. A wheel over a group's children then latched onto that inner box and stopped
   dead: it could not scroll itself and Chrome does not chain out of a scroll container. The result
   was a sidebar that scrolled over Dashboard and Customers but not over anything inside an open
   group, which reads as scrolling that is stuck, dead, or intermittent depending on where the
   pointer happens to be. Sidebar descriptions turned it from occasional into constant, because
   taller rows mean an open group fills most of the visible list.
   Verified on the live page: same wheel, same spot, 0px before this rule and 300px after. */
.app-drawer .mud-navmenu .mud-navmenu {
    overflow: visible;
}

/* MudBlazor gives .mud-divider flex-grow: 1. In this column that made it swallow the leftover
   space and draw its line at the *top* of it — so the rule sat directly under the last nav item
   and slid up and down as groups were expanded, instead of sitting above the version footer. */
.app-drawer .mud-divider {
    flex: none;
}

/* No room for even a thin scrollbar beside 48px icon pills — the wheel still scrolls. */
.app-drawer.pf-rail-collapsed .mud-navmenu {
    scrollbar-width: none;
}

.app-drawer.pf-rail-collapsed .mud-navmenu::-webkit-scrollbar {
    display: none;
}

/* The rail runs the full height of the window. MudBlazor's Docked clip mode still pushes the
   drawer below the app bar, which left an empty app-bar-height gap above the rail now that the
   bar starts beside it — so take the top back explicitly. Only the mini variant is affected;
   the other layouts keep their clipped-below-the-bar drawers. */
.mud-drawer-mini.app-drawer {
    top: 0 !important;
    height: 100% !important;
}

/* MudTooltip wraps its child in .mud-tooltip-root, which is inline-block by default. Inside the
   drawer that collapses every wrapped nav link and nav group to its content width, so items
   stopped filling the rail and groups clipped. Nav items are block-level, tooltip or not. */
.app-drawer .mud-tooltip-root {
    display: block;
    width: 100%;
}

/* ---------- Keeping the collapse/expand animation honest ----------
   Only the rail's width should move. MudNavLink transitions `padding`, and the collapsed rail
   zeroes it: expanding therefore slid every icon left and then pushed it back as the padding
   grew — worst inside an expanded group, whose children carry the largest indent, which is why
   the Administration menu wobbled. Row metrics now snap while the rail wipes open. */
.app-drawer .mud-nav-link {
    transition: background-color .25s cubic-bezier(.4, 0, .2, 1);
}

/* Labels are revealed by the widening rail rather than reflowed by it: without this they wrap
   at narrow widths and every row changes height mid-animation. */
.app-drawer .mud-nav-link-text,
.app-drawer .nav-section-label,
.app-drawer .nav-subsection-label {
    white-space: nowrap;
}

/* Nothing in a drawer scrolls sideways; without this the wipe flashes a horizontal scrollbar.
   The nested-menu rule above is more specific and resets both axes, so a group's own menu stays
   `visible` here too — overflow-x alone would make it a scroll container again, which is the same
   trap in the other direction. */
.app-drawer,
.app-drawer .mud-navmenu {
    overflow-x: hidden;
}

/* Collapsed rail = icon-only. Expansion is driven by the app-bar menu button beside the rail
   — no hover expansion. MudBlazor's own text-hiding rule requires
   .mud-drawer-content > .mud-navmenu, which our flex wrapper breaks, so hide
   .mud-nav-link-text ourselves. */
.pf-rail-collapsed .nav-section-label,
.pf-rail-collapsed .nav-subsection-label,
.pf-rail-collapsed .nav-footer,
.pf-rail-collapsed .mud-nav-link-text,
.pf-rail-collapsed .pf-nav-group-title,
/* The drawer's only divider belongs to the version footer above; with that hidden it would be a
   stray hairline across the bottom of the rail. */
.pf-rail-collapsed .mud-divider {
    display: none;
}

/* Nav-group headers and their children are laid out relative to the group, not the menu, so
   they came out full-rail-width inside the rail and overflowed past its right edge by the 4px
   margin. Pin the width so every row — top level, group header, group child — is the same
   centred pill. */
.pf-rail-collapsed .mud-nav-link {
    width: calc(100% - 8px);
    box-sizing: border-box;
    margin: 2px 4px;
    /* !important earns its place here: MudBlazor indents the children of a nav group with
       `.mud-nav-group * .mud-navmenu .mud-nav-item .mud-nav-link` — five classes — so a group's
       items kept a 36px indent inside a 64px rail and their icons sat half off the pill. Matching
       that selector would mean hard-coding MudBlazor's DOM shape into ours; this does not.
       (Toggling the rail appeared to fix it only because re-rendering swapped the tooltip wrapper
       in and out of that selector's path.) */
    padding-left: 0 !important;
    padding-right: 0 !important;
    justify-content: center;
}

.pf-rail-collapsed .mud-nav-link .mud-icon-root {
    margin-right: 0;
}

/* ---------- Nav groups in the collapsed rail ----------
   Groups still expand in place here (clicking the icon opens the group, not the rail), so the
   rail needs to say "this is a group" and to set an expanded group's children apart from the
   top-level items they'd otherwise be indistinguishable from. */

/* The header is the tooltip's positioning context, and the anchor for the chevron below. */
.pf-rail-collapsed .mud-nav-group > .mud-nav-link {
    position: relative;
}

/* The group's tooltip has to survive the label being hidden. MudNavGroup renders TitleContent
   inside .mud-nav-link-text — the very element the rule above hides — so for group headers that
   container stays displayed and becomes an invisible overlay across the whole header, with only
   the label text inside it hidden. Without this the hover target is 0×0 and the group has no
   tooltip at all. (Targeted by element rather than a class on MudTooltip: its Class parameter
   lands on the popover bubble, not on this wrapper.) */
.pf-rail-collapsed .mud-nav-group > .mud-nav-link > .mud-nav-link-text {
    display: block;
    position: absolute;
    inset: 0;
}

.pf-rail-collapsed .mud-nav-group > .mud-nav-link .mud-tooltip-root {
    position: absolute;
    inset: 0;
    width: auto;
}

/* The chevron is the only cue that an icon is a group, so it stays — tucked into the corner of
   the 48px pill at half size instead of sitting in the row, where it shoved the icon
   off-centre and hung over the edge of the rail. */
.pf-rail-collapsed .mud-nav-link-expand-icon {
    position: absolute;
    right: 1px;
    bottom: 1px;
    margin: 0;
    width: 14px;
    height: 14px;
    font-size: 14px;
    opacity: .65;
}

/* An expanded group's children read as one block rather than more top-level icons. */
.pf-rail-collapsed .mud-nav-group > .mud-collapse-container {
    background: color-mix(in srgb, var(--pf-chrome-text) 5%, transparent);
}


/* Narrow screens have no room for a rail: hide it entirely when collapsed (or on first
   render) and show a full overlay drawer via the menu button instead. Applies regardless of
   touch capability now that expand/collapse is an explicit button, not hover. */
@media (max-width: 959.95px) {
    .app-drawer.pf-rail-collapsed {
        width: 0 !important;
        min-width: 0 !important;
        visibility: hidden;
    }

    /* MudLayout reserves a left margin for the (now hidden) rail — reclaim it,
       and don't shift content when the overlay drawer opens either. */
    .mud-layout[class*="mini"] .mud-main-content,
    .mud-layout[class*="mini"] .mud-appbar {
        margin-left: 0 !important;
        width: 100% !important;
    }

    /* Expanded on a phone = a full overlay drawer, labels and all. */
    .app-drawer.mud-drawer-mini:not(.pf-rail-collapsed) {
        width: 240px !important;
        visibility: visible;
    }

    /* Down here every drawer floats *over* the page instead of docking beside it, and MudBlazor's
       own overlay is fully transparent — so an open drawer takes its shadow back. A 1px hairline
       is no separation at all against content scrolling underneath it. */
    .app-drawer.mud-drawer--open:not(.pf-rail-collapsed) {
        box-shadow: 0 5px 5px -3px rgba(0, 0, 0, .2), 0 8px 10px 1px rgba(0, 0, 0, .14), 0 3px 14px 2px rgba(0, 0, 0, .12);
    }
}

/* ---------- Top navigation variant ---------- */

/* Takes the bar's leftover space instead of pushing the search field and account menu off the end.
   js/theme.js measures scrollWidth against clientWidth here to decide what moves into the More
   menu; scrollWidth reports the overflow whether or not it is clipped, so the clipping below is
   only cosmetic — it hides any spill in the moment before the first measurement lands.

   :not(:has(details[open])) is load-bearing: an open dropdown is positioned inside this element,
   so clipping unconditionally would cut every menu off at the edge of the bar. */
.topnav {
    flex: 1 1 auto;
    min-width: 0;
    gap: 2px;
}

.topnav:not(:has(details[open])) {
    overflow: hidden;
}

/* MudButton sets display: inline-flex, which beats the UA stylesheet's [hidden] { display: none }.
   Without this the "hidden" entries stay in the bar and nothing ever fits. */
.topnav [hidden] {
    display: none !important;
}

/* Entries keep their natural width. As flex children they would otherwise shrink and wrap their
   labels ("Dashboa…", "Custome…") instead of overflowing — and with nothing overflowing, the
   fitting above sees a bar that "fits" and never moves anything into the More menu. */
.topnav > *,
.topnav .mud-button {
    flex: none;
    white-space: nowrap;
}

.topnav .mud-button {
    text-transform: none;
    opacity: .85;
}

.topnav .mud-button:hover {
    opacity: 1;
}

/* CSS-only dropdown (native <details>) so it works on statically rendered pages */
.topnav-dropdown {
    position: relative;
}

.topnav-dropdown summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 6px 12px;
    border-radius: 6px;
    font-size: .875rem;
    font-weight: 500;
    opacity: .85;
}

.topnav-dropdown summary::-webkit-details-marker {
    display: none;
}

.topnav-dropdown summary:hover {
    opacity: 1;
    /* Was a hard-coded white wash, invisible on a light app bar (see the chrome tones above). */
    background: var(--pf-chrome-hover);
}

.topnav-dropdown[open] > .topnav-dropdown-panel {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    min-width: 200px;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    border-radius: 8px;
    box-shadow: var(--mud-elevation-6);
    padding: 6px;
    z-index: 1400;
}

.topnav-dropdown-item {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 12px;
    border-radius: 6px;
    color: var(--mud-palette-text-primary);
    text-decoration: none;
    font-size: .875rem;
}

.topnav-dropdown-item:hover {
    background: color-mix(in srgb, var(--mud-palette-primary) 10%, transparent);
}

.topnav-dropdown-divider {
    border-top: 1px solid var(--mud-palette-lines-default);
    margin: 6px 4px;
}

/* A module with sub-sections lays them out side by side, so a long menu reads as a few short
   lists instead of one column running down the page. Wraps rather than overflowing the window
   when a module has many groups or the screen is narrow. */
.topnav-dropdown-columns {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: 4px 24px;
    /* The panel is absolutely positioned, so its width is shrink-to-fit — and the shrink-to-fit
       size of a *wrapping* flex container is one column wide, which stacked the groups on top of
       each other. max-content asks for the row instead; the clamp is what actually wraps them,
       when the window is too narrow to lay them out. */
    width: max-content;
    max-width: calc(100vw - 32px);
}

.topnav-dropdown-column {
    min-width: 180px;
}

/* Set by js/theme.js when the panel would run off the right of the window. */
.topnav-dropdown[open] > .topnav-dropdown-panel.topnav-dropdown-flip {
    left: auto;
    right: 0;
}

.topnav-dropdown-heading {
    padding: 8px 12px 4px;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

/* Second level, used only in the stacked lists (phone menu, More menu) where a module's own
   groups have to sit under the module name rather than replace it. */
.topnav-dropdown-subheading {
    padding: 6px 12px 2px;
    font-size: .72rem;
    font-weight: 600;
    color: var(--mud-palette-text-disabled);
}

/* The More menu can hold every module, so cap it and let it scroll. */
.topnav-more > .topnav-dropdown-panel {
    max-height: 70vh;
    overflow-y: auto;
}

/* A group heading is only worth showing when the group is; the divider above the first visible
   one would otherwise float at the top of the menu. */
.topnav-more-group[hidden] {
    display: none;
}

.topnav-more-group:first-child > .topnav-dropdown-divider {
    display: none;
}

/* Mobile menu trigger (TopNav layout) */
.topnav-mobile summary {
    padding: 8px;
    border-radius: 50%;
    color: inherit;
}

.topnav-mobile[open] > .topnav-dropdown-panel {
    max-height: 70vh;
    overflow-y: auto;
}

/* ---------- Appearance settings panel (right slide-out) ---------- */

.pf-settings {
    padding: 16px 20px 32px;
}

.pf-settings-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 2px;
}

.pf-settings-label {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
    font-size: .8125rem;
    font-weight: 600;
    color: var(--mud-palette-text-secondary);
}

/* Mode: three equal segmented buttons */
.pf-mode-row {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 8px;
}

.pf-mode-btn {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 4px;
    padding: 10px 4px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    background: transparent;
    color: var(--mud-palette-text-primary);
    font: inherit;
    font-size: .75rem;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}

.pf-mode-btn:hover {
    background: var(--mud-palette-action-default-hover);
}

.pf-mode-active {
    border-color: var(--mud-palette-primary);
    background: color-mix(in srgb, var(--mud-palette-primary) 12%, transparent);
    color: var(--mud-palette-primary);
}

/* Primary colour swatches */
.pf-swatch-row {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.pf-swatch {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 34px;
    height: 34px;
    padding: 0;
    border: 2px solid transparent;
    border-radius: 8px;
    color: #fff;
    cursor: pointer;
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .1);
}

.pf-swatch-active {
    border-color: var(--mud-palette-text-primary);
}

/* Self-contained right slide-out panel + scrim (sits above the app bar). */
.pf-settings-panel {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1401;
    width: 330px;
    max-width: 100vw;
    height: 100%;
    overflow-y: auto;
    background: var(--mud-palette-surface);
    box-shadow: -8px 0 24px rgba(0, 0, 0, .18);
    transition: transform .25s ease;
    /* Open/closed transform is bound inline in MainLayout to avoid any cascade ambiguity. */
}

.pf-settings-scrim {
    position: fixed;
    inset: 0;
    z-index: 1400;
    background: rgba(0, 0, 0, .32);
    transition: opacity .2s ease, visibility .2s ease;
}

/* ---------- Widget dashboard ----------
   One wrapping row of cells, each a whole number of twelfths wide. The same classes size the cells
   in view mode and inside MudDropZone while editing, so what you arrange is exactly what you get.
   Width with a gap: (w/12) of the row plus its share of the gaps, minus one gap. */

.pf-dash-grid {
    display: flex;
    flex-wrap: wrap;
    align-items: stretch;
    gap: 24px;
    --pf-dash-gap: 24px;
}

.pf-dash-cell {
    display: flex;
    flex-direction: column;
    width: 100%;
    min-width: 0;
    /* Lets a widget respond to its own width rather than the window's: a quarter-width tile is
       narrow on a laptop and roomy on a wide screen, and only the cell knows which. */
    container-type: inline-size;
}

/* A narrow tile drops its icon so the figure and label keep the room, and a narrow edit bar drops
   the title (the widget underneath says what it is) so its controls do not squeeze it to "C…". */
@container (max-width: 220px) {
    .pf-dash-tile .pf-dash-tile-icon,
    .pf-dash-edit-title {
        display: none;
    }
}

/* The widget fills its cell, so tiles in a row — and the two panels under them — are one height. */
.pf-dash-cell > *:not(.pf-dash-edit-bar) {
    flex: 1 1 auto;
}

/* Tablet: tiles (a quarter or a third) go two to a row; anything wider takes the row. Pairing the
   5/12 engagement chart as well left it alone at half width with an empty half beside it, because
   the 7/12 table after it is full width here. */
@media (min-width: 600px) {
    .pf-dash-w-3, .pf-dash-w-4 {
        width: calc(50% - var(--pf-dash-gap) / 2);
    }
}

@media (min-width: 960px) {
    .pf-dash-w-3 { width: calc(3 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-4 { width: calc(4 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-5 { width: calc(5 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-6 { width: calc(6 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-7 { width: calc(7 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-8 { width: calc(8 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-9 { width: calc(9 / 12 * (100% + var(--pf-dash-gap)) - var(--pf-dash-gap)); }
    .pf-dash-w-12 { width: 100%; }
}

.pf-dash-tile,
.pf-dash-panel {
    height: 100%;
}

.pf-dash-tile-link {
    display: block;
    height: 100%;
    color: inherit;
    text-decoration: none;
    border-radius: var(--mud-default-borderradius);
}

.pf-dash-tile-link:hover .pf-dash-tile {
    background: color-mix(in srgb, var(--mud-palette-primary) 4%, var(--mud-palette-surface));
}

.pf-dash-tile-link:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.pf-dash-tile-icon {
    flex-shrink: 0;
    color: var(--mud-palette-text-secondary);
}

.pf-dash-ellipsis {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The name column takes what is left and truncates, so a long campaign name never pushes the
   status and date off the card. */
.pf-dash-table td.pf-dash-ellipsis {
    max-width: 0;
    width: 50%;
}

/* Editing: each widget gets a dashed frame and a bar of controls on top. MudDropZone puts the item
   classes on its own draggable wrapper, which is what carries pf-dash-cell. */
.pf-dash-cell-editing {
    cursor: grab;
}

.pf-dash-cell-editing:active {
    cursor: grabbing;
}

/* MudDropZone (AllowReorder) adds an invisible 20px strip across the top of the zone — absolutely
   positioned, full width, z-index 1 — as a "drop before the first item" target. In this grid it lay
   exactly over the first row's control bars, so their arrows, width menus and remove buttons only
   answered a click below it: the controls felt dead or intermittent. Dropping on the first card
   already puts a widget first, so the strip is not needed as a target at all. */
.pf-dash-grid > .mud-drop-item-preview-start {
    pointer-events: none;
}

/* The placeholder MudBlazor shows where a dragged card would land. As shipped it is a padded box
   in the flow, and in a wrapping grid it pushed cards along — under the pointer, so the drop target
   moved and the drag jittered. Here it is a thin line that takes no room: 4px wide, with negative
   margins cancelling its own width plus the gap it adds, so nothing moves while you drag. */
.pf-dash-grid > .mud-dropitem-placeholder:not(.d-none) {
    flex: 0 0 4px;
    width: 4px;
    min-height: 96px;
    margin: 0 -14px;
    padding: 0 !important;
    border: none !important;
    border-radius: 2px;
    background: var(--mud-palette-primary);
    align-self: stretch;
}

.pf-dash-dragging {
    opacity: .45;
}

.pf-dash-edit-bar {
    display: flex;
    align-items: center;
    gap: 2px;
    padding: 2px 4px 2px 8px;
    border: 1px dashed var(--mud-palette-primary);
    border-bottom: none;
    border-radius: var(--mud-default-borderradius) var(--mud-default-borderradius) 0 0;
    background: color-mix(in srgb, var(--mud-palette-primary) 7%, var(--mud-palette-surface));
}

.pf-dash-drag {
    color: var(--mud-palette-text-secondary);
}

.pf-dash-edit-preview {
    display: flex;
    flex-direction: column;
    padding: 8px;
    border: 1px dashed var(--mud-palette-primary);
    border-radius: 0 0 var(--mud-default-borderradius) var(--mud-default-borderradius);
}

.pf-dash-edit-preview > * {
    flex: 1 1 auto;
}

/* Add widgets panel (shares the theme picker's slide-out shell). */
.pf-dash-picker {
    padding: 16px 20px 24px;
}

.pf-dash-picker-heading {
    padding-top: 8px;
    font-weight: 700;
    letter-spacing: .08em;
}

.pf-dash-picker-item {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 0;
    border-bottom: 1px solid var(--mud-palette-divider);
}

/* ---------- Impersonation (floating pill + right-hand panel) ----------
   Rendered outside MudLayout (see MainLayout.ImpersonationControl), so only the --mud-palette-*
   variables MudThemeProvider puts on :root are available here — no --pf-chrome-*. */

.pf-impersonation > summary {
    list-style: none;
}

.pf-impersonation > summary::-webkit-details-marker {
    display: none;
}

/* Above the page, the drawer (1100) and the app bar (1300); below dialogs (1400), which should
   cover it like anything else on the page. */
.pf-impersonation-pill {
    position: fixed;
    right: 16px;
    bottom: 16px;
    z-index: 1350;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    max-width: min(320px, calc(100vw - 32px));
    padding: 8px 16px 8px 12px;
    border-radius: 999px;
    background: var(--mud-palette-warning);
    color: var(--mud-palette-warning-text);
    font-size: .875rem;
    font-weight: 500;
    line-height: 1.25;
    cursor: pointer;
    box-shadow: 0 4px 14px rgba(0, 0, 0, .22);
    user-select: none;
}

.pf-impersonation-pill:hover {
    background: var(--mud-palette-warning-darken);
}

.pf-impersonation-pill:focus-visible {
    outline: 2px solid var(--mud-palette-warning-darken);
    outline-offset: 3px;
}

.pf-impersonation-pill-text {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The panel replaces the pill while it is open; two orange things in one corner is one too many. */
.pf-impersonation[open] > .pf-impersonation-pill {
    visibility: hidden;
}

/* Same shell as the theme picker's slide-out, so the two read as the same kind of drawer. */
.pf-impersonation-scrim {
    position: fixed;
    inset: 0;
    z-index: 1400;
    background: rgba(0, 0, 0, .32);
}

.pf-impersonation-panel {
    position: fixed;
    top: 0;
    right: 0;
    z-index: 1401;
    width: 330px;
    max-width: 100vw;
    height: 100%;
    overflow-y: auto;
    padding: 16px 20px 24px;
    background: var(--mud-palette-surface);
    border-top: 4px solid var(--mud-palette-warning);
    box-shadow: -8px 0 24px rgba(0, 0, 0, .18);
    animation: pf-impersonation-in .2s ease-out;
}

@keyframes pf-impersonation-in {
    from { transform: translateX(100%); }
    to { transform: none; }
}

@media (prefers-reduced-motion: reduce) {
    .pf-impersonation-panel {
        animation: none;
    }
}

.pf-impersonation-head {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 20px;
}

.pf-impersonation-close {
    display: inline-flex;
    padding: 6px;
    margin: -6px -8px -6px 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    cursor: pointer;
}

.pf-impersonation-close:hover {
    background: var(--mud-palette-action-default-hover);
}

.pf-impersonation-who {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-bottom: 16px;
}

.pf-impersonation-ellipsis {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.pf-impersonation-self {
    padding: 10px 12px;
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-background-gray);
}

/* Room under the page for the pill, so the end of a page — a grid's pager, a form's buttons — can
   always be scrolled clear of it. --pf-pad-bottom rather than padding, because the viewport-filling
   grids size themselves from it and would otherwise run their pager underneath. */
body:has(details.pf-impersonation) .pf-main-pad {
    --pf-pad-bottom: 72px;
}

/* ---------- Notification centre (app-bar bell dropdown) ---------- */
.pf-notif-panel {
    width: 360px;
    max-width: calc(100vw - 24px);
}

.pf-notif-head {
    display: flex;
    align-items: center;
    padding: 8px 8px 8px 16px;
}

.pf-notif-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 32px 16px;
}

.pf-notif-list {
    max-height: 60vh;
    overflow-y: auto;
}

.pf-notif-item {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    padding: 10px 16px;
    /* Every notification is clickable — one that goes nowhere still marks itself read. */
    cursor: pointer;
    border-bottom: 1px solid var(--mud-palette-divider);
}

.pf-notif-item:last-child {
    border-bottom: none;
}

.pf-notif-item:hover {
    background: var(--mud-palette-action-default-hover);
}

.pf-notif-unread {
    background: color-mix(in srgb, var(--mud-palette-primary) 7%, transparent);
}

.pf-notif-item-icon {
    margin-top: 2px;
}

.pf-notif-item-body {
    flex: 1;
    min-width: 0;
}

.pf-notif-item-title {
    font-weight: 500;
}

.pf-notif-item-msg {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.pf-notif-dot {
    flex-shrink: 0;
    width: 8px;
    height: 8px;
    margin-top: 6px;
    border-radius: 50%;
    background: var(--mud-palette-primary);
}

/* Pulled into the row's padding so the button's hit area does not push the text inward. */
.pf-notif-clear {
    flex-shrink: 0;
    margin: -6px -10px -6px -6px;
    opacity: .6;
}

.pf-notif-clear:hover,
.pf-notif-clear:focus-within {
    opacity: 1;
}

/* ---------- Global search dialog (command palette) ---------- */

.pf-search-dialog {
    margin-top: 10vh;
}

/* Magnifier and outlined field. The field's border and focus ring are MudBlazor's own outlined
   variant, so they take the theme's input line and primary colours. */
.pf-search-box {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
}

.pf-search-box-icon {
    color: var(--mud-palette-text-secondary);
}

/* MudBlazor's input control carries vertical margins meant for a form; here it is one item in a
   row, so it drops them and takes the rest of the row after the icon. */
.pf-search-box .pf-search-input {
    flex: 1;
    min-width: 0;
    margin: 0;
}

.pf-search-input .mud-input {
    font-size: .9375rem;
}

/* Touch screens have no Enter key to press. */
@media (pointer: coarse) {
    .pf-search-hit .pf-kbd {
        display: none;
    }
}

.pf-search-results {
    min-height: 120px;
    max-height: 50vh;
    overflow-y: auto;
    /* No top padding: the first row sits directly under the divider below the field. */
    padding-bottom: 8px;
    /* Arrowing scrolls the selected row into view (pfRevealSearchHit); this stops it just short
       of the edge, so the last row brings the padding below it along instead of sitting flush. */
    scroll-padding-block: 8px;
}

.pf-search-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding: 32px 16px;
}

.pf-search-cat {
    padding: 10px 16px 4px;
    font-size: .64rem;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.pf-search-hit {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px 16px;
    cursor: pointer;
}

/* Hover is lighter than the keyboard selection: the two can sit on different rows, and the
   stronger tint marks the one Enter opens. */
.pf-search-hit:hover {
    background: color-mix(in srgb, var(--mud-palette-primary) 5%, transparent);
}

.pf-search-hit-active,
.pf-search-hit-active:hover {
    background: color-mix(in srgb, var(--mud-palette-primary) 11%, transparent);
}

.pf-search-hit-text {
    flex: 1;
    min-width: 0;
}

.pf-search-hit-text .mud-typography {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The key legend along the bottom: MudDialog's actions bar, turned from a right-aligned button row
   into a left-aligned hint line under a divider. Three classes to beat .mud-dialog-actions. */
.pf-search-dialog .mud-dialog-actions.pf-search-footer {
    justify-content: flex-start;
    padding: 6px 16px;
    border-top: 1px solid var(--mud-palette-divider);
}

@media (pointer: coarse) {
    .pf-search-dialog .mud-dialog-actions.pf-search-footer {
        display: none;
    }
}

.pf-search-legends {
    display: flex;
    flex-wrap: wrap;
    gap: 4px 16px;
}

.pf-search-legend {
    display: inline-flex;
    align-items: center;
    gap: 3px;
}

/* A hint line, so a size down from the theme's caption. Two classes to beat
   .mud-typography-caption. */
.pf-search-legend .mud-typography {
    font-size: .65625rem;
}

/* The arrow and return keys are icons in the same chip as "Esc", sized to its text so all four
   chips are the same height. */
.pf-search-legend .pf-kbd {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 18px;
    padding: 1px 4px;
    font-size: .625rem;
    line-height: 12px;
}

/* 12px = the chip's line-height, which is what sets the "Esc" chip's height; a smaller icon left
   the icon chips a pixel short of it. */
.pf-search-legend .pf-kbd .mud-icon-root {
    font-size: 12px;
}

/* ---------- Role permission matrix (groups left, permissions right) ---------- */

.pf-perm-toolbar {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 12px;
}

.pf-perm-layout {
    display: flex;
    gap: 16px;
    min-height: 340px;
    max-height: 52vh;
}

.pf-perm-groups {
    width: 230px;
    flex-shrink: 0;
    overflow-y: auto;
    padding-right: 2px;
}

.pf-perm-group {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    margin-bottom: 8px;
    padding: 10px 14px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    font: inherit;
    font-size: .8125rem;
    text-align: left;
    cursor: pointer;
    transition: border-color .15s ease, background .15s ease;
}

.pf-perm-group:hover {
    border-color: var(--mud-palette-primary);
}

.pf-perm-group.pf-active {
    background: var(--mud-palette-primary);
    border-color: var(--mud-palette-primary);
    color: var(--mud-palette-primary-text);
}

.pf-perm-group-count {
    margin-left: auto;
    font-size: .6875rem;
    opacity: .75;
}

.pf-perm-detail {
    flex: 1;
    min-width: 0;
    overflow-y: auto;
    padding: 4px 16px 8px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
}

.pf-perm-actions {
    margin-left: 34px;
    display: flex;
    flex-wrap: wrap;
    column-gap: 4px;
}

/* Small screens: groups become a horizontal strip above the detail panel. */
@media (max-width: 699.95px) {
    .pf-perm-layout {
        flex-direction: column;
        max-height: none;
    }

    .pf-perm-groups {
        display: flex;
        gap: 8px;
        width: 100%;
        overflow-x: auto;
        padding-bottom: 4px;
    }

    .pf-perm-group {
        width: auto;
        white-space: nowrap;
        margin-bottom: 0;
    }
}

/* ---------- Error boundary (AppErrorBoundary) ---------- */

.pf-error-boundary {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 48px 0;
}

/* Development-only exception detail: wraps rather than forcing the card wide, and scrolls
   instead of running off the page on a long stack trace. */
.pf-error-detail {
    margin: 8px 0 0;
    padding: 12px;
    max-height: 220px;
    overflow: auto;
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-background-gray);
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .75rem;
    line-height: 1.5;
    white-space: pre-wrap;
    word-break: break-word;
}

/* ---------- Empty states (EmptyState) ---------- */

.pf-empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    /* Generous but not cavernous — an empty grid shouldn't feel like a broken page. */
    padding: 56px 24px;
}

.pf-empty-state-text {
    max-width: 420px;
}

/* ---------- Loading skeletons (TableSkeleton) ---------- */

/* Worth knowing before trying to line these bars up with the column headers above them: you can't.
   MudDataGrid renders LoadingContent inside a single merged cell —
   <tr><th class="mud-table-empty-row" colspan="1000"> — so the skeleton lives in one box spanning
   the whole table and has no column boundaries to align to. Measured on /dev/ui-probe: the real
   columns came out 196/264/142/158/179/109px wide, sized by their header text and content, while
   the skeleton's cells are equal shares of the same space. Matching row heights and column *counts*
   is achievable and worth doing; matching column positions is not, and weighting the cells only
   moves which columns are wrong. */
.pf-skeleton-table {
    padding: 8px 16px;
}

.pf-skeleton-row {
    display: flex;
    align-items: center;
    gap: 24px;
    border-bottom: 1px solid var(--mud-palette-table-lines);
}

/* Table-shaped rows, from sm up; below that the skeleton is a stacked block whose height is the
   sum of its lines. Deliberately no row height: a real row is as tall as its tallest cell plus the
   cell padding, so the placeholder is built the same way and the height falls out on its own. That
   is what makes a column hidden at a breakpoint shrink the placeholder too — a table of height
   constants would have to be kept in step with every column's breakpoint by hand, and the one for
   "comfortable, with buttons, chip hidden below md" does not exist.

   The five numbers are measured MudBlazor values: cells pad 6px vertically when dense and 16px
   when not, around content that is a 20px line of text, a 33px chip, or a 26px icon button. */
@media (min-width: 600px) {
    .pf-skeleton-row {
        padding: 6px 0;
    }

    .pf-skeleton-comfortable .pf-skeleton-row {
        padding: 16px 0;
    }

    .pf-skeleton-cell {
        min-height: 20px;
    }

    .pf-skeleton-chip {
        min-height: 33px;
    }

    .pf-skeleton-control {
        min-height: 26px;
    }

    /* No bar for an actions column in the table layout. The real one holds a single icon button in
       a 60px slot, so a bar there was both wider than the thing it stood for and the last item on
       the row, which made it the place the eye went looking for alignment that a skeleton cannot
       give (see the note on the merged loading cell below). The cell itself stays: it keeps the
       column's slot and its min-height still feeds the row height. The stacked layout below sm
       keeps its bar, where the actions really are a line of their own. */
    .pf-skeleton-control .pf-skeleton-value {
        display: none;
    }
}

.pf-skeleton-row:last-child {
    border-bottom: none;
}

/* Flex at every width, not just when stacked, so the responsive d-*-flex classes a caller passes
   through ColumnClasses restore the display the cell already had. */
.pf-skeleton-cell {
    display: flex;
    align-items: center;
    flex: 1;
    min-width: 0;
}

/* A checkbox or a pair of icon buttons — 60px, the width the real actions column is given. */
.pf-skeleton-control {
    flex: 0 0 60px;
    justify-content: flex-end;
}

.pf-skeleton-value {
    flex: 1 1 auto;
    min-width: 0;
}

.pf-skeleton-control .pf-skeleton-value {
    flex: 0 0 auto;
}

/* The label bar belongs to the stacked layout below. Up here the column headings are already
   sitting at the top of the table, so a second bar per cell would be inventing a column. */
.pf-skeleton-label {
    display: none;
}

/* Below MudBlazor's sm breakpoint a table row stops being a row: each cell becomes its own
   "label | value" line and the row becomes a tall block. A 40px strip of thin columns is not a
   placeholder for that, it is a placeholder for something else — so the skeleton takes the same
   shape. Heights stay approximate on purpose: the real block grows with its content (a name that
   wraps to two lines makes a taller card), and chasing that exactly would need the data we are
   still waiting for. */
@media (max-width: 599.95px) {
    /* No vertical padding anywhere above the first bar: a real stacked row starts flush against
       the top of the table body, and a gap there is the one misalignment you cannot miss. */
    .pf-skeleton-table {
        padding: 0 16px;
    }

    .pf-skeleton-row {
        display: block;
    }

    /* Our own stacked-row rule further up gives a fill or list table's rows 8px of bottom padding
       at this width, to separate cards MudBlazor leaves touching. The placeholder sits inside those
       same tables, so its rows carry it too — without this every row is 8px short of the one
       replacing it, and eight rows of that is a visible jump. */
    .pf-grid-fill .pf-skeleton-row,
    .pf-list-table .pf-skeleton-row {
        padding-bottom: 8px;
    }

    /* What a real stacked cell measures: 32px dense, 48px comfortable. */
    .pf-skeleton-cell {
        justify-content: space-between;
        gap: 24px;
        height: 32px;
    }

    .pf-skeleton-comfortable .pf-skeleton-cell {
        height: 48px;
    }

    /* A chip line is taller than a text line — measured 44px dense against 32px, which was the
       last visible difference between a stacked placeholder and the row that replaced it. */
    .pf-skeleton-chip {
        height: 44px;
    }

    .pf-skeleton-comfortable .pf-skeleton-chip {
        height: 56px;
    }

    /* Control cells stack like any other line down here — the narrow width is a table-layout idea.
       They do run taller than a line of text, though: a checkbox or an icon button is 40px in a
       dense row and 52px in a comfortable one. Declared after the two rules above so it wins on
       order, since .pf-skeleton-comfortable .pf-skeleton-cell carries the same weight.

       justify-content is repeated here for the same reason: the cell rule above sets
       space-between and carries equal weight, so it would otherwise win on order — and
       space-between on a cell with one child parks that child at the *start*, which put the
       actions bar on the left where the real buttons sit hard right. */
    .pf-skeleton-control {
        flex: none;
        height: 40px;
        justify-content: flex-end;
    }

    .pf-skeleton-comfortable .pf-skeleton-control {
        height: 52px;
    }

    .pf-skeleton-label {
        display: block;
        flex: 0 0 auto;
    }

    /* Right-aligned like the real values, and a flex item rather than the whole cell so the bar's
       percentage width is measured against the space left over — otherwise a 90% bar would sit on
       top of its own label. */
    .pf-skeleton-value {
        display: flex;
        justify-content: flex-end;
    }
}

/* ---------- Card grid density ---------- */

/* MudCard has no Dense parameter (it is a row-table concept), so the card grid's Dense switch
   tightens padding here instead. Kept to padding only — nothing that changes the card's anatomy. */
.pf-card-dense .mud-card-header,
.pf-card-dense .mud-card-actions {
    padding: 8px 12px;
}

.pf-card-dense .mud-card-content {
    padding: 0 12px 8px;
}

/* A card that opens the editor on click has to look like it will. The grids get this free —
   MudDataGrid and MudTable style their own clickable rows — but MudCard has no such notion, so
   the affordance is ours: pointer cursor plus a small lift, matched to MudBlazor's own easing. */
.pf-card-clickable {
    cursor: pointer;
    transition: box-shadow 150ms cubic-bezier(0.4, 0, 0.2, 1), transform 150ms cubic-bezier(0.4, 0, 0.2, 1);
}

.pf-card-clickable:hover {
    box-shadow: var(--mud-elevation-8);
    transform: translateY(-2px);
}

/* ---------- Grid / list switch (the card grid's toolbar) ----------
   A segmented frame around two small icon buttons, styled as an outlined primary button like the
   Filters button beside it: primary frame, primary icons. The selected one is marked by a primary
   tint behind its icon, so it reads in any theme, light or dark. */
.pf-layout-toggle {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    padding: 2px;
    border: 1px solid var(--mud-palette-primary);
    border-radius: var(--mud-default-borderradius);
}

/* 5px around the 20px icon, not a small icon button's 3px: 30px buttons inside the frame's 2px
   padding and 1px border make it 36px, level with the Filters button beside it. */
.pf-layout-toggle .mud-icon-button {
    padding: 5px;
    border-radius: calc(var(--mud-default-borderradius) - 1px);
}

.pf-layout-toggle .mud-icon-button.is-selected {
    background-color: color-mix(in srgb, var(--mud-palette-primary) 14%, transparent);
}

/* ---------- A card drawn as a row (the card grid's list view) ----------
   The same MudCard, laid out along its header: avatar, then name / details / chips as columns,
   then the actions corner. Wide screens get three columns; below md the details fold under the
   name, and below sm the chips follow them, so nothing is squeezed to an ellipsis on a phone. */

/* Centred rather than MudBlazor's top-aligned corner: a row is one line tall, and a kebab pinned
   to its top edge reads as belonging to the row above. */
.pf-card-row .mud-card-header-actions {
    align-self: center;
    margin-top: 0;
}

/* Each row is its own grid, so an auto chip column would size to that row's chips ("Active" is
   narrower than "Inactive", amounts differ) and the details column would start at a different
   point on every row. A 172px floor fits the usual pair, keeping the columns lined up down the
   list; max-content lets an unusually long amount grow its row rather than overflow. The details
   wrap to two lines on their own, which is why they get barely more room than the name. */
.pf-card-row-body {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr) minmax(172px, max-content);
    grid-template-areas: "title details chips";
    align-items: center;
    gap: 4px 24px;
}

.pf-card-row-title {
    grid-area: title;
    min-width: 0;
}

/* A customer with no city or country still gets the place line's height, so every row in the list
   (and the skeleton standing in for it) is the same height; an empty caption collapsed to nothing
   and made that one row 9px shorter than the rest. */
.pf-card-row-title .mud-typography-caption:empty::before {
    content: "\00a0";
}

.pf-card-row-details {
    grid-area: details;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 2px 20px;
    min-width: 0;
}

.pf-card-row-detail {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    max-width: 100%;
}

/* Block, because MudText renders a caption as an inline <span>, and an inline box ignores
   overflow — a long description ran on underneath the chips beside it instead of ending in "…". */
.pf-card-row-title .mud-typography,
.pf-card-row-detail .mud-typography {
    display: block;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.pf-card-row-chips {
    grid-area: chips;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 4px;
}

@media (max-width: 959.95px) {
    .pf-card-row-body {
        grid-template-columns: minmax(0, 1fr) minmax(172px, max-content);
        grid-template-areas:
            "title chips"
            "details details";
    }
}

@media (max-width: 599.95px) {
    .pf-card-row-body {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "title"
            "details"
            "chips";
    }

    .pf-card-row-chips {
        justify-content: flex-start;
    }
}

/* Two columns, title and details, for a list whose chips vary in number and so live in the
   details column (the Themes page). Two classes, so these win over the three-column rules and
   their breakpoints above at every width. */
.pf-card-row-body.is-two-col {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
    grid-template-areas: "title details";
}

@media (max-width: 959.95px) {
    .pf-card-row-body.is-two-col {
        grid-template-columns: minmax(0, 1fr);
        grid-template-areas:
            "title"
            "details";
    }
}

/* The Themes list leads each row with the theme's preview at thumbnail size, in the slot the
   Customers list gives its avatar. ThemePreview sizes itself from its container's width (16:10),
   so the width is the only thing set here: 128 x 80, or 96 x 60 on a phone. */
.pf-theme-row-thumb {
    width: 128px;
}

@media (max-width: 599.95px) {
    .pf-theme-row-thumb {
        width: 96px;
    }

    /* A theme row carries three action buttons, not the Customers list's one kebab, and on a phone
       they took so much of the row that the chips beside the preview wrapped one per line. The
       buttons drop to a line of their own instead, right-aligned under the row. */
    .pf-theme-row .mud-card-header {
        flex-wrap: wrap;
        row-gap: 4px;
    }

    .pf-theme-row .mud-card-header-content {
        flex: 1 1 0;
        min-width: 0;
    }

    .pf-theme-row .mud-card-header-actions {
        flex-basis: 100%;
        display: flex;
        justify-content: flex-end;
        margin: 0;
    }
}

/* ---------- Mobile row actions ---------- */

/* Below the breakpoint MudTable turns every cell into its own full-width "label | value" row.
   Where a row's actions are split across two cells — CustomersTable in inline mode keeps Delete in
   the row template while MudTable appends its own edit-button cell — that stacks them vertically.
   Collapsing both to inline-flex puts them back on one line.

   Scoped to the class on the row template's action cell, so the editing row (which renders
   RowEditingTemplate and never carries the class) keeps MudBlazor's own layout. */
@media (max-width: 599.98px) {
    .mud-table-row > td.pf-row-action-cell,
    .mud-table-row > td.pf-row-action-cell + td {
        display: inline-flex;
        width: auto;
        padding-top: 0;
        padding-bottom: 0;
    }

    /* Those cells have no data label, and an empty ::before still reserves space. */
    .mud-table-row > td.pf-row-action-cell::before,
    .mud-table-row > td.pf-row-action-cell + td::before {
        content: none;
    }

    /* Inline-level cells follow the row's text alignment; :has keeps this off every other row.
       Unsupported browsers simply get the pair left-aligned, still on one line. */
    .mud-table-row:has(> td.pf-row-action-cell) {
        text-align: right;
    }
}

/* ---------- Mobile sorting ---------- */

/* MudTable's built-in small-device sort select ships with no label, which reads as a mystery
   dropdown. It is a wrapper div and there is no parameter for a caption, so generate one. */
.mud-table-smalldevices-sortselect::before {
    content: "Sort by";
    display: block;
    font-size: 0.75rem;
    line-height: 1.5;
    color: var(--mud-palette-text-secondary);
}

/* Opt out of that select entirely. MudTable renders it whenever the header holds sort labels and
   offers no parameter to suppress it, so the only lever is display. Worth being deliberate about:
   this is the only way to sort the Customers table on a phone, and switching it off trades that
   away for a shorter page. The desktop sort headers are untouched. */
.pf-table-no-mobile-sort .mud-table-smalldevices-sortselect {
    display: none;
}

/* Announced to screen readers, invisible on screen. */
.pf-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------- Email template preview (admin) ---------- */

/* The preview deliberately uses a light card regardless of app theme: mail clients render on white,
   so previewing on a dark surface would misrepresent the message. */
/* Settings → Email templates: the account email in a sandboxed frame, on the email's own backdrop.
   Literal colours, not theme variables, on purpose — this is the email's world (EmailLayout's Fog and
   Hairline), and it must look the same whatever theme the admin is wearing. The phone width is a
   real 375px viewport, so the email's own media query takes over just as it would on a phone. */
.pf-email-stage {
    padding: 12px 0;
    border-radius: var(--mud-default-borderradius);
    background: #EEF1F4;
}

.pf-email-frame {
    display: block;
    width: 100%;
    height: 760px;
    margin: 0 auto;
    border: 0;
    background: #EEF1F4;
}

/* Settings → General: the logo on white, at the size the email draws it — the email's letterhead,
   not the app's surface, so the colours are the email's own. */
.pf-logo-tile {
    display: flex;
    align-items: center;
    min-height: 88px;
    padding: 20px 24px;
    border: 1px solid #DCE1E7;
    border-radius: var(--mud-default-borderradius);
    background: #FFFFFF;
}

.pf-logo-tile img {
    display: block;
    max-width: 100%;
    height: auto;
}

.pf-email-frame.is-phone {
    width: 375px;
    max-width: 100%;
    border: 1px solid #DCE1E7;
    border-radius: 16px;
}

.pf-email-source {
    margin: 0;
    padding: 12px;
    border-radius: var(--mud-default-borderradius);
    background: var(--mud-palette-background-gray);
    font-size: .8rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.pf-email-token {
    word-break: break-all;
}

/* ---------- Grid settings (per-page slide-out) ---------- */

/* The wrapper is what's pinned to the right edge — MudTooltip inserts a block-level root around
   its child, so the fixed positioning has to sit outside that or the wrapper stays in normal flow
   (adding page height) and the tooltip anchors to the wrong place. Sits below the panel and scrim
   in the stack so it tucks behind them while the panel is open. */
.pf-grid-settings-launcher {
    position: fixed;
    top: 50%;
    right: 0;
    transform: translateY(-50%);
    z-index: 1399;
}

.pf-grid-settings-tab {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 48px;
    padding: 0;
    border: 1px solid var(--mud-palette-lines-default);
    border-right: none;
    border-radius: 24px 0 0 24px;
    /* Opaque in both states: a translucent hover overlay let the page show through and read as
       the button disappearing. */
    background: var(--mud-palette-surface);
    color: var(--mud-palette-text-primary);
    box-shadow: -2px 2px 10px rgba(0, 0, 0, .18);
    cursor: pointer;
    transition: width .15s ease, color .15s ease;
}

.pf-grid-settings-tab:hover {
    width: 52px;
    color: var(--mud-palette-primary);
}

.pf-grid-settings-tab:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* ---------- Component gallery (/components) ---------- */

/* MudDropZone has no size of its own: it is only as tall as the items in it, so an empty column
   collapses to nothing and there is nowhere left to drop. Give it a floor and a visible edge. */
.pf-drop-zone {
    min-height: 88px;
    padding: 4px;
    border: 1px dashed var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
}

.pf-drop-zone-can {
    border-color: var(--mud-palette-success);
    background: var(--mud-palette-success-hover);
}

.pf-drop-zone-no {
    border-color: var(--mud-palette-error);
    background: var(--mud-palette-error-hover);
}

/* The gallery has ten tab headers; below md they scroll rather than shrink to unreadable stubs. */
.pf-gallery-tabs .mud-tabs-toolbar {
    border-bottom: 1px solid var(--mud-palette-lines-default);
}

/* ---------- Sidebar item descriptions ----------
   Off by default and switched on per user in the appearance panel, which puts .pf-nav-described
   on the drawer (MainLayout, DrawerClass). Every rule below is scoped to that class, so with the
   preference off the sidebar renders exactly the CSS it always did. */

/* The sidebar keeps MudBlazor's 240px whether descriptions are on or not. Widening it to 280
   worked, but it made the classic sidebar wider than the expanded mini rail, and two layouts
   disagreeing about how much room navigation takes reads as a bug rather than a preference. The
   descriptions are tighter for it — that is the trade, taken deliberately. */

/* Stock MudNavLink is a single centred row. Descriptions make it two stacked lines, so the row
   grows to fit its content and the icon aligns to the first line rather than the middle of the
   block — centring it against two lines drops it visually below the title it belongs to. */
.app-drawer.pf-nav-described .mud-nav-link {
    align-items: flex-start;
    height: auto;
    min-height: 48px;
    padding-top: 8px;
    padding-bottom: 8px;
}

.app-drawer.pf-nav-described .mud-nav-link .mud-icon-root {
    margin-top: 2px;
}

/* MudBlazor lays the label out inline; two spans need a column. */
.app-drawer.pf-nav-described .mud-nav-link-text {
    display: flex;
    flex-direction: column;
    /* Overrides the nowrap the rail's open/close animation needs (see "Keeping the collapse/expand
       animation honest"): a description that cannot wrap is a description you cannot read. Titles
       keep the nowrap below. */
    white-space: normal;
    line-height: 1.35;
}

.app-drawer.pf-nav-described .pf-nav-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Two lines, then ellipsis: without the clamp one long description makes its row twice the height
   of its neighbours, and the eye reads that as a grouping that isn't there. The registry test caps
   descriptions at 48 characters so this should rarely bite — it is the backstop, not the plan. */
.app-drawer.pf-nav-described .pf-nav-desc {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    margin-top: 1px;
    /* em, not px: the base font size is itself a preference, and a fixed 11px description under a
       16px title reads as a rendering bug. */
    font-size: .78em;
    line-height: 1.3;
    color: var(--pf-chrome-text-muted);
    font-weight: 400;
}

/* The active item bolds its title (see the nav-link rules above). Letting that reach the
   description makes the whole block shout. */
.app-drawer.pf-nav-described .mud-nav-link.active .pf-nav-desc {
    font-weight: 400;
    color: var(--pf-chrome-text-secondary);
}

/* Group children are indented by MudBlazor and then given a description as well, which leaves the
   text column narrow enough to clamp almost everything. Buy some of it back. */
.app-drawer.pf-nav-described .mud-nav-group .mud-nav-link {
    padding-right: 8px;
}

/* ---------- Grid with its controls in the header (Data Grid page, "In the grid header") ----------
   MudDataGrid has no slot between the toolbar and the column headers, so the filter panel has to
   ride in the toolbar. A toolbar is a fixed-height, non-wrapping flex row by default, which would
   clip it — so this one becomes a column of two blocks: the controls row, then the panel. */

/* Matched through our own row, not by descending into MudBlazor's classes. The opt-in class sits
   on the grid, so a plain `.pf-grid-toolbar .mud-toolbar` also caught the pager in the footer —
   a MudToolBar too — and outranked its own 52px height and nowrap. Keying on .pf-grid-toolbar-row
   means only the toolbar we actually build here can be touched. */
.pf-grid-toolbar .mud-toolbar:has(> .pf-grid-toolbar-row) {
    height: auto;
    min-height: 64px;
    flex-direction: column;
    /* The query container for the action buttons below. It sits here rather than on the row
       because container-type also brings layout containment, and on the row — a wrapping flex
       container — that inflated its height from 48px to 190px. On the toolbar the width being
       measured is the same and nothing else changes. */
    container-type: inline-size;
    container-name: pf-toolbar;
    /* Both children span the toolbar. This is what used to need `flex: 1 0 100%` on the panel —
       and note that rule would now be actively wrong, since flex-basis in a column is height.
       The row-gap .mud-table-toolbar sets for wrapped lines becomes the gap between the two
       blocks, which is why the closed height works out unchanged. */
    align-items: stretch;
    padding-top: 4px;
    padding-bottom: 4px;
}

/* 48px is what the row measured as a wrapped flex line, and the numbers have to keep adding up to
   the same total: 48 here + the 8px row-gap that .mud-table-toolbar (top of this file) puts
   between the row and the panel + 8px of padding = the 64px the closed toolbar has always been.
   Pinning it is the fix. Previously these controls were a flex line in a wrapping container, and
   such a container shares its spare cross-axis space out across its lines — so whenever the
   tallest control was under 48px (no search box, say) the row sat lower while the panel was shut
   and snapped back up when it opened. A column has no spare space to share, at any content
   height. */
.pf-grid-toolbar-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    min-height: 48px;
}

/* The search asks for its minimum when the row is deciding where to break, and grows into whatever
   the buttons leave afterwards. Left at flex-basis auto it asked for its full content width —
   around 590px — so the actions were pushed onto a second line while 39px of room still remained
   beside them. Wrapping is settled before shrinking, so a greedy basis wraps the row on its own. */
.pf-grid-toolbar-row .mud-input-control {
    flex: 1 1 150px;
}

/* The action buttons come in two sizes — labelled, and icons only — and which one fits depends on
   how wide this toolbar is, not how wide the window is. MudBlazor's d-md-* utilities only know the
   viewport, so with the sidebar open a 1200px window could leave the toolbar at 850px: wide enough
   to be handed the labelled buttons, too narrow to fit them, and the row wrapped with the actions
   stranded on a second line.

   Two tiers, because the grids do not carry the same buttons. Measured one-line minimums with the
   search at its 150px floor: Table 751px, Data Grid 857px (it has Refresh), Multi-select more
   again (Refresh and a bulk Delete). The first two share a threshold sized to the wider of them;
   the multi-select opts into a higher one with .pf-toolbar-roomy. Re-measure if buttons change.

   CustomerFilterPopover reuses this same pair for its own two variants, so the Filters button
   loses its label on the same tick as the actions beside it instead of on a breakpoint of its
   own. The thresholds are unaffected: above them the labelled Filters button renders exactly as
   it did when they were measured, and below them everything only gets narrower. */
.pf-toolbar-actions-labelled {
    display: none;
}

.pf-toolbar-actions-icons {
    display: flex;
    align-items: center;
}

@container pf-toolbar (min-width: 880px) {
    .pf-grid-toolbar-row:not(.pf-toolbar-roomy) .pf-toolbar-actions-labelled {
        display: flex;
        align-items: center;
        gap: 4px;
    }

    .pf-grid-toolbar-row:not(.pf-toolbar-roomy) .pf-toolbar-actions-icons {
        display: none;
    }
}

@container pf-toolbar (min-width: 1000px) {
    .pf-toolbar-roomy .pf-toolbar-actions-labelled {
        display: flex;
        align-items: center;
        gap: 4px;
    }

    .pf-toolbar-roomy .pf-toolbar-actions-icons {
        display: none;
    }
}

/* Keeps the button and its popover in one positioning context, as the gallery example does.
   inline-flex so wrapping the button changes nothing about how it sits in the toolbar row.
   Clipping is not a worry either way: MudPopoverProvider hosts popovers at the document root,
   outside the grid card and its overflow. */
.pf-filter-anchor {
    position: relative;
    display: inline-flex;
    /* These toolbars set row-gap but leave column-gap at normal, so neighbours are spaced by their
       own margins — the search box's mx-2, the button groups' gap-1. The component carries its own
       for the same reason, or it ends up flush against whichever group it lands beside. */
    margin-inline: 4px;
}

/* Two columns of fields at full size, and never wider than the window it has to fit inside — at
   which point the fields fall back to one column on their own (xs=12) rather than being clipped.
   That is the whole small-screen story: no breakpoint fallback to maintain, no second host. */
.pf-filter-popover {
    width: min(560px, calc(100vw - 32px));
}

/* ---------- Top nav tooltips ----------
   Only the top nav turns the description into a tooltip; both drawer layouts either show it on the
   row or (collapsed rail) show the page name alone, so neither needs styling here. */

/* A tooltip in a dropdown panel wraps its child in .mud-tooltip-root, which is inline-block —
   the same thing that used to collapse nav rows in the drawer, and here it would shrink the
   full-width dropdown links to their text. */
.topnav-dropdown-panel .mud-tooltip-root {
    display: block;
    width: 100%;
}

/* The bar's entries are flex items; the wrapper that carries data-nav-item must not change how
   they sit or the fitter measures something other than what it hides. */
.topnav-entry {
    display: inline-flex;
    align-items: center;
}

/* ---------- Theme preview (Components/Shared/ThemePreview.razor) ----------
   A miniature of a theme's chrome, shown on the Themes admin cards and in the app-bar picker.
   Every colour here comes from a --pf-chrome-* custom property, so these rules never name a
   colour: the element carrying .pf-chrome-light / .pf-chrome-dark decides, and a nested element
   with the other class simply redefines the properties for its own subtree. That is what lets one
   preview show a dark sidebar against a light page without a second palette living in this file. */
.pf-theme-preview {
    display: flex;
    /* Column by default: the app bar spans the top and the drawer sits under it, which is the
       classic sidebar (and top navigation, which has no drawer). */
    flex-direction: column;
    /* 16:10 keeps the shape recognisably a screen at any card width. */
    aspect-ratio: 16 / 10;
    width: 100%;
    overflow: hidden;
    border-radius: 6px;
    border: 1px solid var(--mud-palette-lines-default);
    background: var(--pf-chrome-bg);
    /* cqw so the whole mock scales with the card it is dropped into: every size below is in em,
       and this is the only place a real length is chosen. */
    container-type: inline-size;
    font-size: clamp(6px, 1.6cqw, 10px);
    user-select: none;
}

/* The mini rail owns the top-left corner, so the whole preview turns on its side: rail down the
   full height, everything else stacked beside it. */
.pf-theme-preview.is-railed {
    flex-direction: row;
}

/* The area under a full-width app bar: drawer (if any) beside the page. */
.pf-theme-preview-row {
    display: flex;
    flex: 1 1 auto;
    min-height: 0;
}

.pf-theme-preview-side {
    display: flex;
    flex-direction: column;
    gap: 0.5em;
    flex: 0 0 26%;
    padding: 0.6em 0.5em;
    background: var(--pf-chrome-bg);
    /* The real chrome's hairline, and the reason a light sidebar on a light page still reads as a
       sidebar rather than dissolving into it. */
    border-right: 1px solid var(--pf-chrome-line);
}

/* The rail is the same column at icon width — no room for labels, which is the point of it. */
.pf-theme-preview-side.is-rail {
    flex-basis: 12%;
    align-items: center;
}

.pf-theme-preview-brand {
    display: block;
    height: 0.9em;
    width: 60%;
    border-radius: 2px;
    background: var(--pf-preview-accent);
    margin-bottom: 0.3em;
}

.pf-theme-preview-side.is-rail .pf-theme-preview-brand {
    width: 1em;
}

.pf-theme-preview-brand.is-inline {
    margin-bottom: 0;
    width: 1.2em;
    flex: 0 0 auto;
}

.pf-theme-preview-navitem {
    display: flex;
    align-items: center;
    gap: 0.4em;
    width: 100%;
}

.pf-theme-preview-dot {
    flex: 0 0 auto;
    width: 0.6em;
    height: 0.6em;
    border-radius: 2px;
    background: var(--pf-chrome-text-muted);
}

.pf-theme-preview-line {
    height: 0.4em;
    flex: 1 1 auto;
    border-radius: 2px;
    background: var(--pf-chrome-text-muted);
    opacity: 0.55;
}

/* The selected item, in the theme's own accent — the one place the brand colour shows in nav. */
.pf-theme-preview-navitem.is-active .pf-theme-preview-dot,
.pf-theme-preview-navitem.is-active .pf-theme-preview-line {
    background: var(--pf-preview-accent);
    opacity: 1;
}

.pf-theme-preview-main {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-width: 0;
}

.pf-theme-preview-bar {
    display: flex;
    align-items: center;
    gap: 0.4em;
    flex: 0 0 22%;
    padding: 0 0.6em;
    background: var(--pf-chrome-bg);
    border-bottom: 1px solid var(--pf-chrome-line);
}

.pf-theme-preview-tab {
    width: 1.4em;
    height: 0.4em;
    border-radius: 2px;
    background: var(--pf-chrome-text-muted);
    opacity: 0.55;
}

.pf-theme-preview-tab.is-active {
    background: var(--pf-preview-accent);
    opacity: 1;
}

.pf-theme-preview-spacer {
    flex: 1 1 auto;
}

.pf-theme-preview-avatar {
    width: 0.8em;
    height: 0.8em;
    border-radius: 50%;
    background: var(--pf-preview-accent);
}

/* The page area. Its background is the mode's chrome background, set on the root, so a dark theme
   previews dark even while the app around it is light. */
.pf-theme-preview-body {
    display: flex;
    flex-wrap: wrap;
    align-content: flex-start;
    gap: 0.4em;
    flex: 1 1 auto;
    padding: 0.6em;
    background: var(--pf-chrome-bg);
}

.pf-theme-preview-card {
    height: 1.4em;
    flex: 1 1 40%;
    border-radius: 3px;
    background: var(--pf-chrome-hover);
    border: 1px solid var(--pf-chrome-line);
}

.pf-theme-preview-card.is-accent {
    background: var(--pf-preview-accent);
    border-color: transparent;
}

.pf-theme-preview-card.is-wide {
    flex-basis: 100%;
}

/* ---------- Theme picker (the app-bar palette button's panel) ----------
   Reuses .pf-settings-panel's slide-in shell; only the card list is new. */
.pf-theme-picker-list {
    display: grid;
    gap: 12px;
}

/* A button, not a div: choosing a theme is an action, and this way it is keyboard-reachable and
   announced without any role/tabindex patching. */
.pf-theme-card {
    display: block;
    width: 100%;
    text-align: left;
    padding: 8px;
    border-radius: 8px;
    border: 2px solid var(--mud-palette-lines-default);
    background: var(--mud-palette-surface);
    color: inherit;
    font: inherit;
    cursor: pointer;
    transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}

.pf-theme-card:hover {
    border-color: var(--mud-palette-primary);
    transform: translateY(-1px);
    box-shadow: var(--mud-elevation-4);
}

.pf-theme-card:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

/* The applied theme, marked the way the screenshot marks it: a full-strength border plus a badge,
   so it survives being the only visual difference on a page full of similar cards. */
.pf-theme-card.is-active {
    border-color: var(--mud-palette-success);
}

.pf-theme-card-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
}

.pf-theme-card-name {
    font-weight: 600;
    line-height: 1.2;
}

.pf-theme-card-desc {
    font-size: 0.75rem;
    color: var(--mud-palette-text-secondary);
    line-height: 1.3;
}

/* ---------- Theme style modes (body classes, see Theme/ThemeStyle.cs) ----------
   The two parts of a theme MudBlazor has no setting for. Both live on <body> rather than on the
   layout, because dialogs, popovers and snackbars render at the document root and would otherwise
   keep the default look while the page behind them changed.

   Scoped to .pf-flat / .pf-compact so a theme that chooses neither costs nothing at all. */

/* Flat: no shadows anywhere, a hairline instead. This is the difference between the "material"
   and "modern flat" looks. Elevation classes are how MudBlazor draws every shadow, so overriding
   the variables they resolve to catches paper, cards, menus, dialogs and the app bar in one go
   rather than chasing each component. */
.pf-flat .mud-elevation-1,
.pf-flat .mud-elevation-2,
.pf-flat .mud-elevation-3,
.pf-flat .mud-elevation-4,
.pf-flat .mud-elevation-6,
.pf-flat .mud-elevation-8,
.pf-flat .mud-elevation-12,
.pf-flat .mud-elevation-16,
.pf-flat .mud-elevation-24 {
    box-shadow: none !important;
}

/* Without a shadow a card has no edge, so give it one. Only surfaces that sit on the page get it —
   not the popovers, which are already separated by the overlay behind them. */
.pf-flat .mud-paper:not(.mud-popover):not(.mud-dialog):not(.mud-alert),
.pf-flat .mud-card,
.pf-flat .mud-table {
    border: 1px solid var(--mud-palette-lines-default);
}

/* Menus and dialogs float above the page and need to stay legible against it: they keep a border
   rather than a shadow, so they are still distinguishable from what is behind them. */
.pf-flat .mud-popover,
.pf-flat .mud-dialog {
    border: 1px solid var(--mud-palette-lines-default);
}

/* Compact: one spacing scale, applied to the things that actually carry the app's height —
   table rows, inputs, list items, the app bar and cards. Not a blanket transform: scaling
   everything (icons, avatars, typography) makes a cramped app rather than a dense one. */
.pf-compact .mud-table-row .mud-table-cell {
    padding-top: 6px;
    padding-bottom: 6px;
}

.pf-compact .mud-table-dense .mud-table-row .mud-table-cell {
    padding-top: 2px;
    padding-bottom: 2px;
}

.pf-compact .mud-card-content {
    padding: 10px;
}

.pf-compact .mud-card-header,
.pf-compact .mud-card-actions {
    padding: 8px 10px;
}

.pf-compact .mud-list-item {
    padding-top: 4px;
    padding-bottom: 4px;
}

.pf-compact .mud-nav-link {
    padding-top: 6px;
    padding-bottom: 6px;
}

/* The page gutter, which is where most of the reclaimed space is. These are the real variables
   .pf-main-pad publishes, and pf-page-fill's height calc reads the same ones — so overriding them
   here keeps the viewport-filling grids sized correctly instead of leaving a gap at the bottom.

   One value at every width rather than a set of media queries: this selector is (0,2,0) and the
   responsive .pf-main-pad rules are (0,1,0), so anything set here would win at every breakpoint
   anyway. Matching the comfortable layout's own mobile gutter keeps it honest. */
.pf-compact .pf-main-pad {
    --pf-pad-x: 16px;
    /* The gap only. --pf-pad-top is composed from this and the theme's bar height on .pf-main-pad,
       so overriding it here would throw the bar height away — which is exactly what it used to do. */
    --pf-pad-gap: 6px;
    --pf-pad-bottom: 16px;
}

/* ---------- Theme preview: the style-bundle settings ----------
   Each variable falls back to the chrome value the tone classes already publish, so a theme that
   sets none of these previews exactly as it did before the settings existed. */
.pf-theme-preview-body {
    background: var(--pf-preview-bg, var(--pf-chrome-bg));
}

.pf-theme-preview-card {
    background: var(--pf-preview-surface, var(--pf-chrome-hover));
    border-radius: var(--pf-preview-radius, 3px);
}

/* Flat: the mock loses its card fill difference, so the border does the separating — the same
   trade the real .pf-flat makes. */
.pf-theme-preview.is-flat .pf-theme-preview-card {
    border-color: var(--pf-chrome-text-muted);
}

/* Compact: more rows in the same box, which is what density actually buys. */
.pf-theme-preview.is-compact .pf-theme-preview-body {
    gap: 0.25em;
    padding: 0.35em;
}

.pf-theme-preview.is-compact .pf-theme-preview-card {
    height: 0.9em;
}

.pf-theme-preview.is-compact .pf-theme-preview-side {
    gap: 0.3em;
    padding: 0.35em 0.35em;
}

/* ---------- Nav active-item treatments (body classes, see Theme/ThemeStyle.cs NavStyle) ----------
   MudBlazor has no setting for this — it tints the whole row and stops there — so these are ours.
   Both variants key off .mud-nav-link.active inside the app drawer only, leaving any other nav
   list alone, and both use the chrome variables so they follow the sidebar tone rather than
   introducing a colour of their own. */

/* Pill: an inset rounded fill. Reads as "selected object" rather than "highlighted row", which is
   the modern convention. Margin is what insets it; without that it is just a rounded row.

   The shape goes on EVERY row, not just the active one, for two reasons. Hover paints the same
   background, so shaping only the active row left the hover as a full-bleed square sitting next to
   a rounded pill. And margin is layout: insetting only the active row jogged its icon 8px right of
   every other icon in the list, and insetting on :hover instead would slide the row sideways under
   the cursor. Constant geometry, only the paint changes.

   The 2px block margin is not decoration: two painted rows next to each other (hovering the item
   above or below the selected one) met with no gap, and two rounded rectangles sharing an edge
   read as one mis-drawn shape rather than two pills. The collapsed rail already spaces its rows
   for the same reason.

   The Tab style starts from these same rules (see below): it is a pill wherever the tab itself
   cannot be drawn, and where it can, it keeps the pill's leading inset so no icon moves. */
.pf-nav-pill .app-drawer .mud-nav-link,
.pf-nav-pill .app-drawer button.mud-nav-link,
.pf-nav-tab .app-drawer .mud-nav-link,
.pf-nav-tab .app-drawer button.mud-nav-link {
    margin: 2px 8px;
    /* MudBlazor sizes the row at 100% width, so margin on its own does not inset the pill — it
       pushes the box 16px past the drawer, where overflow-x: hidden shears the trailing corner
       off. The width has to come back down by exactly what the margin adds. */
    width: calc(100% - 16px);
    border-radius: var(--mud-default-borderradius);
}

.pf-nav-pill .app-drawer .mud-nav-link.active,
.pf-nav-tab .app-drawer .mud-nav-link.active {
    background-color: var(--pf-chrome-hover);
}

/* The rail collapses to icon width, where an inset pill leaves almost nothing to click. */
.pf-nav-pill .pf-rail-collapsed .mud-nav-link,
.pf-nav-pill .pf-rail-collapsed button.mud-nav-link,
.pf-nav-tab .pf-rail-collapsed .mud-nav-link,
.pf-nav-tab .pf-rail-collapsed button.mud-nav-link {
    margin-inline: 4px;
    width: calc(100% - 8px);
}

/* Bar: the row tint plus an accent edge. The classic "you are here" marker, and the one that
   survives being scanned quickly down a long list. */
.pf-nav-bar .app-drawer .mud-nav-link.active {
    position: relative;
    background-color: var(--pf-chrome-hover);
}

.pf-nav-bar .app-drawer .mud-nav-link.active::before {
    content: "";
    position: absolute;
    inset-inline-start: 0;
    top: 6px;
    bottom: 6px;
    width: 3px;
    border-radius: 0 3px 3px 0;
    background: var(--pf-chrome-accent);
}

/* Tab: the selected item stops being a mark *on* the sidebar and becomes part of the page — the
   page's own colour, open on the page side, with the sidebar curving into it above and below. A
   pill everywhere else (rules above), because the shape only works where the drawer meets the page
   edge to edge: not the mini rail, and not below md, where the drawer floats over the page.
   The layout is told apart by .pf-classic-sidebar, which MainLayout sets from the layout itself —
   MudBlazor's variant classes change with the breakpoint, so they cannot answer that question.

   The page's colours are captured here, on <body>, rather than read inside the drawer. The drawer
   points --mud-palette-primary at the sidebar's accent (see .app-drawer.pf-chrome-*), and the tab
   sits on the page, not on the sidebar — its icon wants the colour chosen against the page. A
   custom property is resolved where it is declared and inherited already resolved, so these keep
   the page's values all the way down.

   The curve follows the theme's corner radius, doubled because the tab is a much larger shape
   than the buttons that radius was picked for, and clamped: below 14px there is no curve to see,
   and past 22px it outgrows a single-line row. */
.pf-nav-tab {
    --pf-tab-ground: var(--mud-palette-background);
    --pf-tab-text: var(--mud-palette-text-primary);
    --pf-tab-text-secondary: var(--mud-palette-text-secondary);
    --pf-tab-accent: var(--mud-palette-primary);
    --pf-tab-r: clamp(14px, calc(var(--mud-default-borderradius) * 2), 22px);
}

@media (min-width: 960px) {
    /* The hairline would draw straight across the tab's open side. The shape is the edge now. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar {
        box-shadow: none;
    }

    /* A scrollbar would sit exactly between the tab and the page it is meant to join. The wheel
       still scrolls — the collapsed rail makes the same trade for the same lack of room. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu {
        scrollbar-width: none;
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu::-webkit-scrollbar {
        display: none;
    }

    /* MudBlazor clips an open group's children (`.mud-collapse-entered .mud-collapse-wrapper` is
       overflow-y: auto, which clips both axes), so the first or last page in a group lost the curve
       that reaches outside its row. Only once the group has finished opening: while it animates,
       the container's own clipping is what makes the wipe a wipe. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-collapse-entered .mud-collapse-wrapper {
        overflow: visible;
    }

    /* Room under the last row for its lower curve. Without it the curve hangs below the list, and
       a list that fitted becomes one that scrolls by a few pixels. The top needs nothing: the list
       always opens with a section label. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu:not(.mud-nav-group .mud-navmenu)::after {
        content: "";
        display: block;
        height: var(--pf-tab-r);
    }

    /* The row runs to the drawer's edge; the leading inset stays the pill's, so its icon does not
       move when it is selected. Seven classes, and the state variants eight, because MudBlazor's
       own active rules reach seven with their :hover/:focus-visible/:active forms — and the one that
       lost would repaint the tab in the sidebar's hover tint whenever the pointer crossed it.

       No background transition: the curves are drawn by the row's wrapper and appear the instant
       the class moves, so a fill fading in underneath them showed page-coloured corners hanging off
       a tab that had not arrived yet. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu .mud-nav-link {
        transition: none;
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled),
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled):is(:hover, :focus-visible, :active) {
        margin-right: 0;
        width: calc(100% - 8px);
        border-radius: var(--pf-tab-r) 0 0 var(--pf-tab-r);
        background-color: var(--pf-tab-ground);
        color: var(--pf-tab-text);
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu .mud-nav-link.active:not(.mud-nav-link-disabled) .mud-icon-root {
        color: var(--pf-tab-accent);
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-navmenu .mud-nav-link.active .pf-nav-desc {
        color: var(--pf-tab-text-secondary);
    }

    /* The two curves: a square of page colour above and below the tab's open end, with a quarter
       circle of transparency cut out of each so the sidebar shows through in a concave arc.

       On the row's wrapper rather than the link. A MudBlazor ripple host is position: relative
       *and* overflow: hidden, and a clipped box cannot draw outside itself — so the wrapper gives
       up its clipping for the selected row, and that row gives up its ripple, which would otherwise
       spill out of it. Nothing is lost: clicking the page you are already on goes nowhere.
       z-index lifts the wrapper over the rows after it: those are positioned too, and a hovered
       neighbour otherwise paints its tint across the lower curve.

       The 2px is the pill's block margin, which sits inside the wrapper (a flex container, so the
       margin does not collapse through it): the curves have to meet the tab, not the wrapper. */
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled)) {
        position: relative;
        z-index: 1;
        overflow: visible;
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled)) .mud-ripple-effect {
        display: none;
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled))::before,
    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled))::after {
        content: "";
        position: absolute;
        right: 0;
        width: var(--pf-tab-r);
        height: var(--pf-tab-r);
        pointer-events: none;
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled))::before {
        bottom: calc(100% - 2px);
        background: radial-gradient(circle at top left,
                    transparent var(--pf-tab-r), var(--pf-tab-ground) calc(var(--pf-tab-r) + 0.5px));
    }

    .pf-nav-tab .app-drawer.pf-classic-sidebar .mud-nav-item:has(> .mud-nav-link.active:not(.mud-nav-link-disabled))::after {
        top: calc(100% - 2px);
        background: radial-gradient(circle at bottom left,
                    transparent var(--pf-tab-r), var(--pf-tab-ground) calc(var(--pf-tab-r) + 0.5px));
    }
}

/* ---------- Theme cards in Administration → Themes ----------
   Four small icon buttons (default, duplicate, edit, delete) take 104px of the header, which on a
   220px card left the name column narrower than the single word in it. MudBlazor's header is a flex
   row whose content child will not shrink past min-content, so the header pushed 7px out of its own
   card. The cards are three-across from lg now rather than four, which is the actual fix; these two
   rules are the belt to that pair of braces, for a theme named something long.

   Not flex-wrap: dropping the actions onto their own line overlapped them with the description by
   4px, because the header's height does not grow to take a second line. Shrink and ellipsize. */
.pf-theme-admin-card .mud-card-header-content {
    min-width: 0;
}

.pf-theme-admin-card .mud-card-header-content .mud-typography-subtitle1 {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* An explicit app bar colour on a preview, falling back to the chrome tone when the theme only
   picks a tone. Mirrors AppTheme.AppBarOverride, which does the same on the real bar. */
.pf-theme-preview-bar {
    background: var(--pf-preview-appbar, var(--pf-chrome-bg));
}

/* An explicit sidebar colour on a preview, and marks that lift off it. Both fall back to the
   chrome tone, so a theme that only picks a tone previews exactly as before. */
.pf-theme-preview-side {
    background: var(--pf-preview-sidebar, var(--pf-chrome-bg));
}

.pf-theme-preview-dot,
.pf-theme-preview-line {
    background: var(--pf-preview-navmark, var(--pf-chrome-text-muted));
}

/* The Tab nav style in miniature — the same construction as .pf-nav-tab on the real sidebar. The
   page colour is captured on the preview's root for the same reason it is there: inside the
   sidebar, --pf-chrome-bg is the sidebar's, and the tab is the page's. Resolved where it is
   declared, so the side inherits the page value rather than re-reading its own. */
.pf-theme-preview {
    --pf-preview-page: var(--pf-preview-bg, var(--pf-chrome-bg));
}

/* The side's right padding moves onto the rows, so the selected one can reach the page. Three
   classes so the compact density's padding (also three, earlier) cannot put it back. */
.pf-theme-preview .pf-theme-preview-side.is-tab {
    padding-right: 0;
    border-right: 0;
}

.pf-theme-preview-side.is-tab .pf-theme-preview-navitem {
    position: relative;
    padding: 0.2em 0.5em 0.2em 0.3em;
}

.pf-theme-preview-side.is-tab .pf-theme-preview-navitem.is-active {
    border-radius: 0.5em 0 0 0.5em;
    background: var(--pf-preview-page);
}

.pf-theme-preview-side.is-tab .pf-theme-preview-navitem.is-active::before,
.pf-theme-preview-side.is-tab .pf-theme-preview-navitem.is-active::after {
    content: "";
    position: absolute;
    right: 0;
    width: 0.5em;
    height: 0.5em;
}

.pf-theme-preview-side.is-tab .pf-theme-preview-navitem.is-active::before {
    bottom: 100%;
    background: radial-gradient(circle at top left, transparent 0.5em, var(--pf-preview-page) calc(0.5em + 0.5px));
}

.pf-theme-preview-side.is-tab .pf-theme-preview-navitem.is-active::after {
    top: 100%;
    background: radial-gradient(circle at bottom left, transparent 0.5em, var(--pf-preview-page) calc(0.5em + 0.5px));
}

/* ---------- Theme-level grid treatments (body classes, see ThemeStyle.GridStriped/GridBordered) --
   A theme that restyles the chrome around a table but nothing about the table leaves most of a
   business app untouched. These sit underneath the per-grid switches rather than fighting them: a
   grid that sets Striped itself lands on the same shading, so the two agree instead of stacking. */

/* nth-of-type, not nth-child: MudDataGrid emits hidden filter and loading rows into the same tbody,
   and counting those makes the stripe flip halfway down the table. */
/* MudBlazor's own stripe colour, the one its `Striped` tables use, so a theme's striping and a
   grid that asked for striping itself are the same tint rather than two near-misses.
   It was `--mud-palette-background-grey` — with an E, which MudBlazor does not define (its own is
   `-gray`). An undefined custom property makes the whole declaration invalid, so the rule matched,
   applied nothing, and striping looked like a setting that did not work. Three other rules in this
   file had the same typo and were silently painting nothing. */
.pf-grid-striped .mud-table-body .mud-table-row:nth-of-type(even) {
    /* Fallback first: MudBlazor's own stripe, for anything without color-mix. */
    background-color: var(--mud-palette-table-striped);
    /* Then a tint mixed from the theme's text colour, because MudBlazor's two values are 2% black
       in light and 20% white in dark — one invisible, the other a bit loud, and a setting called
       "Striped rows" that shows nothing when you switch it on is indistinguishable from a bug.
       Mixing from the text colour follows whatever palette the theme is wearing. */
    background-color: color-mix(in srgb, var(--mud-palette-text-primary) 5%, transparent);
}

/* The hover tint has to stay visible on a striped row, or the pointer stops giving feedback on
   every second line. The table's own hover colour, so a striped row and a plain one answer the
   pointer identically. */
.pf-grid-striped .mud-table-body .mud-table-row:nth-of-type(even):hover {
    background-color: var(--mud-palette-table-hover);
}

.pf-grid-bordered .mud-table-body .mud-table-cell,
.pf-grid-bordered .mud-table-head .mud-table-cell {
    border-inline-end: 1px solid var(--mud-palette-table-lines);
}

/* The last cell in a row would otherwise draw a line down the table's own right edge, doubling it. */
.pf-grid-bordered .mud-table-body .mud-table-cell:last-child,
.pf-grid-bordered .mud-table-head .mud-table-cell:last-child {
    border-inline-end: 0;
}

/* ---------- Theme editor: the Colors tab ----------
   Two dozen colour fields is a lot of hex to read down. Two things make it navigable: a filled
   block of the actual colour beside every field, and a visible boundary around the dark-mode half,
   which is otherwise just more fields in the same column. */

.pf-color-field {
    display: flex;
    align-items: flex-start;
    gap: 10px;
}

/* The picker takes the remaining width, or a long label pushes the swatch off the row. */
.pf-color-field > .mud-input-control {
    flex: 1;
    min-width: 0;
}

.pf-color-swatch {
    flex: none;
    width: 26px;
    height: 26px;
    margin-top: 6px;
    border-radius: 6px;
    background: var(--pf-swatch, transparent);
    border: 1px solid var(--mud-palette-lines-default);
    /* An inner highlight so a swatch the same colour as the dialog still has an edge. */
    box-shadow: inset 0 0 0 1px rgb(255 255 255 / 12%);
}

/* Inherited rather than chosen: the same colour, stated differently. A dashed edge and some
   transparency separates "this is the theme's answer" from "this is my answer" without inventing a
   second colour to explain the first. */
.pf-color-swatch.is-inherited {
    border-style: dashed;
    opacity: 0.55;
    box-shadow: none;
}

/* No resolvable colour at all, which is a gradient's missing second stop. Hatched rather than
   black: an empty slot painting itself black is the exact confusion this screen exists to undo. */
.pf-color-swatch:not([style*="--pf-swatch"]) {
    background: repeating-linear-gradient(45deg,
        var(--mud-palette-lines-default) 0 2px,
        transparent 2px 5px);
}

/* The halves of the tab. Each is a panel with its own edge, so the eye can find where light stops
   instead of reading a heading and then more of the same column. */
.pf-color-group {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    padding: 0 16px 16px;
}

/* The dark half sits on a darker ground — the cheapest possible signal that these fields describe
   a different mode, and it costs no extra explanation. */
.pf-color-group-dark {
    background: color-mix(in srgb, var(--mud-palette-dark) 5%, transparent);
}

.pf-color-group-title {
    display: flex;
    align-items: center;
    gap: 8px;
}

.pf-color-group-title .mud-icon-root {
    color: var(--mud-palette-text-secondary);
}

/* Sub-group labels inside a colour panel. A rule rather than a heading: seventeen fields in a row
   need dividing more than they need announcing, so the line does the work and the word just says
   which part you are in. */
.pf-color-sub {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 4px;
    font-size: 0.72rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--mud-palette-text-secondary);
}

.pf-color-sub::after {
    content: "";
    flex: 1;
    border-top: 1px solid var(--mud-palette-lines-default);
}

/* ---- Colour families ------------------------------------------------------------------------
   One row per semantic colour. The closed state has to be worth reading on its own, or an accordion
   is just a list of words hiding the content: the swatch pair is the palette overview, and it is
   what makes collapsing the rows an improvement rather than a place to lose things. */
.pf-family-list {
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    overflow: hidden;
}

.pf-family-list .mud-expand-panel {
    background: transparent;
}

.pf-family-head {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

/* Two swatches, overlapped slightly so light and dark read as one object — a colour and its other
   half — rather than as two unrelated chips. */
.pf-family-swatches {
    display: inline-flex;
    flex: none;
}

.pf-family-swatches .pf-color-swatch {
    margin-top: 0;
}

.pf-family-swatches .pf-color-swatch + .pf-color-swatch {
    margin-left: -8px;
}

.pf-family-name {
    font-weight: 600;
    flex: none;
}

/* The help text is the first thing to go when the row runs out of width: the name and the swatches
   are what identify the row, and the sentence is a convenience. */
.pf-family-help {
    color: var(--mud-palette-text-secondary);
    font-size: 0.8rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

@media (max-width: 720px) {
    .pf-family-help {
        display: none;
    }
}

/* How many of the row's fields are filled in, so a closed row still says whether anything is inside
   it. Pushed to the end rather than sitting after the name, where it would read as part of it. */
.pf-family-count {
    margin-left: auto;
    flex: none;
    font-size: 0.72rem;
    color: var(--mud-palette-text-secondary);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 999px;
    padding: 1px 8px;
}

.pf-family-col {
    display: flex;
    flex-direction: column;
    gap: 14px;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 10px;
    padding: 12px 14px 16px;
    height: 100%;
}

.pf-family-col.is-dark {
    background: color-mix(in srgb, var(--mud-palette-dark) 5%, transparent);
}

.pf-family-col-head {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--mud-palette-text-secondary);
}

/* Only shown for a colour the derived step cannot serve, so it should read as a note rather than a
   warning — nothing is wrong, there is just something worth doing. */
.pf-family-hint {
    color: var(--mud-palette-text-secondary);
    border-left: 2px solid var(--mud-palette-lines-default);
    padding-left: 10px;
}

/* The first sub-group sits right under the panel's own caption and needs no extra air above it. */
.pf-color-group .mud-grid > .mud-grid-item:first-child .pf-color-sub {
    margin-top: 0;
}

/* ==========================================================================
   Marketing Mailer
   ========================================================================== */

/* An email rendered in a sandboxed iframe. White, fixed-height and borderless on purpose: the
   document inside paints its own page background, and anything this side drew around it would
   read as part of the email. */
.pf-mailer-preview .mud-dialog-content {
    padding: 0;
}

.pf-mailer-preview .mud-dialog-title {
    border-bottom: 1px solid var(--mud-palette-divider);
}

.pf-mailer-preview .mud-dialog-actions {
    border-top: 1px solid var(--mud-palette-divider);
    background-color: var(--mud-palette-background-gray);
}

.pf-mailer-preview-frame {
    display: block;
    width: 100%;
    height: 70vh;
    border: none;
    background-color: #fff;
}

/* The two panes of the template builder. Each scrolls on its own so a long design column cannot
   push the live preview off the screen — watching the effect while editing is the whole point. */
.pf-mailer-builder-pane {
    max-height: calc(100vh - 170px);
    overflow-y: auto;
}

.pf-mailer-builder-frame {
    width: 100%;
    height: calc(100vh - 240px);
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: 8px;
    background: #fff;
}

.pf-mailer-builder-html {
    max-height: calc(100vh - 240px);
    overflow-y: auto;
}

.pf-mailer-html-output textarea {
    font-family: monospace;
    font-size: 12px;
}

/* The native colour input, stripped of its chrome so it reads as a swatch beside its label
   rather than as a form control. */
.pf-mailer-color {
    width: 42px;
    height: 32px;
    border: none;
    background: none;
    cursor: pointer;
    padding: 0;
}

/* The tracking funnel. auto-fill rather than a fixed column count, so ten tiles reflow from one
   row to three between a wide dialog and a phone without a breakpoint for each shape. */
.pf-mailer-tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
    gap: 8px;
}

/* A resolve-dialog row holding edits that have not been saved yet. */
.pf-row-dirty > td {
    background: color-mix(in srgb, var(--mud-palette-warning) 10%, transparent);
}

/* Each tile filters the grid below it, so it is a button: it looks like an outlined card, presses
   with the keyboard, and the active filter is lifted and outlined in the theme's primary. The
   button's own font, colour and background are reset so the card look comes from these rules. */
.pf-mailer-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    width: 100%;
    padding: 8px;
    font: inherit;
    color: inherit;
    background: transparent;
    border: 1px solid var(--mud-palette-lines-default);
    border-radius: var(--mud-default-borderradius);
    cursor: pointer;
    transition: box-shadow 150ms ease, border-color 150ms ease;
}

.pf-mailer-tile:hover {
    border-color: var(--mud-palette-primary);
}

.pf-mailer-tile.is-selected {
    border-color: var(--mud-palette-primary);
    box-shadow: var(--mud-elevation-4);
}

/* A figure this campaign's sender never reports: a dashed card that does not react, so it reads as
   "not available" rather than as a nought that can be clicked. */
.pf-mailer-tile.is-unreported,
.pf-mailer-tile.is-unreported:hover {
    border-style: dashed;
    border-color: var(--mud-palette-lines-default);
    cursor: default;
}

.pf-mailer-tile:focus-visible {
    outline: 2px solid var(--mud-palette-primary);
    outline-offset: 2px;
}

.pf-mailer-tile-value {
    font-size: 1.25rem;
    font-weight: 500;
    line-height: 1.6;
}

.pf-mailer-tile-label {
    font-size: .75rem;
    color: var(--mud-palette-text-secondary);
}

/* ---------- Capturmatic: the PDF canvas and the mapper's lists ----------
   The canvas sits on the theme's grey so a white PDF page reads as a sheet in light and dark mode
   alike. The Konva layer is stacked exactly over the pdf.js canvas; both are sized by script. */

.pf-cm-canvas-scroll {
    overflow: auto;
    max-height: 75vh;
    background: var(--mud-palette-background-gray);
    border-radius: var(--mud-default-borderradius);
    text-align: center;
}

.pf-cm-canvas-stack {
    position: relative;
    display: inline-block;
    margin: 12px;
    box-shadow: var(--mud-elevation-2);
    background: #fff;
    vertical-align: top;
}

.pf-cm-canvas-stack canvas {
    display: block;
}

.pf-cm-konva {
    position: absolute;
    top: 0;
    left: 0;
}

/* The right-hand column scrolls on its own beside the page on a wide screen. */
@media (min-width: 1280px) {
    .pf-cm-side {
        max-height: calc(75vh + 56px);
        overflow: auto;
    }
}

.pf-cm-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 6px;
    padding: 6px 8px;
    border-radius: var(--mud-default-borderradius);
    cursor: pointer;
}

.pf-cm-row:hover {
    background: var(--mud-palette-action-default-hover);
}

.pf-cm-row-selected,
.pf-cm-row-selected:hover {
    background: var(--mud-palette-primary-hover);
}

.pf-cm-pre {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: .78rem;
    margin: 0;
}

.pf-cm-output {
    max-height: 320px;
    overflow: auto;
    padding: 8px;
    background: var(--mud-palette-background-gray);
    border-radius: var(--mud-default-borderradius);
}