/* /Layout/MainLayout.razor.rz.scp.css */
/* ── App shell: the concourse gantry ──────────────────────────────────────
   The header is the brushed-steel frame the boards hang in. They
   are the only steel on the page besides board rails, which is what makes the
   frame read as structure rather than decoration.

   Contrast note: steel is a light surface, so all text on it is near-black
   (#14161a on the mid-steel #6E747D ≈ 5.6:1). Do not put --steel-dark or any
   of the board's dim greys on the rails — they were computed for flap-black. */

/* The gantry is bolted, like the reference board's frame. The bolt heads are a background
   layer at the rail's two ends rather than pseudo-elements, because ::before/::after on a
   sticky header collide with the reveal shadow below. */
.app-header[b-duu2a000yx] {
    position: sticky;
    top: 0;
    z-index: 100;
    background-image:
        radial-gradient(circle at 11px 50%, rgba(0,0,0,0.55) 0 2.5px, rgba(255,255,255,0.28) 2.6px 3.4px, transparent 3.5px),
        radial-gradient(circle at calc(100% - 11px) 50%, rgba(0,0,0,0.55) 0 2.5px, rgba(255,255,255,0.28) 2.6px 3.4px, transparent 3.5px),
        var(--steel-face);
    border-bottom: 1px solid rgba(0,0,0,0.65);
    box-shadow: 0 2px 10px rgba(0,0,0,0.6);
}

/* The board hangs off the gantry: a dark reveal under the steel. */
.app-header[b-duu2a000yx]::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: -3px;
    height: 3px;
    background: linear-gradient(180deg, rgba(0,0,0,0.55), transparent);
    pointer-events: none;
}

/* .mock-data-banner is NOT styled here. MockDataBanner is a child component, so Blazor's
   scoped-CSS attribute never lands on its markup and every rule in this file misses it —
   the same trap already documented for .app-nav__user. Its rules live in app.css. */

/* Radzen used to paint anchors out from under this rule. It now loads into the
   `vendor` cascade layer (see css/foundation.css), so these no longer need
   !important — they win on ordinary source order. Keep them: the rail is steel and
   must never inherit the board's amber link colour. */
.app-header a[b-duu2a000yx],
.app-header a:link[b-duu2a000yx],
.app-header a:visited[b-duu2a000yx],
.app-header a:hover[b-duu2a000yx] {
    background: transparent;
    box-shadow: none;
    text-decoration: none;
}

/* 1fr | auto | 1fr: the links own the centre track, so nothing arriving in the end cell after
   first paint (cost chip, signed-in name, jobs tray) can move them — while there is room.
   The side tracks never shrink below their content: a plain 1fr let the end cell (jobs tray +
   cost + env badge + sign-out) get narrower than what it holds, and its flex-end content then
   spilled LEFT over the links ("JOBS 1" printed on top of VIDEO). When the row runs short the
   links now give up exact centring instead of being painted over. */
.app-nav[b-duu2a000yx] {
    display: grid;
    grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
    align-items: center;
    padding: 0 1.25rem;
    height: 60px;
    max-width: var(--rail-max);
    margin: 0 auto;
    width: 100%;
    gap: 0.75rem;
}

.app-nav__brand[b-duu2a000yx] {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    justify-self: start;
    text-decoration: none;
    background: transparent;
}

.app-nav__end[b-duu2a000yx] {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 0.4rem;
    min-width: 0;
}

/* The wordmark sits in real flap cells, so the brand is the board naming itself. FlapText is a
   child component, so these need ::deep — without it they never matched, and the wordmark
   rendered at full flap size and pushed the nav off a phone screen. */
.app-nav__brand[b-duu2a000yx]  .flapstrip { gap: 1px; }
.app-nav__brand[b-duu2a000yx]  .flap { font-size: var(--text-sm); min-width: 0.7em; padding: 0.24em 0.1em; }

.app-nav__brand-icon[b-duu2a000yx] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    background: var(--flap-black);
    color: var(--amber);
    flex-shrink: 0;
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.6);
}

/* Link, user-name and sign-out styling is global (app.css): NavMenu and LoginDisplay render
   those elements, and this scoped sheet cannot reach them. Copies of those rules used to sit
   here, matching nothing.

   No min-height on .app-main: body already paints the board ground, and the old
   calc(100vh - 60px) ignored the mock banner (and a phone's URL bar), so every page scrolled
   by the banner's 29px even when it had nothing below the fold. */

/* ── Session status: jobs, cost, environment ─────────────────────────────── */
.app-nav__status[b-duu2a000yx] { display: flex; align-items: center; gap: 0.4rem; }

.app-nav__env-badge[b-duu2a000yx] {
    background: var(--flap-black);
    color: var(--amber);
    border: 1px solid rgba(0,0,0,0.5);
    border-radius: 0;
    padding: 0.15rem 0.5rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
}

/* The cost counter sits on steel, so it needs the dark cell. On the rail it is one line:
   the flap digits must not wrap (they stacked three rows deep and shoved the nav into the
   brand), and the op count moves to the breakdown popover it opens. */
.app-nav__status[b-duu2a000yx]  .session-cost {
    background: var(--flap-black);
    color: var(--flap-white);
    box-shadow: inset 0 0 0 1px rgba(0,0,0,0.5);
    white-space: nowrap;
}

.app-nav__status[b-duu2a000yx]  .session-cost .flapstrip { flex-wrap: nowrap; }

.app-nav__status[b-duu2a000yx]  .session-cost__meta { display: none; }

/* The rail now also carries session status, so the signed-in name yields first when the
   row runs short; Sign out stays. */
@media (max-width: 1399.98px) {
    .app-nav[b-duu2a000yx]  .app-nav__user { display: none; }
}

/* ── Narrow widths ────────────────────────────────────────────────────────
   Below md the feature links leave the rail for the bottom tab bar (NavMenu.razor.css), so the
   rail is just brand + status. Between md and ~1100px the links stay, and the wordmark yields —
   up to 1100 rather than lg (1024) because with the jobs tray and cost meter showing, 1024–1100
   was still ~40px short and clipped GALLERY. */
@media (max-width: 1099.98px) {
    .app-nav__brand[b-duu2a000yx]  .flapstrip { display: none; }
}

@media (max-width: 767.98px) {
    .app-nav[b-duu2a000yx] { grid-template-columns: auto 1fr; padding: 0 0.6rem; height: 54px; gap: 0.4rem; }
    .app-nav__env-badge[b-duu2a000yx] { display: none; }
    .app-nav__brand[b-duu2a000yx]  .flapstrip { display: flex; }
}

/* Only the narrowest phones drop the wordmark; with the links in the tab bar, a 390px rail has
   room for it beside the status cell. */
@media (max-width: 359.98px) {
    .app-nav__brand[b-duu2a000yx]  .flapstrip { display: none; }
}

/* ── ErrorBoundary fallback (shared Po convention) ─────────────────────────── */
.po-error-panel[b-duu2a000yx] {
    margin: 1rem auto;
    max-width: 40rem;
    padding: 1rem 1.25rem;
    border: 1px solid var(--po-error-border, #b3261e);
    border-radius: 0.5rem;
    background: var(--po-error-bg, rgba(179, 38, 30, 0.08));
    color: inherit;
}

.po-error-panel h2[b-duu2a000yx] {
    margin: 0 0 0.5rem;
    font-size: 1.1rem;
}

.po-error-panel p[b-duu2a000yx] {
    margin: 0;
}
/* /Layout/NavMenu.razor.rz.scp.css */
/* Individual link styling is global (app.css .app-nav__link) because these anchors are
   rendered by NavLink inside this component and this scoped sheet cannot reach them without
   ::deep. Only the rail's own layout, and the phone tab bar, belong here. */

/* `safe center` + overflow-x: if the rail ever runs short again (a longer cost figure, another
   status chip), the links scroll inside their own track instead of spilling over the brand or
   the status cell. Plain `center` would push the first link off the left edge, unreachable. */
.app-nav__links[b-jaxy1nnjrx] {
    display: flex;
    align-items: center;
    gap: 0.125rem;
    min-width: 0;
    justify-content: safe center;
    overflow-x: auto;
    scrollbar-width: none;
}

.app-nav__links[b-jaxy1nnjrx]  .app-nav__link-icon { display: none; }

/* md–lg: six labels plus the status cell can outrun a 768px rail. Scroll inline rather than
   wrap, with an edge fade so the overflow reads as deliberate. */
@media (max-width: 1023.98px) {
    .app-nav__links[b-jaxy1nnjrx] {
        justify-content: flex-start;
        overflow-x: auto;
        scrollbar-width: none;
        mask-image: linear-gradient(to right, transparent 0, black 8px, black calc(100% - 8px), transparent 100%);
    }
}

/* ── Phone: bottom tab bar ────────────────────────────────────────────────
   On a phone the rail has room for the brand and the session status, not six destinations.
   The wrap/scroll fallback that lived here left ONE label visible at 390px (the rest scrolled
   off behind the wordmark), and none once the cost chip appeared. A fixed bar under the thumb
   shows all six. --tabbar-h (app.css) is what the sticky CTA and body padding clear. */
@media (max-width: 767.98px) {
    .app-nav__links[b-jaxy1nnjrx] {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
        z-index: 1040;
        height: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px));
        padding-bottom: env(safe-area-inset-bottom, 0px);
        display: grid;
        grid-template-columns: repeat(6, minmax(0, 1fr));
        gap: 0;
        overflow: visible;
        mask-image: none;
        background: var(--steel-face);
        border-top: 1px solid rgba(0,0,0,0.65);
        box-shadow: 0 -2px 10px rgba(0,0,0,0.6);
    }

    .app-nav__links[b-jaxy1nnjrx]  .app-nav__link {
        flex-direction: column;
        justify-content: center;
        gap: 0.25rem;
        height: 100%;
        padding: 0 0.1rem;
        font-size: var(--text-3xs);
        letter-spacing: 0.06em;
        border-bottom: 0;
        border-top: 2px solid transparent;
        overflow: hidden;
    }

    .app-nav__links[b-jaxy1nnjrx]  .app-nav__link.active { border-top-color: var(--amber); }

    .app-nav__links[b-jaxy1nnjrx]  .app-nav__link-icon { display: block; font-size: 1.15rem; line-height: 1; }
}
/* /Pages/BulkGenerate/BulkGallery.razor.rz.scp.css */
/* Scoped CSS for BulkGallery.razor.

   This is the surface the chosen world was picked for: ten slots that each move
   independently from QUEUED through WORKING to DONE or FAILED. So it is built as
   the departures board proper — a fixed grid of numbered bays, each one a flap
   cell that turns in place. Columns never move; only the cells' contents change.

   The old sheet leaned on light-mode fallbacks (#f8fafc, #f1f5f9, #334155) that
   are invisible on flap-black, and on a blue→violet gradient fill. Both are gone.

   Status colour law, shared with the rest of the app and never extended:
     steel-dark = QUEUED · amber = WORKING · flap-white = DONE · red = FAILED
   Every state also carries its own word, so the board reads in monochrome. */

/* ── Empty state: an unlit board waiting for a service ─────────────────────
   Solid rule, not dashed. In this world a dashed edge means "you can drop a file
   here", and it is used by the intake zone directly above this panel. This bay is
   an output area — giving it the same dashed edge invited a drop that does nothing. */
.bulk-empty-state[b-6z247pq4c3] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: var(--spacing-xl) var(--spacing-lg);
    text-align: center;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    min-height: 260px;
}

.bulk-empty-state__icon[b-6z247pq4c3] { font-size: var(--text-3xl); color: var(--steel-dark); margin-bottom: var(--spacing-md); }

.bulk-empty-state__title[b-6z247pq4c3] {
    font-family: var(--font-flap);
    font-size: var(--text-md);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--flap-white);
    margin-bottom: 0.5rem;
}

.bulk-empty-state__hint[b-6z247pq4c3] { font-size: var(--text-md); color: var(--steel); max-width: 42ch; margin: 0 auto; line-height: 1.55; }

/* ── Board progress header ───────────────────────────────────────────────── */
.bulk-progress-header[b-6z247pq4c3] {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
    margin-bottom: 0.4rem;
    font-variant-numeric: tabular-nums;
}

/* A strip of unlit flaps that light amber as slots complete. */
.bulk-progress-bar[b-6z247pq4c3] {
    height: 10px;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border-radius: 0;
    overflow: hidden;
    background-image: repeating-linear-gradient(90deg, transparent 0 7px, var(--cell-split) 7px 8px);
}

.bulk-progress-bar__fill[b-6z247pq4c3] {
    height: 100%;
    width: 100%;
    background: var(--amber);
    border-radius: 0;
    transform: scaleX(var(--progress, 0));
    transform-origin: left center;
    transition: transform var(--motion-slow) var(--motion-ease-in-out);
    will-change: transform;
}

/* ── The board grid (virtualized — see <Virtualize> in the component) ────── */
.bulk-gallery[b-6z247pq4c3] {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 1px;
    background: var(--rule);
    margin-top: 0;
    scrollbar-gutter: stable;
}

/* Portrait: two bays across keeps the images large enough to judge. A single
   column would push slot 10 far below the fold on a phone. */
@media (max-width: 639.98px) {
    .bulk-gallery[b-6z247pq4c3] { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 479.98px) {
    .bulk-gallery[b-6z247pq4c3] { grid-template-columns: 1fr; }
}

/* ── A bay ───────────────────────────────────────────────────────────────── */
.bulk-slot[b-6z247pq4c3] {
    border-radius: 0;
    overflow: hidden;
    background: var(--flap-black);
    border: 1px solid transparent;
    transition: border-color var(--motion-fast);
}

.bulk-slot:focus-within[b-6z247pq4c3] { outline: 2px solid var(--amber); outline-offset: -2px; }

.bulk-slot--pending[b-6z247pq4c3]  { }
.bulk-slot--processing[b-6z247pq4c3] { border-color: var(--amber); }
.bulk-slot--complete[b-6z247pq4c3] { }
.bulk-slot--failed[b-6z247pq4c3]   { border-color: var(--cancel-red); }

/* Bay header: the position number in its own cell, status on the right. */
.bulk-slot__header[b-6z247pq4c3] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    padding: 0.35rem 0.5rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    border-bottom: 1px solid var(--rule);
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
}

.bulk-slot__num[b-6z247pq4c3] {
    font-weight: 700;
    color: var(--flap-white);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.08em;
}

.bulk-slot__status--queued[b-6z247pq4c3] { font-size: var(--text-2xs); }

/* ── Image bay (1:1, so the grid's rows stay aligned across the board) ───── */
.bulk-slot__img-area[b-6z247pq4c3] {
    aspect-ratio: 1 / 1;
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    overflow: hidden;
}

.bulk-slot__img-area img[b-6z247pq4c3] {
    width: 100%;
    height: 100%;
    object-fit: cover;
    cursor: zoom-in;
    /* The finished image is revealed, not scaled on hover: a photo that jumps
       under the cursor makes a ten-up comparison harder to read. */
    opacity: 0;
    animation: slotReveal-b-6z247pq4c3 var(--motion-slow) var(--motion-ease-in-out) forwards;
}

@keyframes slotReveal-b-6z247pq4c3 { to { opacity: 1; } }

/* A bay that lands flips over like a board leaf. fx.js hears the animation start and clacks. */
.bulk-slot__img-area--complete[b-6z247pq4c3] { animation: bulkFlip-b-6z247pq4c3 560ms var(--motion-ease-in-out) both; }

@keyframes bulkFlip-b-6z247pq4c3 {
    from { transform: perspective(700px) rotateY(-90deg); filter: brightness(0.35); }
    to   { transform: perspective(700px) rotateY(0deg);   filter: brightness(1); }
}

@media (prefers-reduced-motion: reduce) {
    .bulk-slot__img-area--complete[b-6z247pq4c3] { animation: none; }
}

.bulk-slot__img-area img:focus-visible[b-6z247pq4c3] { outline: 2px solid var(--amber); outline-offset: -2px; }

.bulk-slot__img-area--pending[b-6z247pq4c3] {
    color: var(--steel-dark);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    letter-spacing: 0.16em;
}

.bulk-slot__img-area--processing[b-6z247pq4c3] {
    flex-direction: column;
    gap: 0.5rem;
    color: var(--amber);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
}

.bulk-slot__img-area--failed[b-6z247pq4c3] {
    color: var(--cancel-red-tx);
    font-size: var(--text-xs);
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.75rem;
    text-align: center;
    line-height: 1.4;
}

/* The working indicator is the board's row lamp, not a spinning ring. */
.bulk-slot__spinner[b-6z247pq4c3] {
    width: 26px;
    height: 10px;
    border: none;
    border-radius: 0;
    background: radial-gradient(circle at 50% 50%, var(--amber) 0 22%, transparent 24%) 0 0 / 5px 5px;
    animation: lampRun-b-6z247pq4c3 1.1s steps(5, end) infinite;
}

@keyframes lampRun-b-6z247pq4c3 {
    0%   { background-position: 0 0; opacity: 1; }
    50%  { opacity: 0.45; }
    100% { background-position: 25px 0; opacity: 1; }
}

/* A queued bay: the unlit sweep crossing dark flaps. */
.bulk-slot__shimmer[b-6z247pq4c3] {
    width: 100%;
    height: 100%;
    background-color: var(--flap-shadow);
    background-image: linear-gradient(90deg,
        transparent 0%, rgba(255,180,0,0.14) 45%, rgba(255,180,0,0.14) 55%, transparent 100%);
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: bulk-shimmer-b-6z247pq4c3 1.5s linear infinite;
}

@keyframes bulk-shimmer-b-6z247pq4c3 {
    0%   { background-position: 220% 0; }
    100% { background-position: -120% 0; }
}

/* ── Bay footer ──────────────────────────────────────────────────────────── */
.bulk-slot__footer[b-6z247pq4c3] {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.4rem;
    padding: 0.35rem 0.5rem;
    min-height: 2.2rem;
    border-top: 1px solid var(--rule);
}

.bulk-slot__prompt-preview[b-6z247pq4c3] {
    font-size: var(--text-xs);
    color: var(--steel-dark);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 60%;
}

.bulk-slot__actions[b-6z247pq4c3] { display: flex; gap: 1px; flex-shrink: 0; }

/* ── Inline bay actions: three outlined cells, one grammar ───────────────── */
.btn-dl-slot[b-6z247pq4c3],
.btn-heart-slot[b-6z247pq4c3],
.btn-reroll-slot[b-6z247pq4c3] {
    background: transparent;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    color: var(--steel-dark);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 0.22rem 0.45rem;
    cursor: pointer;
    transition: border-color var(--motion-fast), color var(--motion-fast);
    flex-shrink: 0;
}

.btn-dl-slot:hover[b-6z247pq4c3],
.btn-heart-slot:hover[b-6z247pq4c3],
.btn-reroll-slot:hover:not(:disabled)[b-6z247pq4c3] { border-color: var(--amber); color: var(--amber); background: transparent; }

.btn-dl-slot:focus-visible[b-6z247pq4c3],
.btn-heart-slot:focus-visible[b-6z247pq4c3],
.btn-reroll-slot:focus-visible[b-6z247pq4c3] { outline: 2px solid var(--amber); outline-offset: 2px; }

/* A kept variation is a confirmed departure: the flap turns amber and stays. */
.btn-heart-slot--active[b-6z247pq4c3] {
    color: var(--flap-black);
    border-color: var(--amber);
    background: var(--amber);
}

.btn-heart-slot--active:hover[b-6z247pq4c3] { color: var(--flap-black); background: var(--flap-white); border-color: var(--flap-white); }

.btn-reroll-slot:disabled[b-6z247pq4c3] { opacity: 0.5; cursor: not-allowed; }

/* ── Re-roll panel: a supplementary service, shown under the main board ──── */
.bulk-reroll-panel[b-6z247pq4c3] {
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: var(--spacing-md);
    box-shadow: var(--shadow-md);
}

.bulk-reroll-grid[b-6z247pq4c3] { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 1px; }

.bulk-reroll-cell[b-6z247pq4c3] {
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border: 1px solid transparent;
    border-radius: 0;
    overflow: hidden;
    transition: border-color var(--motion-fast);
}

.bulk-reroll-cell:hover[b-6z247pq4c3] { border-color: var(--steel-dark); }
.bulk-reroll-cell img[b-6z247pq4c3] { width: 100%; aspect-ratio: 1 / 1; object-fit: cover; display: block; }
/* /Pages/Login.razor.rz.scp.css */
/* Scoped styles for Login.razor — the ticket window before the concourse.

   The card is a steel-framed board with a single dark bay in it. The old 3px
   gradient stripe across the top is gone: a coloured hairline above a panel is
   the AI side-tab tell, and this world already marks a panel by framing it. */

.login-page[b-ah696wlyti] {
    min-height: 80vh;
    display: flex;
    align-items: center;
    justify-content: center;
}

.login-card[b-ah696wlyti] {
    background: var(--flap-black);
    border: 1px solid var(--steel-dark);
    border-radius: 0;
    padding: 0;
    width: 100%;
    max-width: 400px;
    box-shadow: var(--shadow-xl);
    position: relative;
    overflow: hidden;
}

/* The card's own steel rail carries the title, matching every other board. */
.login-card__rail[b-ah696wlyti] {
    background: var(--steel-face);
    border-bottom: 1px solid rgba(0,0,0,0.6);
    padding: 0.7rem 1.1rem;
}

.login-card__body[b-ah696wlyti] { padding: 1.5rem 1.5rem 1.75rem; }

.login-title[b-ah696wlyti] {
    font-family: var(--font-flap);
    font-size: var(--text-md);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    margin: 0;
    color: #14161a;
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
}

.login-subtitle[b-ah696wlyti] {
    font-size: var(--text-md);
    color: var(--steel);
    margin: 0 0 1.25rem;
    line-height: 1.55;
}

/* Microsoft sign-in keeps its own brand plate — a white flap, deliberately the
   one light cell on the page, because it represents someone else's identity. */
.btn-ms-signin[b-ah696wlyti] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.5rem;
    background: var(--flap-white);
    color: #14161a;
    border: 1px solid var(--flap-white);
    border-radius: 0;
    padding: 0.7rem 1rem;
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    text-decoration: none;
    transition: background var(--motion-fast), border-color var(--motion-fast);
}

.btn-ms-signin:hover[b-ah696wlyti] {
    background: var(--amber);
    border-color: var(--amber);
    color: var(--flap-black);
}

.login-divider[b-ah696wlyti] {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    color: var(--steel-dark);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.2em;
    margin: 1.25rem 0;
}

.login-divider[b-ah696wlyti]::before,
.login-divider[b-ah696wlyti]::after {
    content: '';
    flex: 1;
    height: 1px;
    background: var(--rule-strong);
}

.btn-anon-signin[b-ah696wlyti] {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    background: transparent;
    color: var(--steel);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 0.7rem 1rem;
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    text-decoration: none;
    transition: border-color var(--motion-fast), color var(--motion-fast);
}

.btn-anon-signin:hover[b-ah696wlyti] {
    background: transparent;
    border-color: var(--amber);
    color: var(--amber);
}

/* A guest pass is provisional work, so it takes amber — the working colour. */
.anon-badge[b-ah696wlyti] {
    font-family: var(--font-flap);
    font-size: var(--text-3xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--flap-black);
    background: var(--amber);
    border: none;
    border-radius: 0;
    padding: 0.1rem 0.35rem;
    margin-left: 0.4rem;
}
/* /Pages/NotFound.razor.rz.scp.css */
/* Scoped styles for NotFound.razor. Inline styles are forbidden (NET_RULES §4), so the
   404's composition lives here.

   The oversized de-emphasised glyph is gone: this world announces a dead route the same
   way the board announces a dropped service — with the word CANCELLED in red, then the
   destination that isn't running. That is more legible than a faded 4rem icon and it
   reuses a status vocabulary the user has already seen on every other page. */

.not-found[b-v2en5wj55g] {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.85rem;
    max-width: 46ch;
    margin: 0 auto;
    padding: 1.5rem;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    box-shadow: var(--shadow-md);
}

.not-found__title[b-v2en5wj55g] { margin: 0; }

.not-found__hint[b-v2en5wj55g] {
    margin: 0;
    color: var(--steel);
    font-size: var(--text-lg);
    line-height: 1.6;
}

.not-found__back[b-v2en5wj55g] { align-self: flex-start; text-decoration: none; }
/* /Pages/RapRoast.razor.rz.scp.css */
/* Scoped styles for RapRoast.razor.

   Every colour resolves from the board tokens. The previous sheet hard-coded slate
   greys (#475569, #334155) that were computed for a light page; on flap-black they
   fall to ~2:1. Contrast floors used here, all against --flap-shadow #1B1B1E:
     --flap-white 17:1 · --steel 9.4:1 · --steel-dark 4.8:1 · --amber 10:1

   The `border-left: 4px` accent on .roast-refused is gone. A thick coloured edge on
   a callout is the AI side-tab tell; the refusal now reads as a full amber-framed
   cell, which is also how every other state in this world announces itself. */

.roast-disclaimer[b-er5f24d7zj] { margin-top: 0.6rem; font-size: var(--text-xs); color: var(--steel-dark); }
.roast-disclaimer summary[b-er5f24d7zj] { cursor: pointer; color: var(--steel); width: fit-content; }
.roast-disclaimer summary:hover[b-er5f24d7zj] { color: var(--amber); }
.roast-disclaimer[open] summary[b-er5f24d7zj] { margin-bottom: 0.35rem; }

/* Explicit toggle. A parental-advisory sticker rather than a switch, because that is the
   universally read symbol for exactly this setting — and it earns the row without adding
   another lamp that would read as a fourth intensity stop. */
.roast-explicit[b-er5f24d7zj] {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin-top: 0.6rem;
    padding: 0.45rem 0.7rem;
    border: 1px solid var(--rule-strong);
    background: var(--flap-black);
    cursor: pointer;
}

.roast-explicit input[b-er5f24d7zj] { accent-color: var(--amber); width: 1rem; height: 1rem; flex: none; }

.roast-explicit__badge[b-er5f24d7zj] {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.35rem;
    height: 1.35rem;
    border: 1px solid var(--steel-dark);
    color: var(--steel-dark);
    font-size: var(--text-xs);
    font-weight: 700;
    line-height: 1;
}

.roast-explicit__text[b-er5f24d7zj] { display: flex; flex-direction: column; line-height: 1.25; }
.roast-explicit__name[b-er5f24d7zj] { color: var(--steel); font-size: var(--text-sm); }
.roast-explicit__meta[b-er5f24d7zj] { color: var(--steel-dark); font-size: var(--text-xs); }

.roast-explicit--on[b-er5f24d7zj] { border-color: var(--amber); }
.roast-explicit--on .roast-explicit__badge[b-er5f24d7zj] { border-color: var(--amber); color: var(--amber); }
.roast-explicit--on .roast-explicit__name[b-er5f24d7zj] { color: var(--flap-white); }

/* Filter report. Deliberately unalarming when clean — a green-on-black "0%" is a receipt,
   not a warning, and only the amber frame appears once a gate has actually bitten. */
.roast-filters[b-er5f24d7zj] {
    margin-top: 0.75rem;
    padding: 0.65rem 0.75rem;
    border: 1px solid var(--rule-strong);
    background: var(--flap-black);
}

.roast-filters--hit[b-er5f24d7zj] { border-color: var(--amber); }

.roast-filters__headline[b-er5f24d7zj] {
    margin: 0 0 0.5rem;
    font-size: var(--text-xs);
    color: var(--steel);
}

.roast-filters__pct[b-er5f24d7zj] {
    font-size: var(--text-sm);
    font-weight: 700;
    color: var(--steel);
    margin-right: 0.3rem;
}

.roast-filters--hit .roast-filters__pct[b-er5f24d7zj] { color: var(--amber); }

.roast-filters__list[b-er5f24d7zj] { list-style: none; margin: 0; padding: 0; }

.roast-filters__row[b-er5f24d7zj] {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.2rem 0;
    font-size: var(--text-xs);
    color: var(--steel-dark);
    border-top: 1px solid var(--rule);
}

.roast-filters__gate[b-er5f24d7zj] { color: var(--steel); min-width: 6.5rem; }
.roast-filters__verdict[b-er5f24d7zj] { margin-left: auto; text-transform: uppercase; letter-spacing: 0.05em; }
.roast-filters__row.is-rejected .roast-filters__verdict[b-er5f24d7zj] { color: var(--amber); }
.roast-filters__row.is-passed .roast-filters__verdict[b-er5f24d7zj] { color: var(--steel-dark); }

.roast-filters__session[b-er5f24d7zj] {
    margin: 0.5rem 0 0;
    font-size: var(--text-xs);
    color: var(--steel-dark);
}

.roast-player[b-er5f24d7zj] {
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 1rem;
}

.roast-player__audio[b-er5f24d7zj] { width: 100%; }

/* A refusal is a cancelled row, so it takes the cancelled colour — no side tab. */
.roast-refused[b-er5f24d7zj] {
    display: flex;
    align-items: flex-start;
    gap: 0.6rem;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border: 1px solid var(--cancel-red);
    border-radius: 0;
    padding: 0.85rem 1rem;
    color: var(--cancel-red-tx);
}

.roast-softened[b-er5f24d7zj] { margin-top: 0.65rem; color: var(--steel); }

.roast-lyrics[b-er5f24d7zj] {
    margin-top: 1.25rem;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 1rem 1.25rem;
}

.roast-lyrics__heading[b-er5f24d7zj] {
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
    margin-bottom: 0.6rem;
}

/* Lyrics are pre-formatted: section tags and line breaks carry meaning, so whitespace
   is preserved, but long bars still wrap rather than scrolling the page sideways. */
.roast-lyrics__text[b-er5f24d7zj] {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: var(--font-ui);
    font-size: var(--text-lg);
    line-height: 1.7;
    color: var(--flap-white);
    max-width: 60ch;
    margin: 0;
}

.roast-seen[b-er5f24d7zj] { margin-top: 1rem; font-size: var(--text-md); }

.roast-seen summary[b-er5f24d7zj] {
    cursor: pointer;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
}

.roast-seen summary:hover[b-er5f24d7zj] { color: var(--amber); }

.roast-subject[b-er5f24d7zj] {
    margin: 0 0 1rem;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 0.75rem;
    text-align: center;
}

/* Capped by viewport height as well as width: a tall portrait photo would otherwise
   push the audio player and the bars entirely below the fold. */
.roast-subject__img[b-er5f24d7zj] {
    max-width: 100%;
    max-height: 340px;
    width: auto;
    border-radius: 0;
    display: block;
    margin: 0 auto;
    background: var(--flap-shadow);
}

.roast-subject__caption[b-er5f24d7zj] {
    margin-top: 0.5rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
}

.roast-seen__filtered[b-er5f24d7zj] { color: var(--steel-dark); font-weight: 400; }

.roast-seen__thin[b-er5f24d7zj] { margin-top: 0.5rem; font-size: var(--text-xs); color: var(--steel); }

/* Structured scene slots. A definition list is the correct semantic — each label
   genuinely describes its value — and it gives screen readers the pairing for free.
   Visually it is the board's two-column manifest. */
.scene-slots[b-er5f24d7zj] {
    display: grid;
    grid-template-columns: minmax(90px, auto) 1fr;
    gap: 0.4rem 0.85rem;
    margin: 0.5rem 0 0;
    font-size: var(--text-sm);
}

.scene-slots dt[b-er5f24d7zj] {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
}

.scene-slots dd[b-er5f24d7zj] { margin: 0; color: var(--steel); }

/* OCR lines are quoted verbatim off the image, so they sit in monospaced cells —
   measurement, not costume: this is literal text read out of the photograph. */
.scene-slots__chip[b-er5f24d7zj] {
    display: inline-block;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border: none;
    border-radius: 0;
    padding: 0.1rem 0.4rem;
    margin: 0 0.25rem 0.25rem 0;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: var(--text-xs);
    color: var(--flap-white);
}

@media (max-width: 479.98px) {
    .scene-slots[b-er5f24d7zj] { grid-template-columns: 1fr; gap: 0.1rem 0; }
    .scene-slots dd[b-er5f24d7zj] { margin-bottom: 0.5rem; }
}

/* ── Sync nudge ──────────────────────────────────────────────────────────────
   Sits with the player, not the lyrics, because it corrects the track's alignment
   rather than the words. Only rendered once the karaoke driver reports it is live. */

.roast-sync[b-er5f24d7zj] {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: center;
    gap: 0.5rem 0.85rem;
    margin-top: 0.85rem;
    padding-top: 0.85rem;
    border-top: 1px solid var(--rule);
}

.roast-sync__label[b-er5f24d7zj] {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
    white-space: nowrap;
}

.roast-sync__value[b-er5f24d7zj] { display: block; color: var(--amber); letter-spacing: 0.08em; }
.roast-sync__range[b-er5f24d7zj] { width: 100%; accent-color: var(--amber); }
.roast-sync__range:focus-visible[b-er5f24d7zj] { outline: 2px solid var(--amber); outline-offset: 3px; }

.roast-sync__hint[b-er5f24d7zj] {
    grid-column: 1 / -1;
    font-size: var(--text-xs);
    line-height: 1.5;
    color: var(--steel-dark);
}

/* ── The script ──────────────────────────────────────────────────────────────
   One bar per row, each a button that seeks the track. The lit row is amber; rows
   already performed drop to steel-dark (4.8:1 on the cell — still AA for body text)
   so the eye lands on what is being said now without the past going unreadable. */

.roast-script[b-er5f24d7zj] {
    list-style: none;
    margin: 0;
    padding: 0.25rem 0 0;
    max-height: 22rem;
    overflow-y: auto;
    scrollbar-width: thin;
}

.roast-script__row[b-er5f24d7zj] { margin: 0; }

.roast-script__line[b-er5f24d7zj] {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: none;
    border-left: 2px solid transparent;
    padding: 0.22rem 0.6rem;
    font-family: var(--font-ui);
    font-size: var(--text-lg);
    line-height: 1.65;
    color: var(--steel);
    cursor: pointer;
    transition: color var(--motion-fast), border-color var(--motion-fast);
}

.roast-script__line:disabled[b-er5f24d7zj] { cursor: default; color: var(--flap-white); }
.roast-script__line:not(:disabled):hover[b-er5f24d7zj] { color: var(--flap-white); border-left-color: var(--steel-dark); }
.roast-script__line:focus-visible[b-er5f24d7zj] { outline: 2px solid var(--amber); outline-offset: -2px; }
.roast-script__line.is-past[b-er5f24d7zj] { color: var(--steel-dark); }

.roast-script__line.is-active[b-er5f24d7zj] {
    color: var(--amber);
    border-left-color: var(--amber);
    font-weight: 600;
    /* --roast-beat is the track's live kick (0–1), published per frame by roastStage.js. */
    transform: translateX(calc(var(--roast-beat, 0) * 6px));
    text-shadow: 0 0 calc(var(--roast-beat, 0) * 16px) rgba(255, 180, 0, 0.75);
}

.roast-script__section[b-er5f24d7zj] {
    margin: 0.9rem 0 0.25rem;
    padding: 0 0.6rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.18em;
    color: var(--steel-dark);
}

.roast-script__section:first-child[b-er5f24d7zj] { margin-top: 0; }

/* ── Export ──────────────────────────────────────────────────────────────────
   The recording is a real-time capture, so the progress bar is measuring playback,
   not encoding. The copy says so — a bar that looks like a normal export would read
   as hung when it is simply listening to a 30-second track. */

.roast-export[b-er5f24d7zj] {
    margin-top: 1rem;
    padding: 0.6rem 0.75rem;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
}

.roast-export__label[b-er5f24d7zj] {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
}

.roast-export__actions[b-er5f24d7zj] { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem; }

.roast-export__style[b-er5f24d7zj] { width: auto; min-width: 0; flex: 1 1 14rem; max-width: 20rem; }

.roast-export__progress[b-er5f24d7zj] { margin-top: 0.75rem; }

.roast-export__bar[b-er5f24d7zj] {
    height: 4px;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
}

.roast-export__bar[b-er5f24d7zj]::before {
    content: "";
    display: block;
    height: 100%;
    width: var(--export, 0%);
    background: var(--amber);
    transition: width var(--motion-base) linear;
}

.roast-export__pct[b-er5f24d7zj] {
    display: block;
    margin-top: 0.4rem;
    font-size: var(--text-xs);
    color: var(--steel);
}

@media (prefers-reduced-motion: reduce) {
    .roast-export__bar[b-er5f24d7zj]::before { transition: none; }
}

/* ── Result: media beside the bars ──────────────────────────────────────────
   Stacked below lg, in the order you use it: photo, track, exports, then the bars. */
@media (min-width: 1024px) {
    .roast-result[b-er5f24d7zj] {
        display: grid;
        grid-template-columns: minmax(0, 5fr) minmax(0, 6fr);
        column-gap: var(--space-4);
        align-items: start;
    }

    /* The column's first block owns the top edge, so the two columns start level. */
    .roast-result__script > :first-child[b-er5f24d7zj] { margin-top: 0; }
}
/* /Shared/AiServicePicker.razor.rz.scp.css */
/* Scoped CSS for AiServicePicker.razor — the routing panel.

   This is where the product tells you which machine handles each step, so it is
   built as the board's patch list: one ruled row per capability, label left,
   selected service right. PRODUCT.md's third principle (cost and execution
   location stay visible) is why this panel is legible rather than tucked away. */

.ai-picker[b-dx24rw2mip] {
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
    background: var(--flap-black);
    box-shadow: var(--shadow-md);
}

.ai-picker__legend[b-dx24rw2mip] {
    font-family: var(--font-flap);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: #14161a;
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
    background: var(--steel-face);
    border-bottom: 1px solid rgba(0,0,0,0.6);
    padding: 0.5rem 0.85rem;
    width: 100%;
    float: none;
}

/* Label above selector, always. These option strings are long ("Azure Computer Vision —
   Fastest, uses your API key"), and a side-by-side grid truncated every one of them to
   "Azure Computer Visi⌄" inside the Studio's 360px column. A stacked row costs one line
   and shows the whole value, which is the point of disclosing the provider at all. */
.ai-picker__row[b-dx24rw2mip] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0.2rem;
    padding: 0.45rem 0.85rem;
    border-bottom: 1px solid var(--rule);
}

.ai-picker__row:last-of-type[b-dx24rw2mip] { border-bottom: none; }

.ai-picker__label[b-dx24rw2mip] {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
}

.ai-picker__select[b-dx24rw2mip] {
    width: 100%;
    min-width: 0;
    text-overflow: ellipsis;
    padding: 0.4rem 0.6rem;
    font-family: var(--font-ui);
    font-size: var(--text-sm);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--flap-white);
    transition: border-color var(--motion-fast);
}

.ai-picker__select:hover[b-dx24rw2mip] { border-color: var(--steel-dark); }

.ai-picker__select:focus-visible[b-dx24rw2mip] {
    outline: 2px solid var(--amber);
    outline-offset: 1px;
    border-color: var(--amber);
}

.ai-picker__select:disabled[b-dx24rw2mip] {
    background: var(--flap-black);
    color: var(--steel-dark);
    cursor: not-allowed;
}

/* Where the model actually runs. Amber, because execution location is the
   thing this panel exists to disclose. */
.ai-picker__device[b-dx24rw2mip] {
    margin: 0;
    padding: 0.35rem 0.85rem 0.6rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--amber);
}

/* ── Single-provider capabilities ─────────────────────────────────────────────
   Stated on one collapsed line instead of four disabled selects. See the component
   for why: a control that cannot be operated is a fact, and facts do not need a
   full-width form control each. */
.ai-picker__fixed[b-dx24rw2mip] {
    border-top: 1px solid var(--color-border, rgba(255,255,255,0.08));
    margin-top: 0.35rem;
    padding-top: 0.4rem;
}

.ai-picker__fixed-summary[b-dx24rw2mip] {
    cursor: pointer;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--steel);
    padding: 0.5rem 0.25rem;
    list-style: none;
}

.ai-picker__fixed-summary[b-dx24rw2mip]::-webkit-details-marker { display: none; }

.ai-picker__fixed-summary[b-dx24rw2mip]::before {
    content: "▸ ";
    color: var(--amber);
}

.ai-picker__fixed[open] .ai-picker__fixed-summary[b-dx24rw2mip]::before { content: "▾ "; }

.ai-picker__fixed-summary:hover[b-dx24rw2mip] { color: var(--flap-white); }

.ai-picker__fixed-summary:focus-visible[b-dx24rw2mip] {
    outline: 2px solid var(--amber);
    outline-offset: 2px;
}

.ai-picker__fixed-list[b-dx24rw2mip] {
    list-style: none;
    margin: 0;
    padding: 0.15rem 0.25rem 0.35rem;
}

.ai-picker__fixed-item[b-dx24rw2mip] {
    display: flex;
    justify-content: space-between;
    gap: 0.75rem;
    padding: 0.3rem 0;
    font-size: var(--text-xs);
    line-height: 1.3;
}

.ai-picker__fixed-label[b-dx24rw2mip] {
    color: var(--steel);
    text-transform: uppercase;
    font-family: var(--font-flap);
    letter-spacing: 0.08em;
}

.ai-picker__fixed-value[b-dx24rw2mip] { color: var(--flap-white); text-align: right; }
/* /Shared/FeatureShell.razor.rz.scp.css */
/* The three measures, as CSS Grid rather than a 12-column flex grid.

   Grid is the right tool here and the old markup was using the wrong one: a
   `row`/`col-lg-7 col-md-9` pair expresses "seven twelfths at large, nine twelfths
   at medium", which is a proportion of a container nobody chose. What the page
   actually wants is "as wide as reads well, centred" — a measure, which `max-width`
   states directly and a column count only approximates. */

.feature-shell[b-cq3qcttah0] {
    /* One padding decision for every feature page. MainLayout no longer adds its own,
       so this is the only vertical rhythm at the top of a feature. */
    width: 100%;
    max-width: var(--rail-max);
    margin-inline: auto;
    padding-inline: var(--space-3);
}

/* ── Focused ──────────────────────────────────────────────────────────────
   Setting the column from the text it holds — instead of from a twelfth of a
   viewport — is why this stays comfortable from a 1280px laptop to a 4K monitor,
   where `col-lg-7` would have grown to a 900px line nobody can track.

   The measure is on the SHELL, not on the body: constraining only the body left the
   page title ranged full-width above a centred column, an L-shape with the heading
   detached from the thing it titles. A measure is a column of content, and the
   heading is the top of that column. */
.feature-shell--focused[b-cq3qcttah0] {
    max-width: 46rem;
}

/* From lg the focused pages are two columns themselves — the upload panel puts the photo
   beside its options, Rap Roast puts the track beside the bars — so the measure is two
   comfortable columns rather than one 46rem column with half the screen empty beside it. */
@media (min-width: 1024px) {
    .feature-shell--focused[b-cq3qcttah0] { max-width: 72rem; }
}

/* ── Split ────────────────────────────────────────────────────────────────
   The rail is sized to its contents (an upload panel and its controls), not to a
   fraction; the workspace takes the rest. minmax(0, 1fr) rather than 1fr because a
   grid item's automatic minimum size is its content, and a wide result image inside
   the workspace would otherwise push the whole layout past the viewport. */
.feature-shell__split[b-cq3qcttah0] {
    display: grid;
    gap: var(--space-4);
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 1024px) {
    .feature-shell__split[b-cq3qcttah0] { grid-template-columns: minmax(280px, 22rem) minmax(0, 1fr); }
}

.feature-shell__rail[b-cq3qcttah0] { min-width: 0; }
.feature-shell__work[b-cq3qcttah0] { min-width: 0; }

/* ── Header ───────────────────────────────────────────────────────────────
   One line: title and the model badge. Wraps under the title on a phone. */
.feature-head[b-cq3qcttah0] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.35rem var(--space-3);
    margin-bottom: var(--space-3);
}

/* ── Vertical rhythm ─────────────────────────────────────────────────────── */
.feature-shell[b-cq3qcttah0] { padding-block: var(--space-4) var(--space-5); }

@media (max-width: 639.98px) {
    .feature-shell[b-cq3qcttah0] { padding-inline: var(--space-2); padding-block: var(--space-3) var(--space-4); }
}
/* /Shared/JobTray.razor.rz.scp.css */
.job-tray[b-imn12rnown] { position: relative; }

.job-tray__toggle[b-imn12rnown] {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    background: none;
    border: 1px solid var(--rule-strong);
    color: var(--flap-white);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    padding: 0.35rem 0.6rem;
    cursor: pointer;
}
.job-tray__toggle:hover[b-imn12rnown],
.job-tray__toggle:focus-visible[b-imn12rnown] { border-color: var(--amber); }

.job-tray__badge[b-imn12rnown] {
    min-width: 1.2rem;
    padding: 0 0.3rem;
    text-align: center;
    background: var(--flap-white);
    color: var(--flap-black);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}
.job-tray__badge--working[b-imn12rnown] { background: var(--amber); }

.job-tray__panel[b-imn12rnown] {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    z-index: 1050;
    width: min(340px, calc(100vw - 1.5rem));
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.85);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--flap-white);
}

.job-tray__list[b-imn12rnown] { list-style: none; margin: 0; padding: 0; max-height: 60vh; overflow-y: auto; }
.job-tray__row[b-imn12rnown] { padding: 0.65rem 0.75rem; border-bottom: 1px solid var(--rule-strong); }
.job-tray__row:last-child[b-imn12rnown] { border-bottom: 0; }
.job-tray__main[b-imn12rnown] { display: flex; justify-content: space-between; align-items: center; gap: 0.5rem; }
.job-tray__name[b-imn12rnown] { font-weight: 700; }
.job-tray__detail[b-imn12rnown] { margin-top: 0.3rem; color: var(--steel-dark); text-transform: none; letter-spacing: 0.02em; }
.job-tray__actions[b-imn12rnown] { display: flex; gap: 0.4rem; margin-top: 0.5rem; }

@media (max-width: 575.98px) {
    .job-tray__label[b-imn12rnown] { display: none; }
}
/* /Shared/MyImagesGallery.razor.rz.scp.css */

/* Gallery unreachable — the amber warning state. Distinct from the empty state next to it,
   because "no images yet" and "cannot reach your images" must not look the same. */
.gallery-bar--error[b-roaeppb5wj] { border-color: var(--amber-dim, rgba(255,180,0,0.4)); }

.gallery-bar__empty-hint--error[b-roaeppb5wj] { color: var(--amber); }
