/* ═══════════════════════════════════════════════════════════════════════════
   PoRedoImage — COMPONENTS
   Seed key 11888fdd. The world is a railway departure board: matte flap faces in
   ruled cells, brushed-steel frames, paint-white condensed caps, amber for work in
   progress, red for failure.

   Tokens, reset, grid and utilities live in css/foundation.css and load first.
   This sheet is the board's own furniture and nothing else.

   Rules this sheet holds to, so later edits do not drift out of the world:
     · Corners are square. --radius-* exist only because older markup names them.
     · No glass, no backdrop blur as decoration, no gradient text, no colored glow.
     · Steel and lamp are the only gradients, and both are material, not ornament.
     · Status is carried by four values only: STEEL=queued, AMBER=working,
       WHITE=done, RED=failed. Never invent a fifth.
     · One authored motion: the flap cascade. Everything else is a 120ms state change.
     · Every font-size is a --text-* step. Every breakpoint is one of the four.
     · No !important. The cascade layers below are what make that possible: a
       component rule beats a utility because of its LAYER, not its specificity.
   ═══════════════════════════════════════════════════════════════════════════ */

@layer components {
/* ═══ Materials ══════════════════════════════════════════════════════════
   Three reusable surfaces. Everything else is composed from these.        */
/* A steel-framed board panel. */
.board,
.upload-panel,
.card,
.diag-stat,
.shimmer-wrapper,
.meme-template-card,
.send-to,
.gallery-bar {
    position: relative;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    box-shadow: var(--shadow-md);
}

/* The steel top rail that names a board. */
.card-header {
    background: var(--steel-face);
    color: #14161a;
    font-family: var(--font-flap);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: var(--text-sm);
    padding: 0.55rem 0.9rem;
    border: none;
    border-bottom: 1px solid rgba(0,0,0,0.6);
    border-radius: 0;
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
}

/* ═══ Flap character cells ═══════════════════════════════════════════════
   FlapText renders one .flap per character. The cascade below is this
   build's single authored motion moment: characters settle in sequence,
   from an already-visible default, with an exponential ease-out.          */
/* Cells wrap. They must: flap cells are fixed-width, so a long destination like
   "IMAGE REGENERATION" is ~330px of cells and ran off the right edge of a 390px
   phone viewport. A real board wraps or splits a long name too — what it never
   does is let the row scroll sideways. */
.flapstrip {
    display: inline-flex;
    flex-wrap: wrap;
    gap: 2px;
    row-gap: 3px;
    min-width: 0;
    font-family: var(--font-flap);
    font-weight: 700;
    text-transform: uppercase;
    line-height: 1;
    vertical-align: baseline;
}

.flap {
    --i: 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 0.72em;
    padding: 0.16em 0.06em;
    background: var(--flap-shadow);
    color: var(--flap-white);
    box-shadow: var(--inset-cell);
    /* The split across the middle of every physical flap. */
    background-image: linear-gradient(to bottom,
        transparent calc(50% - 1px),
        var(--cell-split) calc(50% - 1px),
        var(--cell-split) calc(50% + 1px),
        transparent calc(50% + 1px));
    animation: flapSettle 320ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
    animation-delay: calc(var(--i) * 26ms);
    transform-origin: center top;
}

.flap--gap {
    background: none;
    box-shadow: none;
    background-image: none;
    min-width: 0.34em;
    animation: none;
}

@keyframes flapSettle {
    from { transform: rotateX(-72deg); filter: brightness(0.35); }
    to   { transform: rotateX(0deg);   filter: brightness(1); }
}

@media (prefers-reduced-motion: reduce) {
/* The cascade collapses to a single update, per the world's own rule. */
.flap { animation: none; }

}

/* Display-scale flaps, for the brand and page titles. */
.flapstrip--display .flap { font-size: clamp(1.1rem, 5.2vw, 2rem); min-width: 0.76em; }

.flapstrip--sm .flap { font-size: var(--text-xs); padding: 0.2em 0.14em; }

/* ═══ Status vocabulary ══════════════════════════════════════════════════
   Four values, one visual law. Status never relies on colour alone: each
   carries its own word, so the row reads correctly in monochrome too.     */
.status {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-family: var(--font-flap);
    font-weight: 700;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    padding: 0.2rem 0.5rem;
    border: 1px solid currentColor;
    white-space: nowrap;
}

.status--queued { color: var(--steel-dark); }

.status--working { color: var(--amber); }

.status--done { color: var(--flap-white); }

.status--failed { color: var(--cancel-red-tx); border-color: var(--cancel-red); }

/* The lamp beside a working row. Inline-block so it also works inside a
   paragraph, not only inside a flex row. */
.lamp {
    display: inline-block;
    vertical-align: middle;
    width: 10px;
    height: 10px;
    flex: 0 0 auto;
    background: var(--lamp-dark);
}

.lamp--lit {
    background: var(--lamp-lit);
    animation: lampPulse 1.4s steps(2, end) infinite;
}

@keyframes lampPulse { 0%, 60% { opacity: 1; } 61%, 100% { opacity: 0.42; } }

/* Column labels above a set of rows — the board's header strip. The template matches
   .studio-feature-card exactly (34px glyph bay, name, status) so SERVICE sits over the
   name and STATUS over the status, rather than approximately near them. */
.row-head {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) auto;
    gap: 0.85rem;
    padding: 0.4rem 0.9rem;
    border-bottom: 1px solid var(--rule-strong);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--steel-dark);
}

/* ═══ Chrome: header, nav, footer ════════════════════════════════════════
   The mock-data banner must live in this global sheet: MockDataBanner is a child
   component, so MainLayout's scoped stylesheet cannot reach it and it would render
   as an unstyled white bar across the top of every page.

   It is amber because running on mocks is a provisional state — the same meaning
   amber carries everywhere else in this world. The detail list is clamped to one
   line so six mock service names cannot eat a third of a phone viewport. */
.mock-data-banner {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 0.4rem;
    background: var(--amber);
    color: var(--flap-black);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    padding: 0.3rem 0.9rem;
    text-align: center;
}

.mock-data-banner i { flex: 0 0 auto; }

.mock-data-banner__details {
    font-weight: 600;
    letter-spacing: 0.1em;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* On a phone the banner states the condition; the service list is detail the
   header has no room for. */
@media (max-width: 639.98px) {
.mock-data-banner__details { display: none; }

}

/* The rail is BRUSHED STEEL — a light surface. --steel (#B6BBC2) is a board grey meant
   for the dark flap faces; on the rail's own gradient it measures 1.3:1 at the light end
   and 2.4:1 at the middle, so "SIGN OUT" and the feature links were washed out at every
   width. The rail's ink is --steel-ink, which is what MainLayout.razor.css already used
   for the same elements — but that sheet is scoped to MainLayout and cannot reach anchors
   NavMenu and LoginDisplay render, so this global rule won and undid it. */
.app-nav__link {
    color: var(--steel-ink);
    text-decoration: none;
    font-family: var(--font-flap);
    font-size: var(--text-xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    padding: 0.5rem 0.75rem;
    border-radius: 0;
    border-bottom: 2px solid transparent;
    transition: color var(--motion-fast), border-color var(--motion-fast);
    background: transparent;
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    line-height: 1;
    white-space: nowrap;
    box-shadow: none;
}

.app-nav__link:hover {
    background: transparent;
    color: var(--flap-black);
    border-bottom-color: rgba(0,0,0,0.45);
}

/* Active gets the amber it always had, but on the dark cell rather than bare steel:
   amber on light steel is 1.9:1 and the current page is the one label that must be
   unmistakable. The cell is the board's own way of lighting a value. */
.app-nav__link.active {
    background: var(--flap-black);
    color: var(--amber);
    border-bottom-color: var(--amber);
    box-shadow: none;
}

/* Rail controls sit on a 54-60px bar, so they cannot be 44px tall without filling it.
   The tap target is extended past the painted box instead — the pointer/touch area is
   what the guideline is about, not the ink. */
.app-nav__link::after {
    content: "";
    position: absolute;
    inset: 50% 0 auto 0;
    height: 44px;
    transform: translateY(-50%);
}

.app-nav__link { position: relative; }

/* ── Skip link ───────────────────────────────────────────────────────────────
   Hidden until focused, then a real flap cell at the head of the rail. Every page
   put six or more rail stops in front of its own first control without it. */
.skip-link {
    position: absolute;
    left: 0.5rem;
    top: -100%;
    z-index: 2000;
    background: var(--flap-black);
    color: var(--amber);
    font-family: var(--font-flap);
    font-size: var(--text-xs);
    font-weight: 700;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    text-decoration: none;
    padding: 0.75rem 1rem;
    border: 1px solid var(--amber);
    transition: top var(--motion-fast);
}

.skip-link:focus-visible {
    top: 0.5rem;
    outline: 2px solid var(--amber);
    outline-offset: 2px;
}

/* The skip target must not paint a focus ring when it receives focus programmatically —
   it is a landmark, not a control. */
#main-content:focus { outline: none; }

.app-nav__brand {
    color: var(--flap-white);
    text-decoration: none;
    background: transparent;
    box-shadow: none;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
}

.app-nav__brand:hover { color: var(--flap-white); }

.app-nav__brand:hover .flap { color: var(--amber); }

.app-nav__user {
    /* Same steel-surface rule as .app-nav__link above: board greys are illegal on the rail. */
    color: var(--steel-ink);
    font-size: var(--text-xs);
    font-family: var(--font-flap);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    max-width: 200px;
}

.feature-page-icon,
.drop-zone__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 46px;
    height: 46px;
    flex: 0 0 auto;
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--amber);
    font-size: var(--text-xl);
}

.feature-page-title,
.diag-title {
    min-width: 0;
    font-family: var(--font-flap);
    font-size: clamp(1.4rem, 5.5vw, 2.1rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    /* No gradient text — emphasis is weight and scale. */
    color: var(--flap-white);
    background: none;
    -webkit-text-fill-color: currentColor;
    margin: 0;
    line-height: 1.1;
}

.feature-page-desc {
    font-size: var(--text-lg);
    color: var(--steel);
    max-width: 62ch;
    margin: 0.35rem 0 0;
    line-height: 1.55;
}

.feature-page-header {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
}

/* The title block is a flex item next to the glyph bay; without min-width:0 it
   refuses to shrink and pushes the flap cells past the right edge on a phone. */
.feature-page-header > .d-flex { min-width: 0; }

.feature-page-header > .d-flex > div { min-width: 0; }

/* "How it works" — a route strip, read like stops on a line. */
.feature-how-it-works {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex-wrap: wrap;
    font-family: var(--font-flap);
}

.feature-how-it-works__step {
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    border: none;
    border-radius: 0;
    padding: 0.24rem 0.6rem;
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--steel);
    white-space: nowrap;
}

.feature-how-it-works__arrow { color: var(--steel-dark); font-size: var(--text-2xs); }

/* ═══ Intake ═════════════════════════════════════════════════════════════
   The upload zone is the board's source row: a recessed flap bay waiting
   for a photo to be loaded into it.                                       */
.upload-panel { padding: 0; }

.drop-zone {
    position: relative;
    border: 1px dashed var(--steel-dark);
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    transition: border-color var(--motion-base), background var(--motion-base);
    cursor: pointer;
    overflow: hidden;
    min-height: 200px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.drop-zone--active {
    border-color: var(--amber);
    border-style: solid;
    background: rgba(255,180,0,0.06);
}

.drop-zone:hover { border-color: var(--steel); }

.drop-zone__input {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
    z-index: 2;
}

.drop-zone__empty { text-align: center; padding: 2.25rem 1.25rem; pointer-events: none; }

.drop-zone__icon { margin-bottom: 0.9rem; }

.drop-zone__title {
    font-family: var(--font-flap);
    font-size: var(--text-lg);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--flap-white);
    margin: 0 0 0.35rem;
}

.drop-zone__sub { font-size: var(--text-md); color: var(--steel); margin: 0 0 0.4rem; }

.drop-zone__browse {
    color: var(--amber);
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 2px;
}

.drop-zone__formats,
.drop-zone__paste {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--steel-dark);
    margin: 0.3rem 0 0;
}

.drop-zone__paste kbd {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    padding: 0.1rem 0.35rem;
    border-radius: 0;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    color: var(--flap-white);
}

.drop-zone--has-image {
    min-height: unset;
    border-style: solid;
    border-color: var(--rule-strong);
}

.drop-zone__preview { position: relative; width: 100%; }

.drop-zone__preview img.preview-image {
    display: block;
    max-width: 100%;
    max-height: 320px;
    width: auto;
    height: auto;
    margin: 0 auto;
    border-radius: 0;
}

.drop-zone__preview-overlay {
    position: absolute;
    inset: 0;
    background: rgba(7,7,8,0.72);
    display: flex;
    align-items: center;
    justify-content: center;
    opacity: 0;
    transition: opacity var(--motion-fast);
}

.drop-zone:hover .drop-zone__preview-overlay,
.drop-zone:focus-within .drop-zone__preview-overlay { opacity: 1; }

.drop-zone__preview-hint {
    font-family: var(--font-flap);
    color: var(--flap-white);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.16em;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    padding: 0.45rem 0.9rem;
    border-radius: 0;
    pointer-events: none;
}

.drop-zone__title--desktop { display: block; }

.drop-zone__title--mobile { display: none; }

.drop-zone__sub--desktop { display: block; }

@media (pointer: coarse) {
.drop-zone__title--desktop { display: none; }

.drop-zone__title--mobile { display: block; }

.drop-zone__sub--desktop { display: none; }

.drop-zone { min-height: 168px; }

.drop-zone__empty { padding: 1.75rem 1rem; }

}

/* Window-wide drop target. */
body.po-drop-active::after {
    content: "DROP IMAGE ANYWHERE";
    position: fixed;
    inset: 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-flap);
    font-size: clamp(1rem, 3vw, 1.6rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.28em;
    color: var(--amber);
    background: rgba(7,7,8,0.82);
    border: 2px dashed var(--amber);
    pointer-events: none;
}

/* ═══ Choice cells ═══════════════════════════════════════════════════════
   ONE selectable cell. `.flap-choice` is a flap you pick; picking it turns it amber.

   A detail level, a beat style, a roast intensity, a meme template and a style
   direction are the same object wearing five names, and before this they were five
   independent rule sets that had already drifted: the AI-detail presets centred
   their label, the beat styles left-aligned theirs, and the intensity dial invented
   its own lamp column. They now differ only by layout modifier.

   Modifiers:
     --stack  label over meta, centred. Presets, beat styles.
     --lamp   status lamp in a left bay, label and meta stacked beside it.
     --tile   label over category, left-aligned. Template pickers.
     --active picked. Always paired with aria-checked / :checked on the real input.

   Parts: __name (the tracked cap), __meta (the sub-label), __badge (corner flag),
          __lamp (the dot).                                                        */

.flap-choice {
    position: relative;
    display: flex;
    gap: 0.7rem;
    padding: 0.7rem 0.85rem;
    border: 1px solid transparent;
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--steel);
    cursor: pointer;
    text-align: left;
    font-family: var(--font-ui);
    transition: color var(--motion-fast), border-color var(--motion-fast), background var(--motion-fast);
    user-select: none;
}

.flap-choice--stack { flex-direction: column; align-items: center; gap: 0.2rem; text-align: center; }
.flap-choice--tile  { flex-direction: column; gap: 0.15rem; }

.flap-choice--lamp {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas: "lamp name" "lamp meta";
    align-items: center;
    column-gap: 0.55rem;
}

.flap-choice:hover {
    color: var(--flap-white);
    border-color: var(--steel-dark);
    background: #22232a;
}

/* The real radio/checkbox is visually replaced by the cell, so the cell has to
   carry the focus ring — remove this and the control becomes keyboard-invisible. */
.flap-choice:focus-within { outline: 2px solid var(--amber); outline-offset: 2px; }

/* Selected = the flap has turned. Amber border, amber label, lit ground. */
.flap-choice--active,
.studio-feature-card.flap-choice--active {
    border-color: var(--amber);
    background: rgba(255,180,0,0.08);
    color: var(--flap-white);
    box-shadow: var(--inset-cell);
}

.flap-choice__name {
    grid-area: name;
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--track-caps);
    color: var(--flap-white);
}

.flap-choice--active .flap-choice__name { color: var(--amber); }

.flap-choice__meta {
    grid-area: meta;
    font-family: var(--font-flap);
    font-size: var(--text-3xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--steel-dark);
}

/* The beat/intensity hints are sentences, not caps — they read as prose in the cell. */
.flap-choice--lamp .flap-choice__meta,
.flap-choice--stack .flap-choice__meta {
    font-family: var(--font-ui);
    font-size: var(--text-xs);
    text-transform: none;
    letter-spacing: 0;
    line-height: var(--lh-snug);
    color: var(--steel);
}

.flap-choice__lamp {
    grid-area: lamp;
    width: 10px;
    height: 10px;
    border-radius: 50%;
    background: var(--amber-dim);
}

.flap-choice--active .flap-choice__lamp { background: var(--amber); box-shadow: 0 0 8px rgba(255,180,0,0.55); }

.flap-choice__badge {
    position: absolute;
    top: -1px;
    right: -1px;
    background: var(--amber);
    color: var(--flap-black);
    font-family: var(--font-flap);
    font-size: var(--text-3xs);
    font-weight: 700;
    letter-spacing: var(--track-caps);
    padding: 1px 5px;
    border-radius: 0;
    white-space: nowrap;
    text-transform: uppercase;
}

/* The input is the control; the cell is its face. Hidden visually, never from AT. */
.flap-choice input[type="radio"],
.flap-choice input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 1px;
    height: 1px;
    margin: 0;
}

/* ── Fieldsets that hold a row of choices ── */
.flap-fieldset {
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: var(--flap-black);
    padding: 0.9rem 1rem 1rem;
    margin: 0;
}

.flap-fieldset__legend {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--track-wide);
    color: var(--steel-dark);
    padding: 0 0.35rem;
    width: auto;
    margin-bottom: 0.5rem;
}

/* One track for every set of choices. auto-fit means a two-stop dial and a
   five-stop one both fill the row without either being told a column count. */
.flap-choices {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 1px;
}

.flap-choices--tight { grid-template-columns: repeat(auto-fit, minmax(104px, 1fr)); }

.preset-section__label,
.send-to__label,
.prompt-history__label,
.gallery-bar__label,
.diag-stat__label,
.split-compare__label {
    display: block;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    color: var(--steel-dark);
    text-transform: uppercase;
    letter-spacing: var(--track-wide);
    margin-bottom: 0.5rem;
}

.preset-section { border-top: 1px solid var(--rule); padding-top: 1rem; }

/* ═══ Buttons ════════════════════════════════════════════════════════════
   ONE button. `.flap-btn` is a lit flap you can press; the modifiers change how
   loudly it says so, never what it is.

   This replaces ~20 differently-named classes (btn-process, btn-generate-all,
   btn-result-primary, btn-result-secondary, btn-download-favs, btn-dl-modal,
   btn-heart-modal, studio-surprise, send-to__btn, compare-modes__btn,
   prompt-drawer__btn, gallery-undo__btn, …) that were held in sync only by two
   very long selector lists. A selector list is not consolidation: every new
   button still had to be added to it by hand in three places (base, :hover,
   :disabled), and any that was missed silently fell back to the browser default.

   Modifiers:
     --primary  amber fill; the one action a screen is for. At most one per view.
     (default)  outlined cell; everything else.
     --danger   destructive, red outline.
     --quiet    borderless; inline text actions inside a cell.
     --sm       compact — toolbars, result rows, chips.
     --icon     square, glyph only. Always needs an aria-label.
     --block    full width — the bottom of a form or a mobile rail.
     --active   pressed/selected state for toggle buttons; reads as --primary.  */

.flap-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.4rem;
    padding: 0.5rem 0.9rem;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: transparent;
    color: var(--steel);
    font-family: var(--font-flap);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: var(--lh-tight);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    text-decoration: none;
    cursor: pointer;
    transition: border-color var(--motion-fast), color var(--motion-fast),
                background var(--motion-fast);
}

.flap-btn:hover:not(:disabled) {
    border-color: var(--amber);
    color: var(--amber);
    background: transparent;
}

.flap-btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; box-shadow: none; }

/* Disabled is a cancelled row: it keeps its cell and loses its lamp. Never a
   fade — opacity on an already-dim label drops it under the contrast floor. */
.flap-btn:disabled,
.flap-btn[aria-disabled="true"] {
    background: transparent;
    border-color: var(--rule-strong);
    color: var(--steel-dark);
    cursor: not-allowed;
    box-shadow: none;
    opacity: 1;
}

.flap-btn--primary {
    padding: 0.75rem 1.25rem;
    border-color: var(--amber);
    background: var(--amber);
    color: var(--flap-black);
    font-size: var(--text-md);
    font-weight: 700;
    box-shadow: var(--shadow-sm);
}

.flap-btn--primary:hover:not(:disabled) {
    background: var(--flap-white);
    border-color: var(--flap-white);
    color: var(--flap-black);
}

.flap-btn--danger { border-color: var(--cancel-red); color: var(--cancel-red-tx); }
.flap-btn--danger:hover:not(:disabled) { border-color: var(--cancel-red); background: var(--cancel-red); color: var(--flap-white); }

.flap-btn--quiet { border-color: transparent; padding-inline: 0.4rem; }
.flap-btn--quiet:hover:not(:disabled) { border-color: transparent; }

.flap-btn--sm { padding: 0.35rem 0.7rem; font-size: var(--text-3xs); letter-spacing: 0.12em; }
.flap-btn--primary.flap-btn--sm { padding: 0.45rem 0.85rem; font-size: var(--text-2xs); }

.flap-btn--icon { padding: 0.4rem; aspect-ratio: 1; gap: 0; }
.flap-btn--icon.flap-btn--sm { padding: 0.28rem; }

.flap-btn--block { width: 100%; }

/* A pressed toggle reads exactly like the primary action, because that is what it
   has become: the flap has turned and this is the state now in force. */
.flap-btn--active {
    border-color: var(--amber);
    background: var(--amber);
    color: var(--flap-black);
    font-weight: 700;
}

.flap-btn--active:hover:not(:disabled) { background: var(--flap-white); border-color: var(--flap-white); color: var(--flap-black); }

/* Touch targets. WCAG 2.5.8 asks for 24px; a coarse pointer wants 44. The board's
   compact buttons are 26–30px tall by design, so they grow only where the pointer
   is actually coarse rather than being padded out for everyone. */
@media (pointer: coarse) {
    .flap-btn { min-height: 44px; }
    .flap-btn--sm { min-height: 38px; }
    .flap-btn--icon { min-width: 44px; }
}

/* ═══ Progress: the row working its way across the board ═════════════════ */
.shimmer-wrapper { padding: 1.1rem; }

/* Status word beside the stage text, on one line at every width the header survives. */
.shimmer-head-line {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.shimmer-header { display: flex; align-items: center; gap: 0.9rem; }

.shimmer-spinner { flex-shrink: 0; color: var(--amber); display: inline-flex; align-items: center; }

/* A run of amber lamps stepping left to right — the board saying "this one is live". */
.lamp-run {
    display: inline-block;
    width: 30px;
    height: 10px;
    background: var(--lamp-lit);
    animation: lampRunStrip 1.1s steps(6, end) infinite;
}

@keyframes lampRunStrip {
    0%   { background-position: 0 0; opacity: 1; }
    50%  { opacity: 0.45; }
    100% { background-position: 30px 0; opacity: 1; }
}

@keyframes spinRotate { to { transform: rotate(360deg); } }

.shimmer-status-label {
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--amber);
    min-height: 1.25rem;
}

/* The progress track is a strip of unlit flaps that light amber as they turn. */
.shimmer-progress {
    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);
}

.shimmer-progress__bar {
    height: 100%;
    width: 100%;
    background: var(--amber);
    border-radius: 0;
    transform: scaleX(var(--progress, 0));
    transform-origin: left center;
    /* transform, not width — a width transition relayouts every frame. */
    transition: transform var(--motion-slow) var(--motion-ease-in-out);
    will-change: transform;
}

/* Indeterminate: a lit run of flaps sweeping the track, because the length of the job
   is genuinely unknown. The alternative — parking a determinate bar at an invented 85%
   until the model answers — reads as a stall, which is precisely the bug this replaced. */
.shimmer-progress--indeterminate .shimmer-progress__bar {
    transform: none;
    width: 34%;
    animation: shimmer-sweep 1.4s var(--motion-ease-in-out) infinite;
    transition: none;
}

@keyframes shimmer-sweep {
    0%   { margin-left: -34%; }
    100% { margin-left: 100%; }
}

/* Honour reduced motion: hold a static lit segment rather than sweeping. */
@media (prefers-reduced-motion: reduce) {
    .shimmer-progress--indeterminate .shimmer-progress__bar {
        animation: none;
        width: 100%;
        opacity: 0.45;
    }
}

.shimmer-pct {
    font-family: var(--font-flap);
    font-size: var(--text-md);
    font-weight: 700;
    color: var(--amber);
    flex-shrink: 0;
    min-width: 42px;
    text-align: right;
    font-variant-numeric: tabular-nums;
}

/* A row waiting to turn: the unlit flap sweep, not a soft-shadow shimmer. */
.shimmer-anim,
.gallery-thumb--skeleton {
    background-color: var(--flap-shadow);
    background-image: linear-gradient(90deg,
        transparent 0%, rgba(255,180,0,0.16) 45%, rgba(255,180,0,0.16) 55%, transparent 100%);
    background-size: 220% 100%;
    background-repeat: no-repeat;
    animation: flapSweep 1.5s linear infinite;
    box-shadow: var(--inset-cell);
    border-radius: 0;
}

@keyframes flapSweep {
    0%   { background-position: 220% 0; }
    100% { background-position: -120% 0; }
}

/* Sticky working pill — a lit board row that follows you down the page. */
.processing-pill {
    position: fixed;
    bottom: 1.25rem;
    right: 1.25rem;
    background: var(--flap-shadow);
    border: 1px solid var(--amber);
    box-shadow: var(--shadow-lg);
    color: var(--amber);
    border-radius: 0;
    padding: 0.5rem 0.75rem;
    font-family: var(--font-flap);
    font-size: var(--text-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    z-index: 9999;
    display: flex;
    align-items: center;
    gap: 0.6rem;
    animation: riseIn var(--motion-slow) var(--motion-ease-in-out);
}

@keyframes riseIn { from { transform: translateY(10px); opacity: 0; } to { transform: none; opacity: 1; } }

.processing-pill__cancel {
    background: none;
    border: none;
    color: var(--steel);
    cursor: pointer;
    padding: 0 0.1rem;
    font-size: var(--text-md);
    line-height: 1;
    transition: color var(--motion-fast);
}

.processing-pill__cancel:hover { color: var(--flap-white); }

.results-actions { display: flex; gap: 1px; flex-wrap: wrap; }

/* .results-more__toggle is gone with the "More options" disclosure it opened; the panel's
   secondary actions are always visible now. */
.description-text {
    line-height: 1.65;
    font-size: var(--text-lg);
    text-align: left;
    max-width: 68ch;
    max-height: 300px;
    overflow-y: auto;
    padding-right: 6px;
    white-space: pre-line;
    color: var(--steel);
}

/* ── Compare: two boards side by side ─────────────────────────────────── */
.split-compare-wrapper { border: 1px solid var(--rule-strong); border-radius: 0; overflow: hidden; box-shadow: var(--shadow-md); }

.split-compare { display: grid; grid-template-columns: 1fr auto 1fr; background: var(--flap-black); }

.split-compare__side { position: relative; overflow: hidden; max-height: 420px; }

.split-compare__img {
    width: 100%; height: 100%; object-fit: cover;
    opacity: 0;
    transition: opacity var(--motion-slow) var(--motion-ease-in-out);
    display: block;
}

.split-compare__img.loaded { opacity: 1; }

.split-compare__label {
    position: absolute;
    top: 8px;
    left: 8px;
    margin: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--flap-white);
    font-size: var(--text-3xs);
    padding: 0.2rem 0.5rem;
    border-radius: 0;
    pointer-events: none;
}

.split-compare__divider { display: flex; flex-direction: column; align-items: center; background: var(--steel-face); width: 18px; }

.split-compare__divider-line { flex: 1; width: 1px; background: rgba(0,0,0,0.5); }

.split-compare__divider-pill {
    background: var(--flap-black);
    color: var(--amber);
    border-radius: 0;
    width: 18px; height: 22px;
    display: flex; align-items: center; justify-content: center;
    font-size: var(--text-2xs);
    margin: 6px 0;
    flex-shrink: 0;
}

.split-compare__unavailable {
    width: 100%; min-height: 160px;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-flap);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
    font-size: var(--text-xs);
}

/* ── Send result onward: the connecting-services row ──────────────────── */
.send-to { padding: 0.85rem 1rem; }

.send-to__row { display: flex; flex-wrap: wrap; gap: 1px; }

/* ═══ Accordions and disclosure ══════════════════════════════════════════ */
.description-accordion__toggle {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.6rem;
    cursor: pointer;
    text-align: left;
    transition: filter var(--motion-fast);
}

.description-accordion { border: 1px solid var(--rule-strong); border-radius: 0; overflow: hidden; }

.description-accordion__toggle {
    background: var(--steel-face);
    color: #14161a;
    font-family: var(--font-flap);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    font-size: var(--text-xs);
    border: none;
    padding: 0.55rem 0.9rem;
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
}

.description-accordion__toggle:hover { filter: brightness(1.08); }

.description-accordion__meta { font-size: var(--text-2xs); color: #2b2f36; letter-spacing: 0.1em; }

.description-accordion__time { font-variant-numeric: tabular-nums; }

.description-accordion__body { padding: 0.85rem 1rem; background: var(--flap-black); }

.gallery-bar__count,
.prompt-drawer__count,
.prompt-drawer__fab-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    letter-spacing: 0.08em;
    background: var(--amber);
    color: var(--flap-black);
    padding: 0.1rem 0.45rem;
    border-radius: 0;
    min-width: 1.35rem;
    font-variant-numeric: tabular-nums;
}

/* ═══ Diagnostics — the board read as a manifest ═════════════════════════ */
.diag-stat { padding: 0.8rem 1rem; }

.diag-stat__label { margin-bottom: 0.35rem; }

.diag-stat__value {
    font-family: var(--font-flap);
    font-size: var(--text-lg);
    font-weight: 700;
    letter-spacing: 0.06em;
    color: var(--flap-white);
    word-break: break-all;
}

.diag-stat__value--env { color: var(--amber); text-transform: uppercase; }

/* The layout shell no longer pads its content slot, so the two pages that are not
   built on FeatureShell state their own rhythm. Both are boards, not documents:
   they take the full rail width rather than a reading measure. */
.studio-page,
.diag-page {
    width: 100%;
    max-width: var(--rail-max);
    margin-inline: auto;
    padding: var(--space-4) var(--space-3) var(--space-5);
}

@media (max-width: 639.98px) {
    .studio-page,
    .diag-page { padding: var(--space-3) var(--space-2) var(--space-4); }
}

/* ═══ Studio — the concourse board ═══════════════════════════════════════
   The board head (.studio-hero*) and the saved-images strip (.studio-gallery-row)
   were removed from Studio.razor at the user's request, so their rules are deleted
   here too rather than left as dead selectors. The page now opens on the intake bay,
   which is why .studio-body carries the top spacing the hero used to provide. */
.studio-body {
    display: grid;
    grid-template-columns: minmax(0, 360px) minmax(0, 1fr);
    gap: 1.5rem;
    align-items: start;
    margin-top: 1.5rem;
}

.studio-upload-col,
.studio-features-col { min-width: 0; }

/* Intake sits at the head of the board at EVERY width, and the routing panel below it.
   The contract's FIRST VIEWPORT promises the intake bay first; with the AI picker on top
   the primary action sat under six selectors — below the fold on a phone, and second in
   the reading order on desktop. Provider choice has a working default and is a refinement;
   loading the photo is the job. This is an order rule, not a breakpoint rule, which is why
   it lives outside the media query. */
.studio-upload-col { display: flex; flex-direction: column; gap: 1rem; }

.studio-upload-col > .ai-picker { order: 2; }

@media (max-width: 1023.98px) {
.studio-body { grid-template-columns: 1fr; gap: 1.25rem; }

}

/* The five transformations are ruled board rows, not a grid of cards. */
.studio-feature-cards {
    display: flex;
    flex-direction: column;
    border: 1px solid var(--rule-strong);
    background: var(--flap-black);
    box-shadow: var(--shadow-md);
}

.studio-feature-card {
    display: grid;
    grid-template-columns: 34px minmax(0, 1fr) auto;
    align-items: center;
    gap: 0.85rem;
    padding: 0.8rem 0.9rem;
    border: none;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;
    background: transparent;
    text-decoration: none;
    color: var(--flap-white);
    transition: background var(--motion-fast);
}

.studio-feature-card:last-child { border-bottom: none; }

.studio-feature-card:hover { background: rgba(255,255,255,0.04); color: var(--flap-white); transform: none; box-shadow: none; }

.studio-feature-card__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 34px; height: 34px;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--steel);
    font-size: var(--text-lg);
}

.studio-feature-card:hover .studio-feature-card__icon { color: var(--amber); }

.studio-feature-card__title {
    font-family: var(--font-flap);
    font-weight: 700;
    font-size: var(--text-lg);
    text-transform: uppercase;
    letter-spacing: 0.12em;
}

.studio-feature-card__desc {
    font-size: var(--text-sm);
    color: var(--steel);
    margin: 0.15rem 0 0;
    line-height: 1.45;
    max-width: 56ch;
}

.studio-feature-card__cta {
    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;
    align-self: center;
}

/* Image loaded: every row's departure is now callable. */
.studio-feature-card--ready .studio-feature-card__cta { color: var(--amber); }

.studio-feature-card--ready .studio-feature-card__icon { color: var(--amber); }

.feature-card {
    display: grid;
    grid-template-columns: 34px minmax(0,1fr) auto;
    align-items: center;
    gap: 0.85rem;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--rule);
    border-radius: 0;
    box-shadow: none;
    padding: 0.8rem 0.9rem;
    cursor: pointer;
    overflow: visible;
    transition: background var(--motion-fast);
}

.feature-card::before,
.feature-card::after { content: none; }

.feature-card:last-child { border-bottom: none; }

.feature-card:hover { background: rgba(255,255,255,0.04); transform: none; box-shadow: none; border-color: var(--rule); }

.feature-card__icon {
    display: inline-flex; align-items: center; justify-content: center;
    width: 34px; height: 34px;
    background: var(--flap-shadow); box-shadow: var(--inset-cell);
    color: var(--steel); font-size: var(--text-lg); margin: 0;
}

.feature-card__title {
    font-family: var(--font-flap);
    font-size: var(--text-lg); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.12em;
    color: var(--flap-white);
}

.feature-card__desc { font-size: var(--text-sm); color: var(--steel); line-height: 1.45; margin: 0.15rem 0 0; max-width: 56ch; }

.feature-card__cta {
    margin: 0; padding: 0;
    font-family: var(--font-flap);
    font-size: var(--text-2xs); font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.16em;
    color: var(--steel-dark);
    display: flex; align-items: center; gap: 0.35rem;
    white-space: nowrap;
}

/* ═══ Galleries — contact strips of turned flaps ═════════════════════════ */
.gallery-bar {
    display: flex;
    align-items: center;
    gap: 0.7rem;
    padding: 0.5rem 0.7rem;
    margin-bottom: 0;
    overflow: hidden;
}

.gallery-bar__label { margin-bottom: 0; flex: 0 0 auto; white-space: nowrap; display: inline-flex; align-items: center; gap: 0.35rem; }

.gallery-bar__scroll {
    display: flex;
    align-items: center;
    gap: 4px;
    flex: 1 1 auto;
    min-width: 0;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 2px 0;
    scrollbar-width: thin;
}

.gallery-bar__empty-hint {
    flex: 1 1 auto;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.16em;
    color: var(--steel-dark);
    display: inline-flex;
    align-items: center;
}

.gallery-bar__refresh {
    flex: 0 0 auto;
    background: transparent;
    border: 1px solid var(--rule-strong);
    color: var(--steel-dark);
    cursor: pointer;
    border-radius: 0;
    width: 30px; height: 30px;
    display: inline-flex; align-items: center; justify-content: center;
    transition: color var(--motion-fast), border-color var(--motion-fast);
}

.gallery-bar__refresh:hover { color: var(--amber); border-color: var(--amber); }

.gallery-thumb {
    position: relative;
    flex: 0 0 auto;
    width: 46px; height: 46px;
    padding: 0;
    border-radius: 0;
    overflow: hidden;
    border: 1px solid var(--rule-strong);
    background: var(--flap-shadow);
    cursor: pointer;
    transition: border-color var(--motion-fast);
    aspect-ratio: 1 / 1;
}

.gallery-thumb:hover { border-color: var(--amber); transform: none; }

.gallery-thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: 0; display: block; }

/* Kind is carried by the badge glyph in the corner, which is a drawn icon and reads
   without colour at all. The frame only echoes it as a 1px ring — a thicker coloured
   edge on one side would be the side-tab tell, and it would fight the photograph. */
.gallery-thumb.kind-original,
.gallery-thumb--original { border-color: var(--steel); }

.gallery-thumb.kind-regeneration,
.gallery-thumb--regen { border-color: var(--flap-white); }

.gallery-thumb.kind-meme,
.gallery-thumb--meme { border-color: var(--amber); }

.gallery-thumb.kind-bulkvariation,
.gallery-thumb--bulk { border-color: var(--steel-dark); }

.gallery-thumb__badge {
    position: absolute;
    bottom: 3px; right: 2px;
    font-size: var(--text-3xs);
    background: var(--flap-black);
    color: var(--flap-white);
    border-radius: 0;
    width: 16px; height: 16px;
    display: flex; align-items: center; justify-content: center;
    box-shadow: none;
}

.gallery-thumb--skeleton { border-color: transparent; cursor: default; }

/* Skip layout, paint and style for thumbs scrolled out of the strip. A gallery grows
   without bound — it is the one element on the board that does — and every saved image
   was previously a fully laid-out, fully decoded DOM node whether or not it was on
   screen. contain-intrinsic-size supplies the placeholder geometry so the scrollbar
   still measures the whole strip and does not jump as items come into view.

   NOT <Virtualize>: that component spaces items in the block axis, and this strip
   scrolls inline. It would render every item and add a component for the privilege. */
.gallery-thumb-wrap {
    content-visibility: auto;
    contain-intrinsic-size: auto 72px;
}

.gallery-thumb-wrap { position: relative; flex: 0 0 auto; }

.gallery-thumb__zoom,
.gallery-thumb__delete {
    position: absolute;
    width: 18px; height: 18px;
    display: flex; align-items: center; justify-content: center;
    padding: 0;
    border: none;
    border-radius: 0;
    font-size: var(--text-3xs);
    color: var(--flap-white);
    cursor: pointer;
    opacity: 0;
    transition: opacity var(--motion-fast), background var(--motion-fast);
}

.gallery-thumb__zoom { top: 2px; left: 2px; background: rgba(7,7,8,0.85); }

.gallery-thumb__delete { top: 2px; right: 2px; background: var(--cancel-red); }

.gallery-thumb__zoom:hover { background: var(--flap-black); }

.gallery-thumb__delete:hover { background: #b71c1c; }

.gallery-thumb-wrap:hover .gallery-thumb__zoom,
.gallery-thumb-wrap:hover .gallery-thumb__delete,
.gallery-thumb-wrap:focus-within .gallery-thumb__zoom,
.gallery-thumb-wrap:focus-within .gallery-thumb__delete { opacity: 1; }

@media (hover: none) {
.gallery-thumb__zoom,
.gallery-thumb__delete { opacity: 1; }

}

/* Undo strip — amber, because a pending deletion is work in progress. */
.gallery-undo {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0.5rem 0;
    padding: 0.5rem 0.7rem;
    border-radius: 0;
    background: var(--flap-shadow);
    border: 1px solid var(--amber);
    box-shadow: var(--inset-cell);
    font-size: var(--text-sm);
}

.gallery-undo__thumb { width: 30px; height: 30px; object-fit: cover; border-radius: 0; }

.gallery-undo__text { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--steel); }

.gallery-undo__dismiss { background: transparent; border: none; color: var(--steel-dark); cursor: pointer; }

.gallery-undo__dismiss:hover { color: var(--flap-white); }

/* ═══ Overlays ═══════════════════════════════════════════════════════════ */
.gallery-lightbox {
    position: fixed;
    inset: 0;
    z-index: 10000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
    background: rgba(7,7,8,0.88);
    animation: fadeIn var(--motion-fast) ease;
}

.gallery-lightbox { z-index: 1600; padding: 1.5rem; }

@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

.bulk-modal-panel,
.gallery-lightbox__panel {
    position: relative;
    background: var(--flap-black);
    border: 1px solid var(--steel-dark);
    border-radius: 0;
    max-width: min(92vw, 640px);
    width: 100%;
    overflow: hidden;
    box-shadow: var(--shadow-xl);
    animation: riseIn var(--motion-base) var(--motion-ease-in-out);
}

.gallery-lightbox__panel {
    max-width: min(920px, 100%);
    max-height: 100%;
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    padding: 1rem;
    overflow: auto;
}

.bulk-modal-img-wrap {
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    display: flex; align-items: center; justify-content: center;
    max-height: 60vh;
    overflow: hidden;
}

.bulk-modal-img-wrap img { max-width: 100%; max-height: 60vh; object-fit: contain; display: block; }

.bulk-modal-footer { padding: 0.85rem 0.9rem; border-top: 1px solid var(--rule); }

.bulk-modal-prompt {
    font-size: var(--text-sm);
    color: var(--steel);
    margin: 0 0 0.75rem;
    line-height: 1.45;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.bulk-modal-actions { display: flex; gap: 1px; }

.gallery-lightbox__img { max-width: 100%; max-height: 65vh; object-fit: contain; border-radius: 0; background: var(--flap-shadow); }

.gallery-lightbox__meta { display: flex; flex-direction: column; gap: 0.15rem; }

.gallery-lightbox__name {
    font-family: var(--font-flap);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

.gallery-lightbox__sub {
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: var(--steel-dark);
}

.gallery-lightbox__actions { display: flex; gap: 1px; flex-wrap: wrap; }

/* ═══ Meme, caption battle, style director ═══════════════════════════════ */
.meme-caption-reveal { text-align: center; padding: 0.5rem 1rem; }

.meme-caption-line {
    font-family: var(--font-flap);
    font-size: clamp(1.1rem, 4.5vw, 1.6rem);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    /* Real depth, offset and blurred — not a chromatic halo. */
    text-shadow: 0 2px 4px rgba(0,0,0,0.8);
}

.meme-template-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 1px;
    max-height: 240px;
    overflow-y: auto;
    padding: 1px;
}

.meme-template-zones { background: var(--flap-shadow); box-shadow: var(--inset-cell); border-radius: 0; padding: 0.85rem; }


/* ═══ Bulk generate ══════════════════════════════════════════════════════ */
.bulk-upload-preview {
    border-radius: 0;
    overflow: hidden;
    aspect-ratio: 16/9;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    display: flex;
    align-items: center;
    justify-content: center;
}

.bulk-upload-preview img { width: 100%; height: 100%; object-fit: cover; }

/* ═══ Prompt drawer ══════════════════════════════════════════════════════ */
.prompt-drawer__fab {
    position: fixed;
    left: 1rem;
    bottom: 1rem;
    z-index: 1045;
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.6rem 1rem;
    border: 1px solid var(--steel-dark);
    border-radius: 0;
    background: var(--flap-shadow);
    color: var(--flap-white);
    font-family: var(--font-flap);
    font-weight: 700;
    font-size: var(--text-xs);
    text-transform: uppercase;
    letter-spacing: 0.14em;
    box-shadow: var(--shadow-lg);
    cursor: pointer;
    transition: border-color var(--motion-fast), color var(--motion-fast);
}

.prompt-drawer__fab:hover { border-color: var(--amber); color: var(--amber); }

.prompt-drawer__fab i { font-size: var(--text-lg); }

.prompt-drawer__fab--active { border-color: var(--amber); color: var(--amber); }

.prompt-drawer__backdrop {
    position: fixed;
    top: 61px; left: 0; right: 0; bottom: 0;
    z-index: 1050;
    background: rgba(7,7,8,0.7);
}

.prompt-drawer {
    position: fixed;
    top: 61px; left: 0; bottom: 0;
    width: min(400px, 90vw);
    z-index: 1055;
    display: flex;
    flex-direction: column;
    background: var(--flap-black);
    border-right: 1px solid var(--steel-dark);
    box-shadow: var(--shadow-xl);
    transform: translateX(-100%);
    transition: transform var(--motion-slow) var(--motion-ease-in-out);
    overflow: hidden;
}

.prompt-drawer--open { transform: translateX(0); }

.prompt-drawer__header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.55rem 0.9rem;
    background: var(--steel-face);
    border-bottom: 1px solid rgba(0,0,0,0.6);
}

.prompt-drawer__title {
    flex: 1;
    display: flex;
    align-items: center;
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.14em;
    color: #14161a;
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
    margin: 0;
}

.prompt-drawer__count { background: var(--flap-black); color: var(--amber); }

.prompt-drawer__close {
    width: 1.9rem; height: 1.9rem;
    border: 1px solid rgba(0,0,0,0.5);
    border-radius: 0;
    background: var(--flap-black);
    color: var(--steel);
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: background var(--motion-fast), color var(--motion-fast);
}

.prompt-drawer__close:hover { background: var(--amber); color: var(--flap-black); }

.prompt-drawer__actions { display: flex; gap: 1px; padding: 0.7rem 0.9rem; border-bottom: 1px solid var(--rule); }



.prompt-drawer__hint {
    padding: 0.5rem 0.9rem;
    font-size: var(--text-xs);
    color: var(--steel);
    background: var(--flap-shadow);
    border-bottom: 1px solid var(--rule);
}

.prompt-drawer__hint code {
    background: var(--flap-black);
    color: var(--amber);
    padding: 0.05rem 0.3rem;
    border-radius: 0;
    font-size: var(--text-xs);
}

.prompt-drawer__list { flex: 1; padding: 0.9rem; display: grid; gap: 0.6rem; overflow-y: auto; }

.prompt-drawer__item { display: flex; align-items: flex-start; gap: 0.5rem; flex-wrap: wrap; }

.prompt-drawer__row { display: flex; align-items: flex-start; gap: 0.5rem; width: 100%; }

/* Row numbers are the board's position column, so they stay tabular. */
.prompt-drawer__num {
    flex-shrink: 0;
    width: 1.6rem; height: 1.6rem;
    margin-top: 0.3rem;
    display: inline-flex; align-items: center; justify-content: center;
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--steel-dark);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    font-variant-numeric: tabular-nums;
}

.prompt-drawer__textarea,
textarea.form-control,
input.form-control {
    flex: 1;
    background: var(--flap-shadow);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    color: var(--flap-white);
    font-family: var(--font-ui);
    font-size: var(--text-md);
    padding: 0.5rem 0.7rem;
    box-shadow: var(--inset-cell);
    transition: border-color var(--motion-fast);
    line-height: 1.5;
}

/* Autosized (ux.js, plus field-sizing natively where supported), so the box always fits its
   text and never scrolls inside the already-scrolling drawer list. resize is off because a
   hand-dragged height is overwritten on the next keystroke. */
.prompt-drawer__textarea {
    resize: none;
    min-height: 48px;
    overflow-y: hidden;
    field-sizing: content;
}

.prompt-drawer__textarea:focus,
textarea.form-control:focus,
input.form-control:focus {
    outline: none;
    border-color: var(--amber);
    box-shadow: var(--inset-cell), 0 0 0 1px var(--amber);
    background: var(--flap-shadow);
    color: var(--flap-white);
}

::placeholder {
    color: var(--steel-dark);
    font-family: var(--font-flap);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: 0.8em;
    opacity: 1;
}

.prompt-drawer__reset {
    flex-shrink: 0;
    margin-top: 0.3rem;
    width: 1.9rem; height: 1.9rem;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: var(--flap-shadow);
    color: var(--steel-dark);
    cursor: pointer;
    display: inline-flex; align-items: center; justify-content: center;
    transition: color var(--motion-fast), border-color var(--motion-fast);
}

.prompt-drawer__reset:hover { color: var(--amber); border-color: var(--amber); transform: none; }


/* ═══ Prompt history ═════════════════════════════════════════════════════ */
.prompt-history { padding: 0.5rem 0.75rem; border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); }

.prompt-history__label { display: flex; align-items: center; margin-bottom: 0.4rem; }

.prompt-history__target { margin-left: auto; text-transform: none; letter-spacing: 0; font-weight: 500; color: var(--steel); }

.prompt-history__list { display: flex; flex-direction: column; gap: 1px; max-height: 190px; overflow-y: auto; }

.prompt-history__chip {
    display: flex;
    align-items: center;
    gap: 0.2rem;
    border: 1px solid transparent;
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
}

.prompt-history__chip--pinned { border-color: var(--amber); background: rgba(255,180,0,0.08); }

.prompt-history__use {
    flex: 1; min-width: 0;
    text-align: left;
    background: transparent; border: none;
    color: var(--steel);
    font-size: var(--text-xs);
    padding: 0.4rem 0.5rem;
    cursor: pointer;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.prompt-history__use:hover { color: var(--amber); }

.prompt-history__pin,
.prompt-history__drop {
    background: transparent; border: none;
    color: var(--steel-dark);
    padding: 0.25rem 0.35rem;
    font-size: var(--text-xs);
    cursor: pointer;
    transition: color var(--motion-fast);
}

.prompt-history__pin:hover,
.prompt-history__drop:hover { color: var(--flap-white); }

.prompt-history__chip--pinned .prompt-history__pin { color: var(--amber); }

/* ═══ Session cost — the fare counter ════════════════════════════════════ */
.session-cost-wrapper {
    position: relative;
    display: inline-block;
}

.session-cost {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    padding: 0.15rem 0.5rem;
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--steel);
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
}

.session-cost:hover {
    color: var(--flap-white);
    background: var(--flap-black);
}

.session-cost__value { font-weight: 700; color: var(--amber); font-variant-numeric: tabular-nums; }

.session-cost__meta { color: var(--steel-dark); }

.session-cost-popover {
    position: absolute;
    bottom: calc(100% + 8px);
    right: 0;
    z-index: 1050;
    min-width: 290px;
    background: var(--flap-black);
    border: 1px solid var(--rule-strong);
    box-shadow: 0 4px 20px rgba(0,0,0,0.85);
    padding: 0.75rem;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--flap-white);
}

.session-cost-popover__header {
    padding-bottom: 0.4rem;
    border-bottom: 1px solid var(--rule);
    margin-bottom: 0.4rem;
    font-size: var(--text-2xs);
}

@media (max-width: 639.98px) {
.session-cost__meta { display: none; }
.session-cost-popover { right: auto; left: 0; max-width: calc(100vw - 2rem); }
}

/* Small caption and chip text used inside the cost popover. Lives in the global
   sheet because SessionCostChip does not own a scoped .razor.css. */
.session-cost-caption { font-size: 0.65rem; }
.session-cost-chip-text { font-size: 0.7rem; }

/* ═══ Mobile sticky action rail ══════════════════════════════════════════ */
@media (max-width: 767.98px) {
.mobile-sticky-cta {
        position: fixed;
        bottom: 0; left: 0; right: 0;
        background: var(--flap-shadow);
        border-top: 1px solid var(--steel-dark);
        box-shadow: 0 -4px 18px rgba(0,0,0,0.6);
        padding: 0.6rem 0.75rem;
        z-index: 9998;
        display: flex;
        gap: 1px;
        animation: riseIn var(--motion-slow) var(--motion-ease-in-out);
    }

.mobile-sticky-cta__btn { flex: 1; padding: 0.7rem 0.9rem; font-size: var(--text-xs); }

    /* `.has-mobile-sticky-cta` is gone. It was a marker class each page had to remember
       to add, and only one of five ever did — the other four parked their last control
       under the rail. FeatureShell reserves the space structurally instead, so a page
       cannot forget. */

}

@media (min-width: 768px) {
.mobile-sticky-cta { display: none; }

}

}

@layer overrides {
/* ═══ Small components that used to come from Bootstrap ══════════════════
   These are no longer overrides of a framework's opinion — they ARE the badge,
   the alert, the spinner and the form control. The CDN build that previously
   supplied them shipped ~230KB to provide these nine rules plus a grid, and the
   sheet then spent 44 more rules and 46 !important declarations disagreeing with
   the rest of it. Owning them outright costs less than arguing with them.       */

.badge {
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.5rem;
    border-radius: 0;
    background: var(--flap-shadow);
    color: var(--steel);
    font-family: var(--font-flap);
    font-size: var(--text-3xs);
    font-weight: 700;
    letter-spacing: var(--track-caps);
    text-transform: uppercase;
    line-height: var(--lh-tight);
    white-space: nowrap;
}

/* Status colours on a badge come from the four-value vocabulary, nowhere else. */
.badge.bg-success { background: var(--flap-white); color: var(--flap-black); }
.badge.bg-warning { background: var(--amber); color: var(--flap-black); }
.badge.bg-danger  { background: var(--cancel-red); color: var(--flap-white); }
.badge.bg-info,
.badge.bg-primary,
.badge.bg-secondary { background: var(--flap-shadow); color: var(--steel); }

.alert {
    border-radius: 0;
    border: 1px solid var(--rule-strong);
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--steel);
    font-size: var(--text-md);
    padding: 0.85rem 1rem;
    margin-bottom: var(--space-3);
}

.alert-danger { border-color: var(--cancel-red); color: var(--cancel-red-tx); }
.alert-info   { border-color: var(--rule-strong); color: var(--steel); }

/* The working lamp as a spinner. One authored rotation, paused under
   prefers-reduced-motion by the global rule in foundation.css. */
.spinner-border {
    display: inline-block;
    width: 1.1rem;
    height: 1.1rem;
    vertical-align: -0.15em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    color: var(--amber);
    animation: flap-spin 0.75s linear infinite;
}

.spinner-border-sm { width: 0.8rem; height: 0.8rem; border-width: 1.5px; }

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

/* Cards are the board panel (see Materials); these are the parts inside it. */
.card-header {
    padding: 0.7rem 1rem;
    border-bottom: 1px solid var(--rule);
    background: var(--flap-shadow);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
    color: var(--steel-dark);
}

.card-body { padding: 1rem; }

.form-label,
.form-check-label {
    display: block;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
    color: var(--steel-dark);
    margin-bottom: 0.35rem;
}

.form-control {
    display: block;
    width: 100%;
    padding: 0.55rem 0.7rem;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background: var(--flap-shadow);
    box-shadow: var(--inset-cell);
    color: var(--flap-white);
    font-family: var(--font-ui);
    font-size: var(--text-lg);
    line-height: var(--lh-snug);
}

.form-control:focus { border-color: var(--amber); outline: none; box-shadow: inset 0 0 0 1px var(--amber); }

/* A switch is a checkbox that says "on/off" rather than "yes/no" — same input, wider
   track, travelling knob. Drawn here rather than inherited from a framework because it
   is the only toggle in the app and it has to read as a lit flap, not a Material pill. */
.form-switch .form-check-input {
    width: 2.1rem;
    background-image: none;
    background-color: var(--flap-shadow);
    position: relative;
}

.form-switch .form-check-input::after {
    content: "";
    position: absolute;
    inset: 1px auto 1px 1px;
    width: calc(1rem - 2px);
    background: var(--steel-dark);
    transition: transform var(--motion-fast), background var(--motion-fast);
}

/* background-image must be reset explicitly: the checkbox tick is drawn with one and
   would otherwise sit under the travelling knob. */
.form-switch .form-check-input:checked { background-color: var(--flap-shadow); border-color: var(--amber); background-image: none; }
.form-switch .form-check-input:checked::after { transform: translateX(1.05rem); background: var(--amber); }

.form-control-sm { padding: 0.32rem 0.5rem; font-size: var(--text-md); }

.form-check { display: flex; align-items: center; gap: 0.5rem; }

.form-check-input {
    appearance: none;
    width: 1rem;
    height: 1rem;
    margin: 0;
    flex-shrink: 0;
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    background-color: var(--flap-shadow);
    cursor: pointer;
}

.form-check-input:checked {
    background-color: var(--amber);
    border-color: var(--amber);
    /* The tick is drawn, not a font glyph: a checkmark character renders in whatever
       weight the platform supplies, which is the emoji problem in a different hat. */
    background-image: linear-gradient(45deg, transparent 44%, var(--flap-black) 44% 56%, transparent 56%),
                      linear-gradient(-45deg, transparent 44%, var(--flap-black) 44% 56%, transparent 56%);
    background-size: 60% 60%;
    background-position: center;
    background-repeat: no-repeat;
}

.form-check-input:focus-visible { outline: 2px solid var(--amber); outline-offset: 2px; }

.form-check-label { margin-bottom: 0; }

/* ═══ Radzen, retinted ═══════════════════════════════════════════════════
   Radzen is themed through its OWN custom properties rather than by out-specifying
   its selectors. That matters here: the previous approach listed six component
   classes (.rz-card, .rz-panel, .rz-dropdown, .rz-textbox, .rz-notification,
   .rz-dialog) and forced each with !important — which is why the one Radzen
   component the app actually leans on, the /diag DataGrid, was never covered and sat
   on the page as a white Material table inside a matte-black departure board.

   Re-pointing the variables reaches every component at once, including ones this app
   has not adopted yet, and it survives a Radzen upgrade that renames a class. */
:root {
    --rz-body-background-color: var(--board-ground);
    --rz-base-background-color: var(--flap-black);
    --rz-text-color: var(--flap-white);
    --rz-text-secondary-color: var(--steel);
    --rz-text-disabled-color: var(--steel-dark);
    --rz-link-color: var(--amber);
    --rz-border-radius: 0;
    --rz-primary: var(--amber);
    --rz-primary-light: var(--amber);
    --rz-primary-dark: var(--amber-dim);
    --rz-danger: var(--cancel-red);
    --rz-success: var(--flap-white);
    --rz-warning: var(--amber);
    --rz-info: var(--steel);

    /* DataGrid: a manifest printed on the board, not a spreadsheet. */
    --rz-grid-background-color: var(--flap-black);
    --rz-grid-border: 1px solid var(--rule-strong);
    --rz-grid-border-radius: 0;
    --rz-grid-cell-color: var(--flap-white);
    --rz-grid-cell-border: 1px solid var(--rule);
    --rz-grid-bottom-cell-border: 1px solid var(--rule);
    --rz-grid-cell-font-size: var(--text-md);
    --rz-grid-header-background-color: var(--flap-shadow);
    --rz-grid-header-color: var(--steel-dark);
    --rz-grid-header-font-size: var(--text-2xs);
    --rz-grid-header-font-weight: 700;
    --rz-grid-header-cell-border: 1px solid var(--rule);
    --rz-grid-header-cell-border-bottom: 1px solid var(--rule-strong);
    --rz-grid-header-sorted-background-color: var(--flap-shadow);
    --rz-grid-filter-background-color: var(--flap-shadow);
    --rz-grid-filter-color: var(--flap-white);
    --rz-grid-filter-border: 1px solid var(--rule-strong);
    --rz-grid-header-filter-icon-active-color: var(--amber);
    --rz-grid-header-filter-icon-hover-color: var(--amber);
    --rz-grid-row-hover-background-color: rgba(255,180,0,0.06);
    --rz-grid-stripe-background-color: var(--flap-shadow);
}

/* Column heads are the board's own tracked caps, which no variable can express. */
.rz-data-grid .rz-column-title,
.rz-datatable .rz-column-title {
    font-family: var(--font-flap);
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
}

/* Radzen paints its own surfaces with hard-coded whites in a few places the theme
   variables do not reach. These are the remaining few, named explicitly. */
.rz-data-grid,
.rz-datatable,
.rz-grid-table,
.rz-grid-table tbody tr,
.rz-datatable-data tr { background-color: var(--flap-black); color: var(--flap-white); }

/* ── Select bar (Gallery kind filter) ─────────────────────────────────────
   Radzen's default paints the selected segment in its own blue and the rest in
   white-on-white, which is the one thing this board cannot absorb. Retinted to the
   four-value vocabulary: steel faces, amber for the active segment, square corners,
   tracked flap caps. Every size class is covered because Size is a component
   parameter — changing it in markup must not silently fall back to the blue. */
.rz-selectbutton {
    /* inline-flex, so the bar is exactly as wide as its segments — as a block it stretched
       and the container's rule colour showed through as a dead grey tail. */
    display: inline-flex;
    flex-wrap: wrap;
    gap: 1px;
    border: 1px solid var(--rule-strong);
    background: var(--rule-strong);
    border-radius: 0;
    overflow: hidden;
}

.rz-selectbutton .rz-button {
    border-radius: 0 !important;
    border: none;
    background: var(--flap-black);
    color: var(--steel);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
    box-shadow: none;
    transition: color var(--motion-fast), background var(--motion-fast);
}

.rz-selectbutton .rz-button:hover { background: var(--flap-shadow); color: var(--flap-white); }

.rz-selectbutton .rz-button.rz-state-active,
.rz-selectbutton .rz-button.rz-button-xs.rz-state-active,
.rz-selectbutton .rz-button.rz-button-sm.rz-state-active,
.rz-selectbutton .rz-button.rz-button-md.rz-state-active,
.rz-selectbutton .rz-button.rz-button-lg.rz-state-active {
    background: var(--amber);
    color: var(--flap-black);
    font-weight: 700;
}

.rz-selectbutton .rz-button:focus-visible {
    outline: 2px solid var(--amber);
    outline-offset: -2px;
}

/* Stacked select-bar faces (prompt detail presets): a name over a qualifier, which is
   the same two-line face the flap-choice buttons used before Radzen took the control.
   Radzen wraps Template content in its own .rz-button-text span, so the column has to be
   declared on BOTH the button and that wrapper — styling only the button leaves the two
   lines sitting side by side. Each segment also flexes so the bar has no dead tail. */
.rz-selectbutton .rz-button:has(.detail-preset__name),
.rz-selectbutton .rz-button:has(.detail-preset__name) .rz-button-text {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.1rem;
    line-height: var(--lh-tight);
}

.rz-selectbutton .rz-button:has(.detail-preset__name) {
    flex: 1 1 0;
    padding-block: 0.45rem;
}

.preset-section .rz-selectbutton { display: flex; width: 100%; }

.detail-preset__name { font-weight: 700; }

.detail-preset__meta {
    font-family: var(--font-ui);
    font-size: var(--text-2xs);
    text-transform: none;
    letter-spacing: 0;
    opacity: 0.75;
}

/* ── 2x2 comparison matrix (Radzen dialog content) ────────────────────────
   Replaces a Bootstrap modal whose row/col/ratio/border/rounded/bg-black classes all
   stopped existing with the bundle. A real 2-up grid, hairline rules, square corners. */
.compare-matrix { display: flex; flex-direction: column; gap: var(--space-3); }

.compare-matrix__grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
}

/* One column below the fold-out width: two 1:1 tiles side by side on a phone leaves each
   too small to actually compare. */
@media (max-width: 600px) {
    .compare-matrix__grid { grid-template-columns: minmax(0, 1fr); }
}

.compare-matrix__cell {
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
    margin: 0;
    padding: var(--space-2);
    background: var(--flap-shadow);
    border: 1px solid var(--rule);
}

.compare-matrix__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
}

.compare-matrix__img-wrap {
    aspect-ratio: 1 / 1;
    background: var(--flap-black);
    box-shadow: var(--inset-cell);
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* contain, not cover: this panel exists to compare variations, and a crop would hide the
   part of the frame the user is comparing. */
.compare-matrix__img-wrap img { width: 100%; height: 100%; object-fit: contain; display: block; }

.compare-matrix__prompt {
    margin: 0;
    color: var(--steel);
    font-size: var(--text-2xs);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.compare-matrix__actions { display: flex; gap: var(--space-2); align-items: center; margin-top: auto; }

.compare-matrix__actions .rz-button { border-radius: 0; }

.compare-matrix__empty { color: var(--steel); margin: 0; padding: var(--space-4) 0; text-align: center; }

.compare-matrix__footer {
    display: flex;
    justify-content: flex-end;
    gap: var(--space-2);
    padding-top: var(--space-2);
    border-top: 1px solid var(--rule);
}

.compare-matrix__footer .rz-button { border-radius: 0; }

/* ── Gallery card ─────────────────────────────────────────────────────────
   The card, its thumbnail frame and the two overlays. All of this used to lean on
   Bootstrap utilities (card, position-relative/absolute, top-0, start-0, end-0,
   card-img-top, ratio) that stopped existing with the bundle, which is why the kind
   chip and the select box collided on top of each other. */
.gallery-card {
    display: flex;
    flex-direction: column;
    height: 100%;
    background: var(--flap-black);
    border: 1px solid var(--rule);
    border-radius: 0;
}

.gallery-card.border-warning { border-color: var(--amber); }

.gallery-card__thumb-wrapper {
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--flap-shadow);
}

.gallery-card__img { width: 100%; height: 100%; object-fit: cover; display: block; }

.gallery-card__kind { position: absolute; top: 0.5rem; left: 0.5rem; z-index: 1; }

.gallery-card__select { position: absolute; top: 0.5rem; right: 0.5rem; z-index: 1; }

/* Radzen's checkbox on a photo: square, and dark enough to read against a light image. */
.gallery-card__select .rz-chkbox-box,
.gallery-selectall .rz-chkbox-box {
    border-radius: 0;
    background: var(--flap-black);
    border: 1px solid var(--steel);
}

.gallery-card__select .rz-chkbox-box.rz-state-active,
.gallery-selectall .rz-chkbox-box.rz-state-active {
    background: var(--amber);
    border-color: var(--amber);
    color: var(--flap-black);
}

.gallery-selectall {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    cursor: pointer;
    color: var(--steel);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
}

.gallery-selectall:hover { color: var(--flap-white); }

/* ── Gallery card action row ──────────────────────────────────────────────
   Was `btn-group` + `btn-outline-*`, all Bootstrap classes that no longer exist.
   RadzenButton supplies the control; these rules supply the board's hairline rule
   and square corners. */
.gallery-card__actions {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 0.75rem;
    padding-top: 0.5rem;
    border-top: 1px solid var(--rule);
}

.gallery-card__actions-group { display: flex; gap: 1px; }

.gallery-card__actions .rz-button {
    border-radius: 0;
    min-width: 0;
    padding: 0.3rem 0.5rem;
}

.gallery-card__actions .rz-button:hover { background: var(--flap-shadow); }

/* Tooltip, in the board's voice rather than Radzen's rounded grey bubble. */
.flap-tooltip .rz-tooltip-content,
.rz-tooltip.flap-tooltip .rz-tooltip-content {
    background: var(--steel-face);
    color: #14161a;
    border-radius: 0;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
    box-shadow: var(--shadow-md);
}

/* Confirm dialog: square, flap-black, so a destructive question does not arrive
   looking like a different product. */
.flap-confirm .rz-dialog-content { background: var(--flap-black); color: var(--flap-white); }

.flap-confirm .rz-dialog-confirm-buttons .rz-button { border-radius: 0; }

/* ── Text input (Gallery search) ──────────────────────────────────────────── */
.rz-textbox {
    background: var(--flap-shadow);
    border: 1px solid var(--rule-strong);
    border-radius: 0;
    color: var(--flap-white);
    font-family: var(--font-ui);
    box-shadow: var(--inset-cell);
}

.rz-textbox:focus,
.rz-textbox:focus-visible {
    border-color: var(--amber);
    outline: none;
}

.rz-textbox::placeholder {
    color: var(--steel-dark);
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
}

/* Search field: the magnifier sits inside the box, so the input is padded past it
   rather than the icon being a separate input-group segment (the Bootstrap
   input-group classes this used to rely on no longer exist). */
.gallery-search { position: relative; display: flex; align-items: center; }

.gallery-search__icon {
    position: absolute;
    left: 0.6rem;
    color: var(--steel-dark);
    font-size: var(--text-sm);
    pointer-events: none;
    z-index: 1;
}

.gallery-search__input { width: 100%; padding-left: 2rem !important; padding-right: 2rem !important; }

.gallery-search__clear { position: absolute; right: 0.15rem; color: var(--steel); }

.gallery-kinds { display: flex; justify-content: flex-start; }

@media (min-width: 768px) {
    .gallery-kinds { justify-content: flex-end; }
}

.rz-notification-item,
.rz-dialog,
.rz-card,
.rz-panel {
    border-radius: 0;
    box-shadow: var(--shadow-md);
    background-color: var(--flap-black);
    color: var(--flap-white);
    border: 1px solid var(--rule-strong);
}

/* The gallery lightbox is a Radzen dialog now; it keeps its own frameless treatment. */
.rz-dialog.gallery-lightbox .rz-dialog-content { padding: 0; }

/* Dialog chrome. The .rz-dialog rule above paints the panel, but Radzen draws the TITLE BAR
   from its own light theme variables, so every dialog in the app — the variation modal, the
   gallery lightbox, the confirm, the comparison matrix — opened with a white header strip
   above a flap-black body. Styled once here rather than per dialog. */
.rz-dialog-titlebar {
    background: var(--steel-face);
    border-bottom: 1px solid var(--rule-strong);
    border-radius: 0;
    padding: 0.5rem 0.9rem;
}

.rz-dialog-title {
    color: #14161a;
    font-family: var(--font-flap);
    font-size: var(--text-sm);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--track-flap);
    text-shadow: 0 1px 0 rgba(255,255,255,0.35);
}

/* Radzen's close glyph inherits the light theme's ink, which vanishes on the steel face. */
.rz-dialog-titlebar .rz-dialog-titlebar-close { color: #14161a; border-radius: 0; }

.rz-dialog-titlebar .rz-dialog-titlebar-close:hover { background: rgba(0,0,0,0.15); }

.rz-dialog-content { background: var(--flap-black); color: var(--flap-white); }

/* Outlined buttons inside a dialog: Radzen's default is a white pill. Scoped to
   .rz-variant-outlined ONLY — adding .rz-shade-default here also caught the filled
   amber buttons and flattened them to transparent, which cost the dialog its primary
   action. Filled variants keep whatever ButtonStyle the markup asked for. */
.rz-dialog .rz-button.rz-variant-outlined {
    border-radius: 0;
    border-color: var(--rule-strong);
    color: var(--flap-white);
    background: transparent;
}

/* Square corners for every dialog button, whatever its variant. */
.rz-dialog .rz-button { border-radius: 0; }

/* Filled amber is the board's "go" face and it carries BLACK ink, not white. Radzen keeps
   its own light-theme foreground on filled buttons, which put paint-white on #FFB400 — a
   contrast ratio around 1.9:1, well under AA. Same for the danger fill. */
.rz-dialog .rz-button.rz-variant-filled.rz-warning,
.rz-button.rz-variant-filled.rz-warning { color: var(--flap-black); font-weight: 700; }

.rz-dialog .rz-button.rz-variant-filled.rz-danger,
.rz-button.rz-variant-filled.rz-danger { color: var(--flap-white); }

.rz-dialog .rz-button.rz-variant-outlined:hover { background: var(--flap-shadow); }


/* ═══ Chrome the framework still emits ═══════════════════════════════════ */

.blazor-error-boundary {
    background: var(--cancel-red);
    padding: 1rem 1rem 1rem 3.7rem;
    color: var(--flap-white);
    font-family: var(--font-flap);
    text-transform: uppercase;
    letter-spacing: var(--track-caps);
}

.blazor-error-boundary::after { content: "AN ERROR HAS OCCURRED."; }

#blazor-error-ui {
    background: var(--cancel-red);
    color: var(--flap-white);
    font-family: var(--font-flap);
    text-transform: uppercase;
    letter-spacing: 0.12em;
    font-size: var(--text-sm);
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0,0,0,0.4);
    display: none;
    left: 0;
    padding: 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

#blazor-error-ui .dismiss { cursor: pointer; position: absolute; right: 0.75rem; top: 0.6rem; }
#blazor-error-ui .reload { color: var(--flap-black); background: var(--flap-white); padding: 0 0.4rem; text-decoration: none; }

code { color: var(--amber); }

/* ═══ Scrollbars — the board's own gutters ═══════════════════════════════ */

* { scrollbar-color: var(--steel-dark) var(--flap-black); scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--flap-black); }
::-webkit-scrollbar-thumb { background: var(--steel-dark); border: 2px solid var(--flap-black); }
::-webkit-scrollbar-thumb:hover { background: var(--steel); }

/* ═══ Responsive ═════════════════════════════════════════════════════════
   Portrait is the primary composition. Column priority narrows; the board never
   scrolls sideways. Four breakpoints only — see the table in foundation.css.   */

@media (max-width: 639.98px) {
    body { background-image: none; }
    .upload-panel { padding: 0; }

    /* The status column drops below the name rather than squeezing it. Once it does,
       the SERVICE / STATUS header no longer sits over the columns it names, so it is
       withdrawn rather than left pointing at the wrong place. */
    .row-head { display: none; }
    .studio-feature-card { grid-template-columns: 30px minmax(0,1fr); row-gap: 0.35rem; }
    .studio-feature-card__cta { grid-column: 2; }
    .feature-card { grid-template-columns: 30px minmax(0,1fr); row-gap: 0.35rem; }
    .feature-card__cta { grid-column: 2; }

    .processing-pill { bottom: 0.75rem; right: 0.75rem; left: 0.75rem; justify-content: center; }
    .bulk-modal-panel { max-height: 92vh; overflow-y: auto; }
    .bulk-modal-img-wrap { max-height: 45vh; }
    .prompt-drawer__fab { left: 0.75rem; bottom: 0.75rem; }
    .split-compare { grid-template-columns: 1fr; }
    .split-compare__divider { width: auto; height: 14px; flex-direction: row; }
    .split-compare__divider-line { width: auto; height: 1px; flex: 1; }
}

@media (max-width: 479.98px) {
    /* Three named detail levels will not sit on one line in a 320px column without
       the labels truncating, and a truncated choice cannot be chosen confidently. */
    .flap-choices--tight { grid-template-columns: 1fr 1fr; }
}
}

/* ══════════════════════════════════════════════════════════════════════════════
   TOUCH TARGETS
   Measured on a 390×844 viewport, every one of these came in under the 44px
   minimum: the gallery refresh and prompt-drawer close buttons at 30×30, every
   AI picker select at 31px tall, and the small flap buttons at 38px. They are
   fine with a mouse and awkward with a thumb, which is the width where this app
   is most likely to be used on a photo you just took.

   The rule raises the CONTROL where the layout can absorb it and extends the
   hit area past the ink where it cannot — the guideline is about what the
   finger can land on, not how big the paint is.
   ══════════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse), (max-width: 767.98px) {

    .gallery-bar__refresh,
    .prompt-drawer__close {
        min-width: 44px;
        min-height: 44px;
    }

    .ai-picker__select {
        min-height: 44px;
        font-size: var(--text-md);
    }

    .flap-btn--sm { min-height: 44px; }

    /* Hit area extended without moving the painted box, for controls whose size is
       load-bearing on the rail or inside a dense row. */
    .flap-choice,
    .gallery-bar__refresh,
    .prompt-drawer__close { position: relative; }

    .flap-choice::after {
        content: "";
        position: absolute;
        inset: 50% 0 auto 0;
        min-height: 44px;
        height: 100%;
        transform: translateY(-50%);
    }
}

/* The 10px step is an UPPERCASE-ONLY size by the type scale's own rule, and badges
   using it sat at the edge of legibility on a phone. Bump the smallest two steps on
   coarse pointers; the tracking keeps them reading as flap caps either way. */
@media (pointer: coarse), (max-width: 767.98px) {
    .badge,
    .feature-page-desc .badge,
    .ai-picker__fixed-summary { font-size: var(--text-2xs); }
}

/* ── Rap Roast: the primary action, now below the settings it reads ────────── */
.roast-cta {
    margin-top: 1rem;
    display: flex;
    flex-direction: column;
    gap: 0.4rem;
}

.roast-cta__btn { width: 100%; justify-content: center; }

/* A disabled button that does not say why is a dead end. */
.roast-cta__hint {
    margin: 0;
    font-family: var(--font-flap);
    font-size: var(--text-2xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--steel);
    text-align: center;
}

/* Named reason shown when a description was synthesised from tags rather than produced by a
   model that looked at the photo. Amber, not red: the result is usable, just degraded. */
.description-fallback {
    color: var(--flap-amber, #e0a63a);
    border-left: 3px solid var(--flap-amber, #e0a63a);
    background: rgba(224, 166, 58, 0.08);
    padding: 0.5rem 0.75rem;
    border-radius: 0 4px 4px 0;
}

/* ═══ Camera capture ═══════════════════════════════════════════════════
   The live-preview alternative to the drop zone. Same steel frame and square
   corners as the board's other bays, so opening the camera reads as the intake
   bay changing mode rather than a modal arriving from somewhere else. */
.upload-panel__camera-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    margin-top: var(--space-2);
    padding: var(--space-2) var(--space-3);
    background: transparent;
    border: 1px dashed var(--steel);
    border-radius: 0;
    color: var(--flap-white);
    font-family: inherit;
    font-size: var(--text-sm);
    letter-spacing: .04em;
    text-transform: uppercase;
    cursor: pointer;
    transition: border-color .12s linear, color .12s linear;
}

.upload-panel__camera-btn:hover:not(:disabled),
.upload-panel__camera-btn:focus-visible {
    border-color: var(--amber);
    color: var(--amber);
}

.upload-panel__camera-btn:disabled { opacity: .5; cursor: default; }

.camera {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
}

.camera__stage {
    position: relative;
    /* Matches the drop zone's footprint so switching modes does not jump the page. */
    aspect-ratio: 4 / 3;
    background: var(--flap-black);
    border: 1px solid var(--steel);
    overflow: hidden;
}

.camera__video {
    width: 100%;
    height: 100%;
    /* cover, not contain: letterboxing a live preview inside an already-framed bay reads as
       a broken image rather than a deliberate crop. */
    object-fit: cover;
    display: block;
}

/* Front lens only. capturePhoto() mirrors the captured frame to match, so what is saved is
   what was on screen. */
.camera__video--mirrored { transform: scaleX(-1); }

.camera__controls {
    display: flex;
    gap: var(--space-2);
    justify-content: flex-end;
    flex-wrap: wrap;
}

@media (prefers-reduced-motion: reduce) {
    .upload-panel__camera-btn { transition: none; }
}

/* ═══ Classes the markup asked for and never got ═══════════════════════════
   Each of these was referenced from a class attribute with no rule anywhere in the
   app. Some were leaning on Bootstrap utilities that stopped existing with the bundle
   (.gallery-page's padding, the cost table's table-dark/table-sm), the rest were simply
   never written. Marker-only modifiers are NOT listed here: .shimmer-progress--determinate,
   .gallery-bar--loading/--empty and friends sit on bases that already carry their styling,
   so they render correctly with no rule of their own. */

/* Gallery page gutter. Was `p-3 p-md-4`; p-md-4 never existed, so the wider desktop
   gutter silently never applied. */
.gallery-page { padding: var(--space-3); }

@media (min-width: 768px) {
    .gallery-page { padding: var(--space-4); }
}

/* Dashed empty-state frame — the one place the board uses a dashed rule, to say
   "nothing here yet" rather than "a thing with an edge". */
.border-dashed { border-style: dashed !important; }

/* Utilities the markup used that the Bootstrap bundle used to supply. */
.fst-italic { font-style: italic; }
.text-amber { color: var(--amber); }

/* ── Video feature ──────────────────────────────────────────────────────── */
.video-prompt-help {
    display: flex;
    align-items: flex-start;
    gap: 0.4rem;
    margin-top: var(--space-2);
    color: var(--steel);
    font-size: var(--text-2xs);
    line-height: var(--lh-tight);
}

.video-prompt-help i { color: var(--amber); flex-shrink: 0; }

/* Without a width cap a 720p clip pushes the results column wider than the page. */
.video-result__player {
    width: 100%;
    max-width: 100%;
    aspect-ratio: 16 / 9;
    display: block;
    background: var(--flap-black);
    border: 1px solid var(--rule);
    box-shadow: var(--inset-cell);
}

/* ── Session cost breakdown ─────────────────────────────────────────────────
   The popover's figures were in a `table table-sm table-dark table-borderless`, none of
   which exists any more — so the breakdown rendered as a bare browser table on a dark
   panel: serif-ish default type, default cell padding, no alignment. */
.session-cost-popover__body { padding: var(--space-2) var(--space-3) var(--space-3); }

.session-cost-table {
    width: 100%;
    border-collapse: collapse;
    font-size: var(--text-2xs);
    margin-bottom: var(--space-2);
}

.session-cost-table td {
    padding: 0.25rem 0.35rem;
    border: none;
    vertical-align: middle;
}

/* Figures right-aligned and tabular, so the column of prices reads as a column. */
.session-cost-table td:not(:first-child) {
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.session-cost-table tfoot td {
    border-top: 1px solid var(--rule-strong);
    padding-top: 0.4rem;
    font-weight: 700;
}

