/* ==========================================================================
   /tools/ai-generator — AI STUDIO  (prefix: aig-)                v1.0 2026-08-18

   Direction 2 "Atelier": an inset DARK studio slab sitting inside the normal
   light Mono page. Left rail = a style board of real, human-drawn pack art.
   Right column = "the plate", one fixed square that is the empty state, the
   progress state and the result at identical coordinates, so nothing moves.

   READ BEFORE EDITING
   -------------------
   1. Loaded AFTER mono-getillustrations.css, site.css and bundle.min.css.
      Every --mono-* token below comes from mono-getillustrations.css.
   2. Everything is namespaced `aig-`. Nothing here may leak out, and the slab
      must not inherit anything meaningful from bundle.min.css / site.css.
      The three documented traps are defended explicitly, each marked TRAP:
        · site.css:302  a:not(.btn)…:hover { color:#454545 !important }
                        -> beaten with ID-level specificity (id > any classes)
        · site.css:278  h1 { font-size: clamp(…) !important }
                        -> #aig-h1 re-declares with !important
        · site.css:988  @media(max-width:767px){ button,a{min-height:44px} }
                        -> .aig-ctl pins its own min-width/min-height
   3. Design tokens are declared on `.aig-studio` / `.aig-sheet`, NEVER on
      `:root`. That is what keeps the dark surface from bleeding into the site.
   4. `overflow-x: clip` on the slab — never `hidden`. `hidden` creates a
      containing block and kills the sticky rail (2026-08-15 icon-pack finding).
   5. No negative-margin / `right:-100%` full-bleed pseudo-elements anywhere on
      this surface. That pattern produced the invisible 1065px phantom scroll.
   6. Collapse / dim / lock rules are scoped under `#aig-root[data-js="on"]`
      (set by the inline bootstrap). A dead bundle therefore degrades to a
      plain stacked form with every control visible, never a blank page.
   7. This is a PLAIN .css file, so `@media` / `@keyframes` are single-@.
      If any of this is ever inlined into the .cshtml it must become `@@`.

   ATTRIBUTE CONTRACT (written by aiGenerator.js, read by this file)
   -----------------------------------------------------------------
   VERIFIED 2026-08-18 line by line against App.UI/wwwroot/js/aiGenerator.js.
   Every entry below is a token that file actually writes. Anything this
   stylesheet styles that the script does not write is dead paint, and an
   earlier revision of this table carried six such entries (.is-refund,
   .is-promoted, .is-warn on the counter, .is-open on the sheet, [data-phase]
   with future|current|done, .aig-draft--expired). They are corrected here.

     #aig-root       [data-js]               on
     #aig-stage      [data-state]            idle|ready|running|done|failed|
                                             gone|expired|offline
     #aig-rail       [data-mode]             picking|board
     #aig-picker     [data-tab]              packs|styles
     #aig-studio     [data-busy]             true
     .aig-tile       [data-i]                1..4      (also drives the badge)
     #aig-canvas-fail [data-code]            raw failureCode, never rendered
     .aig-phase      .is-current / .is-done  ladder rows; no class = future
     #aig-escalate   text only               the box is reserved from t=0, so
                                             the reveal is driven by :not(:empty)
     #aig-prompt-row .is-invalid             400 invalid_prompt border
     #aig-count      .is-near                counter red at 380 chars
     #aig-reason     .aig-note--info|warn|error   className is REPLACED wholesale
     #aig-balance    .is-empty               out_of_allowance readout
     #aig-balance    .aig-refund             one-shot lime ring
     .aig-canvas     .aig-attend             409 nudge
     .aig-btn        .is-spinning            per-action spinner (replaces label)
     .aig-draft      .is-expired             history tile
     <img>           .is-missing             a reference thumb that 404'd
     <html>          .aig-sheet-open         scroll lock while the sheet is open
     .aig-sheet / .aig-sheet__scrim  [hidden]    the sheet is [hidden]-driven

   The refund count-up is NOT in this list. The script writes a --aig-n custom
   property but never wraps the number in an element that could count, so the
   number swaps instantly and the lime ring carries the signal — which is the
   documented fallback, not a regression. Do not restore the @property block
   without markup for it, and never drop the ring as redundant.

   Nodes the script CREATES and this file must therefore dress:
     p.aig-action-note        transient 8s line inside #aig-actions
     p/span.aig-note-sub      the second, quieter line of a two-line message
     mark.aig-hl              search-match highlight
     a.aig-link               inline link inside a message
     button.aig-btn           Retry / Reload / Try the Style tab
     span#aig-scan.aig-scan__line   rebuilt with the reference field

   ADDITIONALLY: every grid track on this surface is `minmax(0, …)`, never a
   bare `1fr`/`auto`. A bare track takes its AUTO minimum from content, so one
   wide <img> or an inner `overflow-x: auto` strip pushes the whole page
   sideways. That single rule is what makes the 320px floor hold.

   VERIFIED 2026-08-18 against a Chromium harness (mono + site + this file) at
   320 / 375 / 414 / 560 / 720 / 979 / 980 / 1100 / 1400 / 1440 / 1920, in both
   rail modes x six stage states, WITH `overflow-x: clip` REMOVED so a genuine
   overflow could not hide behind it. documentElement.scrollWidth ==
   clientWidth at every combination. Re-run that sweep after any layout edit.

   MOTION INVENTORY (all CSS-only, all prefixed aig-)
     1 aig-scan · 2 aig-pulse · 3 aig-ref-in · 4 aig-drift · 5 aig-resolve +
     aig-develop · 6 aig-slide-up · 7 aig-attend · 8 aig-refund-ring ·
     9 aig-fail-in · 10 aig-spin.  No shimmer. No percentage bar. No shake.
   ========================================================================== */


/* ==========================================================================
   0. PAGE SCAFFOLD — the light half
   Three container measures ON PURPOSE. Wrapping the whole page in one
   .mono-container is the single most likely implementation error.
   ========================================================================== */

.aig { background: var(--mono-white); }

/* Region anchors. The five light sections are otherwise plain .mono-section
   blocks, so what these classes add is one guarantee: no light section on this
   page may ever be the thing that scrolls the document sideways.
   `clip`, never `hidden` — `hidden` makes a scroll container and would take the
   sticky rail inside #aig-tool with it (2026-08-15 icon-pack finding). */
.aig-intro,
.aig-tool,
.aig-recent,
.aig-about,
.aig-faq { overflow-x: clip; }

/* TRAP (site.css:278) — h1 font-size is !important globally. */
#aig-h1.aig-intro__h1 {
    font-family: var(--mono-font-display);
    font-size: clamp(1.875rem, 4vw, 3rem) !important;
    font-weight: var(--mono-w-extrabold);
    line-height: var(--mono-lh-heading);
    letter-spacing: -0.03em;
    color: var(--mono-ink);
    margin: 0;
    max-width: 20ch;
}

#aig-intro .mono-lead {
    max-width: 68ch;
    margin: 16px 0 0;
    font-size: var(--mono-fs-lead);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}

/* Server-rendered, correct at first paint. Never populated by JS on load. */
#aig-balance-line,
.aig-intro__balance {
    margin: 20px 0 0;
    font-size: var(--mono-fs-sm);
    color: var(--mono-gray-700);
}
#aig-balance-line .aig-num,
.aig-intro__balance .aig-num { color: var(--mono-ink); }

/* The tool section is the only one that hosts the dark slab. */
#aig-tool { padding-top: var(--mono-space-6); }

/* TRAP (site.css:302) — a:not(.btn)…:hover is (0,9,1) + !important.
   An ID selector outranks any number of classes, so these win.
   NOTE: #aig-tool is deliberately absent from this list — the dark slab
   inside it has its own lime hover rule in §2 and must not be outranked. */
#aig-intro a:not(.mono-btn):hover,
#aig-tool .aig-noscript a:not(.mono-btn):hover,
#aig-recent a:not(.mono-btn):hover,
#aig-about a:not(.mono-btn):hover,
#aig-license a:not(.mono-btn):hover,
#aig-faq a:not(.mono-btn):hover,
#aig-gate a:not(.mono-btn):hover,
#aig-signin a:not(.mono-btn):hover {
    color: var(--mono-ink) !important;
}

/* The two named link roles on the LIGHT half. site.css:302 is
   `a:not(.btn)…:hover { color:#454545 !important }` at (0,9,1); these carry an
   id from the enclosing region so they outrank it, because an id beats any
   number of classes and !important-vs-!important is decided on specificity.
   .aig-cta sits on the .mono-btn--primary anchors, which the region rules above
   deliberately exclude (`:not(.mono-btn)`) and which therefore had NO defence at
   all: on hover the button fills lime and site.css repainted its label #454545
   on #ccea4a, which is 2.4:1. Mono's own value for that state is --mono-ink. */
#aig-gate a.aig-cta:hover,
#aig-signin a.aig-cta:hover,
#aig-tool a.aig-cta:hover {
    color: var(--mono-ink) !important;
}
#aig-gate a.aig-link:hover,
#aig-signin a.aig-link:hover,
#aig-tool a.aig-link:hover,
#aig-recent a.aig-link:hover {
    color: var(--mono-ink) !important;
}

/* Screen-reader-only helper (used by the 8/20/35s polite announcer). */
.aig-sr {
    position: absolute !important;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
}

/* Skip link — first focusable element inside the slab.
   It must NOT be hidden by translating it off the top: the slab is
   `overflow-x: clip` with overflow-y visible (so the sticky rail survives),
   so an upward offset escapes onto the light page and is plainly readable.
   Opacity + pointer-events is the only correct hide here; the element stays
   focusable, which is the entire point. */
.aig-skip {
    position: absolute;
    left: 12px; top: 12px;
    z-index: 30;
    padding: 10px 16px;
    border-radius: 10px;
    background: #ccea4a;
    color: #0b0b0e;
    font-size: 13px;
    font-weight: 700;
    text-decoration: none;
    opacity: 0;
    pointer-events: none;
    transform: translateY(-6px);
    transition: opacity var(--mono-dur) var(--mono-ease), transform var(--mono-dur) var(--mono-ease);
}
.aig-skip:focus {
    opacity: 1;
    pointer-events: auto;
    transform: translateY(0);
}


/* ==========================================================================
   1. TOKENS — declared on the dark surfaces only, NEVER on :root
   ========================================================================== */

.aig-studio,
.aig-sheet {
    /* -- surfaces ------------------------------------------------------- */
    --aig-ground:      #0b0b0e;   /* the slab */
    --aig-panel:       #0e0e12;   /* rail body, canvas, glass fallback */
    --aig-raised:      #17171b;   /* tiles, list rows, inputs */
    --aig-checker-a:   #17171b;
    --aig-checker-b:   #101014;

    /* -- lines ---------------------------------------------------------- */
    --aig-line:        rgba(255, 255, 255, 0.08);
    --aig-line-strong: rgba(255, 255, 255, 0.14);

    /* -- ink (contrast measured on #0b0b0e) ----------------------------- */
    --aig-t1:          #f4f4f5;   /* 17.9:1 */
    --aig-t2:          #a1a1aa;   /*  7.3:1 */
    --aig-t3:          #71717a;   /*  3.9:1 — ICON STROKES ONLY. Below AA for
                                     text at any size, so no string on this
                                     surface uses it. Audit: grep --aig-t3 */

    /* -- accent: exactly four jobs -------------------------------------- *
       conditioning ring · Generate fill · success check · focus ring       */
    --aig-lime:        #ccea4a;   /* = --mono-lime */
    --aig-lime-dim:    rgba(204, 234, 74, 0.55);
    --aig-lime-wash:   rgba(204, 234, 74, 0.06);

    /* -- semantic ------------------------------------------------------- */
    --aig-warn:        #fbbf24;
    --aig-warn-t:      #fcd34d;
    --aig-danger:      rgba(239, 68, 68, 0.55);
    --aig-danger-t:    #fca5a5;

    /* -- glass (Photogen recipe, verbatim) ------------------------------ */
    --aig-glass-bg:      rgba(8, 8, 10, 0.78);
    --aig-glass-bg-str:  rgba(8, 8, 10, 0.88);
    --aig-glass-brd:     rgba(255, 255, 255, 0.08);
    --aig-glass-brd-str: rgba(255, 255, 255, 0.12);
    --aig-glass-blur:    blur(40px) saturate(180%);
    --aig-glass-shadow:  0 8px 30px -10px rgba(0, 0, 0, 0.6);

    /* -- geometry ------------------------------------------------------- */
    --aig-ctl:      40px;
    --aig-r-ctl:    12px;
    --aig-r-tile:   10px;
    --aig-r-bar:    14px;
    --aig-r-row:    18px;
    --aig-r-canvas: 16px;
    --aig-r-slab:   24px;

    /* -- type ----------------------------------------------------------- */
    --aig-fs-panel: 15px;
    --aig-fs-body:  14px;
    --aig-fs-input: 15px;
    --aig-fs-meta:  12.5px;
    --aig-fs-label: 11px;
    --aig-fs-num:   13px;
    --aig-fs-clock: clamp(18px, 3vw, 26px);

    /* -- header offset, re-measured once by the inline bootstrap -------- */
    --aig-hdr-h: 72px;
}


/* ==========================================================================
   2. THE SLAB
   ========================================================================== */

.aig-studio {
    position: relative;
    max-width: 1320px;
    margin: 0 auto;
    padding: 20px;
    border-radius: var(--aig-r-slab);
    background: var(--aig-ground);
    border: 1px solid rgba(255, 255, 255, 0.06);
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.07);
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    letter-spacing: var(--mono-track-body);
    /* clip, NEVER hidden — hidden would break the sticky rail. */
    overflow-x: clip;
}

.aig-studio *,
.aig-sheet * { box-sizing: border-box; }

/* [hidden] MUST beat every layout `display` declared below it. Without this
   guard a `display:flex` on a component silently un-hides a JS-hidden node —
   the classic `[hidden]` bug. The two documented exceptions follow. */
.aig [hidden],
.aig-studio [hidden],
.aig-sheet [hidden] { display: none !important; }
/* The ONE exception — the escalation box is reserved from t=0, so hiding it
   must never collapse it. ZERO CLS is the requirement, not a preference.
   (aiGenerator.js never sets [hidden] here; it writes and clears the text. The
   guard stays because the reveal must survive either mechanism.) */
#aig-escalate[hidden] { display: grid !important; visibility: hidden; opacity: 0; }

/* Mono's card/button lift must not apply on a tool surface. */
.aig-studio .mono-btn:hover,
.aig-studio .mono-card:hover,
.aig-sheet .mono-btn:hover { transform: none; }

/* TRAP (site.css:302) — dark-surface links go lime on hover. ID wins. */
#aig-studio a,
#aig-sheet a { color: var(--aig-t1); text-decoration: none; }
#aig-studio a.aig-link,
#aig-sheet a.aig-link {
    color: var(--aig-lime);
    border-bottom: 1px solid var(--aig-lime-dim);
}
#aig-studio a:not(.aig-btn):not(.aig-ctl):hover,
#aig-sheet a:not(.aig-btn):not(.aig-ctl):hover {
    color: var(--aig-lime) !important;
}

.aig-studio__grid {
    display: grid;
    /* minmax(0, 1fr), never a bare 1fr. A bare auto/1fr track is sized by
       max-content and will happily blow past a definite container — that is
       what makes an inner `overflow-x: auto` strip (the mobile style board)
       push the whole page sideways instead of scrolling inside itself.
       Every grid on this surface uses the minmax(0, …) form for that reason. */
    grid-template-columns: 360px minmax(0, 1fr);
    gap: 20px;
    align-items: start;
}

/* --- the mono face is for MEASURED QUANTITIES ONLY ------------------------
   elapsed time · character counts · credit counts · result counts · file
   sizes · path counts · job ids · clock times · countdowns. Nothing else.
   A number always looks like a number; a claim always looks like a claim. */
.aig-num,
.aig-studio .aig-num,
.aig-sheet .aig-num {
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--aig-fs-num);
    letter-spacing: 0.02em;
}

/* The 11px uppercase zone label — RECENTLY USED / YOUR PACKS / ALL PACKS /
   STYLE BOARD. One class, because they are one thing. */
.aig-zone__label {
    display: block;
    margin: 0 0 8px 2px;
    font-size: var(--aig-fs-label);
    font-weight: 700;
    line-height: 1.2;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--aig-t2);
}

/* The rows a zone owns. aiGenerator.js clears and refills this exact container
   (field(zone, 'rows')), which is also why the 12 skeletons live inside it. */
.aig-zone__rows {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}


/* ==========================================================================
   3. GLASS — restricted to five short chrome bars plus the sheet.
   The tall rail body and the canvas stay OPAQUE: backdrop-filter over a
   scrolling image grid is a real paint cost and buys nothing.
   ========================================================================== */

.aig-glass {
    background: var(--aig-glass-bg);
    border: 1px solid var(--aig-glass-brd);
    -webkit-backdrop-filter: var(--aig-glass-blur);
    backdrop-filter: var(--aig-glass-blur);
    box-shadow: var(--aig-glass-shadow);
}
.aig-glass--strong {
    background: var(--aig-glass-bg-str);
    border-color: var(--aig-glass-brd-str);
}

@supports not (backdrop-filter: blur(1px)) {
    .aig-glass,
    .aig-glass--strong { background: #0e0e12; }
}
@media (prefers-reduced-transparency: reduce) {
    .aig-glass,
    .aig-glass--strong {
        background: #0e0e12;
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
    }
}


/* ==========================================================================
   4. CONTROL LANGUAGE
   40x40, radius 12, no hover transform. A tool's controls must not move
   under a cursor that is about to click them.
   ========================================================================== */

.aig-ctl {
    /* TRAP (site.css:988) — pins its own box against the ≤767px 44px floor. */
    width: var(--aig-ctl);
    height: var(--aig-ctl);
    min-width: var(--aig-ctl);
    min-height: var(--aig-ctl);
    display: inline-grid;
    place-items: center;
    padding: 0;
    border-radius: var(--aig-r-ctl);
    background: rgba(255, 255, 255, 0.03);
    border: 1px solid rgba(255, 255, 255, 0.10);
    color: var(--aig-t2);
    cursor: pointer;
    font: inherit;
    transition:
        background var(--mono-dur) var(--mono-ease),
        border-color var(--mono-dur) var(--mono-ease),
        color var(--mono-dur) var(--mono-ease);
}
.aig-ctl:not(:disabled):hover {
    border-color: var(--aig-lime-dim);
    color: var(--aig-lime);
    background: var(--aig-lime-wash);
}
.aig-ctl:active { transform: scale(0.96); }
.aig-ctl:disabled,
.aig-ctl[aria-disabled="true"] { opacity: 0.38; cursor: not-allowed; }
.aig-ctl svg { width: 18px; height: 18px; stroke-width: 1.75; }

/* The 32px variant the 320px floor needs is applied directly to
   #aig-source-change in §14 — it is the only element that ever bends, and a
   named modifier class nobody could apply anywhere else is not a variant. */

/* Text control (Stop watching, inline retries). */
#aig-stop,
.aig-ctl--text {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    min-width: 0;
    min-height: 0;
    width: auto; height: auto;
    padding: 6px 2px;
    background: none;
    border: 0;
    border-radius: 6px;
    color: var(--aig-t2);
    font: inherit;
    font-size: var(--aig-fs-meta);
    font-weight: 500;
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.22);
    cursor: pointer;
    transition: color var(--mono-dur) var(--mono-ease);
}
.aig-ctl--text:hover {
    color: var(--aig-t1);
    text-decoration-color: var(--aig-lime-dim);
}

/* Labelled control — squared (12), because the studio is squared and the
   marketing page is pill. That difference IS the seam, applied consistently. */
.aig-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: var(--aig-ctl);
    min-height: var(--aig-ctl);
    padding: 0 16px;
    border-radius: var(--aig-r-ctl);
    background: rgba(255, 255, 255, 0.04);
    border: 1px solid rgba(255, 255, 255, 0.10);
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-body);
    font-weight: 600;
    letter-spacing: var(--mono-track-body);
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition:
        background var(--mono-dur) var(--mono-ease),
        border-color var(--mono-dur) var(--mono-ease),
        color var(--mono-dur) var(--mono-ease);
}
.aig-btn:not(:disabled):hover {
    border-color: var(--aig-lime-dim);
    color: var(--aig-lime);
    background: var(--aig-lime-wash);
}
.aig-btn:active { transform: scale(0.98); }
.aig-btn:disabled,
.aig-btn[aria-disabled="true"] { opacity: 0.38; cursor: not-allowed; }
.aig-btn svg { width: 16px; height: 16px; flex: 0 0 auto; }
/* The label half of a two-part button (label + price pill). min-width:0 is what
   lets it ellipsis instead of forcing the button wider than the wrapping bar. */
.aig-btn__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Two accents, and only two.
   --lime-outline is the DEFAULT download in a bar of peers (Download PNG): it
   reads as first among equals without being a second filled-lime element, which
   #aig-submit is the only one of on this surface.
   --lime is the RECOVERY action — Try again, Download SVG — where the bar it
   sits in has no other lime and the user has just been told something failed. */
.aig-btn--lime-outline {
    border-color: var(--aig-lime-dim);
    color: var(--aig-lime);
}
.aig-btn--lime-outline:not(:disabled):hover {
    background: var(--aig-lime-wash);
    border-color: var(--aig-lime);
}
.aig-btn--lime {
    background: var(--aig-lime);
    border-color: var(--aig-lime);
    color: #0b0b0e;
    font-weight: 700;
}
.aig-btn--lime:not(:disabled):hover {
    background: #d7f062;
    border-color: #d7f062;
    color: #0b0b0e;
}

/* Per-action spinner. aiGenerator.js only toggles .is-spinning on the button,
   so the spinner is drawn here and it REPLACES the label rather than sitting
   beside it — the button must not change width while it works (motion #12).
   Never page-level: this one button is working, nothing else is. */
.aig-btn.is-spinning {
    position: relative;
    color: transparent;
    pointer-events: none;
}
.aig-btn.is-spinning > * { visibility: hidden; }
.aig-btn.is-spinning::after {
    content: "";
    position: absolute;
    left: 50%; top: 50%;
    width: 16px; height: 16px;
    margin: -8px 0 0 -8px;
    border-radius: 50%;
    border: 2px solid rgba(204, 234, 74, 0.25);
    border-top-color: var(--aig-lime);
    animation: aig-spin 720ms linear infinite;
}

/* FOCUS — one rule, two designed exceptions (textarea + .aig-src-row). */
.aig-studio :focus-visible,
.aig-sheet :focus-visible {
    outline: 2px solid var(--aig-lime);
    outline-offset: 2px;
}

/* The pill that states a price ON the control that spends it. Every price on
   this page is attached to its own button; there is no price list anywhere. */
.aig-pill {
    flex: 0 0 auto;
    padding: 3px 8px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.08);
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 11.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    white-space: nowrap;
}

/* NOTE ON LABEL CROSS-FADES (motion #12). The two-layer .aig-swap grid this
   file used to carry was never applied to anything — aiGenerator.js swaps a
   label with textContent. What actually matters about that motion entry is the
   part that IS enforced: #aig-submit never transitions `width`, so the rate-limit
   countdown cannot make the layout breathe. See §8. Do not re-add a cross-fade
   shell without markup to hang it on. */


/* ==========================================================================
   5. NOTES — four appearances, reused everywhere
   ========================================================================== */

.aig-note {
    display: block;
    padding: 10px 12px;
    border-radius: 10px;
    border: 1px solid var(--aig-line);
    background: rgba(255, 255, 255, 0.03);
    color: var(--aig-t2);
    font-size: var(--aig-fs-meta);
    font-weight: 500;
    line-height: 1.5;
}
.aig-note strong { color: var(--aig-t1); font-weight: 700; }

/*  The three tones. aiGenerator.js applies them BY CONCATENATION —
    `el.className = 'aig-reason' + ' aig-note--' + tone` — so grepping the
    script for the literal string finds nothing and an audit will call these
    dead. They are not. Tone comes from reason(text, tone) and from the
    server-rendered #aig-picker-empty / #aig-stale. */
.aig-note--info  { border-left: 3px solid rgba(255, 255, 255, 0.28); }
.aig-note--warn  { border-left: 3px solid var(--aig-warn); color: var(--aig-warn-t); }
.aig-note--error { border-left: 3px solid var(--aig-danger); color: var(--aig-danger-t); }

/*  A note carries at most two lines, and the second one is always the quieter
    half of the pair: "Nothing was charged.", "Styles still work.",
    "Generating again costs 1 credit." aiGenerator.js builds it as
    .aig-note-sub, and it is the ONLY sub-line class on the surface. */
.aig-note-sub {
    display: block;
    margin-top: 2px;
    font-size: 12px;
    line-height: 1.45;
    font-weight: 400;
    color: var(--aig-t2);
}

/* Title + body inside a server-rendered note (#aig-picker-empty). The script
   replaces these nodes with plain <p> + .aig-note-sub when it rewrites the box,
   so the two paths have to land on the same measurements. */
.aig-note__title {
    margin: 0;
    font-size: var(--aig-fs-meta);
    font-weight: 600;
    line-height: 1.5;
    color: var(--aig-t1);
}
.aig-note__body {
    margin: 2px 0 0;
    font-size: 12px;
    line-height: 1.45;
    color: var(--aig-t2);
}
.aig-note .aig-btn,
.aig-note-sub + .aig-btn { margin-top: 10px; }


/* ==========================================================================
   6. RAIL
   ========================================================================== */

/* The rail frame itself is TRANSPARENT so the two glass chrome bars sit on
   the slab ground and the filter has a real backdrop. The tall scrolling
   body is the opaque surface. Painting the whole rail opaque would make the
   head/foot backdrop-filter pure cost for zero effect. */
.aig-rail {
    display: grid;
    grid-template-rows: auto minmax(0, 1fr) auto;
    grid-template-columns: minmax(0, 1fr);   /* see note in .aig-studio__grid */
    min-width: 0;
    border-radius: var(--aig-r-canvas);
    background: transparent;
    border: 1px solid var(--aig-line);
    overflow: hidden;
    position: sticky;
    top: calc(var(--aig-hdr-h) + 16px);
}

.aig-rail__head {
    padding: 12px;
    border-bottom: 1px solid var(--aig-line);
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    border-top: 0;
    box-shadow: none;
    position: relative;
    z-index: 2;
}

.aig-rail__body {
    /* OPAQUE on purpose — no backdrop-filter on a tall scrolling image grid. */
    background: var(--aig-panel);
    padding: 16px 12px;
    overflow-y: auto;
    overscroll-behavior: contain;
    max-height: 56vh;
    min-height: 240px;
    scrollbar-width: thin;
    scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
}
.aig-rail__body::-webkit-scrollbar { width: 10px; }
.aig-rail__body::-webkit-scrollbar-thumb {
    background: rgba(255, 255, 255, 0.14);
    border-radius: 99px;
    border: 3px solid var(--aig-panel);
}

.aig-rail__foot {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border: 0;
    border-top: 1px solid var(--aig-line);
    border-radius: 0;
    box-shadow: none;
    position: relative;
    z-index: 2;
}
.aig-rail__foot > :first-child { flex: 1 1 auto; min-width: 0; }

/* The foot line. Two elements share it and only ever one at a time: the honest
   result count while picking, the conditioning sentence once a source is
   chosen. Same measure, same colour, so the swap does not twitch. */
.aig-rail__count {
    margin: 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.45;
    color: var(--aig-t2);
}
.aig-rail__count .aig-num { color: var(--aig-t1); }

/* The picker's tabpanel. It is only ever a container, but it is the container
   the roving-tabindex handler is bound to, so it must not be able to shrink
   below its rows. */
.aig-picker__panel {
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}

/* --- mode switch: picking <-> board --------------------------------------
   Without JS both halves render (stacked). With JS one is collapsed. */
#aig-root[data-js="on"] .aig-rail[data-mode="picking"] #aig-source-chip,
#aig-root[data-js="on"] .aig-rail[data-mode="picking"] #aig-board,
#aig-root[data-js="on"] .aig-rail[data-mode="board"]   #aig-picker,
#aig-root[data-js="on"] .aig-rail[data-mode="board"]   #aig-picker-panel { display: none; }

/* The foot swaps meaning with the mode: an honest result count while picking,
   the conditioning line once a source is chosen. Never both at once. */
#aig-root[data-js="on"] .aig-rail[data-mode="picking"] #aig-board-info,
#aig-root[data-js="on"] .aig-rail[data-mode="picking"] #aig-board-foot { display: none; }
#aig-root[data-js="on"] .aig-rail[data-mode="board"]   #aig-picker-count,
#aig-root[data-js="on"] .aig-rail[data-mode="board"]   #aig-picker-more { display: none; }


/* --- picker: tabs --------------------------------------------------------- */
.aig-picker__tabs {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 4px;
    padding: 4px;
    border-radius: var(--aig-r-ctl);
    background: rgba(255, 255, 255, 0.04);
}
.aig-tab {
    height: 32px;
    min-height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    border: 0;
    border-radius: 9px;
    background: transparent;
    color: var(--aig-t2);
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-body);
    font-weight: 600;
    cursor: pointer;
    transition:
        background var(--mono-dur) var(--mono-ease),
        color var(--mono-dur) var(--mono-ease);
}
.aig-tab:hover { color: var(--aig-t1); }
.aig-tab[aria-selected="true"] {
    background: var(--aig-raised);
    color: var(--aig-t1);
    box-shadow: inset 0 0 0 1px var(--aig-line-strong);
}

/* --- picker: search ------------------------------------------------------- */
.aig-picker__search {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    height: var(--aig-ctl);
    padding: 0 12px;
    border-radius: var(--aig-r-ctl);
    background: var(--aig-raised);
    border: 1px solid var(--aig-line);
    transition: border-color var(--mono-dur) var(--mono-ease);
}
.aig-picker__search:focus-within { border-color: var(--aig-lime-dim); }
.aig-picker__search svg { width: 16px; height: 16px; color: var(--aig-t3); flex: 0 0 auto; }
.aig-picker__search input {
    flex: 1 1 auto;
    min-width: 0;                 /* mandatory or the flex child won't shrink */
    height: 100%;
    background: none;
    border: 0;
    outline: none;
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-body);
}
.aig-picker__search input::placeholder { color: var(--aig-t2); }
.aig-picker__search input:focus-visible { outline: none; }

/* Style tab has 8 items — no search field is rendered, and none is shown. */
#aig-root[data-js="on"] .aig-picker[data-tab="styles"] #aig-picker-search { display: none !important; }
/* [data-tab] lives on #aig-picker in the rail HEAD while the zones live in
   the rail BODY, so they are neither siblings nor ancestor/descendant.
   :has() on the shared rail is the only correct hook. If :has() is ever
   unavailable both zone sets simply render — degraded, never broken. */
#aig-root[data-js="on"] .aig-rail:has(.aig-picker[data-tab="styles"]) #aig-picker-recent,
#aig-root[data-js="on"] .aig-rail:has(.aig-picker[data-tab="styles"]) #aig-picker-owned,
#aig-root[data-js="on"] .aig-rail:has(.aig-picker[data-tab="styles"]) #aig-picker-list,
#aig-root[data-js="on"] .aig-rail:has(.aig-picker[data-tab="packs"])  #aig-picker-styles,
#aig-root[data-js="on"] .aig-sheet:has(.aig-picker[data-tab="styles"]) #aig-picker-recent,
#aig-root[data-js="on"] .aig-sheet:has(.aig-picker[data-tab="styles"]) #aig-picker-owned,
#aig-root[data-js="on"] .aig-sheet:has(.aig-picker[data-tab="styles"]) #aig-picker-list,
#aig-root[data-js="on"] .aig-sheet:has(.aig-picker[data-tab="packs"])  #aig-picker-styles { display: none !important; }

/* --- picker: zones -------------------------------------------------------- */
.aig-picker__zone { margin: 0 0 16px; }
.aig-picker__zone:last-child { margin-bottom: 0; }
.aig-picker__zone[hidden] { display: none; }

/* "Show all 137" / "Show 40 more". Full width, because a half-width button at
   the end of a list of full-width rows reads as belonging to the last row. */
.aig-picker__more {
    width: 100%;
    margin-top: 8px;
}

/* --- picker: a source row (list, not a grid — 1,500 items scan faster) ---- */
.aig-src-row {
    display: flex;
    align-items: center;
    gap: 12px;
    width: 100%;
    height: 56px;
    padding: 0 8px;
    border: 0;
    border-radius: var(--aig-r-ctl);
    background: transparent;
    color: inherit;
    text-align: left;
    cursor: pointer;
    /* NO transform transition — a fast scroll through 60 rows must cost 0. */
    transition:
        background var(--mono-dur-fast) var(--mono-ease),
        box-shadow var(--mono-dur-fast) var(--mono-ease);
}
.aig-src-row:hover { background: var(--aig-raised); }
.aig-src-row[aria-selected="true"] {
    background: var(--aig-raised);
    box-shadow: inset 0 0 0 1px var(--aig-lime-dim);
}
/* Designed exception #2: an INSET ring so it never clips on the scroller. */
.aig-src-row:focus-visible {
    outline: none;
    box-shadow: inset 0 0 0 2px var(--aig-lime);
}
.aig-src-row__thumb {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    border-radius: 8px;
    background: var(--aig-raised);
    object-fit: cover;
    display: block;
}
.aig-src-row__txt {
    flex: 1 1 auto;
    min-width: 0;                 /* mandatory — see checklist item 10 */
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
}
.aig-src-row__name {
    font-size: var(--aig-fs-body);
    font-weight: 600;
    color: var(--aig-t1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.aig-src-row__meta {
    font-size: var(--aig-fs-meta);
    color: var(--aig-t2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* Search highlight. */
.aig-hl {
    background: var(--aig-lime-wash);
    color: var(--aig-t1);
    border-radius: 3px;
    padding: 0 1px;
}

/*  Skeleton rows — flat, NEVER a shimmer. A shimmer is a lie about progress in
    exactly the way a fake progress bar is, and this list has no progress to
    report: it is one request that either lands or does not.
    They are 12 bare blocks at the row height, sitting inside .aig-zone__rows so
    that the same fillZone() call that appends the real rows removes them. */
.aig-src-skels {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    pointer-events: none;
}
.aig-src-skel {
    display: block;
    height: 56px;
    border-radius: var(--aig-r-ctl);
    /* One flat wash, no gradient, no animation. */
    background:
        linear-gradient(to right, var(--aig-raised) 0 40px, transparent 40px 52px,
                        var(--aig-raised) 52px 62%, transparent 62%)
        no-repeat 0 8px / 100% 40px;
}

/* --- picker: style grid (8 cards, 2 columns) ------------------------------ */
.aig-style-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
}
.aig-style-card {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    padding: 8px;
    border: 1px solid var(--aig-line);
    border-radius: var(--aig-r-ctl);
    background: var(--aig-raised);
    color: inherit;
    text-align: left;
    cursor: pointer;
    transition:
        border-color var(--mono-dur) var(--mono-ease),
        background var(--mono-dur) var(--mono-ease);
}
.aig-style-card:hover { border-color: var(--aig-lime-dim); }
.aig-style-card[aria-selected="true"] { border-color: var(--aig-lime); }
.aig-style-card__thumb {
    aspect-ratio: 1 / 1;
    width: 100%;
    display: block;
    border-radius: 8px;
    background: var(--aig-panel);
    object-fit: cover;
}
.aig-style-card__name {
    font-size: var(--aig-fs-body);
    font-weight: 600;
    color: var(--aig-t1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* "126 packs · 11,608 illustrations" — two measured quantities, so it is set
   in the mono face even though it is not marked .aig-num: a style card meta
   line is nothing BUT counts. */
.aig-style-card__meta {
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 11.5px;
    letter-spacing: 0.02em;
    color: var(--aig-t2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* The one sentence under the eight style cards. It spans both columns because
   it is about the tab, not about a card. */
.aig-style-note {
    grid-column: 1 / -1;
    margin: 4px 0 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}

/*  --- the rail foot's two lines --------------------------------------------
    Whichever one the mode is showing has to take the free width and let the
    40px info control sit at the end. :first-child cannot do this job: it is
    structural, so when #aig-picker-count is display:none the board line is
    still not the first child and would size to its content. */
#aig-picker-count,
#aig-board-foot { flex: 1 1 auto; min-width: 0; }


/* --- source chip (rail head, mode=board) --------------------------------- */
.aig-source {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
}
#aig-source-thumb {
    flex: 0 0 auto;
    width: 40px; height: 40px;
    border-radius: 8px;
    background: var(--aig-raised);
    object-fit: cover;
    display: block;
}
.aig-source__txt {
    flex: 1 1 auto;
    min-width: 0;                 /* mandatory */
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
}
/* JS never assigns this thumb a src, so it must not be able to draw a
   broken-image glyph in the chip. */
#aig-source-thumb:not([src]) { visibility: hidden; }
#aig-source-name {
    font-size: var(--aig-fs-body);
    font-weight: 700;
    color: var(--aig-t1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* 180ms cross-fade when the selection slot changes hands. */
    animation: aig-label-in 180ms var(--mono-ease);
}
#aig-source-meta {
    font-size: var(--aig-fs-meta);
    color: var(--aig-t2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#aig-source-change { flex: 0 0 auto; }


/* --- style board ---------------------------------------------------------- */
.aig-board {
    min-width: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}
#aig-board-sub,
.aig-board__sub {
    margin: 0 0 12px;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}
.aig-tilegrid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
}
.aig-tile {
    position: relative;
    aspect-ratio: 1 / 1;
    border-radius: var(--aig-r-tile);
    background: var(--aig-raised);
    overflow: hidden;
}
.aig-tile img {
    width: 100%; height: 100%;
    display: block;
    object-fit: cover;
    /* White base: most previews are transparent PNGs of dark line art, invisible
       on the dark tile (owner report, 2026-08-19). Only the img gets the white -
       a failed load still degrades to the flat --aig-raised tile underneath. */
    background: #fff;
    /* CDN 403 -> the flat --aig-raised rectangle underneath is the fallback,
       with the alt text suppressed so a dead thumb degrades to a plain tile.
       Generate is NEVER gated on board imagery. */
    color: transparent;
    font-size: 0;
}
/* aiGenerator.js:532 adds .is-missing from img.onerror. Suppressing the alt text
   (above) is not enough on its own - the browser still paints its native
   broken-image glyph, which is stark on the dark --aig-raised tile. Hiding the
   element with visibility (not display) keeps the tile's box, so the board does
   not reflow when a single reference thumb 404s. */
.aig-tile img.is-missing { visibility: hidden; }
/* Tiles are BUTTONS since the reference-picking build: strip the UA chrome but keep
   the box the grid rules above define. width:100% because a button's intrinsic size
   is its content, and an empty (image-failed) tile would otherwise collapse. */
button.aig-tile {
    display: block;
    width: 100%;
    padding: 0;
    border: 0;
    font: inherit;
    background: var(--aig-raised);
    cursor: pointer;
}
button.aig-tile:focus-visible {
    outline: 2px solid var(--aig-lime);
    outline-offset: 2px;
}
/* The four conditioning tiles: lime ring + numbered badge. This is the
   entire premise made visible, so it is a RESTING state, not an animation. */
.aig-tile--ref { box-shadow: 0 0 0 2px var(--aig-lime); }
.aig-tile--ref::after {
    content: attr(data-i);
    position: absolute;
    left: 6px; bottom: 6px;
    min-width: 18px; height: 18px;
    display: grid;
    place-items: center;
    padding: 0 5px;
    border-radius: 6px;
    background: var(--aig-lime);
    color: #0b0b0e;
    font-family: var(--mono-font-mono);
    font-size: 11px;
    font-weight: 700;
    line-height: 1;
}

/*  The "how the board is used" popover. It hangs off the rail foot, which is
    the last 40px strip of a sticky column, so it opens UPWARD — a popover that
    opened downward would land outside the slab on the light page. */
#aig-board-popover,
.aig-board-popover {
    position: absolute;
    left: 12px; right: 12px;
    bottom: calc(100% + 8px);
    z-index: 6;
    margin: 0;
    padding: 12px;
    border-radius: var(--aig-r-ctl);
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}
.aig-board-popover strong { color: var(--aig-t1); font-weight: 700; }


/* ==========================================================================
   7. STAGE + THE PLATE
   The progress frame IS the result frame: identical box, identical
   coordinates, zero layout shift on completion.
   ========================================================================== */

.aig-stage {
    display: grid;
    grid-template-columns: minmax(0, 1fr);   /* see note in .aig-studio__grid */
    gap: 20px;
    min-width: 0;
}

.aig-canvas {
    position: relative;
    width: 100%;
    max-width: min(100%, 62vh);
    /* Left-aligned, NOT centred. When the vh cap makes the plate narrower
       than its column, centring floats it away from the receipt, the stale
       note, the seed chips and the prompt bar, which are all full width.
       Sharing one left edge is what makes the stage read as a single column
       instead of a picture hung on a wall. */
    margin-inline: 0;
    aspect-ratio: 1 / 1;
    min-height: 420px;
    border-radius: var(--aig-r-canvas);
    border: 1px solid var(--aig-line);
    /* The plate is a plain dark panel while it is empty or working. The
       checkerboard belongs to the RESULT — it exists so post-cut-out
       transparency is legible, and showing it under the ladder is noise. */
    background: var(--aig-panel);
    overflow: hidden;
    isolation: isolate;
}

/* --- layer stack ---------------------------------------------------------
   Without JS every layer simply stacks in normal flow and is visible.
   With JS they become co-located and data-state picks exactly one. */
.aig-canvas__layer { position: relative; z-index: 1; }

#aig-root[data-js="on"] .aig-canvas__layer {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    padding: 24px;
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transition:
        opacity var(--mono-dur-slow) var(--mono-ease),
        visibility 0s linear var(--mono-dur-slow);
}
/* Each layer also carries a name of its own, and the name is what the layer's
   own geometry hangs off — the .aig-canvas__layer above only co-locates them. */
.aig-canvas__empty { z-index: 2; }
.aig-canvas__prog  { z-index: 2; }
.aig-canvas__fail  { z-index: 3; }

#aig-root[data-js="on"] .aig-stage[data-state="idle"]    #aig-canvas-empty,
#aig-root[data-js="on"] .aig-stage[data-state="ready"]   #aig-canvas-empty,
#aig-root[data-js="on"] .aig-stage[data-state="offline"] #aig-canvas-empty,
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-canvas-prog,
#aig-root[data-js="on"] .aig-stage[data-state="done"]    #aig-canvas-img,
#aig-root[data-js="on"] .aig-stage[data-state="failed"]  #aig-canvas-fail,
#aig-root[data-js="on"] .aig-stage[data-state="gone"]    #aig-canvas-fail,
#aig-root[data-js="on"] .aig-stage[data-state="expired"] #aig-canvas-fail {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
}

/* --- the reference field (2x2 refThumbs, public watermarked _md ONLY) -----
   ⚠ DO NOT move opacity/filter onto #aig-canvas-bg itself. The region map
   nests #aig-scan INSIDE it, and a `filter` on an ancestor blurs every
   descendant with no way out — the scan band would disappear. The treatment
   therefore lives on the img children, which also makes the markup correct
   whether #aig-scan ends up nested or a sibling.
   The base over-scale (1.22) exists so each quadrant's 60px blur bleeds over
   its neighbours; without it four independently-blurred images leave a
   visible dark cross down the middle of the plate.                        */
.aig-canvas__bg {
    position: absolute;
    inset: 0;
    z-index: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    overflow: hidden;
    border-radius: inherit;
    pointer-events: none;
}
.aig-canvas__bg > img {
    width: 100%; height: 100%;
    display: block;
    object-fit: cover;
    color: transparent;           /* suppress broken-image alt text */
    font-size: 0;
    opacity: 0;
    filter: blur(60px);
    transform: scale(1.22);
    transition: opacity var(--mono-dur-slow) var(--mono-ease);
}
/* S4 ready — a STATIC faint presence. No animation. The first hint of payoff. */
#aig-root[data-js="on"] .aig-stage[data-state="ready"] #aig-canvas-bg > img { opacity: 0.10; }
/* S5 running — rises to .18 and starts a one-way 30s drift (never a loop:
   a loop visibly resets and exposes itself as decoration). */
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-canvas-bg > img {
    opacity: 0.18;
    animation: aig-drift 30s var(--mono-ease) forwards;
}
/* S6 done — the two-phase resolve. SEQUENCING IS A HARD REQUIREMENT: the
   backdrop is built from WATERMARKED previews, so opacity must reach 0
   BEFORE the blur comes down or legible wordmark text passes through the
   mid-frames. Verify by frame extraction + 2.6x contrast, never by eye. */
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-canvas-bg > img {
    animation: aig-resolve 420ms var(--mono-ease) forwards;
}
/* Failure — the field desaturates. A real visual statement that this did
   not happen, instead of a red fill nobody can read. */
#aig-root[data-js="on"] .aig-stage[data-state="failed"] #aig-canvas-bg > img {
    opacity: 0.18;
    filter: blur(60px) grayscale(1);
    transition: filter 300ms var(--mono-ease), opacity var(--mono-dur-slow) var(--mono-ease);
}

/* --- scan band ------------------------------------------------------------
   Ambient, asserts no quantity. The honest substitute for a percentage we
   do not have. There is no percentage bar on this page, ever. */
.aig-scan__line {
    position: absolute;
    inset: 0;
    z-index: 1;
    pointer-events: none;
    opacity: 0;
    background:
        linear-gradient(
            to bottom,
            rgba(204, 234, 74, 0) 0,
            rgba(204, 234, 74, 0.10) 68%,
            rgba(204, 234, 74, 0.55) 94%,
            var(--aig-lime) 100%
        ) no-repeat;
    background-size: 100% 60px;
}
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-scan {
    opacity: 1;
    will-change: transform;
    animation: aig-scan 2.4s cubic-bezier(0.45, 0, 0.55, 1) infinite;
}
/* NOTE: there was a `.aig-stage.is-offscreen #aig-scan { animation-play-state:
   paused }` rule here, documented as being driven by an IntersectionObserver in
   aiGenerator.js. No such observer exists and nothing ever adds .is-offscreen, so
   the rule was dead and the comment was actively misleading. Removed rather than
   implemented: the scan only runs while a generation is in flight, which is
   precisely when the user is watching it, so pausing it off-screen would save
   nothing. If the stage ever animates at rest, add the observer AND the rule
   together. */

/* --- empty plate ---------------------------------------------------------- */
.aig-canvas__empty-inner {
    width: 100%;
    height: 100%;
    display: grid;
    place-items: center;
    padding: 20px;
    border: 1px dashed rgba(255, 255, 255, 0.12);
    border-radius: 12px;
    text-align: center;
}
#aig-canvas-empty-text {
    max-width: 34ch;
    margin: 0;
    font-size: var(--aig-fs-body);
    line-height: 1.55;
    color: var(--aig-t2);
}
#aig-canvas-empty-text strong { color: var(--aig-t1); font-weight: 700; }

/* --- progress plate ------------------------------------------------------- */
.aig-prog {
    width: 100%;
    max-width: 320px;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
    justify-items: center;
    text-align: center;
}
#aig-elapsed {
    margin: 0;
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--aig-fs-clock);
    font-weight: 500;
    line-height: 1;
    color: var(--aig-t1);
}
#aig-expect {
    margin: 0;
    font-size: var(--aig-fs-meta);
    color: var(--aig-t2);   /* t3 is 3.9:1 — never on 12.5px text */
}

/* "Stop watching" is the control; this is the sentence that stops it reading
   as "Cancel". It has to sit right under the control, quiet, and never wrap to
   three lines on the 272px plate. */
.aig-stop__help {
    max-width: 30ch;
    margin: 0;
    font-size: 12px;
    line-height: 1.4;
    color: var(--aig-t2);
}
/* Reserved from t=0 with visibility:hidden -> ZERO CLS when it fades in. */
#aig-escalate {
    min-height: 2.9em;
    display: grid;
    align-content: center;
    margin: 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.45;
    color: var(--aig-t2);
    visibility: hidden;
    opacity: 0;
    transition: opacity var(--mono-dur-slower) var(--mono-ease);
}
/*  THE REVEAL IS DRIVEN BY CONTENT, NOT BY A CLASS. aiGenerator.js escalates by
    writing the 8s / 20s / 35s sentence into this element and by clearing it at
    t=0; it never adds a class. Keying the reveal off :not(:empty) means the box
    can never be visible-and-blank or full-and-invisible, which a class-based
    reveal permits and which is exactly how an escalation gets stuck on screen
    after the job resolves. */
#aig-escalate:not(:empty) { visibility: visible; opacity: 1; }

/* --- the three-phase ladder (driven by real poll status, never a timer) --- */
#aig-ladder,
.aig-ladder {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    justify-items: start;
    width: 100%;
    text-align: left;
}
/*  display:grid is load-bearing twice over: it lays the row out, AND it takes
    each <li> out of `display: list-item`, which is what stops the <ol> printing
    its own "1." over the phase dot. Do not change it without adding
    list-style: none to .aig-ladder. */
#aig-phase-1,
#aig-phase-2,
#aig-phase-3,
.aig-phase {
    display: grid;
    grid-template-columns: 14px minmax(0, 1fr);
    align-items: center;
    gap: 8px;
    font-size: var(--aig-fs-meta);
    line-height: 1.35;
    color: var(--aig-t2);
    /* A row with neither state class is a FUTURE row. That is the resting
       value, so the ladder is complete and legible before any script runs. */
    opacity: 0.4;
    transition:
        color var(--mono-dur) var(--mono-ease),
        opacity var(--mono-dur) var(--mono-ease);
}
.aig-phase__label {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}
.aig-phase__dot {
    width: 6px; height: 6px;
    margin: 0 4px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.4;
}
/*  .is-current / .is-done are the only two tokens aiGenerator.js writes here
    (setPhase toggles both on every row on every call). There is no
    [data-phase] state attribute — an earlier revision styled one and the
    script never wrote it. */
.aig-phase.is-current { color: var(--aig-t1); opacity: 1; }
.aig-phase.is-current .aig-phase__dot {
    background: var(--aig-lime);
    opacity: 1;
    animation: aig-pulse 1.4s ease-in-out infinite alternate;
}
.aig-phase.is-done { color: var(--aig-t2); opacity: 1; }
/* Done rows carry a STATIC lime check — the state is legible with zero motion. */
.aig-phase.is-done .aig-phase__dot {
    width: 14px; height: 14px;
    margin: 0;
    border-radius: 0;
    background: none;
    opacity: 1;
    box-shadow: none;
    position: relative;
}
.aig-phase.is-done .aig-phase__dot::after {
    content: "";
    position: absolute;
    left: 3px; top: 1px;
    width: 5px; height: 9px;
    border: solid var(--aig-lime);
    border-width: 0 2px 2px 0;
    transform: rotate(45deg);
}

/*  --- result image ---------------------------------------------------------
    #aig-canvas-img IS the <img>, not a frame around one: aiGenerator.js sets
    .src on it and hangs onload/onerror off it directly. So the checkerboard is
    the image's OWN background, which is the reason a cut-out reads as
    transparent instead of as a white square. A wrapper here would break the
    script silently. Dark checker, never light: a light one glares on this
    ground. */
.aig-canvas__img {
    z-index: 2;
    /* block, not the layer's grid: .aig-canvas__layer sets display:grid for the
       panels, and a replaced element has no business being a grid container. */
    display: block;
    width: 100%;
    height: 100%;
    max-width: 100%;
    padding: 0;
    object-fit: contain;
    background:
        repeating-conic-gradient(var(--aig-checker-a) 0 25%, var(--aig-checker-b) 0 50%)
        0 0 / 16px 16px;
}
#aig-root[data-js="on"] .aig-canvas__img { padding: 0; }
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-canvas-img {
    animation: aig-develop 520ms var(--mono-ease) both;
}

/* --- failure panel -------------------------------------------------------- */
.aig-fail {
    width: 100%;
    max-width: 340px;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
    justify-items: center;
    text-align: center;
    padding: 20px;
    border-radius: 14px;
    border: 1px solid var(--aig-line);
    background: var(--aig-glass-bg);
    -webkit-backdrop-filter: var(--aig-glass-blur);
    backdrop-filter: var(--aig-glass-blur);
    box-shadow: var(--aig-glass-shadow);
}
#aig-root[data-js="on"] .aig-stage[data-state="failed"] .aig-fail {
    animation: aig-fail-in 240ms var(--mono-ease) both;
    border-color: var(--aig-danger);
}
#aig-fail-title {
    margin: 0;
    font-size: var(--aig-fs-panel);
    font-weight: 700;
    color: var(--aig-t1);
}
#aig-fail-body {
    margin: 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}
.aig-fail__row {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
}
/* An expiry is NOT a danger state — nothing went wrong. Neutral chrome. */
#aig-root[data-js="on"] .aig-stage[data-state="expired"] .aig-fail,
#aig-root[data-js="on"] .aig-stage[data-state="gone"] .aig-fail {
    border-color: var(--aig-line-strong);
    animation: none;
}
#aig-root[data-js="on"] .aig-stage[data-state="gone"] .aig-fail { max-width: 300px; min-height: 96px; }

/* --- job meta pill (top-left, glass) -------------------------------------- */
.aig-stage__meta {
    position: absolute;
    left: 12px; top: 12px;
    z-index: 4;
    padding: 5px 10px;
    border-radius: 999px;
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 11px;
    letter-spacing: 0.02em;
    color: var(--aig-t2);
    pointer-events: none;
}

/* --- actions bar (bottom-centre, glass) -----------------------------------
   Present in the DOM at every state (nothing waits on a script), collapsed
   by data-state once JS is alive. */
#aig-root[data-js="on"] #aig-actions,
#aig-root[data-js="on"] #aig-jobmeta { display: none; }
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-actions { display: flex; }
/*  The job-meta pill is shown at RUNNING as well as at done — that is where the
    45-second job id lands for support, and hiding it there would mean the
    script writes an id into an invisible element. */
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-jobmeta,
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-jobmeta { display: block; }

/* The result actions are flex children of a bar that WRAPS. */
#aig-act-bg,
#aig-act-vec,
#aig-act-dl,
#aig-act-again { flex: 0 1 auto; min-width: 0; }
/* "View full size" is quiet text at the end of the row. */
#aig-act-full { margin-left: auto; }

/* --- history: show-more + the promptcraft disclosure ---------------------- */
.aig-recent__more {
    display: block;
    margin: 20px auto 0;
}
.aig-promptcraft {
    margin-top: 10px;
    font-size: var(--aig-fs-meta);
    color: var(--aig-t2);
}
.aig-promptcraft summary {
    cursor: pointer;
    width: fit-content;
    color: var(--aig-t2);
    text-decoration: underline;
    text-underline-offset: 3px;
    text-decoration-color: rgba(255, 255, 255, 0.25);
}
.aig-promptcraft summary:hover { color: var(--aig-t1); }
.aig-promptcraft ul {
    margin: 10px 0 0;
    padding-left: 18px;
    display: grid;
    gap: 6px;
    line-height: 1.5;
}
.aig-promptcraft strong { color: var(--aig-t1); font-weight: 600; }

/*  Normal-flow row BELOW the plate (2026-08-19). The earlier glass overlay sat
    on the bottom third of the artwork — exactly where an isolated-on-white
    subject ends — and needed measured margin reserves at ≤560 to detach.
    Static flow retires both problems at once. The markup moved with it:
    #aig-actions is now a SIBLING after #aig-canvas, no longer a layer in it. */
.aig-canvas__bar {
    margin-top: 12px;
    /* Shares the plate's cap so the row reads as the plate's own chrome. Keep
       in step with .aig-canvas at every breakpoint that changes the cap. */
    max-width: min(100%, 62vh);
    display: flex;
    flex-wrap: wrap;              /* WRAPS. Never nowrap + flex-shrink:0 —
                                     that is the exact pattern that produced
                                     the 374px pack sticky-bar overflow. */
    align-items: center;
    justify-content: flex-start;
    gap: 8px;
    padding: 10px;
    border-radius: var(--aig-r-bar);
    background: var(--aig-raised);
    border: 1px solid var(--aig-line);
}
/*  Bar visibility follows the stage, mirroring what JS's show() writes — a
    plain row below the plate must not occupy space in idle/running states
    when scripting is dead and [hidden] came off in an earlier session. */
#aig-actions[hidden] { display: none !important; }
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-actions {
    animation: aig-slide-up 220ms var(--mono-ease) 80ms both;
}
/*  The transient line inside the actions bar — failure rows 11 and 12.
    It takes a full row of its own so the buttons never reflow around it, and
    it self-removes after 8s. NO credit language ever appears here for a
    background-removal failure: no credit was involved, and mentioning one
    would imply that one had been at risk. */
.aig-action-note {
    flex: 1 1 100%;
    margin: 0;
    padding: 2px 4px;
    font-size: var(--aig-fs-meta);
    line-height: 1.45;
    text-align: center;
    color: var(--aig-t2);
}

/* Segmented Original | Cut-out (32px). Both images are kept; switching is an
   instant client-side swap with no refetch. */
#aig-toggle,
.aig-seg {
    display: inline-flex;
    padding: 3px;
    gap: 3px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
}
#aig-toggle button,
#aig-toggle-orig,
#aig-toggle-cut,
.aig-seg button {
    height: 26px;
    min-height: 26px;
    padding: 0 10px;
    border: 0;
    border-radius: 7px;
    background: transparent;
    color: var(--aig-t2);
    font-family: var(--mono-font-display);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    transition: background var(--mono-dur) var(--mono-ease), color var(--mono-dur) var(--mono-ease);
}
.aig-seg button[aria-pressed="true"],
#aig-toggle button[aria-pressed="true"] { background: var(--aig-raised); color: var(--aig-t1); }


/* --- split view (after vectorize) ----------------------------------------- */
.aig-split {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 16px;
    align-items: start;
}

/* --- the animated clip (Seedance) ---------------------------------------- */
.aig-motion {
    margin-top: 16px;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
    /* Shares the plate's cap like the actions bar — same chrome, same column. */
    max-width: min(100%, 62vh);
}
.aig-motion > video {
    width: 100%;
    height: auto;
    display: block;
    border-radius: var(--aig-r-canvas);
    border: 1px solid var(--aig-line);
    background: var(--aig-panel);
}
.aig-motion__note {
    margin: 0;
    font-size: 11px;
    color: var(--aig-t2);
}
/*  Each half is a <figure>: the frame and the caption are one unit, which is
    the whole reason a figure is the right element here. The 1px divider is the
    second half's own border rather than a spacer column — a bare grid track
    with a background is an element that exists only to be a line, and it has
    to be deleted by hand in every stacked breakpoint. */
.aig-split__half {
    min-width: 0;
    margin: 0;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 8px;
}
.aig-split__half + .aig-split__half {
    padding-left: 16px;
    border-left: 1px solid var(--aig-line);
}
.aig-split__half > img,
.aig-split__half > object {
    width: 100%;
    aspect-ratio: 1 / 1;
    display: block;
    object-fit: contain;
    border-radius: var(--aig-r-canvas);
    border: 1px solid var(--aig-line);
    background:
        repeating-conic-gradient(var(--aig-checker-a) 0 25%, var(--aig-checker-b) 0 50%)
        0 0 / 16px 16px;
}
.aig-split__half > figcaption { color: var(--aig-t2); }

/*  The honesty line. It replaces the 2025 page's false "scalable editable SVG
    Vector" claim, so it spans both halves: it is about the file, not about the
    pane it happens to sit under. 60ch because it must read as a sentence. */
.aig-split__honesty {
    grid-column: 1 / -1;
    max-width: 60ch;
    margin: 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.55;
    color: var(--aig-t2);
}
.aig-split__row {
    grid-column: 1 / -1;
    display: flex;
    flex-wrap: wrap;          /* wraps, never nowrap + flex-shrink:0 */
    align-items: center;
    gap: 8px;
}
#aig-split-dl,
#aig-split-again { flex: 0 1 auto; min-width: 0; }

/* "SVG 0.8 MB · 3,991 paths" — measured quantities from the response, so it
   keeps the mono face .aig-num gives it and only adds the wrap guard. */
#aig-svg-note {
    margin: 0;
    color: var(--aig-t2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* --- receipt: the design's central claim, made visible -------------------- */
.aig-receipt {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    border-radius: var(--aig-r-ctl);
    border: 1px solid var(--aig-line);
    background: rgba(255, 255, 255, 0.03);
}
#aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-receipt {
    animation: aig-slide-up 300ms var(--mono-ease) 180ms both;
}
#aig-receipt-thumbs {
    flex: 0 0 auto;
    display: flex;
    gap: 8px;
}
#aig-receipt-thumbs img {
    width: 32px; height: 32px;
    display: block;
    border-radius: 6px;
    /* White, not --aig-raised: most previews are transparent PNGs of dark line art,
       which vanish on a dark base (owner report, 2026-08-19). */
    background: #fff;
    object-fit: cover;
    box-shadow: 0 0 0 1px var(--aig-lime-dim);
}
#aig-receipt-text {
    flex: 1 1 auto;
    min-width: 0;                 /* mandatory */
    font-size: var(--aig-fs-meta);
    line-height: 1.45;
    color: var(--aig-t2);
}
#aig-receipt-text strong { color: var(--aig-t1); font-weight: 700; }


/* ==========================================================================
   8. PROMPT BAR
   ========================================================================== */

.aig-prompt { display: grid; grid-template-columns: minmax(0, 1fr); gap: 12px; }

.aig-prompt__chips {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    min-width: 0;
}
.aig-chip {
    max-width: 100%;
    padding: 7px 12px;
    border-radius: 999px;
    border: 1px solid var(--aig-line);
    background: rgba(255, 255, 255, 0.03);
    color: var(--aig-t2);
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-meta);
    font-weight: 500;
    line-height: 1.3;
    text-align: left;
    cursor: pointer;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition:
        border-color var(--mono-dur) var(--mono-ease),
        color var(--mono-dur) var(--mono-ease),
        background var(--mono-dur) var(--mono-ease);
}
.aig-chip:hover {
    border-color: var(--aig-lime-dim);
    color: var(--aig-lime);
    background: var(--aig-lime-wash);
}

.aig-prompt__row {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    align-items: end;
    gap: 12px;
    padding: 12px;
    border-radius: var(--aig-r-row);
    transition: box-shadow var(--mono-dur) var(--mono-ease), border-color var(--mono-dur) var(--mono-ease);
}
/* Designed exception #1: the textarea drops its own ring and the CONTAINER
   takes it, so the ring traces the whole 18px bar and says "this bar is live". */
.aig-prompt__row:focus-within {
    box-shadow: 0 0 0 2px rgba(204, 234, 74, 0.75), var(--aig-glass-shadow);
}
.aig-prompt__row.is-invalid { border-color: var(--aig-danger); }

#aig-source-btn { align-self: end; }

#aig-prompt {
    min-width: 0;
    width: 100%;
    min-height: 72px;
    max-height: 168px;
    padding: 8px 2px;
    resize: none;
    background: none;
    border: 0;
    outline: none;
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-input);
    line-height: 1.5;
    letter-spacing: var(--mono-track-body);
}
#aig-prompt::placeholder { color: var(--aig-t2); }
#aig-prompt:focus-visible { outline: none; }

#aig-count {
    align-self: end;
    padding-bottom: 12px;
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--aig-fs-num);
    letter-spacing: 0.02em;
    color: var(--aig-t2);   /* a measured quantity must be readable */
    white-space: nowrap;
}
/* .is-near, not .is-warn — the token countPrompt() actually writes at 380. */
#aig-count.is-near { color: var(--aig-danger-t); }

/* The ONE commitment button — the only filled-lime element on the surface. */
#aig-submit {
    height: 48px;
    min-height: 48px;
    padding: 0 20px;
    border: 0;
    border-radius: var(--aig-r-bar);
    background: var(--aig-lime);
    color: #0b0b0e;
    font-family: var(--mono-font-display);
    font-size: var(--aig-fs-body);
    font-weight: 700;
    letter-spacing: var(--mono-track-body);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 10px;
    white-space: nowrap;
    /* Transition COLOUR only, never width, never scale. A button that pops
       when it becomes available reads as a toy. */
    transition:
        background 200ms var(--mono-ease),
        color 200ms var(--mono-ease),
        transform var(--mono-dur) var(--mono-ease);
}
#aig-submit:not(:disabled):hover { transform: translateY(-1px); background: #d7f062; }
#aig-submit:disabled {
    background: rgba(255, 255, 255, 0.07);
    color: var(--aig-t2);
    cursor: not-allowed;
    transform: none;
}
#aig-submit-label { min-width: 0; }
/* On the lime fill the pill needs a DARK wash, not the light one .aig-pill
   carries everywhere else — this is the only lime-filled control on the page. */
#aig-submit-price { background: rgba(11, 11, 14, 0.16); }
#aig-submit:disabled #aig-submit-price { background: rgba(255, 255, 255, 0.08); }
#aig-submit-price[hidden] { display: none; }

/* Meta row: LEFT slot = helper text AND every submit-blocking error.
             RIGHT slot = the credits readout. One slot, one meaning. */
.aig-prompt__meta {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 0 4px;
}
/*  ONE SLOT, ONE MEANING. This element carries the helper text AND every
    submit-blocking error, and aiGenerator.js REPLACES its className on each
    message with "aig-reason aig-note--<tone>". So the tone rules live on
    .aig-note--* (see §5) and everything structural lives here, on both the id
    and the class, because after the first message only the class is certain to
    be intact. */
#aig-reason,
.aig-reason {
    flex: 1 1 auto;
    min-width: 0;
    margin: 0;
    /* It is a note in tone only. No border, no box: it is a line of helper text
       that sometimes turns amber, and boxing it would make the helper state
       look like a permanent warning. */
    border: 0;
    padding: 0;
    background: none;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}
#aig-balance {
    flex: 0 0 auto;
    padding: 2px 0;
    border-radius: 8px;
    font-size: var(--aig-fs-meta);
    color: var(--aig-t2);
    white-space: nowrap;
}
/* out_of_allowance: the readout promotes to full ink and says the reset date.
   .is-empty is the token setBalance() writes. */
#aig-balance.is-empty { color: var(--aig-t1); font-weight: 600; }
/*  THE most trust-critical fact on the page: the credit came back.
    The ring is the real signal, and it is the ONLY signal — the @property
    count-up this file used to carry was never reachable, because nothing ever
    wrapped the number in an element to count. The number swaps instantly and
    the ring fires, which is precisely the documented fallback. If you ever add
    the count-up back, do not remove the ring as redundant: the ring is what
    makes a refund something the user SEES rather than something we assert. */
#aig-balance.aig-refund { animation: aig-refund-ring 600ms var(--mono-ease) 1; }

/* Stale-result guard — the state most likely to make a user believe they
   were charged for the wrong thing. Must be BUILT, not assumed. */
#aig-stale { margin: 0; }

/* Keyboard hint, desktop only. */
.aig-kbd {
    font-family: var(--mono-font-mono);
    font-size: 11px;
    color: var(--aig-t2);
    white-space: nowrap;
}


/* ==========================================================================
   9. GLOBAL SURFACE STATES
   ========================================================================== */

/* Running: the prompt bar dims but stays legible and selectable — the user
   must be able to read what they asked for while they wait. */
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-prompt-form {
    opacity: 0.55;
    pointer-events: none;
}
#aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-prompt { user-select: text; }

/* 503 unavailable — the ONLY full-slab treatment, because it is the only
   condition where nothing works. Recent drafts below stays fully live. */
/*  The dim is keyed off the stage state, which is the one thing the script
    definitely writes (goOffline sets data-state="offline"). The card itself is
    a SIBLING of the grid, not a child, so it is not dimmed by the treatment it
    is explaining. */
#aig-root[data-js="on"] .aig-studio:has(.aig-stage[data-state="offline"]) .aig-studio__grid {
    opacity: 0.55;
    pointer-events: none;
    filter: saturate(0.6);
}
#aig-offline,
.aig-offline {
    position: absolute;
    left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    z-index: 10;
    width: min(420px, calc(100% - 40px));
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
    justify-items: center;
    text-align: center;
    padding: 20px;
    border-radius: 16px;
}
.aig-offline h3 {
    margin: 0;
    font-size: var(--aig-fs-panel);
    font-weight: 700;
    color: var(--aig-t1);
}
.aig-offline p {
    margin: 0;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
}

/* 409 job_in_flight — one attention nudge. There is deliberately NO shake
   anywhere: shake reads as scolding, and this surface costs money to use.
   .aig-attend is the token jobInFlight() writes onto #aig-canvas. */
.aig-canvas.aig-attend { animation: aig-attend 600ms var(--mono-ease) 1; }

/* Any provider action in flight. */
.aig-studio[data-busy="true"] .aig-chip { pointer-events: none; opacity: 0.6; }


/* ==========================================================================
   10. BOTTOM SHEET — the only modal in the design, and it earns its place.
   Rendered at every width but only ever shown ≤979 (see §14).
   ========================================================================== */

/*  THE SHEET IS [hidden]-DRIVEN. aiGenerator.js opens and closes it with
    removeAttribute('hidden') / setAttribute('hidden') and never touches a
    class, so a .is-open contract (which this file used to carry) meant the
    sheet was display:none when closed and, when opened, still sat at
    translateY(100%) with visibility:hidden — permanently invisible. The entry
    is an animation on the visible state instead, which cannot desynchronise
    from the attribute the way a paired class can. */
#aig-sheet-scrim,
.aig-sheet__scrim {
    position: fixed;
    inset: 0;
    z-index: 1200;
    background: rgba(0, 0, 0, 0.62);
    animation: aig-fade-in var(--mono-dur-slow) var(--mono-ease) both;
}
.aig-sheet {
    position: fixed;
    inset: auto 0 0 0;
    z-index: 1201;
    height: 88svh;
    max-height: 88vh;
    display: grid;
    grid-template-rows: auto minmax(0, 1fr);
    grid-template-columns: minmax(0, 1fr);
    border-radius: 20px 20px 0 0;
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    overflow: hidden;
    animation: aig-sheet-in var(--mono-dur-slow) var(--mono-ease) both;
}
/* Scroll lock. aiGenerator.js puts this on <html> for the duration. */
.aig-sheet-open,
.aig-sheet-open body { overflow: hidden; }

/*  Close sits over the grab handle's row rather than taking a row of its own:
    the handle is centred, so the top-right corner is free and a third grid row
    for one 40px control would push the search field further from the thumb. */
#aig-sheet-close,
.aig-sheet__close {
    position: absolute;
    right: 12px; top: 12px;
    z-index: 1;
}
.aig-sheet__grab {
    justify-self: center;
    width: 36px; height: 4px;
    margin: 10px 0 4px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.28);
}
/*  The body is where the picker is relocated to. The search field it receives
    is pinned at the top of this box so it stays above the on-screen keyboard. */
.aig-sheet__body {
    padding: 16px;
    overflow-y: auto;
    overscroll-behavior: contain;
    background: var(--aig-panel);
}
.aig-sheet .aig-style-grid { grid-template-columns: minmax(0, 1fr); }

/* Above 980 the sheet never exists as a visual. */
@media (min-width: 980px) {
    .aig-sheet,
    .aig-sheet__scrim { display: none !important; }
}

/*  ⚠ THE SHEET ONLY EXISTS ONCE SOMETHING PUTS THE PICKER IN IT.
    The design is that at ≤979 aiGenerator.js MOVES #aig-picker and
    #aig-picker-panel into .aig-sheet__body and moves them back above 980, so
    there is exactly one picker in the document and no duplicated ids. The
    script does not currently perform that move — it only opens the sheet.
    Rather than ship a modal with an empty body over a rail that the ≤979 rules
    have hidden (which is a dead end with no way to pick a pack at all), the two
    rules below make the whole thing conditional on the move having happened:
      · picker still in the rail  -> the sheet can never open, the rail stays
      · picker inside the sheet   -> the ≤979 behaviour in §14 takes over
    Both arms are correct on their own, so this degrades instead of breaking,
    and it starts working the moment the relocation lands with no CSS change. */
#aig-root:not(:has(.aig-sheet__body .aig-picker)) .aig-sheet,
#aig-root:not(:has(.aig-sheet__body .aig-picker)) .aig-sheet__scrim {
    display: none !important;
}


/* ==========================================================================
   11. LIGHT PANELS — gate (S1), sign-in (S0), noscript
   Exactly ONE of #aig-studio / #aig-gate / #aig-signin is server-rendered.
   There is never a disabled form for a logged-out user.
   ========================================================================== */

.aig-gate {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
    gap: 32px;
    align-items: center;
    padding: clamp(24px, 4vw, 44px);
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-xl);
}
.aig-gate__h2 {
    margin: 0;
    font-family: var(--mono-font-display);
    font-size: 24px;
    font-weight: var(--mono-w-extrabold);
    letter-spacing: -0.02em;
    color: var(--mono-ink);
}
.aig-gate__body {
    margin: 12px 0 0;
    font-size: var(--mono-fs-lead);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
    max-width: 52ch;
}
.aig-gate__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
    margin-top: 24px;
}
.aig-gate__copy { min-width: 0; }
.aig-gate__row .mono-link { font-size: var(--mono-fs-sm); }
.aig-gate__shot {
    min-width: 0;
    border-radius: var(--mono-radius-lg);
    border: 1px solid var(--mono-border);
    overflow: hidden;
    background: var(--mono-gray-100);
}
.aig-gate__shot img { width: 100%; height: auto; display: block; }

/*  S0 — ANONYMOUS. One static server-rendered screenshot and a panel of words.
    No dark slab and no disabled form: the studio is the reward for being
    entitled, and rendering a dead one is the disabled-form mistake in a
    costume. */
.aig-signin {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
}
/*  It carries .mono-card too, which supplies the surface but no padding and the
    lg radius. These four lines give it the padding and pull the radius up to xl
    so the landing panel and the gate panel are the same object seen twice —
    they are the two halves of one decision and must not look like two. */
.aig-signin__panel {
    padding: clamp(24px, 4vw, 44px);
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-xl);
}
.aig-signin__h2 {
    margin: 0;
    font-family: var(--mono-font-display);
    font-size: 24px;
    font-weight: var(--mono-w-extrabold);
    letter-spacing: -0.02em;
    color: var(--mono-ink);
}
.aig-signin__body {
    margin: 12px 0 0;
    max-width: 60ch;
    font-size: var(--mono-fs-lead);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}
.aig-signin__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
    margin-top: 24px;
}
.aig-signin__row .mono-link { font-size: var(--mono-fs-sm); }

.aig-signin__shot {
    margin-top: 32px;
    border-radius: 20px;
    border: 1px solid var(--mono-border);
    overflow: hidden;
    background: var(--mono-gray-100);
}
.aig-signin__shot img {
    width: 100%;
    height: auto;
    display: block;
    aspect-ratio: 16 / 10;
    object-fit: cover;
}

/* The one-line scale readout under the landing lead. Real numbers, quiet type. */
.aig-signin__scale {
    margin: 14px 0 0;
    font-size: var(--mono-fs-sm);
    color: var(--mono-text-muted);
    letter-spacing: 0.01em;
}

/*  MARKETING SECTIONS (landing + gate share them; members never see them).
    Same light mono vocabulary as the gate card — these are container boxes,
    never wide colored backgrounds (the /bundle design-system rule). */
.aig-mk { margin-top: 24px; }

.aig-mk--proof {
    display: grid;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
    gap: 32px;
    align-items: center;
    padding: clamp(24px, 4vw, 44px);
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-xl);
}
.aig-mk__copy { min-width: 0; }
.aig-mk__h3 {
    margin: 10px 0 0;
    font-family: var(--mono-font-display);
    font-size: 22px;
    font-weight: var(--mono-w-extrabold);
    letter-spacing: -0.02em;
    color: var(--mono-ink);
}
.aig-mk__body {
    margin: 12px 0 0;
    max-width: 58ch;
    font-size: var(--mono-fs-body);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}
.aig-mk__body .aig-link { color: var(--mono-ink); }
.aig-mk__thumbs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px;
    min-width: 0;
}
.aig-mk__thumbs img {
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    display: block;
    background: var(--mono-gray-100);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-lg);
    padding: 8px;
}

.aig-mk--gets {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--mono-space-5);
}
.aig-mk__get {
    min-width: 0;
    padding: 20px;
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-lg);
}
.aig-mk__get h4 {
    margin: 0;
    font-family: var(--mono-font-display);
    font-size: var(--mono-fs-body);
    font-weight: var(--mono-w-bold);
    color: var(--mono-ink);
}
.aig-mk__get p {
    margin: 8px 0 0;
    font-size: var(--mono-fs-sm);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}

.aig-mk--plans {
    display: grid;
    grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr);
    gap: 32px;
    align-items: center;
    padding: clamp(24px, 4vw, 44px);
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-xl);
}
.aig-mk--plans .mono-btn { margin-top: 20px; }
.aig-mk__planlist {
    margin: 0;
    padding: 0;
    list-style: none;
    display: grid;
    gap: 0;
    min-width: 0;
}
.aig-mk__planlist li {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 2px;
    border-bottom: 1px solid var(--mono-border);
}
.aig-mk__planlist li:last-child { border-bottom: 0; }
.aig-mk__plan-name {
    font-size: var(--mono-fs-body);
    color: var(--mono-ink);
    font-weight: var(--mono-w-semibold);
}
.aig-mk__plan-n {
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: var(--mono-fs-sm);
    color: var(--mono-text-muted);
    white-space: nowrap;
}

/*  THE THREE STEPS.
    list-style:none is not cosmetic here. The badge inside each step already
    states the number, so an <ol> left to its own devices renders
    "1. [1] Pick a look" — the marker and the badge both, side by side. The
    badge wins because it is the one that survives at 320px inside the card. */
.aig-steps {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--mono-space-5);
    margin: var(--mono-space-8) 0 0;
    padding: 0;
    list-style: none;
}
.aig-step {
    min-width: 0;
    padding: 20px;
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-lg);
    background: var(--mono-white);
}
.aig-step__n {
    font-family: var(--mono-font-mono);
    font-size: var(--mono-fs-sm);
    color: var(--mono-lime-ink);
    background: var(--mono-lime-pale);
    border-radius: 999px;
    padding: 3px 10px;
    display: inline-block;
}
/*  Both of these are <span>s, so the display:block is what stops the title and
    the body running together on one line. A margin on an inline element does
    nothing vertically, which is precisely how "2. Describe the scene Plain
    words." happens. */
.aig-step__t {
    display: block;
    margin: 12px 0 6px;
    font-size: var(--mono-fs-h4);
    font-weight: var(--mono-w-bold);
    line-height: var(--mono-lh-heading);
    color: var(--mono-ink);
}
.aig-step__d {
    display: block;
    margin: 0;
    font-size: var(--mono-fs-sm);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}

.aig-noscript {
    padding: clamp(20px, 3vw, 32px);
    border-radius: var(--mono-radius-xl);
    border: 1px solid var(--mono-border);
    background: var(--mono-gray-50);
}
.aig-noscript__title {
    margin: 0;
    font-family: var(--mono-font-display);
    font-size: var(--mono-fs-h4);
    font-weight: var(--mono-w-bold);
    color: var(--mono-ink);
}
.aig-noscript__body {
    margin: 8px 0 0;
    font-size: var(--mono-fs-sm);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}
.aig-noscript__row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px 20px;
    margin: 16px 0 0;
    font-size: var(--mono-fs-sm);
}

/* Licence note is a PLACEHOLDER pending the owner's wording (§7.5). */
.aig-license {
    padding: 20px 24px;
    border-left: 3px solid var(--mono-lime);
    background: var(--mono-gray-50);
    border-radius: 0 var(--mono-radius-md) var(--mono-radius-md) 0;
}
.aig-license h3 {
    margin: 0 0 8px;
    font-size: var(--mono-fs-h3);
    font-weight: var(--mono-w-bold);
    color: var(--mono-ink);
}
.aig-license p {
    margin: 0;
    max-width: 76ch;
    font-size: var(--mono-fs-sm);
    line-height: var(--mono-lh-body);
    color: var(--mono-text-muted);
}


/* ==========================================================================
   12. RECENT DRAFTS — light, server-rendered for first paint
   Wraps; never scrolls horizontally.
   ========================================================================== */

.aig-draft {
    display: block;
    position: relative;
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-md);
    overflow: hidden;
    background:
        repeating-conic-gradient(var(--mono-gray-100) 0 25%, var(--mono-white) 0 50%)
        0 0 / 14px 14px;
    cursor: pointer;
    text-align: left;
    padding: 0;
    transition: border-color var(--mono-dur) var(--mono-ease);
}
.aig-draft:hover { border-color: var(--mono-ink); }
/* The frame owns the square and the clipping; the caption sits below it. */
.aig-draft__frame {
    position: relative;
    display: block;
    aspect-ratio: 1 / 1;
    width: 100%;
    overflow: hidden;
}
.aig-draft__frame img {
    width: 100%;
    height: 100%;
    display: block;
    object-fit: contain;
}
.aig-draft__caption {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 8px 10px;
    background: var(--mono-white);
    border-top: 1px solid var(--mono-border);
    font-family: var(--mono-font-mono);
    font-variant-numeric: tabular-nums;
    font-size: 11.5px;
    color: var(--mono-gray-600);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.aig-draft__svg {
    flex: 0 0 auto;
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--mono-lime);
}
/*  Expired tiles stay in place and get labelled. Deleting a tile out from under
    the cursor is worse than labelling it. The label text is a real element in
    the markup — user-visible copy never lives in a stylesheet — and .is-expired
    is the token aiGenerator.js writes (the server renders the same class). */
.aig-draft.is-expired .aig-draft__frame img { opacity: 0.35; }
.aig-draft.is-expired::after {
    content: "";
    position: absolute;
    inset: 0;
    border: 1px dashed var(--mono-gray-400);
    border-radius: inherit;
    pointer-events: none;
}
/* The template ships the label on every tile; only an expired one shows it. */
.aig-draft__expired { display: none; }
.aig-draft.is-expired .aig-draft__expired { display: block; }
.aig-draft__expired {
    position: absolute;
    left: 10px; top: 10px;
    z-index: 1;
    padding: 3px 8px;
    border-radius: 6px;
    background: rgba(255, 255, 255, 0.92);
    box-shadow: 0 1px 2px rgba(17, 24, 39, 0.10);
    font-family: var(--mono-font-mono);
    font-size: 11px;
    color: var(--mono-gray-600);
}
#aig-recent-empty {
    padding: 28px;
    border: 1px dashed var(--mono-border-strong);
    border-radius: var(--mono-radius-lg);
    text-align: center;
    color: var(--mono-text-muted);
    font-size: var(--mono-fs-sm);
}


/* ==========================================================================
   12b. ID ANCHORS
   Every id in the markup is a JS hook — aiGenerator.js reaches all of them
   through getElementById. Their appearance comes from the classes above; what
   is declared here is only what has to be attached to the unique element
   itself. Two reasons this block exists rather than being folded upward:

   (a) An audit that walks the markup's ids and greps this file is how a typo'd
       id gets caught. Every id resolving to a real rule is the invariant; an id
       that resolves to nothing is either dead markup or a misspelling, and
       those two look identical until something breaks in production.
   (b) Adding an id to a base rule's selector list silently raises that rule to
       (1,0,0) and every class-level @media override below it stops applying.
       That is a real trap on this file, where the responsive section restyles
       .aig-rail, .aig-canvas, .aig-tilegrid and .aig-split by class. Anchors
       therefore only ever declare values no breakpoint contests.
   ========================================================================== */

/*  min-width: 0 on every grid/flex box on this surface. This is the same rule
    as the minmax(0, …) tracks and for the same reason: an auto minimum is
    content-sized, so one wide <img> or one inner overflow-x strip pushes the
    page sideways instead of scrolling inside itself. No breakpoint sets a
    min-width on any of these, so the anchor can never fight one. */
#aig-rail,
#aig-rail-head,
#aig-rail-body,
#aig-rail-foot,
#aig-stage,
#aig-canvas,
#aig-chips,
#aig-prompt-row,
#aig-board-tiles,
#aig-split,
#aig-split-png,
#aig-split-svg { min-width: 0; }

/* Buttons that are flex children of a row which wraps. */
#aig-fail-retry,
#aig-fail-edit,
#aig-tab-packs,
#aig-tab-styles { flex: 0 1 auto; min-width: 0; }

/*  THE SKIP LINK IS AN ANCHOR ON A LIME PLATE, INSIDE THE DARK SLAB.
    Both hover rules that reach it are wrong for it: site.css:302 repaints it
    #454545 (3.9:1 on #ccea4a), and this file's own dark-surface rule paints it
    var(--aig-lime) — lime on lime, which is a link that vanishes when you point
    at it. It is the first focusable element in the studio, so it is exactly the
    control that must not disappear. Its own id settles it. */
#aig-skip:hover,
#aig-skip:focus,
#aig-skip:focus-visible { color: #0b0b0e !important; }

/* Inline in the rail, never a toast — so it needs the separation a toast
   would have given it for free. */
#aig-picker-empty { margin-top: 12px; }

/*  Templates hold markup, not layout. <template> content is inert and never
    rendered, so this is belt and braces — but a stray `display` inherited from
    a future global reset would dump five hidden components into the rail, and
    that failure is far too cheap to prevent to leave to chance. */
#aig-tpl-src-row,
#aig-tpl-style-card,
#aig-tpl-tile,
#aig-tpl-zone,
#aig-tpl-draft { display: none !important; }


/* ==========================================================================
   13. MOTION — the complete inventory. All CSS-only. No JS tweening.
   ========================================================================== */

/* 1 — scan: "something is being examined". Asserts no quantity. */
@keyframes aig-scan {
    from { transform: translateY(-8%); }
    to   { transform: translateY(108%); }
}

/* 2 — pulse: "this specific step is happening now". Only one dot pulses. */
@keyframes aig-pulse {
    from { opacity: 0.35; }
    to   { opacity: 1; }
}

/* 3 — ref-in: THE PREMISE. We are reading your pack's drawings, one at a
   time, and here they are. This is why the first two seconds feel purposeful
   rather than dead. */
@keyframes aig-ref-in {
    from { box-shadow: 0 0 0 0 rgba(204, 234, 74, 0); transform: scale(0.97); filter: brightness(0.7); }
    to   { box-shadow: 0 0 0 2px var(--aig-lime);      transform: scale(1);    filter: brightness(1); }
}
#aig-grid:has(.aig-stage[data-state="running"]) .aig-tile--ref {
    animation: aig-ref-in 260ms var(--mono-ease) both;
}
.aig-tile--ref[data-i="1"] { animation-delay: 0.4s; }
.aig-tile--ref[data-i="2"] { animation-delay: 0.9s; }
.aig-tile--ref[data-i="3"] { animation-delay: 1.4s; }
.aig-tile--ref[data-i="4"] { animation-delay: 1.9s; }

/* 4 — drift: "something is forming". One-way, never a loop. */
@keyframes aig-drift {
    /* Same +0.06 scale delta and -1.5% travel as specified, offset by the
       1.22 base over-scale the reference field needs (see §7). */
    from { transform: scale(1.22) translateY(0); }
    to   { transform: scale(1.28) translateY(-1.5%); }
}

/* 5 — resolve + develop: the draft resolving out of our own art. The one
   moment on this page worth half a second. Phases are SEQUENCED. */
@keyframes aig-resolve {
    0%   { opacity: 0.18; filter: blur(60px); }
    57%  { opacity: 0;    filter: blur(60px); }   /* 240ms of 420ms */
    100% { opacity: 0;    filter: blur(0); }
}
@keyframes aig-develop {
    from { opacity: 0; filter: blur(14px); transform: scale(1.02); }
    to   { opacity: 1; filter: blur(0);    transform: scale(1); }
}

/* 6 — slide-up: the provenance is part of the result, not an afterthought. */
@keyframes aig-slide-up {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* 7 — attend: an attention nudge for 409. No shake, anywhere, ever. */
@keyframes aig-attend {
    0%   { box-shadow: 0 0 0 2px var(--aig-lime); }
    100% { box-shadow: 0 0 0 2px rgba(204, 234, 74, 0); }
}

/* 8 — refund ring: the credit came back, SEEN not asserted. */
@keyframes aig-refund-ring {
    from { box-shadow: 0 0 0 0 rgba(204, 234, 74, 0.6); }
    to   { box-shadow: 0 0 0 10px rgba(204, 234, 74, 0); }
}

/* 9 — fail-in: calm. No bounce, no red fill. */
@keyframes aig-fail-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* 10 — spin: per-action only, never page-level. */
@keyframes aig-spin {
    to { transform: rotate(360deg); }
}

/* 12 — label swaps (the source chip's 180ms cross-fade when the one selection
   slot changes hands). */
@keyframes aig-label-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}

/* The sheet's entry. It is an animation, not a paired class, because the only
   thing that opens the sheet is [hidden] coming off. */
@keyframes aig-fade-in {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes aig-sheet-in {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
}


/* ==========================================================================
   14. RESPONSIVE — Mono's five breakpoints + a 380 checkpoint + a 320 floor.

   The slab keeps a 12px side inset at every width. It is NEVER full-bleed,
   because full-bleed inside .mono-container needs negative margins and that
   is precisely the documented horizontal-overflow trap.
   ========================================================================== */

/* --- 1100 to 1399 --------------------------------------------------------- */
@media (max-width: 1399px) {
    .aig-studio__grid { grid-template-columns: 340px minmax(0, 1fr); }
}

/* --- 980 to 1099 ---------------------------------------------------------- */
@media (max-width: 1099px) {
    .aig-studio__grid { grid-template-columns: 300px minmax(0, 1fr); }
    /* Third seed chip hides. */
    .aig-prompt__chips > .aig-chip:nth-child(3) { display: none; }
    .aig-split { gap: 12px; }
    /* Mono drops .mono-grid--5 to 3 here; the drafts ladder is 5 -> 4 -> 3 -> 2. */
    #aig-recent-grid.mono-grid--5 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* --- ≤979 — THE ONE STRUCTURAL CHANGE ------------------------------------- */
@media (max-width: 979px) {
    .aig-studio { max-width: none; }

    .aig-studio__grid { grid-template-columns: minmax(0, 1fr); }

    /* Rail moves ABOVE the stage and loses sticky. */
    .aig-rail {
        position: static;
        top: auto;
        grid-template-rows: auto auto auto;
    }
    .aig-rail__body {
        max-height: none;
        min-height: 0;
        overflow-y: visible;
    }

    /*  The picker becomes the bottom sheet; the board strip stays inline. The
        sheet replaces only PICKING, so with nothing picked yet the rail has
        nothing left to say and steps out entirely — the prompt bar's source
        button is the trigger. A 1,500-row list inline in a narrow column with
        a page below it is unusable, and on a phone you do one thing at a time.

        ⚠ GATED ON THE RELOCATION HAVING HAPPENED (see §10). These rules are
        only correct once the picker is actually inside .aig-sheet__body. Left
        unconditional they hide the inline picker on behalf of a sheet that is
        empty, and a phone user has NO WAY to choose a pack at all — a dead end
        strictly worse than the inline list these rules exist to improve on.
        With the guard, ≤979 degrades to the inline picker until the move
        lands, and adopts the sheet the moment it does. */
    #aig-root[data-js="on"]:has(.aig-sheet__body .aig-picker) .aig-rail #aig-picker-panel,
    #aig-root[data-js="on"]:has(.aig-sheet__body .aig-picker) .aig-rail #aig-picker-tabs,
    #aig-root[data-js="on"]:has(.aig-sheet__body .aig-picker) .aig-rail #aig-picker-search { display: none !important; }
    #aig-root[data-js="on"]:has(.aig-sheet__body .aig-picker) .aig-rail[data-mode="picking"] { display: none; }

    /* Board: one scrolling row, conditioning tiles pulled to the front so
       the proof is always visible without scrolling. */
    .aig-tilegrid {
        grid-template-columns: none;
        grid-auto-flow: column;
        grid-auto-columns: 96px;
        grid-template-rows: 96px;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scroll-snap-type: x proximity;
        padding-bottom: 4px;
    }
    .aig-tilegrid > .aig-tile { scroll-snap-align: start; }
    .aig-tile--ref { order: -1; }

    .aig-canvas {
        max-width: min(100%, 56vh);
        min-height: 300px;
    }
    .aig-canvas__bar { max-width: min(100%, 56vh); }

    /*  A fresh pick auto-scrolls to the prompt (aiGenerator.js selectSource),
        and while typing the bar must not vanish under the fold: the one glass
        row the page already has becomes sticky within the stage column. */
    .aig-prompt__row {
        position: sticky;
        bottom: 10px;
        z-index: 5;
    }

    /* Split stacks with the SVG first — it is the new thing. The divider is the
       second half's left border, so stacking simply drops it. */
    .aig-split { grid-template-columns: minmax(0, 1fr); }
    .aig-split__half + .aig-split__half {
        padding-left: 0;
        border-left: 0;
        order: -1;
    }

    /* iOS zoom floor. Non-negotiable. */
    #aig-prompt,
    .aig-picker__search input,
    .aig-sheet input { font-size: 16px; }

    .aig-gate { grid-template-columns: minmax(0, 1fr); }
    .aig-mk--proof,
    .aig-mk--plans { grid-template-columns: minmax(0, 1fr); }
    .aig-mk--gets { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .aig-kbd { display: none; }

    #aig-recent-grid.mono-grid--5 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

/* --- ≤720 ----------------------------------------------------------------- */
@media (max-width: 720px) {
    .aig-studio {
        padding: 16px;
        border-radius: 20px;
    }

    /* A 40px button squeezed beside a textarea at this width is a mis-tap
       magnet: Generate leaves the row and becomes its own full-width row. */
    .aig-prompt__row {
        grid-template-columns: auto minmax(0, 1fr) auto;
    }
    #aig-submit {
        grid-column: 1 / -1;
        width: 100%;
        margin-top: 4px;
    }

    .aig-prompt__meta {
        flex-direction: column;
        align-items: flex-start;
        gap: 6px;
    }

    /* Seed chips: one horizontally scrolling row, scrollbar hidden. */
    .aig-prompt__chips {
        flex-wrap: nowrap;
        overflow-x: auto;
        overscroll-behavior-x: contain;
        scrollbar-width: none;
        padding-bottom: 2px;
    }
    .aig-prompt__chips::-webkit-scrollbar { display: none; }
    .aig-prompt__chips > .aig-chip { flex: 0 0 auto; }
    .aig-prompt__chips > .aig-chip:nth-child(3) { display: inline-block; }

    /* Drop the vh cap: a pure square. */
    .aig-canvas {
        max-width: 100%;
        min-height: 0;
    }

    .aig-steps { grid-template-columns: minmax(0, 1fr); }
}

/* --- ≤560 ----------------------------------------------------------------- */
@media (max-width: 560px) {
    .aig-tilegrid { grid-auto-columns: 72px; grid-template-rows: 72px; }

    .aig-picker__tabs { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

    /*  The bar is a normal-flow sibling of the plate at every width now, so
        the old absolute-detach + measured-margin-reserve machinery is gone.
        What ≤560 still wants is the 2-column button grid: full-width targets
        instead of a ragged wrap of narrow pills. */
    .aig-canvas__bar {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        align-content: center;
    }
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-actions { display: grid; }
    .aig-canvas__bar > * { width: 100%; }
    .aig-canvas__bar .aig-action-note,
    .aig-canvas__bar #aig-toggle { grid-column: 1 / -1; }

    .aig-receipt { flex-wrap: wrap; }

    /* Mono collapses --grid--5 to 1 column here; drafts hold at 2. */
    #aig-recent-grid.mono-grid--5 { grid-template-columns: repeat(2, minmax(0, 1fr)); }

    .aig-mk--gets { grid-template-columns: minmax(0, 1fr); }
}

/* --- ≤380 -----------------------------------------------------------------
   THE 320px BUDGET, computed rather than assumed:
     320 − 24 (12px slab inset each side, from --mono-container-pad)
         = 296 slab − 24 (12px slab padding each side) = 272px usable.
   Every element below is checked against 272. Nothing carries a min-width
   above it, no font drops below 12.5px, and the only sub-40px tap target is
   the documented 32px Change control.                                      */
@media (max-width: 380px) {
    #aig-tool { --mono-container-pad: 12px; }
    .aig-studio { padding: 12px; }
    .aig-tilegrid { grid-auto-columns: 64px; grid-template-rows: 64px; }

    /* The ONE place the 40x40 rule bends, and it bends to 32, never below. */
    #aig-source-thumb { width: 32px; height: 32px; }
    #aig-source-change { width: 32px; height: 32px; min-width: 32px; min-height: 32px; border-radius: 10px; }
    #aig-source-change svg { width: 16px; height: 16px; }

    /* 272px of usable width will not hold two 96px style cards plus their gap
       and the card padding, so the style grid drops to one column here too. */
    .aig-style-grid { grid-template-columns: minmax(0, 1fr); }

    /* The source button moves ABOVE the textarea onto the chip row, so the
       textarea keeps its full 272px at three rows. */
    .aig-prompt__row { grid-template-columns: minmax(0, 1fr) auto; }
    #aig-source-btn { grid-row: 1; grid-column: 1; justify-self: start; }
    #aig-prompt { grid-row: 2; grid-column: 1 / -1; }
    #aig-count { grid-row: 1; grid-column: 2; align-self: center; padding-bottom: 0; }
    #aig-submit { grid-row: 3; }

    #aig-elapsed { font-size: 18px; }
    #aig-receipt-thumbs img { width: 28px; height: 28px; }
    .aig-receipt { gap: 8px; }
    .aig-src-row { height: 52px; }
}

/* Below 360 the expectation line is the least load-bearing thing on the
   plate; the clock plus the ladder still tell the whole story. */
@media (max-width: 359px) {
    #aig-expect { display: none; }
}

/* Header offset shrinks with the mobile header. */
@media (max-width: 575px) {
    .aig-studio, .aig-sheet { --aig-hdr-h: 58px; }
}


/* ==========================================================================
   15. REDUCED MOTION
   The generating state stays COMPLETELY LEGIBLE with zero motion, because
   everything load-bearing — the ladder, the checks, the clock, the resting
   rings, every failure note — is static state, not animation. That is the
   test of whether any of it was ever decorative, and this passes it by
   construction.
   ========================================================================== */

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

    /* 1 — scan becomes a static 1px lime rule at 50% height. */
    #aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-scan {
        animation: none;
        opacity: 0.5;
        background: none;
        border-top: 1px solid var(--aig-lime);
        height: 0;
        inset: 50% 0 auto 0;
    }

    /* 2, 3, 4, 7, 9 — off. Resting states carry the meaning. */
    .aig-phase__dot,
    .aig-tile--ref,
    #aig-grid:has(.aig-stage[data-state="running"]) .aig-tile--ref,
    #aig-root[data-js="on"] .aig-stage[data-state="running"] #aig-canvas-bg > img,
    .aig-canvas.aig-attend,
    .aig-sheet,
    .aig-sheet__scrim,
    #aig-root[data-js="on"] .aig-stage[data-state="failed"] .aig-fail {
        animation: none !important;
    }
    .aig-tile--ref { box-shadow: 0 0 0 2px var(--aig-lime); }

    /* 5 — instant swap. */
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-canvas-bg > img,
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-canvas-img {
        animation: none !important;
        transition: none !important;
    }
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-canvas-bg > img {
        opacity: 0;
        filter: none;
    }

    /* 6 — keep the opacity, drop the translate. */
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-actions,
    #aig-root[data-js="on"] .aig-stage[data-state="done"] #aig-receipt {
        animation: aig-fail-in 200ms linear both;
    }

    /* 8 — the number swaps instantly, BUT THE RING STAYS. It is the real
       signal, and it is the only one: a refund the user cannot see is a claim. */
    #aig-balance.aig-refund { animation: aig-refund-ring 600ms linear 1; }

    /* 10 — a spinner that does not spin is a lie. Slow it, never stop it. */
    .aig-btn.is-spinning::after { animation-duration: 1.4s; }

    /* Everything else: kill the micro-transitions. */
    .aig-ctl, .aig-btn, .aig-chip, .aig-src-row, .aig-tab,
    .aig-style-card, .aig-draft, .aig-canvas__layer, .aig-phase, .aig-sheet {
        transition-duration: 0.01ms !important;
    }
    .aig-ctl:active, .aig-btn:active, #aig-submit:hover { transform: none; }
}


/* ==========================================================================
   16. FORCED COLOURS — keep the ring and the state legible.
   ========================================================================== */

@media (forced-colors: active) {
    .aig-tile--ref { outline: 2px solid Highlight; }
    .aig-glass, .aig-glass--strong { background: Canvas; }
    .aig-canvas { border-color: CanvasText; }
    #aig-submit { border: 1px solid CanvasText; }
}


/* ==========================================================================
   V2 — MISSION CONTROL (2026-08-19)
   The studio stops being a card in a light page and becomes the page: a
   full-bleed dark surface directly under the header, content on the SITE
   RAIL (1660px / 32px pads — the .mono-container standard), the stage
   centred so a wide viewport has no dead corner, and the prompt bar riding
   sticky at the bottom of the surface like a launch console.

   This is an override LAYER on the v1 rules above — same ids, same JS
   contract, later in the file so it wins on order. The landing (flag off)
   never gets .aig-tool--v2 and keeps every v1 rule untouched.
   ========================================================================== */

.aig-tool--v2 {
    /* The section is outside the token scope (tokens live on .aig-studio),
       so the ground colour is stated literally — keep equal to --aig-ground. */
    background: #0b0b0e;
    padding: 26px 0 44px;
    overflow-x: clip;
}

/* The unified page rail. Matches .mono-container exactly. */
.aig-shell {
    max-width: 1660px;
    margin: 0 auto;
    padding: 0 32px;
}
@media (max-width: 720px) {
    .aig-shell { padding: 0 20px; }
}

/* The slab dissolves into the surface: the section owns the ground now. */
.aig-tool--v2 .aig-studio {
    max-width: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
    background: transparent;
}

/* --- head: the page's H1 lives on the dark surface ------------------------ */
.aig-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 8px 24px;
    padding: 4px 0 20px;
}
.aig-head__copy { min-width: 0; }
.aig-head__eyebrow {
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.14em;
    color: var(--aig-lime);
}
/* TRAP (site.css:278) — h1 font-size is !important globally; the id wins. */
#aig-h1.aig-head__h1 {
    margin: 4px 0 0;
    font-family: var(--mono-font-display);
    font-size: clamp(1.5rem, 2.4vw, 2.1rem) !important;
    font-weight: 800;
    letter-spacing: -0.02em;
    color: var(--aig-t1);
}
.aig-head__balance {
    margin: 0 0 4px;
    max-width: 52ch;
    font-size: var(--aig-fs-meta);
    line-height: 1.5;
    color: var(--aig-t2);
    text-align: right;
}
@media (max-width: 979px) {
    .aig-head__balance { text-align: left; }
}

/* --- proportions: tighter rail, centred stage ----------------------------- */
@media (min-width: 980px) {
    .aig-tool--v2 .aig-studio__grid {
        grid-template-columns: 340px minmax(0, 1fr);
        gap: 28px;
    }
}

/*  The stage centres its children; every block that was left-aligned against
    a shared edge now shares a CENTRE line instead, which is what kills the
    dead right-hand corner on wide viewports. Widths are per-block. */
.aig-tool--v2 .aig-stage {
    display: flex;
    flex-direction: column;
    align-items: center;
}
.aig-tool--v2 .aig-canvas,
.aig-tool--v2 .aig-canvas__bar,
.aig-tool--v2 .aig-motion {
    width: 100%;
    max-width: min(100%, 68vh, 720px);
    margin-inline: auto;
}
.aig-tool--v2 .aig-split,
.aig-tool--v2 .aig-receipt,
.aig-tool--v2 #aig-stale,
.aig-tool--v2 #aig-offline {
    width: 100%;
    max-width: min(100%, 960px);
}

/* --- mission control: the prompt console rides the bottom ----------------- */
.aig-tool--v2 .aig-prompt {
    position: sticky;
    bottom: 0;
    z-index: 6;
    width: 100%;
    max-width: 960px;
    margin-top: 20px;
    padding: 14px 0 16px;
    /* Content scrolling underneath fades into the ground instead of clipping
       against a hard edge — the console floats, the page does not shear. */
    background: linear-gradient(180deg, rgba(11, 11, 14, 0) 0%, #0b0b0e 22px);
}
.aig-tool--v2 .aig-prompt__row {
    box-shadow: 0 -6px 30px -12px rgba(0, 0, 0, 0.65), var(--aig-glass-shadow);
}
/* The v1 ≤979 sticky targeted the row alone; v2 makes the whole console
   sticky at every width, so the narrow variant stands down. */
@media (max-width: 979px) {
    .aig-tool--v2 .aig-prompt__row { position: static; }
}


/* ==========================================================================
   V3 — THE GROK SHELL (2026-08-19, same day as v2)
   Organization copied from the reference the owner picked (Grok Imagine):
   a full-height browsable SIDEBAR, a centred hero heading with the console
   under it, a tile GALLERY that is the room's main wall (pack covers before
   a pick — browsing IS picking — the style board after), and the console
   DOCKING to the viewport bottom once its hero slot scrolls away, so the
   prompt is always there. Appended after v2; later-in-file wins.
   ========================================================================== */

/* --- main column ---------------------------------------------------------- */
.aig-main { min-width: 0; }

/* --- hero head: centred over the console (overrides v2's split row) ------- */
.aig-tool--v2 .aig-head {
    display: block;
    text-align: center;
    padding: 20px 0 2px;
}
.aig-tool--v2 .aig-head__balance {
    text-align: center;
    margin: 10px auto 0;
    max-width: 64ch;
}
#aig-h1.aig-head__h1 { margin-top: 6px; }

/* --- sidebar: full height, its own scroll --------------------------------- */
@media (min-width: 980px) {
    .aig-tool--v2 .aig-rail {
        position: sticky;
        top: calc(var(--aig-hdr-h, 72px) + 14px);
        max-height: calc(100vh - var(--aig-hdr-h, 72px) - 28px);
    }
}

/* --- the plate only exists once there is something to show ---------------- */
#aig-root[data-js="on"] .aig-tool--v2 .aig-stage[data-state="idle"] .aig-canvas,
#aig-root[data-js="on"] .aig-tool--v2 .aig-stage[data-state="ready"] .aig-canvas {
    display: none;
}

/* --- console: hero at rest ------------------------------------------------ */
.aig-tool--v2 .aig-prompt {
    position: static;
    width: 100%;
    max-width: 860px;
    margin: 16px auto 6px;
    padding: 0;
    background: none;
}

/* --- console: docked (JS adds .is-docked once the hero slot scrolls away) -- */
.aig-tool--v2 .aig-prompt.is-docked {
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: 14px;
    z-index: 95;               /* over the launcher (90), under the header (998) */
    width: min(860px, calc(100vw - 32px));
    margin: 0;
    padding: 0;
}
/* Docked = compact: the row is the console; the coaching stays in the hero. */
.aig-tool--v2 .aig-prompt.is-docked .aig-prompt__chips,
.aig-tool--v2 .aig-prompt.is-docked .aig-kbd,
.aig-tool--v2 .aig-prompt.is-docked .aig-promptcraft { display: none; }
/* The meta line keeps errors readable while floating over light sections. */
.aig-tool--v2 .aig-prompt.is-docked .aig-prompt__meta {
    margin-top: 6px;
    padding: 4px 12px;
    border-radius: 10px;
    background: rgba(8, 8, 10, 0.88);
}

/* --- the source chip, console edition ------------------------------------- */
#aig-prompt-row .aig-source {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: 220px;
    padding: 4px 6px 4px 4px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--aig-line);
    flex: 0 1 auto;
    min-width: 0;
}
#aig-prompt-row .aig-source img {
    width: 28px;
    height: 28px;
    border-radius: 7px;
    object-fit: cover;
    background: #fff;
}
#aig-prompt-row .aig-source__txt { min-width: 0; display: grid; }
#aig-prompt-row #aig-source-name {
    font-size: 12.5px;
    font-weight: 700;
    color: var(--aig-t1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
#aig-prompt-row #aig-source-meta { display: none; }   /* the console is tight; meta lives in the board sub */
#aig-prompt-row #aig-source-change {
    width: 26px;
    height: 26px;
    min-width: 26px;
    min-height: 26px;
    border-radius: 8px;
}

/* --- the gallery ---------------------------------------------------------- */
.aig-gallery { width: 100%; margin-top: 28px; }

.aig-packwall__tiles {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 14px;
    margin-top: 12px;
}
.aig-pw-tile {
    position: relative;
    display: block;
    aspect-ratio: 1 / 1;
    padding: 0;
    overflow: hidden;
    border: 1px solid var(--aig-line);
    border-radius: var(--aig-r-tile);
    background: var(--aig-raised);
    cursor: pointer;
}
.aig-pw-tile img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    background: #fff;
    opacity: 0.94;
    transition: transform 180ms var(--mono-ease), opacity 180ms var(--mono-ease);
}
.aig-pw-tile:hover img { transform: scale(1.04); opacity: 1; }
.aig-pw-tile::after {
    content: "";
    position: absolute;
    inset: auto 0 0 0;
    height: 46%;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.58));
    pointer-events: none;
    z-index: 1;
}
.aig-pw-tile__name {
    position: absolute;
    left: 10px;
    right: 10px;
    bottom: 8px;
    z-index: 2;
    text-align: left;
    font-size: 12.5px;
    font-weight: 700;
    color: #fff;
}
.aig-pw-tile__meta {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 2;
    padding: 2px 7px;
    border-radius: 8px;
    font-size: 10.5px;
    color: #fff;
    background: rgba(0, 0, 0, 0.45);
    opacity: 0;
    transition: opacity 150ms var(--mono-ease);
}
.aig-pw-tile:hover .aig-pw-tile__meta { opacity: 1; }
.aig-pw-tile:focus-visible {
    outline: 2px solid var(--aig-lime);
    outline-offset: 2px;
}

/* The style board grows up in the gallery: real tiles, not a rail strip. */
.aig-gallery .aig-tilegrid {
    grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
    gap: 12px;
    margin-top: 12px;
}
.aig-board__footrow {
    position: relative;
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 12px;
}
.aig-board__footrow .aig-board-popover {
    position: absolute;
    bottom: calc(100% + 8px);
    left: 0;
    max-width: 420px;
}

/* Gallery collapses politely on narrow screens. */
@media (max-width: 560px) {
    .aig-packwall__tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* The console row grows a 5th track only while the chip is visible ([hidden]
   removes it from the grid entirely, so the base 4-track layout still holds). */
.aig-prompt__row:has(.aig-source:not([hidden])) {
    grid-template-columns: auto auto minmax(0, 1fr) auto auto;
}
/* Phones: the console is too tight for a chip; the pack name lives in the
   board sub-line and the grid button still opens the picker. */
@media (max-width: 720px) {
    #aig-prompt-row .aig-source { display: none !important; }
    .aig-tool--v2 .aig-prompt.is-docked { bottom: 8px; width: calc(100vw - 16px); }
}

/* While the console floats, the gallery keeps clearance so its last rows and
   the board foot can scroll out from underneath it. */
.aig-tool--v2:has(.aig-prompt.is-docked) .aig-gallery { padding-bottom: 150px; }


/* ==========================================================================
   V4 — THE OWNER'S CONSOLE + THE DOTTED ROOM (2026-08-19, wave 4)
   Direction from the owner's own mockups (Downloads/Ai gen): the Design
   Studio's dotted ground and clean grays, a console card with the reference
   slots and credits IN it, an imagery-first sidebar (covers, 2-up, style
   FILTER instead of a second source kind), and an inspiration masonry of the
   library's most-downloaded art with hover "Use as reference".
   Later-in-file override layer, like v2 and v3 before it.
   ========================================================================== */

/* --- the room: dotted ground, less void, lime where it matters ------------ */
.aig-tool--v2 {
    background-color: #101114;
    background-image: radial-gradient(circle, rgba(255, 255, 255, 0.07) 1px, transparent 1px);
    background-size: 22px 22px;
    padding: 16px 0 40px;
}

/* --- head: one quiet line, not a hero ------------------------------------- */
.aig-tool--v2 .aig-head {
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 14px;
    padding: 8px 0 12px;
    text-align: left;
}
#aig-h1.aig-head__h1 {
    margin: 0;
    font-size: clamp(1.15rem, 1.8vw, 1.45rem) !important;
}
#aig-studio a.aig-head__guide {
    font-size: 12.5px;
    color: var(--aig-t2);
    border-bottom: 1px solid rgba(255, 255, 255, 0.25);
}
#aig-studio a.aig-head__guide:hover { color: var(--aig-lime); }

/* --- the console card ------------------------------------------------------ */
.aig-console {
    background: #17181c;
    border: 1px solid var(--aig-line-strong);
    border-radius: 22px;
    padding: 14px 16px 12px;
    box-shadow: 0 24px 60px -28px rgba(0, 0, 0, 0.8);
}
.aig-console__top {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 10px;
    font-size: 12px;
    color: var(--aig-t2);
}
.aig-console__top #aig-balance { margin: 0; font-size: 12px; }
#aig-studio a.aig-console__lib {
    font-size: 12px;
    font-weight: 700;
    color: var(--aig-t2);
    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
#aig-studio a.aig-console__lib:hover { color: var(--aig-lime); }

/* The reference slots — dashed invitation, then the four thumbs. */
.aig-console__refs {
    display: flex;
    align-items: center;
    min-height: 64px;
    margin-bottom: 10px;
    padding: 8px 12px;
    border: 1.5px dashed rgba(255, 255, 255, 0.22);
    border-radius: 14px;
    cursor: pointer;
    transition: border-color var(--mono-dur) var(--mono-ease);
}
.aig-console__refs:hover { border-color: var(--aig-lime-dim); }
.aig-console__refs:has(.aig-console__strip:not([hidden])) {
    border-style: solid;
    border-color: var(--aig-line);
}
.aig-console__hint { margin: 0; display: grid; gap: 2px; }
.aig-console__hint strong { font-size: 13px; color: var(--aig-t1); font-weight: 700; }
.aig-console__hint span { font-size: 12px; color: var(--aig-t2); }
.aig-console__strip { display: flex; gap: 8px; }
.aig-console__strip img {
    width: 48px;
    height: 48px;
    border-radius: 10px;
    object-fit: cover;
    background: #fff;
    border: 2px solid var(--aig-lime);
}

/* The row inside the card sheds its glass — one surface, one border. */
.aig-console .aig-prompt__row {
    background: #0f1013;
    border: 1px solid var(--aig-line);
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
}
.aig-console .aig-prompt__meta {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 12px;
    margin-top: 8px;
}
#aig-studio a.aig-prompt__tips {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--aig-t2);
    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
#aig-studio a.aig-prompt__tips:hover { color: var(--aig-lime); }

/* Chips: quiet seeds under the card. */
.aig-tool--v2 .aig-prompt__chips { justify-content: center; margin-top: 10px; }

/* Docked = just the row: slots, credits and chips fold into the hero copy. */
.aig-tool--v2 .aig-prompt.is-docked .aig-console__refs,
.aig-tool--v2 .aig-prompt.is-docked .aig-console__top { display: none; }
.aig-tool--v2 .aig-prompt.is-docked .aig-console { padding: 10px; border-radius: 18px; }

/* --- the sidebar: a panel of covers ---------------------------------------- */
.aig-tool--v2 .aig-rail {
    background: #141519;
    border: 1px solid var(--aig-line);
    border-radius: 18px;
    padding: 12px;
}

/* Style FILTER chips (packs stay the one source kind). */
.aig-filter {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    padding: 10px 0 2px;
    scrollbar-width: none;
}
.aig-filter::-webkit-scrollbar { display: none; }
.aig-filter__chip {
    flex: 0 0 auto;
    height: 26px;
    min-height: 26px;
    padding: 0 11px;
    border-radius: 999px;
    border: 1px solid var(--aig-line);
    background: rgba(255, 255, 255, 0.05);
    color: var(--aig-t2);
    font-family: var(--mono-font-display);
    font-size: 11.5px;
    font-weight: 600;
    cursor: pointer;
}
.aig-filter__chip.is-active {
    background: var(--aig-lime);
    border-color: var(--aig-lime);
    color: #111;
}

/* Rows become COVER CARDS: image first, 2-up, name as a small overlay. */
.aig-zone__rows {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
}
.aig-src-row {
    display: block;
    position: relative;
    padding: 0;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border: 1px solid var(--aig-line);
    border-radius: 12px;
    background: var(--aig-raised);
}
.aig-src-row__thumb {
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #fff;
    display: block;
}
.aig-src-row__txt {
    position: absolute;
    inset: auto 0 0 0;
    display: block;
    padding: 18px 8px 6px;
    background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.62));
    pointer-events: none;
}
.aig-src-row__name {
    display: block;
    color: #fff;
    font-size: 11.5px;
    font-weight: 700;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.aig-src-row__meta { display: none; }
.aig-src-row:focus-visible { outline: 2px solid var(--aig-lime); outline-offset: 2px; }

/* The style-as-source cards are a deep-link relic now — never shown. */
#aig-picker-styles { display: none !important; }

/* --- sidebar recent drafts ------------------------------------------------- */
.aig-side-recent {
    margin-top: 12px;
    padding-top: 10px;
    border-top: 1px solid var(--aig-line);
}
.aig-side-recent__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
}
.aig-link-btn {
    padding: 2px;
    border: 0;
    background: none;
    color: var(--aig-lime);
    font-family: var(--mono-font-display);
    font-size: 11.5px;
    font-weight: 700;
    cursor: pointer;
}
.aig-side-recent__grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 6px;
    margin-top: 8px;
}
.aig-side-recent__tile {
    padding: 0;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border: 1px solid var(--aig-line);
    border-radius: 8px;
    background: #fff;
    cursor: pointer;
}
.aig-side-recent__tile img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* --- the inspiration masonry ----------------------------------------------- */
.aig-inspo__tiles {
    columns: 5 180px;
    column-gap: 14px;
    margin-top: 12px;
}
.aig-in-tile {
    position: relative;
    break-inside: avoid;
    margin: 0 0 14px;
    overflow: hidden;
    border: 1px solid var(--aig-line);
    border-radius: 12px;
    background: #fff;
}
.aig-in-tile img {
    width: 100%;
    height: auto;
    display: block;
    transition: opacity 150ms var(--mono-ease);
}
.aig-in-tile:hover img { opacity: 0.7; }
.aig-in-tile__use {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%) scale(0.96);
    height: 32px;
    padding: 0 14px;
    border: 0;
    border-radius: 999px;
    background: var(--aig-lime);
    color: #111;
    font-family: var(--mono-font-display);
    font-size: 12px;
    font-weight: 800;
    cursor: pointer;
    opacity: 0;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.35);
    transition: opacity 150ms var(--mono-ease), transform 150ms var(--mono-ease);
}
.aig-in-tile:hover .aig-in-tile__use,
.aig-in-tile__use:focus-visible {
    opacity: 1;
    transform: translate(-50%, -50%) scale(1);
}
.aig-in-tile__cap {
    position: absolute;
    left: 8px;
    bottom: 6px;
    padding: 2px 7px;
    border-radius: 7px;
    background: rgba(0, 0, 0, 0.5);
    color: #fff;
    font-size: 10.5px;
    opacity: 0;
    transition: opacity 150ms var(--mono-ease);
    pointer-events: none;
}
.aig-in-tile:hover .aig-in-tile__cap { opacity: 1; }

/* --- the drafts wall (dark-surface variant of the draft tiles) ------------- */
.aig-drafts-wall__grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 14px;
    margin-top: 12px;
}
.aig-drafts-wall .aig-draft { background: none; border: 0; padding: 0; cursor: pointer; text-align: left; }
.aig-drafts-wall .aig-draft__frame {
    display: block;
    position: relative;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    border: 1px solid var(--aig-line);
    border-radius: 12px;
    background: #fff;
}
.aig-drafts-wall .aig-draft__frame img { width: 100%; height: 100%; object-fit: contain; display: block; }
.aig-drafts-wall .aig-draft__caption { display: block; margin-top: 6px; font-size: 11px; color: var(--aig-t2); }

/* --- the small links strip (light, under the dark surface) ----------------- */
.aig-links { padding-top: 26px; padding-bottom: 30px; }
.aig-links__row {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 12px 28px;
    margin: 0;
    font-size: var(--mono-fs-sm, 14px);
}

/* --- narrow ---------------------------------------------------------------- */
@media (max-width: 979px) {
    .aig-inspo__tiles { columns: 3 140px; }
    .aig-side-recent__grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
    .aig-inspo__tiles { columns: 2 120px; }
    .aig-side-recent__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
    .aig-tool--v2 .aig-head { flex-wrap: wrap; gap: 6px 14px; }
}

/* ==========================================================================
   THE GUIDE PAGE (/tools/ai-generator-guide) — light, prefix aigg-
   ========================================================================== */
.aigg .aigg-h1 {
    margin: 8px 0 0;
    font-family: var(--mono-font-display);
    font-size: clamp(1.9rem, 4vw, 3rem) !important;
    font-weight: var(--mono-w-extrabold, 800);
    letter-spacing: -0.02em;
    color: var(--mono-ink);
}
.aigg .aigg-h2 {
    margin: 0;
    font-family: var(--mono-font-display);
    font-size: 24px;
    font-weight: 800;
    letter-spacing: -0.02em;
}
.aigg .aigg-h3 {
    margin: 0 0 10px;
    font-family: var(--mono-font-display);
    font-size: 18px;
    font-weight: 700;
    color: var(--mono-ink);
}
.aigg-cta-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px 20px;
    margin: 22px 0 0;
}

/* The capabilities card — the owner's lime mockup, faithfully. */
.aigg-cap {
    background: var(--mono-lime, #CCEA4A);
    border-radius: var(--mono-radius-xl, 24px);
    padding: clamp(24px, 4vw, 44px);
    color: #111;
}
.aigg-cap__body { margin: 12px 0 0; max-width: 62ch; font-size: var(--mono-fs-lead); line-height: 1.55; }
.aigg-cap__icons {
    display: flex;
    flex-wrap: wrap;
    gap: 28px 44px;
    margin: 28px 0 0;
    padding: 0;
    list-style: none;
}
.aigg-cap__icons li { display: grid; justify-items: start; gap: 8px; font-size: 12.5px; font-weight: 700; }
.aigg-cap__icons img { width: 44px; height: 44px; }

.aigg-steps {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 20px;
    margin: 26px 0 0;
    padding: 0;
    list-style: none;
}
.aigg-step {
    min-width: 0;
    padding: 20px;
    background: var(--mono-white);
    border: 1px solid var(--mono-border);
    border-radius: var(--mono-radius-lg, 16px);
}
.aigg-step__n {
    display: inline-grid;
    place-items: center;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    background: var(--mono-lime, #CCEA4A);
    color: #111;
    font-weight: 800;
}
.aigg-step__t { display: block; margin-top: 10px; font-weight: 700; color: var(--mono-ink); }
.aigg-step__d { display: block; margin-top: 4px; font-size: var(--mono-fs-sm); color: var(--mono-text-muted); line-height: 1.5; }

.aigg-tips { margin: 20px 0 0; padding-left: 18px; display: grid; gap: 10px; line-height: 1.55; color: var(--mono-text-muted); }
.aigg-tips strong { color: var(--mono-ink); }

.aigg-plans { margin: 20px 0 0; padding: 0; list-style: none; }
.aigg-plans li {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    padding: 12px 2px;
    border-bottom: 1px solid var(--mono-border);
}
.aigg-plans li:last-child { border-bottom: 0; }
.aigg-plans li span:first-child { font-weight: 600; color: var(--mono-ink); }
.aigg-plans li span:last-child { color: var(--mono-text-muted); white-space: nowrap; }

@media (max-width: 720px) {
    .aigg-steps { grid-template-columns: minmax(0, 1fr); }
}


/* ==========================================================================
   V5 — THE POLISH ROUND (2026-08-19, wave 5, ten owner notes)
   Flush under the header, one clean title, square covers, icon-search +
   style dropdown sidebar, chip+refs in one console row with a ✕ way out,
   tighter dimming masonry with the Use button top-right, count in the meta
   row, and the Chapter-4 pill off this page. Override layer, as ever.
   ========================================================================== */

/* (2) The 32px white band was .main-wrapper's static offset under the fixed
   header — measured live: top 48px puts the dark surface flush. Page-scoped
   via :has so nothing else on the site moves. */
@media (min-width: 992px) {
    body:has(#aig-root.aig) .main-wrapper { top: 48px; }
}

/* (4) No promo pill on the tool. */
body:has(#aig-root.aig) .c4v { display: none !important; }

/* (8) ONE title. */
.aig-tool--v2 { padding-top: 12px; }
.aig-tool--v2 .aig-head { padding: 6px 0 12px; gap: 16px; }
#aig-h1.aig-head__h1 {
    font-size: clamp(1.3rem, 2vw, 1.65rem) !important;
    font-weight: 800;
    letter-spacing: -0.01em;
}

/* (6) Sidebar top row: search icon + one clean dropdown. */
.aig-side-top {
    display: flex;
    align-items: center;
    gap: 8px;
}
.aig-side-top .aig-ctl { flex: 0 0 auto; }
.aig-picker.is-search-open #aig-search-toggle {
    border-color: var(--aig-lime-dim);
    color: var(--aig-lime);
}
.aig-style-select {
    flex: 1 1 auto;
    min-width: 0;
    height: 40px;
    padding: 0 30px 0 12px;
    border: 1px solid var(--aig-line);
    border-radius: var(--aig-r-ctl);
    background-color: #0f1013;
    color: var(--aig-t1);
    font-family: var(--mono-font-display);
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
    appearance: none;
    -webkit-appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23a1a1aa' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 10px center;
}
.aig-style-select:focus-visible {
    outline: 2px solid var(--aig-lime);
    outline-offset: 1px;
}
/* The search box hides behind its icon until asked for. */
.aig-picker .aig-picker__search { display: none; margin-top: 8px; }
.aig-picker.is-search-open .aig-picker__search { display: block; }

/* (1) SQUARE covers — an explicit height in the base rules was beating
   aspect-ratio (measured: 56px tall). */
.aig-src-row { height: auto; min-height: 0; }

/* (5)+(7) The chip lives in the refs row with a ✕; the row wraps cleanly. */
.aig-console__refs { flex-wrap: wrap; gap: 10px 12px; }
.aig-console__refs .aig-source {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    max-width: 260px;
    min-width: 0;
    padding: 4px 6px 4px 4px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.05);
    border: 1px solid var(--aig-line);
    flex: 0 1 auto;
}
.aig-console__refs .aig-source img {
    width: 28px;
    height: 28px;
    border-radius: 7px;
    object-fit: cover;
    background: #fff;
}
.aig-console__refs .aig-source__txt { min-width: 0; display: grid; }
.aig-console__refs #aig-source-name {
    font-size: 12.5px;
    font-weight: 700;
    color: var(--aig-t1);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.aig-console__refs #aig-source-meta { display: none; }
.aig-console__refs #aig-source-change,
.aig-console__refs #aig-source-clear {
    width: 26px;
    height: 26px;
    min-width: 26px;
    min-height: 26px;
    border-radius: 8px;
}
.aig-console__refs #aig-source-clear:hover {
    border-color: var(--aig-danger);
    color: var(--aig-danger-t);
    background: rgba(239, 68, 68, 0.08);
}

/* (5) The count leaves the row for the meta line; the row stays 3 tracks. */
.aig-prompt__meta-right {
    display: flex;
    align-items: baseline;
    gap: 14px;
    flex: 0 0 auto;
}
.aig-prompt__meta-right #aig-count { font-size: 11.5px; color: var(--aig-t2); }

/* (3)+(10) The masonry: smaller, tighter, no card chrome; hovering one dims
   the rest; Use as reference sits top-right with a real border and lift. */
.aig-inspo__tiles {
    columns: 7 150px;
    column-gap: 8px;
    margin-top: 10px;
}
.aig-in-tile {
    margin: 0 0 8px;
    border: 0;
    border-radius: 10px;
    transition: opacity 160ms var(--mono-ease);
}
.aig-inspo__tiles:hover .aig-in-tile { opacity: 0.4; }
.aig-inspo__tiles .aig-in-tile:hover { opacity: 1; }
.aig-in-tile:hover img { opacity: 1; }
.aig-in-tile__use {
    left: auto;
    top: 8px;
    right: 8px;
    transform: none;
    height: 28px;
    padding: 0 11px;
    font-size: 11.5px;
    border: 1px solid rgba(17, 17, 17, 0.35);
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28), inset 0 0 0 1.5px rgba(255, 255, 255, 0.35);
    transition: opacity 150ms var(--mono-ease), transform 150ms var(--mono-ease), box-shadow 150ms var(--mono-ease);
}
.aig-in-tile:hover .aig-in-tile__use,
.aig-in-tile__use:focus-visible { opacity: 1; transform: none; }
.aig-in-tile__use:hover {
    transform: translateY(-1px);
    box-shadow: 0 7px 18px rgba(0, 0, 0, 0.35), inset 0 0 0 1.5px rgba(255, 255, 255, 0.5);
}

@media (max-width: 979px) {
    .aig-inspo__tiles { columns: 4 120px; column-gap: 8px; }
}
@media (max-width: 560px) {
    .aig-inspo__tiles { columns: 3 100px; }
}


/* ==========================================================================
   V6 — MINIMAL PASS (2026-08-19, wave 6)
   Less chrome everywhere: the sidebar loses its panel box, the console keeps
   ONE visible box (the describe row), radii and fonts come down a step, the
   guide links live lime on the dark surface, and the owner's icon set joins
   the action buttons. Override layer, as ever.
   ========================================================================== */

/* --- head: full name, one line under it ------------------------------------ */
.aig-tool--v2 .aig-head {
    display: block;
    text-align: center;
    padding: 16px 0 14px;
}
#aig-h1.aig-head__h1 {
    font-size: clamp(1.25rem, 1.9vw, 1.55rem) !important;
    font-weight: 800;
    letter-spacing: -0.015em;
}
.aig-head__sub {
    margin: 5px 0 0;
    font-size: 12px;
    color: var(--aig-t2);
}
#aig-studio a.aig-head__guide {
    margin-left: 8px;
    color: var(--aig-lime);
    font-weight: 700;
    border-bottom: 1px solid var(--aig-lime-dim);
}
#aig-studio a.aig-head__guide:hover { color: var(--aig-lime); border-bottom-color: var(--aig-lime); }

/* --- guide links on the dark surface, lime --------------------------------- */
.aig-links--dark {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 10px 30px;
    padding: 34px 0 4px;
}
#aig-studio .aig-links--dark a {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--aig-lime);
    border-bottom: 1px solid var(--aig-lime-dim);
    padding-bottom: 1px;
    transition: border-color var(--mono-dur) var(--mono-ease);
}
#aig-studio .aig-links--dark a:hover { color: var(--aig-lime); border-bottom-color: var(--aig-lime); }

/* --- the owner's icons on the action buttons ------------------------------- */
.aig-btn__icon {
    width: 15px;
    height: 15px;
    filter: invert(1);
    opacity: 0.85;
    flex: 0 0 auto;
}

/* --- sidebar: no panel, just content on the dotted ground ------------------ */
.aig-tool--v2 .aig-rail {
    background: transparent;
    border: 0;
    border-radius: 0;
    padding: 0 14px 0 0;
    border-right: 1px solid rgba(255, 255, 255, 0.06);
}
.aig-tool--v2 .aig-rail__head,
.aig-tool--v2 .aig-rail__foot {
    background: none;
    border: 0;
    box-shadow: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    padding-left: 0;
    padding-right: 0;
}
.aig-tool--v2 .aig-rail__body {
    background: none;
    border: 0;
    padding-left: 0;
    padding-right: 0;
}

/* Slimmer top row: quiet icon, quiet select, small chevron. */
.aig-side-top { gap: 6px; }
#aig-search-toggle {
    width: 32px;
    height: 32px;
    min-width: 32px;
    min-height: 32px;
    border-radius: 8px;
    background: transparent;
    border-color: transparent;
}
#aig-search-toggle:hover { border-color: var(--aig-line); }
.aig-style-select {
    height: 32px;
    padding: 0 24px 0 8px;
    border-radius: 8px;
    border-color: transparent;
    background-color: transparent;
    font-size: 12px;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='9' viewBox='0 0 24 24' fill='none' stroke='%2371717a' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    background-position: right 8px center;
    transition: border-color var(--mono-dur) var(--mono-ease), background-color var(--mono-dur) var(--mono-ease);
}
.aig-style-select:hover { border-color: var(--aig-line); background-color: rgba(255, 255, 255, 0.03); }
.aig-picker .aig-picker__search {
    height: 34px;
    border-radius: 8px;
    font-size: 12.5px;
    background: rgba(255, 255, 255, 0.04);
}

/* Covers: smaller radius, quieter names, a lift + lime edge on hover. */
.aig-src-row {
    border-radius: 8px;
    border-color: rgba(255, 255, 255, 0.07);
    transition: transform 140ms var(--mono-ease), border-color 140ms var(--mono-ease), box-shadow 140ms var(--mono-ease);
}
.aig-src-row:hover {
    transform: translateY(-1px);
    border-color: var(--aig-lime-dim);
    box-shadow: 0 8px 20px -10px rgba(0, 0, 0, 0.6);
}
.aig-src-row__name { font-size: 10.5px; font-weight: 600; }
.aig-zone__label {
    font-size: 10px;
    letter-spacing: 0.12em;
    color: var(--aig-t3);
}
.aig-side-recent { border-top-color: rgba(255, 255, 255, 0.06); }
.aig-side-recent__tile { border-radius: 6px; border-color: rgba(255, 255, 255, 0.07); }
.aig-tool--v2 .aig-rail__count { font-size: 10.5px; color: var(--aig-t3); }

/* --- console: one visible box, everything else is air ---------------------- */
.aig-console {
    background: rgba(255, 255, 255, 0.02);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 14px;
    padding: 10px 12px 8px;
    box-shadow: none;
}
.aig-console__top { font-size: 11.5px; margin-bottom: 8px; }
.aig-console__top #aig-balance { font-size: 11.5px; }
#aig-studio a.aig-console__lib { color: var(--aig-lime); border-bottom-color: var(--aig-lime-dim); }
#aig-studio a.aig-console__lib:hover { border-bottom-color: var(--aig-lime); }

.aig-console__refs {
    min-height: 50px;
    padding: 7px 10px;
    border: 1px dashed rgba(255, 255, 255, 0.14);
    border-radius: 10px;
    margin-bottom: 8px;
}
.aig-console__refs:has(.aig-console__strip:not([hidden])),
.aig-console__refs:has(.aig-source:not([hidden]))
{
    border-color: transparent;
    padding-left: 2px;
    padding-right: 2px;
}
.aig-console__hint strong { font-size: 12.5px; }
.aig-console__hint span { font-size: 11.5px; }
.aig-console__strip { gap: 6px; }
.aig-console__strip img {
    width: 42px;
    height: 42px;
    border-radius: 8px;
    border-width: 1.5px;
}
.aig-console__refs .aig-source { border-radius: 8px; background: rgba(255, 255, 255, 0.04); }
.aig-console__refs .aig-source img { width: 26px; height: 26px; border-radius: 6px; }
.aig-console__refs #aig-source-name { font-size: 12px; }

.aig-console .aig-prompt__row {
    background: rgba(0, 0, 0, 0.32);
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 11px;
    padding: 10px;
    gap: 10px;
}
.aig-console .aig-prompt__row:focus-within {
    box-shadow: 0 0 0 1.5px var(--aig-lime-dim), 0 10px 34px -16px rgba(204, 234, 74, 0.25);
    border-color: transparent;
}
.aig-console #aig-prompt { font-size: 13.5px; }
.aig-console .aig-prompt__meta { margin-top: 7px; }
.aig-console #aig-reason { font-size: 11.5px; }
#aig-studio a.aig-prompt__tips { font-size: 11.5px; }
.aig-prompt__meta-right #aig-count { font-size: 10.5px; color: var(--aig-t3); }

/* Chips: quieter still. */
.aig-tool--v2 .aig-chip { font-size: 11.5px; }

/* Docked: same minimal card, tighter. */
.aig-tool--v2 .aig-prompt.is-docked .aig-console {
    padding: 8px;
    border-radius: 12px;
    background: rgba(10, 10, 12, 0.92);
    -webkit-backdrop-filter: blur(20px);
    backdrop-filter: blur(20px);
    border-color: rgba(255, 255, 255, 0.1);
    box-shadow: 0 18px 50px -20px rgba(0, 0, 0, 0.8);
}


/* ==========================================================================
   V7 — ONE CENTRE LINE + THE QUIET PANEL (2026-08-19, wave 7)
   The owner's notes on v6: (1) the describe row's icon, text and button sat
   on three different baselines — they now share ONE centre line and the row
   starts a single line tall (JS autosizes it as the text grows); (2) the
   style dropdown's native popup rendered white-on-white — color-scheme:dark
   hands Chrome the dark popup; (3) v6 stripped the rail bare — its quiet
   panel is back; (4) the docked console aligns with the main column (JS sets
   left/width from the column's rect) and slides in instead of popping; the
   dock trigger itself no longer vibrates (rebuilt in aiGenerator.js).
   Override layer, appended after v6 — later-in-file wins.
   ========================================================================== */

/* --- (1) the describe row: one centre line ---------------------------------- */
.aig-console .aig-prompt__row {
    align-items: center;
    gap: 8px;
    padding: 8px;
}
.aig-console #aig-source-btn {
    align-self: center;
    width: 40px;
    height: 40px;
    min-width: 40px;
    min-height: 40px;
    border-radius: 10px;
    background: rgba(255, 255, 255, 0.04);
    border-color: transparent;
}
.aig-console #aig-source-btn:hover { border-color: var(--aig-line); color: var(--aig-t1); }
.aig-console #aig-prompt {
    min-height: 40px;
    max-height: 132px;
    padding: 9px 4px;
    line-height: 1.45;
    overflow-y: auto;
}
.aig-console #aig-submit {
    height: 40px;
    min-height: 40px;
    padding: 0 18px;
    border-radius: 10px;
    font-size: 13px;
}
#aig-submit:not(:disabled):hover {
    box-shadow: 0 8px 26px -10px rgba(204, 234, 74, 0.5);
}
/* Disabled = a ghost, not a grey slab: the row reads as one quiet surface
   until there is something real to press. */
.aig-console #aig-submit:disabled {
    background: rgba(255, 255, 255, 0.035);
    border: 1px solid rgba(255, 255, 255, 0.07);
    color: var(--aig-t3);
    font-weight: 600;
}

/* --- (2) the style dropdown's popup is dark now ----------------------------- */
/* The select itself was fine; the NATIVE options popup inherited the page's
   (unset) colour scheme and painted light text on a light sheet. */
.aig-style-select { color-scheme: dark; }
.aig-style-select option { background-color: #141519; color: #e7e7ea; }

/* --- (3) the rail's quiet panel is back ------------------------------------- */
.aig-tool--v2 .aig-rail {
    background: rgba(255, 255, 255, 0.025);
    border: 1px solid rgba(255, 255, 255, 0.07);
    border-radius: 14px;
    padding: 12px;
    box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03);
}

/* --- (4) the dock: aligned to the main column, sliding in ------------------- */
@keyframes aig-dock-in {
    from { opacity: 0; transform: translateY(12px); }
    to   { opacity: 1; transform: translateY(0); }
}
.aig-tool--v2 .aig-prompt.is-docked {
    /* left/width come from JS (the main column's rect); the old viewport
       centring (left:50% + translateX) would fight those inline values. */
    transform: none;
    animation: aig-dock-in 220ms var(--mono-ease) both;
}
@media (prefers-reduced-motion: reduce) {
    .aig-tool--v2 .aig-prompt.is-docked { animation: none; }
}


/* ==========================================================================
   V8 — THE WORKSPACE (2026-08-19, wave 8)
   Fixed places, everything in reach: plate on the left, every VERSION of the
   draft as a thumb rail beside it (the big below-the-fold split is gone), one
   compact actions row + a live op progress bar under the plate, the whole-pack
   style board in a denser grid, and the console pinned to the bottom for the
   whole life of a job (JS workspace mode). Override layer, appended after v7.
   ========================================================================== */

/* --- the work grid: plate | versions rail ----------------------------------- */
/* At rest (no second version yet) .aig-work is a plain wrapper and the v3
   68vh caps on its children govern, unchanged. The grid exists only once the
   rail has something to show. */
.aig-tool--v2 .aig-work:has(.aig-work__side:not([hidden])) {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 104px;
    gap: 10px 12px;
    align-items: start;
    width: 100%;
    max-width: min(100%, calc(68vh + 116px), 836px);
    margin-inline: auto;
}
.aig-tool--v2 .aig-work:has(.aig-work__side:not([hidden])) .aig-canvas,
.aig-tool--v2 .aig-work:has(.aig-work__side:not([hidden])) .aig-canvas__bar {
    grid-column: 1;
    max-width: none;
    margin-inline: 0;
}
.aig-work__side {
    grid-column: 2;
    grid-row: 1 / span 3;
    min-width: 0;
}
.aig-opbar { grid-column: 1; }

/* --- the versions rail ------------------------------------------------------ */
.aig-variants { display: flex; flex-direction: column; gap: 8px; }
.aig-variant {
    display: block;
    width: 100%;
    padding: 0;
    cursor: pointer;
    border: 1px solid var(--aig-line);
    border-radius: 10px;
    background: var(--aig-raised);
    overflow: hidden;
    transition: border-color 140ms var(--mono-ease), transform 140ms var(--mono-ease);
}
.aig-variant:hover { transform: translateY(-1px); border-color: var(--aig-lime-dim); }
.aig-variant[aria-pressed="true"] { border-color: var(--aig-lime); box-shadow: 0 0 0 1px var(--aig-lime); }
.aig-variant img,
.aig-variant video {
    width: 100%;
    aspect-ratio: 1 / 1;
    object-fit: contain;
    display: block;
    background:
        repeating-conic-gradient(var(--aig-checker-a) 0 25%, var(--aig-checker-b) 0 50%)
        0 0 / 10px 10px;
}
.aig-variant__label {
    display: block;
    padding: 4px 6px 5px;
    text-align: center;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--aig-t2);
}
.aig-variant[aria-pressed="true"] .aig-variant__label { color: var(--aig-lime); }

/* --- the actions row: one compact line -------------------------------------- */
.aig-tool--v2 .aig-canvas__bar { gap: 6px; margin-top: 10px; }
.aig-tool--v2 .aig-canvas__bar .aig-btn {
    height: 34px;
    min-height: 34px;
    padding: 0 12px;
    border-radius: 9px;
    font-size: 12px;
    gap: 7px;
}
.aig-tool--v2 .aig-canvas__bar .aig-btn .aig-pill { font-size: 9.5px; padding: 2px 6px; }
.aig-tool--v2 .aig-canvas__bar .aig-btn__icon { width: 13px; height: 13px; }

/* Replaced by the versions rail / folded into the plate — never shown again. */
#aig-toggle { display: none !important; }
#aig-split { display: none !important; }
#aig-motion { display: none !important; }

/* --- op progress: alive, honest, timed --------------------------------------- */
.aig-opbar {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
}
.aig-opbar__label { font-size: 12px; color: var(--aig-t2); white-space: nowrap; }
.aig-opbar__track {
    flex: 1 1 auto;
    position: relative;
    height: 3px;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.08);
    overflow: hidden;
}
.aig-opbar__fill {
    position: absolute;
    top: 0; bottom: 0; left: 0;
    width: 34%;
    border-radius: 999px;
    background: var(--aig-lime);
    animation: aig-op-slide 1.15s var(--mono-ease) infinite;
}
@keyframes aig-op-slide {
    from { transform: translateX(-110%); }
    to   { transform: translateX(400%); }
}
#aig-opbar-time { font-size: 11.5px; color: var(--aig-t2); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) {
    .aig-opbar__fill { animation: none; width: 100%; opacity: 0.4; }
}

/* --- the whole-pack style board: denser tiles -------------------------------- */
.aig-gallery .aig-tilegrid {
    grid-template-columns: repeat(auto-fill, minmax(92px, 1fr));
    gap: 8px;
}

/* --- narrow screens: rail becomes a horizontal strip under the plate --------- */
@media (max-width: 979px) {
    .aig-tool--v2 .aig-work:has(.aig-work__side:not([hidden])) {
        display: block;
        max-width: min(100%, 68vh, 720px);
    }
    .aig-work__side { margin-top: 10px; }
    .aig-variants { flex-direction: row; overflow-x: auto; scrollbar-width: none; }
    .aig-variants::-webkit-scrollbar { display: none; }
    .aig-variant { flex: 0 0 84px; }
}


/* ==========================================================================
   V8.5 — THE DOCKED BAR CARRIES THE PICK (2026-08-19, mid-wave-8 owner note)
   Switching packs while a result is on the plate gave ZERO feedback in the
   fixed bottom bar — v4 hid the whole refs row when docked. Now the row stays,
   compact: the pack chip + the four reference thumbs, swapping live as the
   customer picks a different pack, so the flow select → generate → refine →
   select another style never goes dark. The dashed "select references"
   invitation stays hero-only; an empty row shows nothing at all.
   ========================================================================== */

.aig-tool--v2 .aig-prompt.is-docked .aig-console__refs {
    display: flex;
    min-height: 0;
    margin-bottom: 6px;
    padding: 2px 2px 0;
    border: 0;
}
.aig-tool--v2 .aig-prompt.is-docked .aig-console__hint { display: none; }
.aig-tool--v2 .aig-prompt.is-docked .aig-console__strip { gap: 5px; }
.aig-tool--v2 .aig-prompt.is-docked .aig-console__strip img {
    width: 26px;
    height: 26px;
    border-radius: 6px;
    border-width: 1px;
}
.aig-tool--v2 .aig-prompt.is-docked .aig-console__refs .aig-source { padding: 2px 5px 2px 3px; }
.aig-tool--v2 .aig-prompt.is-docked .aig-console__refs .aig-source img {
    width: 20px;
    height: 20px;
    border-radius: 5px;
}
.aig-tool--v2 .aig-prompt.is-docked .aig-console__refs #aig-source-name { font-size: 11px; }
.aig-tool--v2 .aig-prompt.is-docked
    .aig-console__refs:not(:has(.aig-source:not([hidden]))):not(:has(.aig-console__strip:not([hidden]))) {
    display: none;
}


/* ==========================================================================
   V9 — WALL, CHEVRON, SEAM (2026-08-19, wave 9)
   The inspiration wall drops CSS columns for a UNIFORM SQUARE GRID — columns
   balance by estimate and left a visible hole (owner screenshot); a square
   auto-fill grid cannot have one. A shuffled 160-item pool + "Load more"
   lives in aiGenerator.js. The style select gets appearance:none so the big
   native Windows arrow (which also changed on open) never renders — only the
   quiet 9px chevron. And the white 40px seam between the dark shell and the
   footer closes.
   ========================================================================== */

/* --- the wall: square tiles, no holes, ever --------------------------------- */
.aig-inspo__tiles {
    columns: unset;
    column-gap: unset;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
    gap: 8px;
    margin-top: 10px;
}
.aig-in-tile { margin: 0; aspect-ratio: 1 / 1; }
.aig-in-tile img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 979px) {
    .aig-inspo__tiles { columns: unset; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
}
@media (max-width: 560px) {
    .aig-inspo__tiles { columns: unset; grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.aig-inspo__foot { display: flex; justify-content: center; padding: 16px 0 4px; }
#aig-inspo-more {
    height: 36px;
    min-height: 36px;
    padding: 0 22px;
    border-radius: 10px;
    font-size: 12.5px;
}

/* --- the style select: one small chevron, no native arrow, no size jump ----- */
.aig-style-select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background-size: 9px 9px;
}
.aig-style-select:focus,
.aig-style-select:focus-visible {
    outline: none;
    border-color: var(--aig-lime-dim);
}

/* --- close the white seam under the dark shell ------------------------------- */
.aig-tool--v2 { padding-bottom: 0; }


/* ==========================================================================
   V10 — GROUND + BREATHING ROOM (2026-08-19, wave 10)
   The v5 top:48px relative shift on .main-wrapper closes the band under the
   header but leaves the FLOW box 48px short at the bottom — body-white showed
   through above the footer. The page's ground is dark now (body + #aig-root on
   this page only), so whatever the offset arithmetic does, the seam is black,
   and the shell gets comfortable padding before the footer. Second note: the
   hero (title + console + chips) gets air above and the masonry moves a row
   down, centring the generator block in its own space.
   ========================================================================== */

body:has(#aig-root.aig) { background: #0b0b0e; }
#aig-root.aig { background: #0b0b0e; }
.aig-tool--v2 { padding-bottom: 48px; }
.aig-links--dark { padding: 44px 0 8px; }

/* --- the hero block breathes; the wall starts a row lower ------------------- */
.aig-tool--v2 .aig-head { padding: 46px 0 14px; }
.aig-tool--v2 .aig-prompt { margin: 22px auto 10px; }
.aig-tool--v2 .aig-gallery { margin-top: 110px; }

/* --- V11: the top-up offer row in the reason slot --------------------------- */
.aig-topup-offer { display: inline-flex; align-items: center; gap: 10px; margin-left: 6px; }
.aig-topup-offer .aig-btn {
    height: 30px;
    min-height: 30px;
    padding: 0 12px;
    font-size: 12px;
    border-radius: 8px;
}

/* --- V12: the always-there top-up entry beside the balance ------------------ */
.aig-console__toplinks { display: inline-flex; align-items: center; gap: 16px; }
.aig-console__lib--btn {
    background: none;
    border: 0;
    padding: 0;
    cursor: pointer;
    font: inherit;
}

/* --- V13: the Get-credits pill (the v12 button borrowed anchor styles that
   never matched a <button> and rendered invisible) ---------------------------- */
.aig-topup-btn {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    height: 26px;
    padding: 0 11px;
    border: 0;
    border-radius: 999px;
    background: var(--aig-lime);
    color: #111;
    font-family: var(--mono-font-display);
    font-size: 11.5px;
    font-weight: 700;
    cursor: pointer;
    transition: transform 120ms var(--mono-ease), box-shadow 120ms var(--mono-ease);
}
.aig-topup-btn:hover {
    transform: translateY(-1px);
    box-shadow: 0 6px 16px -6px rgba(204, 234, 74, 0.55);
}

/* --- V13b (owner): the console card and the rail panel share one surface
   tone - a half-transparent dark grey instead of the near-invisible wash. --- */
.aig-console { background: rgb(36 36 36 / 50%); }
.aig-tool--v2 .aig-rail { background: rgb(36 36 36 / 50%); }

/* --- V13c: the three-pack picker in the reason slot -------------------------- */
.aig-topup-offer { flex-wrap: wrap; }
.aig-topup-pack { white-space: nowrap; }

/* --- V13d (owner): the prompt control block sits vertically CENTERED between
   the header and the masonry - equal air above the title and below the chips,
   scaling with the viewport instead of two unrelated magic numbers. --- */
.aig-tool--v2 .aig-head { padding: clamp(48px, 9vh, 96px) 0 14px; }
.aig-tool--v2 .aig-gallery { margin-top: clamp(48px, 9vh, 96px); }
