/* ═══════════════════════════════════════════════════════════════════════════
   app.css - Blazing.UI Sample (Tier 3 application styles)

   Mobile-first: base styles target phones (single column, off-canvas sidebar,
   hamburger). min-width media queries layer on the persistent sidebar at the
   library's ResponsiveService "Large" breakpoint (992 px).

   All aliases route through --blazing-* semantic tokens so dark mode
   ([data-theme]), base palettes ([data-palette]) and accents ([data-accent])
   restyle the whole shell automatically.
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── App-level layout variables ───────────────────────────────────── */
:root {
    /* Header */
    --blz-header-bg: var(--blazing-primary);
    --blz-header-text: var(--blazing-text-inverse);
    --blz-header-hover-bg: oklch(from var(--color-white) l c h / 0.12);
    --blz-header-divider: oklch(from var(--color-white) l c h / 0.15);

    /* Sidebar */
    --blz-sidebar-width: 280px;
    --blz-sidebar-bg: var(--blazing-surface-2);
    --blz-sidebar-text: var(--blazing-text);
    --blz-sidebar-text-muted: var(--blazing-text-muted);
    --blz-sidebar-border: var(--blazing-border);
    --blz-sidebar-hover-bg: var(--blazing-surface-3);
    --blz-sidebar-hover-text: var(--blazing-text);
    --blz-sidebar-active-bg: var(--blazing-primary-tint);
    --blz-sidebar-active-text: var(--blazing-primary);
    --blz-sidebar-shadow: 2px 0 10px oklch(from var(--color-shadow) l c h / 0.1);
    --blz-sidebar-overlay: var(--blazing-overlay-modal);

    /* Layout dimensions (Tier 3 owns layout dims) */
    --blz-topnav-height: 64px;
    --blz-footer-height: 48px;
    --blz-content-max-width: 2400px;
    --blz-height-screen: 100vh;
    /* Collapsible nav reveal height - must exceed the tallest expanded group
       list (Overlays & Notifications, 6 items) so no link is clipped by overflow: hidden. */
    --blz-height-subsection-max: 100rem;
    --blz-icon-size-sm: calc(var(--blazing-space-1) * 4.4);
    --blz-width-xs: 10rem;

    /* Error boundary */
    --blz-error-boundary-bg: oklch(from var(--blazing-danger) l c h / 0.15);
}

/* ── Full-height app shell ────────────────────────────────────────── */
html,
body {
    background: var(--app-page-bg, var(--blazing-surface));
    height: 100%;
    margin: 0;
    padding: 0;
}

/* ── App shell (dogfooded BlazingLayout) ──────────────────────────────
   BlazingLayout owns the CSS grid: it sets display:grid and a grid-template computed in C# from the
   sections present (header/main/footer + the self-registering sidebar). This class (passed as its
   CssClass) only layers the shell-specific concerns the library grid does not: the viewport height and
   the theme-drawer page-background level. Scoping every shell override under .app-layout keeps the
   BlazingLayout DEMO instances (/demos/layout, contained in .app-shell-stage) untouched. */
.app-layout {
    min-block-size: var(--blz-height-screen);
    background-color: var(--app-page-bg, var(--blazing-surface));
}

/* Let the main region's 1fr grid row shrink below its content so its own overflow-y:auto engages (a grid
   item defaults to min-block-size:auto = min-content, which would otherwise grow the grid past the
   viewport and defeat the desktop height-lock). The library already sets overflow-y:auto on this region. */
.app-layout .blz-layout__main {
    min-block-size: 0;
    max-inline-size: var(--blz-content-max-width);
    /* Containing block for absolutely-positioned descendants. Visually-hidden helpers (sr-only live
       regions, hidden native inputs) use position:absolute with no inset; without a positioned ancestor
       they resolve against the viewport, land below the fold on tall pages, and inflate the document -
       adding a second, window-level scrollbar. Anchoring them here keeps them inside this scroller. */
    position: relative;
    /* Reserve the scrollbar gutter at all times so toggling the page scrollbar (e.g. when a drawer locks
       scroll) never shifts content. */
    scrollbar-gutter: stable;
    padding: var(--blazing-space-4);
    background: var(--app-page-bg, var(--blazing-surface));
}

/* While any drawer/dialog overlay is shown, lock the main region's scroll. The fixed overlay/panel are
   unaffected (they escape the content box); scrollbar-gutter keeps the reserved space so nothing shifts. */
.app-layout:has(.blazing-drawer-overlay.show) .blz-layout__main,
.app-layout:has(.blz-dialog-overlay) .blz-layout__main {
    overflow-y: hidden;
}

/* ── Page background levels (theme drawer) ────────────────────────────
   Re-point only the page background (not component surfaces) so the four
   levels add depth contrast behind cards. Light = progressively deeper grey;
   dark = progressively lighter. Level 1 is the default (no attribute). */
:root[data-bg-level="2"] {
    --app-page-bg: var(--blazing-neutral-100);
}
:root[data-bg-level="3"] {
    --app-page-bg: var(--blazing-neutral-200);
}
:root[data-bg-level="4"] {
    --app-page-bg: var(--blazing-neutral-300);
}

[data-theme="dark"][data-bg-level="2"] {
    --app-page-bg: var(--blazing-neutral-900);
}
[data-theme="dark"][data-bg-level="3"] {
    --app-page-bg: var(--blazing-neutral-800);
}
[data-theme="dark"][data-bg-level="4"] {
    --app-page-bg: var(--blazing-neutral-700);
}

/* System mode (no [data-theme]) honours the OS preference. Scoped to
   :root:not([data-theme="light"]) so an explicit Light selection still wins
   even when the OS prefers dark. Mirrors the explicit-dark levels above. */
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"])[data-bg-level="2"] {
        --app-page-bg: var(--blazing-neutral-900);
    }
    :root:not([data-theme="light"])[data-bg-level="3"] {
        --app-page-bg: var(--blazing-neutral-800);
    }
    :root:not([data-theme="light"])[data-bg-level="4"] {
        --app-page-bg: var(--blazing-neutral-700);
    }
}

/* Theme-drawer page-background swatches - preview each level for the active theme. */
.theme-drawer__bg-swatch[data-bg-level="1"] {
    background: var(--blazing-neutral-50);
}
.theme-drawer__bg-swatch[data-bg-level="2"] {
    background: var(--blazing-neutral-100);
}
.theme-drawer__bg-swatch[data-bg-level="3"] {
    background: var(--blazing-neutral-200);
}
.theme-drawer__bg-swatch[data-bg-level="4"] {
    background: var(--blazing-neutral-300);
}
[data-theme="dark"] .theme-drawer__bg-swatch[data-bg-level="1"] {
    background: var(--blazing-neutral-950);
}
[data-theme="dark"] .theme-drawer__bg-swatch[data-bg-level="2"] {
    background: var(--blazing-neutral-900);
}
[data-theme="dark"] .theme-drawer__bg-swatch[data-bg-level="3"] {
    background: var(--blazing-neutral-800);
}
[data-theme="dark"] .theme-drawer__bg-swatch[data-bg-level="4"] {
    background: var(--blazing-neutral-700);
}
@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"])
        .theme-drawer__bg-swatch[data-bg-level="1"] {
        background: var(--blazing-neutral-950);
    }
    :root:not([data-theme="light"])
        .theme-drawer__bg-swatch[data-bg-level="2"] {
        background: var(--blazing-neutral-900);
    }
    :root:not([data-theme="light"])
        .theme-drawer__bg-swatch[data-bg-level="3"] {
        background: var(--blazing-neutral-800);
    }
    :root:not([data-theme="light"])
        .theme-drawer__bg-swatch[data-bg-level="4"] {
        background: var(--blazing-neutral-700);
    }
}

/* ── Sidebar (dogfooded BlazingSidebar) ───────────────────────────────
   The sidebar is the first-party BlazingSidebar, which owns its off-canvas drawer, backdrop, header,
   scroll body and resize handle. Inside BlazingLayout it self-registers and places itself into the
   sidebar-start grid area (no grid-area hook needed here); below 992px it is an off-canvas drawer. In
   Rail - Overlay mode a closed panel docks to the icon rail and the auto-sized grid column tracks it. */

/* ── Top header ───────────────────────────────────────────────────────
   Content band inside the layout's <header> region (.blz-layout__header owns the grid placement), so no
   grid-area / sticky here - the region is a fixed grid row outside the scrolling main. */
.app-header {
    align-items: center;
    display: flex;
    gap: var(--blazing-space-3);
    background: var(--blz-header-bg);
    height: var(--blz-topnav-height);
    padding: 0 var(--blazing-space-4);
    color: var(--blz-header-text);
}

.app-header__hamburger {
    align-items: center;
    display: flex;
    flex-shrink: 0;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--blazing-radius-md);
    padding: var(--blazing-space-2);
    color: var(--blz-header-text);
    font-size: var(--blazing-text-xl);
    line-height: 1;
    cursor: pointer;
    transition:
        background var(--blazing-ease-fast),
        box-shadow var(--blazing-ease-fast);
}

.app-header__hamburger:hover {
    background: var(--blz-header-hover-bg);
}

.app-header__hamburger:focus-visible {
    box-shadow: 0 0 0 0.25rem oklch(from var(--blz-header-text) l c h / 0.5);
    outline: 0;
}

.app-header__icon-btn,
.app-header__rail-toggle {
    align-items: center;
    display: flex;
    flex-shrink: 0;
    justify-content: center;
    background: transparent;
    border: none;
    border-radius: var(--blazing-radius-md);
    padding: var(--blazing-space-2);
    color: var(--blz-header-text);
    line-height: 1;
    cursor: pointer;
    transition:
        background var(--blazing-ease-fast),
        box-shadow var(--blazing-ease-fast);
}

.app-header__icon-btn:hover,
.app-header__rail-toggle:hover {
    background: var(--blz-header-hover-bg);
}

.app-header__icon-btn:focus-visible,
.app-header__rail-toggle:focus-visible {
    box-shadow: 0 0 0 0.25rem oklch(from var(--blz-header-text) l c h / 0.5);
    outline: 0;
}

/* The desktop rail expand/collapse toggle replaces the hamburger at >=992px: the hamburger is mobile-only
   (opens the drawer) and this is desktop-only (expands/collapses the rail). Hidden on mobile here; the
   ≥992px block hides the hamburger and shows this one. */
.app-header__rail-toggle {
    display: none;
}

.app-header__title {
    margin: 0;
    min-width: 0;
    overflow: hidden;
    color: var(--blz-header-text);
    font-size: var(--blazing-text-xl);
    font-weight: var(--blazing-font-semibold);
    text-overflow: ellipsis;
    white-space: nowrap;
}

.app-header__spacer {
    flex: 1;
}

/* The header language picker keeps a compact fixed footprint so it does not crowd the title or theme
   controls; it collapses to the glyph-plus-endonym trigger on narrow viewports. */
.app-header__language {
    inline-size: 12rem;
    max-inline-size: 40vw;
}

@media (max-width: 640px) {
    .app-header__language {
        inline-size: 9rem;
    }
}

/* The icon pack switcher keeps a compact fixed footprint for the same reason as the language picker above
   - the longest pack labels ("Material Symbols", "Bootstrap Icons") must not crowd the title or the other
   header controls. */
.app-header__icon-pack {
    inline-size: 11rem;
    max-inline-size: 40vw;
}

@media (max-width: 640px) {
    .app-header__icon-pack {
        inline-size: 8rem;
    }
}

/* The theme-mode segmented control sits on the coloured header bar: its border
   and the segment hover/focus colours derive from the header text token. */
.theme-mode-selector.blz-segmented {
    border-color: oklch(from var(--blz-header-text) l c h / 0.4);
}

.theme-mode-selector .blz-segmented__segment:hover,
.theme-mode-selector .blz-segmented__segment:hover .blz-segmented__icon {
    color: var(--blz-header-text);
}

/* ── Footer ───────────────────────────────────────────────────────────
   Content band inside the layout's <footer> region (.blz-layout__footer owns the grid placement). */
.app-footer {
    align-items: center;
    display: flex;
    justify-content: space-between;
    position: relative;
    background-color: var(--blazing-surface-2);
    border-top: 1px solid var(--blazing-border);
    height: var(--blz-footer-height);
    padding: 0 var(--blazing-space-4);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-xs);
}

/* Centered "Built with ♥ powered by Blazing.Mvvm" credit, anchored to the
   footer regardless of the side spans' widths. */
.app-footer__credit {
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    display: inline-flex;
    align-items: center;
    gap: var(--blazing-space-1);
    color: var(--blazing-text-muted);
    text-decoration: none;
    white-space: nowrap;
}

.app-footer__credit:hover {
    color: var(--blazing-text);
    text-decoration: underline;
}

.app-footer__credit:focus-visible {
    outline: 0;
    box-shadow: 0 0 0 0.25rem var(--blazing-focus-ring);
    border-radius: var(--blazing-radius-sm);
}

.app-footer__heart {
    display: inline-flex;
    color: var(--blazing-danger);
}

/* The shared "heart" chrome glyph is an outline shape whose paths carry fill="none", so the credit's solid
   heart is produced by painting the glyph's interior rather than by a separate filled icon. */
.app-footer__heart svg :is(path, circle, rect, polygon) {
    fill: currentColor;
}

/* Hide the centered credit on narrow viewports where the side spans would
   collide with it. */
@media (max-width: 640px) {
    .app-footer__credit {
        display: none;
    }
}

/* ── Sidebar brand (hosted in a <BlazingSidebarHeader> region) ────────
   The brand link markup lives in MainLayout's BrandHeader fragment, rendered
   inside a <BlazingSidebarHeader> region (composition API). The library's neutral .blz-sidebar__header
   region does not reproduce the sample's primary-coloured brand bar, so this
   minimal, intentional page-scoped rule restyles only the header region of
   THIS app shell's sidebar (scoped by .app-sidebar) to the primary brand bar at the
   top-header height - matching the old .sidebar-header look. The brand-element
   styles below are retained for the relocated markup. */
.app-sidebar .blz-sidebar__header {
    display: flex;
    align-items: center;
    background-color: var(--blz-header-bg);
    border-block-end: 1px solid var(--blz-header-divider);
    block-size: var(--blz-topnav-height);
    padding-block: 0;
}

.sidebar-header__brand {
    align-items: center;
    display: flex;
    gap: var(--blazing-space-3);
    color: var(--blz-header-text);
    text-decoration: none;
}

.sidebar-header__brand:hover {
    color: var(--blz-header-text);
    opacity: var(--blazing-opacity-75);
    text-decoration: none;
}

.sidebar-header__icon {
    flex-shrink: 0;
    font-size: var(--blazing-text-2xl);
}

.sidebar-header__title {
    overflow: hidden;
    font-size: var(--blazing-text-lg);
    font-weight: var(--blazing-font-bold);
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Collapsed icon rail: show only the brand mark (the Flame) and drop the wordmark, so the header matches the
   4rem icon strip instead of clipping "BLAZING UI" to "BL". data-collapsible flips to "" the moment the
   Overlay rail hover-expands, so the full brand returns automatically then - no extra guard needed. */
.app-sidebar[data-collapsible="icon"] .blz-sidebar__header {
    justify-content: center;
    padding-inline: 0;
}

.app-sidebar[data-collapsible="icon"] .sidebar-header__title {
    display: none;
}

/* The collapsible-section content styles (.sidebar-content / .sidebar-section* /
   .sidebar-subsection* / .sidebar-nav* / .sidebar-nav-link* / .sidebar-expand-icon)
   are retired: BlazingSidebarGroup + BlazingSidebarItem now render the sections,
   styled by the library's Themes/BlazingSidebarGroup + BlazingSidebarItem Default.css
   (uppercase header + rotating chevron, 3px active border + tint, inset focus ring). */

/* ── Sidebar visual customisation (sample-scoped, matches NewFolder/OriginalSidebar.png) ──────────
   The library default is the rounded shadcn pill (inset, radius-md). This shell reproduces the original's
   full-bleed look as overrides under .app-sidebar ONLY - the library default + the SidebarDemo instances
   are untouched. The sidebar internals are library-rendered, so (CSS-isolation gotcha)
   these are GLOBAL rules, .app-sidebar-prefixed. Tokens only; no !important; AA carried by the same
   --blazing-hover / --blazing-primary / --blazing-primary-tint tokens the default uses. */

/* (1) Full-bleed rows: drop the body's inline padding so hover/active fills reach BOTH panel edges. The
   item keeps its own padding-inline:var(--blazing-space-3), which still aligns its icon with the group
   headers (also space-3). The depth indent on nested group items is unaffected (it is padding on the
   items container, measured from the now-zero body inset). */
.app-sidebar .blz-sidebar__body {
    padding-inline: 0;
}

/* (2) Square the row so the active 3px border-inline-start reads as a straight vertical bar flush to the
   panel edge (the default border-radius:md would round it). The :focus-visible / roving inset ring is a
   box-shadow, unaffected by squaring, so keyboard focus stays visible. */
.app-sidebar .blz-sidebar__item {
    border-radius: 0;
}

/* (3) Edge-to-edge divider between groups (the original separates each section with a full-width hairline).
   No trailing divider after the last group. Direct-child scope so a future nested group never draws one. */
.app-sidebar .blz-sidebar__body > .blz-sidebar__group:not(:last-child) {
    border-block-end: 1px solid var(--blazing-border);
}

/* ── Sidebar DEMO stages: contain the Overlay-rail hover-expand within the stage ───────────────────────
   The library expands an Overlay rail to `position: fixed` (correct for a viewport-level SHELL sidebar,
   where the collapsed rail already sits at the viewport edge, so expanding in place keeps the pointer over
   it). Inside a small CONTAINED demo stage (`.sidebar-stage` is position:relative + overflow:hidden) that
   fixed panel jumps to the viewport corner - the pointer is then no longer over it → pointer-leave collapses
   it → re-hover re-expands → a rapid flicker. Scoping the expand to `position: absolute` contains it to the
   stage (its own positioned ancestor), so it floats over the stage content in place, no flicker. Global rule
   (the sidebar internals are library-rendered, so this cannot live in the page's scoped .razor.css - the
   CSS-isolation gotcha), scoped to `.sidebar-stage` so the app shell + library default are untouched. */
.sidebar-stage .blz-sidebar[data-rail="overlay"][data-rail-expanded="true"] {
    position: absolute;
}

/* Same containment for the Overlay / Slide MODES and the dimmed backdrop. In a real app shell these are
   viewport-level (`position: fixed`, backdrop `inset: 0` over the whole page - correct there). In a contained
   demo stage that made the backdrop dim the ENTIRE viewport and the panel float to the viewport edge instead
   of the stage. Absolute-positioning them against the stage (its own positioned, overflow-hidden ancestor)
   makes the panel float over the stage content and the backdrop dim only the stage. The `.blz-sidebar`
   compound keeps specificity above the library's persistent-regime `position: relative`. */
.sidebar-stage .blz-sidebar.blz-sidebar--overlay,
.sidebar-stage .blz-sidebar.blz-sidebar--slide {
    position: absolute;
    /* Keep the panel ABOVE its dimmed backdrop. The library restores z-index:fixed only for a CLOSED
       (offcanvas) overlay; an OPEN overlay/slide falls back to the persistent-regime z-index:auto, so the
       backdrop (z-fixed − 1) would paint over the panel. Re-assert z-fixed so the panel sits on top. */
    z-index: var(--blazing-z-fixed);
}

.sidebar-stage .blz-sidebar__backdrop {
    position: absolute;
}

/* A CLOSED (offcanvas) overlay/slide panel is translated just off the stage edge, but the library re-adds
   its drop shadow (shadow-xl) for the float state - at the stage edge that shadow bleeds back INTO the stage
   (a soft dark gradient at the left/right edges: "bleeding from the hidden position"). A hidden panel needs no
   shadow, so suppress it while collapsed; the OPEN panel (data-collapsible="") keeps whatever shadow applies. */
.sidebar-stage .blz-sidebar[data-collapsible="offcanvas"] {
    box-shadow: none;
}

/* A right-docked PUSH sidebar is in-flow (position:relative), so the stage's flex row would place it in DOM
   order - i.e. on the LEFT, before the content - even though data-side="right". Opening it then pushed the
   content from the wrong edge (its content-resize animated left→ instead of ←right). Ordering a right-docked
   sidebar AFTER the content puts it on the right, so the content correctly shrinks from the right as it opens.
   No effect on the overlay/slide panels (they are position:absolute here, out of flow). */
.sidebar-stage .blz-sidebar[data-side="right"] {
    order: 1;
}

/* ── Error boundary ───────────────────────────────────────────────── */
.blazor-error-boundary {
    background: var(--blz-error-boundary-bg);
    border: 1px solid var(--blazing-danger);
    border-radius: var(--blazing-radius-md);
    padding: var(--blazing-space-4);
    color: var(--blazing-danger);
}

/* ── App boot loader (Wasm index.html) ────────────────────────────── */
.app-boot-center {
    align-items: center;
    display: flex;
    justify-content: center;
    background: var(--blazing-surface);
    min-height: var(--blz-height-screen);
    padding: var(--blazing-space-6);
    width: 100%;
}

.app-loading-content {
    align-items: center;
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-4);
    text-align: center;
}

.app-loading-spinner {
    border: 3px solid var(--blazing-border);
    border-radius: var(--blazing-radius-full);
    border-top-color: var(--blazing-primary);
    height: var(--blazing-height-lg);
    width: var(--blazing-height-lg);
    animation: app-boot-spin 0.75s linear infinite;
}

.app-loading-title {
    margin: 0;
    color: var(--blazing-text);
    font-size: var(--blazing-text-2xl);
    font-weight: var(--blazing-font-bold);
}

.app-loading-subtitle {
    margin: 0;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}

@keyframes app-boot-spin {
    to {
        transform: rotate(360deg);
    }
}

/* ── Blazor error UI ──────────────────────────────────────────────── */
#blazor-error-ui {
    display: none;
    bottom: 0;
    left: 0;
    position: fixed;
    right: 0;
    z-index: var(--blazing-z-fixed);
    background: var(--blazing-surface-warning);
    border-top: 1px solid var(--blazing-warning);
    box-shadow: var(--blazing-shadow-md);
    padding: var(--blazing-space-4) var(--blazing-space-6);
}

#blazor-error-ui .dismiss {
    position: absolute;
    right: var(--blazing-space-4);
    top: var(--blazing-space-4);
    cursor: pointer;
}

/* ── Home gallery ─────────────────────────────────────────────────── */
.home-menubar {
    margin-top: var(--blazing-space-4);
}

.home-toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--blazing-space-3);
    margin-top: var(--blazing-space-4);
}

.home-toolbar__filter {
    position: relative;
    flex: 1 1 16rem;
    max-width: 22rem;
}

.home-toolbar__filter-icon {
    position: absolute;
    inset-block-start: 50%;
    inset-inline-start: var(--blazing-space-3);
    display: inline-flex;
    color: var(--blazing-text-muted);
    transform: translateY(-50%);
    pointer-events: none;
}

.home-toolbar__filter .blz-input {
    width: 100%;
    padding-inline-start: var(--blazing-space-8);
}

.home-toolbar__count {
    margin: 0;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
    white-space: nowrap;
}

.home-empty {
    margin-top: var(--blazing-space-6);
    color: var(--blazing-text-muted);
}

.home-gallery-group {
    margin-top: var(--blazing-space-6);
}

.home-gallery-group__title {
    margin: 0;
    padding-bottom: var(--blazing-space-2);
    border-bottom: 1px solid var(--blazing-border);
    color: var(--blazing-text);
    font-size: var(--blazing-text-xl);
    font-weight: var(--blazing-font-semibold);
}

.home-gallery {
    display: grid;
    gap: var(--blazing-space-4);
    grid-auto-rows: 1fr;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    margin-top: var(--blazing-space-4);
}

/* Cancel the global `.blz-card + .blz-card` auto-spacing margin inside the
   gallery grid - spacing is handled by `gap`, and the leaked top margin
   otherwise shrinks/offsets every card after the first. Specificity (0,3,0)
   intentionally beats the (0,2,0) sibling rule in BlazingCard/Default.css. */
.home-gallery > .blz-card + .blz-card {
    margin-top: 0;
}

.home-gallery__card {
    position: relative;
    overflow: hidden;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    padding: var(--blazing-space-4);
    color: var(--blazing-text);
    text-decoration: none;
    transition:
        border-color var(--blazing-ease-fast),
        box-shadow var(--blazing-ease-fast);
}

.home-gallery__card:hover {
    border-color: var(--blazing-primary);
    box-shadow: var(--blazing-shadow-md);
    text-decoration: none;
}

.home-gallery__card:focus-visible {
    box-shadow: 0 0 0 0.25rem var(--blazing-focus-ring);
    outline: 0;
}

/* Decorative hero icon - full-height, right-aligned, tilted, clipped to the
   card, painted in a faint accent tint so it sits behind (and never obscures)
   the title and body text. */
.home-gallery__card-bg {
    position: absolute;
    inset: 0 -2.25rem 0 auto;
    z-index: 0;
    display: flex;
    align-items: center;
    color: var(--blazing-primary);
    opacity: 0.09;
    transform: rotate(-15deg) scale(1.45);
    transform-origin: center right;
    pointer-events: none;
    transition: opacity var(--blazing-ease-fast);
}

/* The wash is one glyph scaled to the card's height. BlazingIconView renders the SVG inside a .blz-icon
   wrapper that is 1em square by default, so the wrapper is re-sized alongside the SVG itself. */
.home-gallery__card-bg .blz-icon,
.home-gallery__card-bg .blz-icon svg {
    block-size: 100%;
    inline-size: auto;
}

.home-gallery__card:hover .home-gallery__card-bg {
    opacity: 0.16;
}

.home-gallery__card-title {
    position: relative;
    z-index: 1;
    align-items: center;
    display: flex;
    gap: var(--blazing-space-2);
    margin: 0 0 var(--blazing-space-2);
    color: var(--blazing-primary);
    font-size: var(--blazing-text-lg);
    font-weight: var(--blazing-font-semibold);
}

/* Sidebar-style leading icon - sized to the title's own line height */
.home-gallery__card-icon {
    display: inline-flex;
    flex-shrink: 0;
}

.home-gallery__card-icon svg {
    height: 1em;
    width: 1em;
}

.home-gallery__card-description {
    position: relative;
    z-index: 1;
    margin: 0;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}

/* Decorative accent arrow pinned to the card's bottom-right corner */
.home-gallery__card-arrow {
    position: relative;
    z-index: 1;
    align-self: flex-end;
    display: inline-flex;
    margin-top: auto;
    padding-top: var(--blazing-space-2);
    color: var(--blazing-primary);
}

@media (prefers-reduced-motion: reduce) {
    .home-gallery__card-bg {
        transition: none;
    }
}

/* ── Grid Demo page ────────────────────────────────────────────── */
/* Scroll frame so the virtualization demo's generated rows genuinely overflow and scroll. */
.grid-virtualized-frame {
    max-height: 420px;
    overflow: auto;
}

.grid-test-layout {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-4);
}

.grid-panels {
    align-items: start;
    display: grid;
    gap: var(--blazing-space-4);
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
}

.grid-filter-form {
    display: flex;
    flex-direction: column;
    gap: 0;
}

.grid-actions {
    display: flex;
    flex-wrap: nowrap;
    gap: var(--blazing-space-2);
}

.grid-detail-list {
    display: grid;
    gap: var(--blazing-space-2) var(--blazing-space-4);
    grid-template-columns: auto 1fr;
    margin: 0;
}

.grid-detail-list dt {
    align-self: start;
    padding-top: calc(var(--blazing-space-1) * 0.4);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
    font-weight: var(--blazing-font-semibold);
}

.grid-detail-list dd {
    margin: 0;
    color: var(--blazing-text);
    font-size: var(--blazing-text-sm);
}

/* ── Tree Grid Demo page ───────────────────────────────────────── */
/* Scroll frame so the sticky header and large hierarchies stay contained. */
.tree-grid-frame {
    max-height: 420px;
    overflow: auto;
}

/* ── Demo button modifiers (extend the library blz-btn system) ────── */
.blz-btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--blazing-text-muted);
}

.blz-btn--ghost:hover:not(:disabled) {
    background: var(--blazing-surface-2);
    border-color: var(--blazing-border);
    color: var(--blazing-text);
}

.blz-btn--full {
    width: 100%;
}

/* ── Demo / test-page layout helpers (sample-app specific) ─────────────
   These scaffold the component test pages only - they are not part of the
   Blazing.UI design system, so they live here rather than in the
   library's blazing-components.css bundle. */

.blz-demo-layout {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-6);
}

.blz-btn-grid {
    display: grid;
    gap: var(--blazing-space-2);
}

.blz-btn-stack {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-2);
}

/* The 3×3 position picker moved into the shared PlacementPicker component
   (Components/Shared/PlacementPicker.razor.css). */

/* Two-column equal-height card grid (mobile-first: single column < 860px) */
.blz-test-grid {
    display: grid;
    grid-auto-rows: 1fr;
    grid-template-columns: 1fr;
    gap: var(--blazing-space-6);
    align-items: stretch;
}

/* Cancel the adjacent-card auto-spacing inside these layouts - their own gap
   governs spacing, and a margin-top here is added on top of it, which also
   breaks the grid's equal-height stretching. The design system cancels the
   same margin for its own row primitives; these two classes are sample-local,
   so they have to opt out here. */
.blz-test-grid > .blz-card + .blz-card,
.blz-demo-layout > .blz-card + .blz-card {
    margin-top: 0;
}

@media (min-width: 860px) {
    .blz-test-grid {
        grid-template-columns: 1fr 1fr;
    }
}

.blz-form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--blazing-space-3);
}

.blz-form-row--3 {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: var(--blazing-space-4);
}

.blz-checkboxes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--blazing-space-4);
    margin-bottom: var(--blazing-space-4);
}

/* Selectable "choice cards": a BlazingCheckBox or BlazingRadioButton whose label is a
   titled card with a description. Each rule pairs the demo class with the component's
   own base class, because the themes write their appearance as compound selectors and
   a single-class rule would lose to them on specificity alone. */
.blz-choice-cards {
    display: flex;
    flex-wrap: wrap;
    gap: var(--blazing-space-3);
}

/* A radio group laid out as choice cards. The horizontal theme centres its options,
   which would leave cards with different description lengths at unequal heights. */
.blz-radio-group.blz-choice-cards {
    align-items: stretch;
}

/* The basis is deliberately narrow: these rows sit inside a demo-grid column, and a wider
   basis wraps two cards onto separate lines there while still looking fine full-width.
   min-inline-size:0 lets a long description shrink the card rather than push the line over. */
.blz-checkbox-field.blz-choice-card,
.blz-radio-field.blz-choice-card {
    align-items: flex-start;
    flex: 1 1 11rem;
    position: relative;
    min-inline-size: 0;
    padding: var(--blazing-space-4);
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-lg);
    background: var(--blazing-surface);
    transition:
        background-color var(--blazing-ease-fast),
        border-color var(--blazing-ease-fast),
        box-shadow var(--blazing-ease-fast);
}

.blz-checkbox-field.blz-choice-card:hover,
.blz-radio-field.blz-choice-card:hover {
    border-color: var(--blazing-primary);
}

/* The inset ring doubles the visible border weight, so selection reads as a thickness
   change as well as a colour change and does not rely on colour alone (WCAG 1.4.1) -
   which is what keeps the markless variant below distinguishable. */
.blz-checkbox-field.blz-choice-card.blz-checkbox-field--checked,
.blz-radio-field.blz-choice-card.blz-radio-field--checked {
    border-color: var(--blazing-primary);
    background: var(--blazing-surface-3);
    box-shadow: inset 0 0 0 1px var(--blazing-primary);
}

.blz-choice-card__title {
    display: block;
    font-weight: var(--blazing-font-semibold);
}

.blz-choice-card__desc {
    display: block;
    margin-top: var(--blazing-space-1);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-xs);
}

/* Markless variant: the card's own border and fill carry the selected state. The box or
   circle is removed visually only - the checkbox's native input still lives inside the
   box, so it is offset rather than hidden and keeps its focus and click behaviour. */
.blz-checkbox-field.blz-choice-card--markless .blz-checkbox-field__box {
    position: absolute;
    width: 1px;
    height: 1px;
    border: 0;
    overflow: hidden;
    clip-path: inset(50%);
}

.blz-radio-field.blz-choice-card--markless .blz-radio-field__circle {
    display: none;
}

/* With the box or circle no longer visible, the ring the themes draw around it has
   nothing to sit on, so the card itself carries the keyboard focus indicator. */
.blz-checkbox-field.blz-choice-card--markless:has(.blz-checkbox-field__input:focus-visible),
.blz-radio-field.blz-choice-card--markless:has(.blz-radio-field__control:focus-visible) {
    border-color: var(--blazing-primary);
    box-shadow: 0 0 0 0.25rem var(--blazing-focus-ring);
}

.blz-checkbox-field.blz-choice-card--markless.blz-checkbox-field--checked:has(.blz-checkbox-field__input:focus-visible),
.blz-radio-field.blz-choice-card--markless.blz-radio-field--checked:has(.blz-radio-field__control:focus-visible) {
    box-shadow:
        inset 0 0 0 1px var(--blazing-primary),
        0 0 0 0.25rem var(--blazing-focus-ring);
}

@media (prefers-reduced-motion: reduce) {
    .blz-checkbox-field.blz-choice-card,
    .blz-radio-field.blz-choice-card {
        transition: none;
    }
}

.blz-event-log {
    max-height: 400px;
    overflow-y: auto;
}

.blz-event-log__item {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: var(--blazing-space-2) 0;
    border-bottom: 1px solid var(--blazing-border);
    font-size: var(--blazing-text-sm);
}

.blz-event-log__item:last-child {
    border-bottom: none;
}

/* Monospace variant used by the shared EventLogPanel. */
.blz-event-log--mono {
    font-family: var(--blazing-font-mono);
    font-size: var(--blazing-text-sm);
}

.blz-note-list {
    margin: var(--blazing-space-2) 0 0;
    padding-left: var(--blazing-space-6);
}

/* ── Preview / Code playground tabs (PlaygroundTabs component) ─────── */
/* The MVVM switch sits on its own row above the strip so it never competes with the
   tab strip's horizontal overflow scrolling on narrow viewports. */
.playground-tabs__mode {
    display: flex;
    justify-content: flex-start;
    margin-bottom: var(--blazing-space-3);
}

.playground-tabs__preview {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-4);
    padding: var(--blazing-space-4);
    background: var(--blazing-surface-1);
    border-radius: var(--blazing-radius-sm);
}

.playground-tabs__code {
    display: flex;
    flex-direction: column;
    min-height: 200px;
}

.playground-tabs__toolbar {
    display: flex;
    justify-content: flex-end;
    margin-bottom: var(--blazing-space-2);
}

.playground-tabs__copy {
    display: inline-flex;
    align-items: center;
    gap: var(--blazing-space-2);
}

.playground-tabs__pre {
    flex: 1 1 auto;
    min-height: 0;
    margin: 0;
    padding: var(--blazing-space-4);
    background: var(--blazing-surface-2);
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    overflow: auto;
    font-size: var(--blazing-text-sm);
    line-height: 1.6;
    color: var(--blazing-text);
}

/* ── CodeBlock (Prism-highlighted snippet + copy button) ──────────── */
.code-block {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.code-block__toolbar {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-2);
    margin-bottom: var(--blazing-space-2);
}

.code-block__label {
    font-size: var(--blazing-text-xs);
    font-weight: 600;
    color: var(--blazing-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.code-block__copy {
    display: inline-flex;
    align-items: center;
    gap: var(--blazing-space-2);
    margin-left: auto;
}

/* The vendored Prism theme paints the dark code background + token colours; this
   only adds a border, rounds the corners and caps the height with scroll. The
   doubled class outranks the theme's pre[class*="language-"] (0,1,1) selector. */
.code-block__pre.code-block__pre {
    margin: 0;
    max-height: 28rem;
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    overflow: auto;
    font-size: var(--blazing-text-sm);
}

/* Shown in a code tab when a component genuinely has no code for that language. */
.code-block__note {
    margin: 0;
    padding: var(--blazing-space-4);
    background: var(--blazing-surface-1);
    border: 1px dashed var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
    line-height: 1.6;
}

/* ── PlaygroundView (config + preview; splitter on desktop) ───────── */
/* Mobile-first: a single stacked column with no splitter in the DOM. */
.playground-view__stack {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-5);
}

/* Events / State sections rendered beneath the configuration/preview split,
   always visible in both the desktop and mobile layouts. */
.playground-view__section {
    margin-top: var(--blazing-space-6);
}

.playground-view__section-title {
    margin: 0 0 var(--blazing-space-3);
    font-size: var(--blazing-text-lg);
    font-weight: var(--blazing-font-semibold);
    color: var(--blazing-text);
}

/* Desktop only: a positioned, fixed-height frame the BlazingSplitter measures
   (ParentContained) and fills with the absolutely-positioned resizable panes. */
.playground-view__frame {
    position: relative;
    height: clamp(28rem, 64vh, 44rem);
}

/* Splitter panes scroll internally; pad their content. */
.playground-view__pane {
    padding: var(--blazing-space-3);
}

/* ── Playground splitter handle ──────────────────────────────────────
   Style the library splitter's handle to match the MainLayout sidebar
   resizer: a transparent bar with an always-visible hairline that
   thickens and takes the accent colour on hover / focus / drag, plus a
   centred grip pill that takes the accent at the same time. The handle is
   rendered by the library component, so (CSS-isolation gotcha) these rules
   are global and scoped to .playground-view to leave other splitters be. */
.playground-view .blz-splitter__handle--default {
    background: transparent;
}

.playground-view .blz-splitter__handle::after {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    width: 2px;
    transform: translateX(-50%);
    background: var(--blazing-border);
    transition: background-color 0.15s ease, width 0.15s ease;
}

.playground-view .blz-splitter__handle:hover::after,
.playground-view .blz-splitter__handle:focus-visible::after,
.playground-view .blz-splitter__handle:active::after {
    width: 3px;
    background: var(--blazing-primary);
}

/* Grip pill matching the sidebar resizer affordance. */
.playground-view .blz-splitter__grip--vertical {
    width: 4px;
    height: 32px;
    background: var(--blazing-border-strong);
    transition: background-color 0.15s ease;
}

.playground-view .blz-splitter__handle:hover .blz-splitter__grip,
.playground-view .blz-splitter__handle:focus-visible .blz-splitter__grip,
.playground-view .blz-splitter__handle:active .blz-splitter__grip {
    background: var(--blazing-primary);
}

@media (prefers-reduced-motion: reduce) {

    .playground-view .blz-splitter__handle::after,
    .playground-view .blz-splitter__grip--vertical {
        transition: none;
    }
}

/* ── Component API documentation block ───────────────────────────── */
.component-docs__table {
    margin-top: var(--blazing-space-2);
}

.component-docs__subhead {
    margin: var(--blazing-space-5) 0 var(--blazing-space-1);
    font-size: var(--blazing-text-sm);
    font-weight: 600;
    color: var(--blazing-text-muted);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.component-docs__subhead:first-of-type {
    margin-top: var(--blazing-space-2);
}

.component-docs__table code {
    white-space: nowrap;
}

.component-docs kbd {
    display: inline-block;
    padding: 0.1rem var(--blazing-space-2);
    background: var(--blazing-surface-2);
    border: 1px solid var(--blazing-border-strong);
    border-radius: var(--blazing-radius-sm);
    box-shadow: 0 1px 0 var(--blazing-border-strong);
    font-family: var(--blazing-font-mono, monospace);
    font-size: var(--blazing-text-xs);
    color: var(--blazing-text);
    white-space: nowrap;
}

/* ── ≥ 576 px (Small): wider spacing ──────────────────────────────── */
@media (min-width: 576px) {
    .app-layout .blz-layout__main {
        padding: var(--blazing-space-6);
    }

    .app-header,
    .app-footer {
        padding-left: var(--blazing-space-6);
        padding-right: var(--blazing-space-6);
    }
}

/* ── ≥ 992 px (Large): viewport-locked shell, rail toggle replaces hamburger ───────
   At the library's "Large" breakpoint the sidebar switches from its off-canvas drawer to a persistent
   in-layout rail/panel (Rail - Overlay), so the shell only pins the grid to the viewport. The viewport-lock
   makes the main region own scrolling and keeps the full-height sidebar exactly viewport-tall; dvh handles
   mobile browser chrome. The sidebar's width (rail 4rem ↔ expanded 280px, and any resize) flows through the
   layout's auto-sized sidebar-start column automatically - no per-instance width override needed. */
@media (min-width: 992px) {
    .app-layout {
        block-size: 100dvh;
        max-block-size: 100dvh;
    }

    /* Pin the desktop shell grid: the sidebar spans the full height (brand bar at the top-left) and the
       header/footer sit only in the content column. This is done here rather than through each section's
       Inner/Outer Position because BlazingLayout stores placement in a circuit-scoped ILayoutService that the
       sample's other BlazingLayout instances (the /demos/layout shells) also write to - so relying on the
       computed template would let those demos clobber this frame. The compound .app-layout.blz-layout selector
       outranks the library's .blz-layout grid-template at equal-class specificity. Section elements still
       self-place into these named areas; the sidebar-start column stays auto so it tracks the panel width
       (the rail regime pins it to the rail width below). */
    .app-layout.blz-layout {
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-rows: auto minmax(0, 1fr) auto;
        grid-template-areas:
            "sidebar-start header"
            "sidebar-start main"
            "sidebar-start footer";
    }

    /* Rail collapse/expand via the header toggle must SNAP - the in-layout panel's inline-size drives the
       auto-sized sidebar-start column, so the library's inline-size transition would animate the main
       content sliding sideways. There is no pin here, so the content must never animate on collapse. Drop
       only the inline-size transition (keep transform); the hover-overlay panel is excluded because it
       floats over content (position:fixed, data-rail-expanded) and so never moves the content column - it
       keeps the library's smooth reveal. */
    .app-layout .app-sidebar.blz-sidebar:not([data-rail-expanded="true"]) {
        transition: transform var(--blazing-ease-normal);
    }

    /* Keep the content STILL under the hover-overlay. In the rail regime - collapsed (data-collapsible=icon)
       or transiently hover-expanded (data-rail-expanded) - the panel is position:fixed and leaves the
       auto-sized sidebar-start column empty, which would collapse it to zero and reflow the main content
       left. Pinning the column to the rail width holds the content at the rail offset so the expanded panel
       floats over it without moving it. When fully expanded (neither attribute) the column stays auto and
       tracks the panel's resized width. Overrides only grid-template-columns of the library's computed
       grid-template (rows/areas are untouched). */
    .app-layout:has(.app-sidebar[data-collapsible="icon"]),
    .app-layout:has(.app-sidebar[data-rail-expanded="true"]) {
        grid-template-columns: var(--blz-sidebar-width-icon, 4rem) minmax(0, 1fr);
    }

    /* The hamburger opens the mobile drawer; on desktop the rail expand/collapse toggle replaces it. */
    .app-header__hamburger {
        display: none;
    }

    .app-header__rail-toggle {
        display: flex;
    }
}

/* ── < 992 px: mobile off-canvas drawer (suppress the dock rail) ────────
   EnableDock's icon rail is a desktop-only affordance here. Below the sidebar's breakpoint a closed sidebar
   must be a pure off-canvas drawer - fully hidden - so the shell matches the mobile design (hamburger only,
   full-width content). Override the collapsed icon-rail (which the library otherwise keeps on screen at the
   rail width) to sit off-canvas at the full drawer width, so the hamburger slides in a clean full drawer. */
@media (max-width: 991.98px) {
    .app-sidebar.blz-sidebar[data-collapsible="icon"] {
        inline-size: var(--blz-sidebar-width, 280px);
        transform: translateX(-100%);
    }

    .app-sidebar.blz-sidebar[data-collapsible="icon"][data-side="right"] {
        transform: translateX(100%);
    }
}

/* The desktop draggable boundary (.app-sidebar-resizer) and the off-canvas scrim
   (.app-sidebar-overlay) are retired: BlazingSidebar now renders its own built-in
   resize handle (drag + keyboard + clamp + persisted width) and its own
   click-dismissible backdrop in the off-canvas drawer regime. */

/* ── Splitter demo (custom handle) ────────────────────────────────────
   Global class for the Splitter Demo "Custom Styling" demo. Splitter
   panels/handles are rendered by the library component, so a custom handle
   class cannot live in the page's scoped .razor.css (CSS isolation gotcha)
   - it must be a global rule. */
.splitter-custom-handle {
    background: var(--blazing-primary);
    cursor: col-resize;
}

/* ── Dialog demo: custom-styled dialogs (ported from BlazorDemo) ───────
   These style the library's blz-dialog parts for the Dialog Demo page's
   "Custom styled" examples. Applied via DialogOptions.Class / OverlayClass. */

/* Glassmorphism: frosted backdrop + translucent glass box. Glass is inherently
   a translucent white film, so the rgba films here are intentional, not tokens. */
.blz-demo-overlay--glass {
    background: rgba(0, 0, 0, 0.3);
    backdrop-filter: blur(6px);
}

.blz-demo-dialog--glass {
    background: rgba(255, 255, 255, 0.12);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.25);
    box-shadow: 0 25px 45px rgba(0, 0, 0, 0.25);
    color: #fff;
}

.blz-demo-dialog--glass .blz-dialog__title,
.blz-demo-dialog--glass .blz-dialog__message,
.blz-demo-dialog--glass .blz-dialog__body {
    color: #fff;
}

.blz-demo-dialog--glass .blz-dialog__close {
    color: rgba(255, 255, 255, 0.85);
}

/* "Danger Will Robinson" custom error: thick danger border + big icon. */
.blz-demo-dialog--robot {
    border: 4px solid var(--blazing-danger);
}

.blz-demo-dialog--robot .blz-dialog__title {
    color: var(--blazing-danger-dark);
}

.blz-demo-robot-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--blazing-space-3);
    text-align: center;
}

.blz-demo-robot-body svg {
    width: 6rem;
    height: 6rem;
    color: var(--blazing-danger);
}

/* Custom modal: accent gradient header band. */
.blz-demo-dialog--custom .blz-dialog__header {
    background: linear-gradient(
        135deg,
        var(--blazing-primary),
        var(--blazing-info)
    );
    color: var(--blazing-text-inverse);
}

.blz-demo-dialog--custom .blz-dialog__title {
    color: var(--blazing-text-inverse);
}

/* File-rename accent dialog. */
.blz-demo-dialog--rename {
    border: 3px solid var(--blazing-info);
}

.blz-demo-rename-body {
    display: flex;
    align-items: flex-start;
    gap: var(--blazing-space-3);
}

.blz-demo-rename-body svg {
    flex-shrink: 0;
    width: 2.5rem;
    height: 2.5rem;
    color: var(--blazing-danger);
}

/* Cookie consent banner: narrow card, icon poking above the top edge. */
.blz-demo-dialog--cookie {
    overflow: visible;
    max-inline-size: 20rem;
    text-align: center;
}

.blz-demo-dialog--cookie .blz-dialog__header {
    display: none;
}

.blz-demo-cookie-body {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--blazing-space-3);
    padding-block-start: 2.5rem;
}

.blz-demo-cookie-body svg {
    position: absolute;
    inset-block-start: -2.5rem;
    inset-inline-start: 50%;
    transform: translateX(-50%);
    width: 5rem;
    height: 5rem;
    filter: drop-shadow(0 3px 4px rgba(0, 0, 0, 0.35));
}

.blz-demo-cookie-body a {
    color: var(--blazing-primary);
    text-decoration: underline;
}

/* EULA accept checkbox row. */
.blz-demo-eula-accept {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-2);
    margin-block-start: var(--blazing-space-3);
}

/* ──────────────────────────────────────────────────────────────────────────
   E-commerce Mega Menu demo (BlazingFlyout). Unscoped so it also styles the
   flyout panel content, which is hosted in a BlazingPopup outside the page's
   CSS-isolation scope. All colours come from --blazing-* tokens.
   ────────────────────────────────────────────────────────────────────────── */

.mm-store {
    position: relative;
    block-size: 30rem;
    overflow-y: auto;
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-lg);
    background: var(--blazing-surface);
}

.mm-header {
    position: sticky;
    inset-block-start: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    gap: var(--blazing-space-4);
    padding: var(--blazing-space-3) var(--blazing-space-4);
    background: var(--blazing-surface);
    border-block-end: 1px solid var(--blazing-border);
}

.mm-brand {
    display: inline-flex;
    align-items: center;
    gap: var(--blazing-space-2);
    font-weight: var(--blazing-font-bold);
    font-size: var(--blazing-text-lg);
    color: var(--blazing-text);
}

.mm-brand__mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 1.75rem;
    block-size: 1.75rem;
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-primary);
    color: #fff;
}

.mm-nav {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-1);
}

.mm-nav-link {
    display: inline-flex;
    align-items: center;
    gap: var(--blazing-space-1);
    padding: var(--blazing-space-2) var(--blazing-space-3);
    border: 0;
    border-radius: var(--blazing-radius-sm);
    background: transparent;
    color: var(--blazing-text);
    font: inherit;
    font-weight: var(--blazing-font-medium);
    cursor: pointer;
}

.mm-nav-link:hover {
    background: var(--blazing-surface-2);
}

.mm-actions {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-2);
    margin-inline-start: auto;
}

.mm-search {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-2);
    padding: var(--blazing-space-2) var(--blazing-space-3);
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-full);
    color: var(--blazing-text-muted);
    background: var(--blazing-surface-2);
}

.mm-search input {
    border: 0;
    outline: 0;
    background: transparent;
    color: var(--blazing-text);
    font: inherit;
    inline-size: 9rem;
}

.mm-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface);
    color: var(--blazing-text);
    cursor: pointer;
}

.mm-icon-btn:hover {
    background: var(--blazing-surface-2);
}

.mm-cart-badge {
    position: absolute;
    inset-block-start: -0.375rem;
    inset-inline-end: -0.375rem;
    min-inline-size: 1.1rem;
    padding: 0 0.25rem;
    border-radius: var(--blazing-radius-full);
    background: var(--blazing-primary);
    color: #fff;
    font-size: var(--blazing-text-xs);
    line-height: 1.1rem;
    text-align: center;
}

.mm-store__body {
    padding: var(--blazing-space-6);
    color: var(--blazing-text-muted);
}

.mm-hero {
    display: grid;
    place-items: center;
    block-size: 16rem;
    margin-block-end: var(--blazing-space-6);
    border-radius: var(--blazing-radius-lg);
    background: linear-gradient(135deg, var(--blazing-surface-2), var(--blazing-surface));
    border: 1px dashed var(--blazing-border);
    text-align: center;
}

/* The mega-menu panel (rendered inside the flyout). No padding of its own - the flyout's
   .blz-popup__content inset is the sole outer band (same convention as the Flyout demo panels). */
.mm-panel {
    display: grid;
    grid-template-columns: 13rem minmax(0, 1fr) 15rem;
    gap: var(--blazing-space-5);
    min-inline-size: 0;
}

.mm-sidebar {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-1);
    padding-inline-end: var(--blazing-space-4);
    border-inline-end: 1px solid var(--blazing-border);
}

.mm-cat {
    display: flex;
    align-items: center;
    gap: var(--blazing-space-2);
    padding: var(--blazing-space-2) var(--blazing-space-3);
    border: 0;
    border-radius: var(--blazing-radius-md);
    background: transparent;
    color: var(--blazing-text);
    font: inherit;
    font-weight: var(--blazing-font-medium);
    text-align: start;
    cursor: pointer;
}

.mm-cat__icon {
    display: inline-flex;
    color: var(--blazing-text-muted);
}

.mm-cat:hover,
.mm-cat:focus-visible {
    background: var(--blazing-surface-2);
    outline: 0;
}

.mm-cat.is-active {
    background: color-mix(in oklch, var(--blazing-primary), transparent 80%);
    color: var(--blazing-primary);
}

.mm-cat.is-active .mm-cat__icon {
    color: var(--blazing-primary);
}

.mm-cat__chev {
    margin-inline-start: auto;
    color: var(--blazing-text-muted);
}

.mm-main {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
    /* Cap the cards column so the sidebar and promo stay static and only the
       product list scrolls (was previously the flyout's MaxPanelHeight on the whole body). */
    max-block-size: 22rem;
}

.mm-main__head {
    flex: 0 0 auto;
    margin-block-end: var(--blazing-space-3);
}

.mm-main__title {
    margin: 0;
    font-size: var(--blazing-text-lg);
    font-weight: var(--blazing-font-bold);
    color: var(--blazing-text);
}

.mm-main__tagline {
    margin: 0;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}

.mm-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: var(--blazing-space-2);
}

/* Playground preview only: the flyout panel is inline-size:max-content, so a bare grid
   shrink-wraps and PanelWidthMode (which only raises a width ceiling) appears to do nothing.
   Declaring a width that tracks the mode's inherited --blz-flyout-max budget makes the panel
   fill each cap exactly and the auto-fill grid reflows more columns as the mode widens.
   --blz-flyout-max is unset for Auto, hence the 40rem fallback. Subtract the panel's fixed
   inset (2px border + .blz-popup__content 2×1rem padding = ~34px) so the grid lands on the
   body's content box instead of overflowing it into a horizontal scrollbar. */
.mm-grid--fill {
    inline-size: calc(min(var(--blz-flyout-max, 40rem), calc(100vw - 4rem)) - 2rem - 2px);
    /* auto-fit (vs the base .mm-grid auto-fill) collapses empty trailing tracks so the cards
       stretch to fill the widened panel instead of leaving dead space at Full/X-Wide. */
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
}

/* Only the cards grid inside the capped main column scrolls; matches the flyout's
   themed thin scrollbar. Scoped to .mm-main so the playground preview grid is unaffected. */
.mm-main .mm-grid {
    flex: 1 1 auto;
    min-block-size: 0;
    overflow-y: auto;
    padding-inline-end: var(--blazing-space-2);
    scrollbar-width: thin;
    scrollbar-color: var(--blazing-border) transparent;
    overscroll-behavior: contain;
}

.mm-main .mm-grid::-webkit-scrollbar {
    inline-size: 0.5rem;
}

.mm-main .mm-grid::-webkit-scrollbar-thumb {
    background: var(--blazing-border);
    border-radius: var(--blazing-radius-full);
}

.mm-main .mm-grid::-webkit-scrollbar-thumb:hover {
    background: var(--blazing-text-muted);
}

.mm-card {
    display: flex;
    align-items: flex-start;
    gap: var(--blazing-space-3);
    padding: var(--blazing-space-3);
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface);
    color: var(--blazing-text);
    text-align: start;
    font: inherit;
    cursor: pointer;
    transition: border-color var(--blazing-ease-fast), transform var(--blazing-ease-fast);
}

.mm-card:hover,
.mm-card:focus-visible {
    border-color: var(--blazing-primary);
    transform: translateY(-2px);
    outline: 0;
}

.mm-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    inline-size: 2.25rem;
    block-size: 2.25rem;
    border-radius: var(--blazing-radius-md);
    background: color-mix(in oklch, var(--blazing-primary), transparent 88%);
    color: var(--blazing-primary);
}

.mm-card__text {
    display: flex;
    flex-direction: column;
    min-inline-size: 0;
}

.mm-card__name {
    font-weight: var(--blazing-font-semibold);
}

.mm-card__desc {
    margin-block-start: 0.125rem;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-xs);
}

.mm-card__count {
    margin-block-start: var(--blazing-space-1);
    color: var(--blazing-primary);
    font-size: var(--blazing-text-xs);
    font-weight: var(--blazing-font-medium);
}

.mm-promo {
    display: flex;
    flex-direction: column;
    align-self: start;
    overflow: hidden;
    border-radius: var(--blazing-radius-lg);
    border: 1px solid var(--blazing-border);
}

.mm-promo__art {
    block-size: 7rem;
}

.mm-promo__body {
    display: flex;
    flex-direction: column;
    gap: var(--blazing-space-2);
    padding: var(--blazing-space-4);
    background: var(--blazing-surface);
}

.mm-promo__eyebrow {
    color: var(--blazing-primary);
    font-size: var(--blazing-text-xs);
    font-weight: var(--blazing-font-bold);
    text-transform: uppercase;
    letter-spacing: 0.04em;
}

.mm-promo__title {
    margin: 0;
    font-size: var(--blazing-text-base);
    font-weight: var(--blazing-font-bold);
    color: var(--blazing-text);
}

.mm-promo__desc {
    margin: 0;
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}

/* Tablet: drop the promo to a full-width row beneath the sidebar + grid. */
@media (max-width: 64rem) {
    .mm-panel {
        grid-template-columns: 12rem minmax(0, 1fr);
    }

    /* Below this width the promo wraps to its own row, so the desktop "static sidebar, only cards
       scroll" model no longer applies. Drop the cards' internal cap so the flyout body (capped to the
       viewport by MaxPanelHeight) is the single scroll container - avoids a nested double scrollbar. */
    .mm-main {
        max-block-size: none;
    }

    .mm-main .mm-grid {
        overflow-y: visible;
        padding-inline-end: 0;
    }

    .mm-promo {
        grid-column: 1 / -1;
        flex-direction: row;
    }

    .mm-promo__art {
        block-size: auto;
        inline-size: 10rem;
        flex-shrink: 0;
    }
}

/* Mobile: stack everything; the category sidebar becomes a horizontal scroller. */
@media (max-width: 40rem) {
    .mm-panel {
        grid-template-columns: 1fr;
    }

    .mm-sidebar {
        flex-direction: row;
        flex-wrap: nowrap;
        overflow-x: auto;
        padding-inline-end: 0;
        padding-block-end: var(--blazing-space-2);
        border-inline-end: 0;
        border-block-end: 1px solid var(--blazing-border);
    }

    .mm-cat {
        flex-shrink: 0;
    }

    .mm-cat__chev {
        display: none;
    }

    .mm-promo {
        flex-direction: column;
    }

    .mm-promo__art {
        inline-size: auto;
        block-size: 6rem;
    }

    .mm-search input {
        inline-size: 6rem;
    }
}

/* ── Tooltip demo page ─────────────────────────────────────────────────── */

/* 12-position grid laid around a centre target (Position demo). 5 columns,
   blank corners - mirrors the PlacementLayouts.PopupLocation cell order. */
.tooltip-position-grid {
    display: grid;
    grid-template-columns: repeat(5, minmax(0, 1fr));
    gap: var(--blazing-space-2);
    align-items: center;
    justify-items: center;
    max-inline-size: 34rem;
}

.tooltip-position-grid > * {
    inline-size: 100%;
}

/* Each edge cell centres its tooltip button within the grid track. */
.tooltip-position-grid__cell {
    display: flex;
    justify-content: center;
}

/* The centre target spans the inner 3×3 region (cols 2–4, rows 2–4). */
.tooltip-position-grid__target {
    grid-column: 2 / 5;
    grid-row: 2 / 5;
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 4rem;
    border: 1px dashed var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface-2);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}

/* Larger hover target for the cursor-follow demo. */
.tooltip-follow-area {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 8rem;
    border: 1px dashed var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface-2);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
    cursor: crosshair;
}

/* Avatar chip used in the rich-template tooltip. */
.tooltip-avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: 2.5rem;
    block-size: 2.5rem;
    flex-shrink: 0;
    border-radius: var(--blazing-radius-full);
    background: var(--blz-tooltip-bg, var(--blazing-surface-3));
    color: inherit;
}

/* ──────────────── Interaction-primitive demo surfaces ──────────────── */

/* A plain block for the press-feedback demos to play on, so the effect is judged on its own rather
   than through a control's own hover and active styling. */
.ripple-swatch {
    display: flex;
    align-items: center;
    justify-content: center;
    min-block-size: 3rem;
    padding-block: var(--blazing-space-2);
    padding-inline: var(--blazing-space-4);
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface-2);
    color: var(--blazing-text);
    user-select: none;
}

/* Layer mode puts the effect inside a surface the page owns, so the surface supplies the
   positioning context the layer stretches across. */
.ripple-swatch--host {
    position: relative;
    cursor: pointer;
}

/* The region a focus-trap demo contains focus within, outlined so the boundary being demonstrated
   is visible rather than implied. */
.trap-region {
    padding: var(--blazing-space-4);
    border: 2px dashed var(--blazing-primary);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface-2);
}

.trap-region--nested {
    border-style: solid;
    border-color: var(--blazing-warning);
}

/* A bounded stage for the container-anchored speed dial, so it pins to a card corner instead of
   escaping to the viewport corner and overlapping the page chrome. */
.fab-menu-stage {
    position: relative;
    block-size: 16rem;
    border: 1px solid var(--blazing-border);
    border-radius: var(--blazing-radius-md);
    background: var(--blazing-surface-2);
    overflow: hidden;
}

/* Four stages in a 2x2, each pairing a direction with the placement that gives it room to open. The
   minmax(0, …) track keeps a stage from being forced wider than its share by the menu inside it, which is
   what stops the inline directions pushing the grid into a horizontal scroll. */
.fab-menu-directions {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--blazing-space-3);
}

/* Tall enough for the trigger, the gap and all three actions - a shorter stage clips the last one against
   its own overflow and the direction reads as having fewer actions than it has. */
.fab-menu-directions .fab-menu-stage {
    block-size: 13rem;
}

.fab-menu-direction__caption {
    display: block;
    margin-block-end: var(--blazing-space-1);
    color: var(--blazing-text-muted);
    font-size: var(--blazing-text-sm);
}
