[x-cloak] { display: none !important; }

/* ── 1. Design Tokens ────────────────────────────────────────── */
/*  OxieAI — Premium minimalist cinematic dark mode.                 */
/*  Single graphite background, restrained accent, hairline borders. */
:root {
    /* Background — deep, near-black graphite. No purple wash. */
    --bg-0: #0A0A0A;     /* page base */
    --bg-1: #121212;     /* surface elevation 1 */

    /* Text — pure white headings, soft gray body, restrained mutes */
    --ink:      #FFFFFF;                   /* headings, on-emphasis copy */
    --ink-dim:  #F4F4F5;                   /* near-white body */
    --ink-2:    #E4E4E7;                   /* secondary body */
    --ink-3:    #A1A1AA;                   /* primary muted (per brief) */
    --muted:    #A1A1AA;
    --muted-2:  #71717A;
    --muted-3:  #52525B;

    /* Surfaces — translucent overlays on the graphite base, hairline edges */
    --line:        rgba(255, 255, 255, 0.08);  /* per brief: very subtle */
    --line-hover:  rgba(255, 255, 255, 0.14);
    --line-focus:  rgba(255, 255, 255, 0.22);
    --card:        rgba(255, 255, 255, 0.03);
    --card-alt:    rgba(255, 255, 255, 0.02);
    --surface-1:   rgba(255, 255, 255, 0.025);
    --surface-2:   rgba(255, 255, 255, 0.04);

    /* Accent — single premium neon-blue. Reserved for primary CTAs
       and active states only. Used sparingly. */
    --accent:        #4F8DFF;
    --accent-hover:  #6BA1FF;
    --accent-soft:   rgba(79, 141, 255, 0.12);
    --accent-ring:   rgba(79, 141, 255, 0.35);
    --accent-fg:     #0A0A0A;              /* text on accent fills */

    --glow:  rgba(79, 141, 255, 0.10);     /* now extremely subtle */
    --gold:  rgba(79, 141, 255, 0.06);     /* legacy var: same family */

    /* Semantic — toned-down, no over-saturation */
    --success:        #34D399;
    --success-bg:     rgba(52, 211, 153, 0.10);
    --success-border: rgba(52, 211, 153, 0.30);
    --danger:         #F87171;
    --danger-bg:      rgba(248, 113, 113, 0.10);
    --danger-border:  rgba(248, 113, 113, 0.30);
    --info-bg:        rgba(255, 255, 255, 0.04);
    --info-border:    rgba(255, 255, 255, 0.08);

    /* Brand — primary CTA token aliases the accent */
    --primary:        #4F8DFF;
    --primary-hover:  #6BA1FF;
    --primary-fg:     #0A0A0A;

    /* Gradients — kept far more restrained. The brand gradient is
       now a near-monochrome accent wash; the headline gradient is
       white-to-soft-blue for cinematic legibility. */
    --gradient-brand: linear-gradient(90deg, #4F8DFF 0%, #6BA1FF 100%);
    --gradient-text:  linear-gradient(90deg, #FFFFFF 0%, #FFFFFF 55%, #C7D7F5 100%);

    /* Spacing (4px base) */
    --sp-1:  0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
    --sp-5:  1.25rem;  --sp-6: 1.5rem;   --sp-8: 2rem;     --sp-10: 2.5rem;  --sp-12: 3rem;

    /* Radii */
    --radius-sm:   0.5rem;
    --radius-md:   0.75rem;
    --radius-lg:   1rem;
    --radius-xl:   1.25rem;
    --radius-2xl:  1.5rem;
    --radius-3xl:  1.75rem;
    --radius-full: 9999px;

    /* Typography — Inter primary per brief, Manrope fallback for
       any locales/views where Inter doesn't load. Headline serif
       retained for the wordmark only. */
    --font-sans:  Inter, "Inter Variable", "SF Pro Text", "SF Pro Display", Manrope, system-ui, -apple-system, sans-serif;
    --font-serif: "DM Serif Display", ui-serif, Georgia, serif;
    --text-xs:    0.75rem;  --text-sm: 0.875rem; --text-base: 1rem;
    --text-lg:    1.125rem; --text-xl: 1.25rem;  --text-2xl:  1.5rem;
    --text-3xl:   1.875rem;

    /* Shadows — minimal. We rely on hairline borders and contrast,
       not heavy drop shadows, to define elevation. */
    --shadow-sm:   0 1px 2px rgba(0, 0, 0, 0.30);
    --shadow-md:   0 4px 16px rgba(0, 0, 0, 0.35);
    --shadow-lg:   0 16px 48px rgba(0, 0, 0, 0.45);
    --shadow-xl:   0 24px 64px rgba(0, 0, 0, 0.50);
    --shadow-glow: 0 0 0 1px rgba(79, 141, 255, 0.18), 0 8px 24px rgba(79, 141, 255, 0.18);

    /* Transitions */
    --tr-fast: 150ms;
    --tr:      200ms;
    --tr-slow: 250ms;
}

/* ── 2. Base ─────────────────────────────────────────────────── */
body.studio-body {
    font-family: var(--font-sans);
    background-color: var(--bg-0);
    color: var(--ink);
    -webkit-font-smoothing: antialiased;
}

/* ── 3. Backdrop layers ──────────────────────────────────────── */
/*  Minimal cinematic backdrop: flat graphite with one ultra-subtle
    top-center accent wash. No competing color radials — images stay
    the visual hero. */
.studio-bg-layer {
    position: fixed; inset: 0; z-index: -1; pointer-events: none;
    background:
        radial-gradient(1200px 600px at 50% -10%, var(--glow), transparent 70%),
        var(--bg-0);
}

.studio-vignette::after {
    content: "";
    position: fixed; inset: 0; pointer-events: none; z-index: 0;
    background: radial-gradient(ellipse at 50% 0%, transparent 55%, rgba(0, 0, 0, 0.40) 100%);
    opacity: 0.5;
}

/* ── 4. Typography helpers ───────────────────────────────────── */
/*  Brand wordmark uses the same clean sans-serif as the rest of the
    UI (per brief). Slight letter-spacing tightening for a refined
    SaaS feel comparable to Linear / Stripe. */
.studio-brand {
    font-family: var(--font-sans);
    font-weight: 600;
    letter-spacing: -0.01em;
}

.studio-gradient-text {
    background: var(--gradient-text);
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    padding-bottom: 0.15em;
    letter-spacing: -0.02em;
}

/* ── 5. Header & Layout Primitives ───────────────────────────── */
/*  Sticky glassmorphic header per brief. Hairline bottom edge,
    pronounced backdrop blur, near-opaque graphite base. */
.studio-header {
    position: sticky;
    top: 0;
    background: rgba(10, 10, 10, 0.72);
    border-bottom: 1px solid var(--line);
    backdrop-filter: blur(20px) saturate(140%);
    -webkit-backdrop-filter: blur(20px) saturate(140%);
}

.studio-panel {
    position: relative;
    border-radius: var(--radius-3xl);
    border: 1px solid var(--line);
}

/*  Studio surface — a flat graphite card with a hairline edge.
    No multi-color radials competing with imagery. The slider/gallery
    is the hero; this is the frame. */
.studio-surface {
    position: relative;
    border-radius: inherit;
    border: 1px solid var(--line);
    background: var(--surface-2);
    box-shadow: var(--shadow-md), inset 0 1px 0 rgba(255, 255, 255, 0.04);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

.studio-surface:hover {
    border-color: var(--line-hover);
    box-shadow: var(--shadow-lg), inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

/* ══════════════════════════════════════════════════════════════
 * 6. DESIGN-SYSTEM COMPONENTS  (use these across all views)
 * ══════════════════════════════════════════════════════════════ */

/* ── 6.1 Panels & Cards ──────────────────────────────────────── */
:where(.ds-panel) {
    border-radius: var(--radius-3xl);
    border: 1px solid var(--line);
    background: var(--surface-1);
    padding: var(--sp-8);
    box-shadow: var(--shadow-xl);
    backdrop-filter: blur(16px);
}

:where(.ds-panel-lg) { padding: var(--sp-10); }
:where(.ds-panel-sm) { padding: var(--sp-6); }

:where(.ds-card) {
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    background: var(--card);
    padding: var(--sp-4);
    transition: border-color var(--tr), background-color var(--tr);
}
:where(.ds-card-dark) {
    border-radius: var(--radius-3xl);
    border: 1px solid var(--line);
    background: var(--surface-2);
    padding: var(--sp-4);
}

:where(.ds-card-dashed) {
    border-radius: var(--radius-3xl);
    border: 1px dashed var(--line);
    background: rgba(0, 0, 0, 0.20);
    padding: var(--sp-10);
    text-align: center;
    font-size: var(--text-sm);
    color: var(--muted-2);
}

/* ── 6.2 Form Inputs (unified) ───────────────────────────────── */
.ds-input {
    width: 100%;
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    background: var(--card);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--text-sm);
    color: var(--ink-dim);
    outline: none;
    transition: border-color var(--tr), box-shadow var(--tr);
    font-family: inherit;
}

.ds-input::placeholder { color: var(--muted-2); }

.ds-input:focus,
.ds-input:focus-visible {
    border-color: var(--line-focus);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.20);
}

.ds-input:disabled { opacity: 0.55; cursor: not-allowed; }

.ds-input-sm   { padding: 0.625rem var(--sp-4); }
.ds-input-dark { background: var(--surface-2); }

.ds-input-search {
    border-radius: var(--radius-full);
    padding: var(--sp-3) var(--sp-4) var(--sp-3) 2.5rem;
}

textarea.ds-input { resize: vertical; min-height: 6rem; }

.ds-hint  { font-size: var(--text-xs); color: var(--muted-2); margin-top: var(--sp-1); }
/* ── Accent hint box (brand-colored) ──────────────────────── */
.ds-hint-brand {
    display: flex;
    align-items: center;
    gap: var(--sp-3);
    border-radius: var(--radius-2xl);
    border: 1px solid rgba(79, 141, 255, 0.20);
    background: rgba(79, 141, 255, 0.08);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--text-sm);
    color: #FFFFFF;
}
.ds-hint-brand svg,
.ds-hint-brand .lucide {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    color: #6BA1FF;
}


/* ── 6.3 Buttons (unified) ───────────────────────────────────── */
.ds-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sp-2);
    padding: 0.65rem 1.35rem;
    border-radius: var(--radius-full);
    font-family: var(--font-sans);
    font-size: var(--text-sm);
    font-weight: 600;
    line-height: 1.4;
    cursor: pointer;
    border: none;
    text-decoration: none;
    transition: filter var(--tr), transform var(--tr-fast),
                background var(--tr), border-color var(--tr), box-shadow var(--tr);
    white-space: nowrap;
}
.ds-btn:disabled,
.ds-btn[disabled] { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
.ds-btn svg, .ds-btn i { width: 16px; height: 16px; flex-shrink: 0; }

.ds-btn-primary {
    background: var(--accent);
    color: var(--accent-fg);
    box-shadow: var(--shadow-glow);
    letter-spacing: 0.01em;
}
.ds-btn-primary:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
    box-shadow: 0 0 0 1px rgba(79, 141, 255, 0.32), 0 12px 32px rgba(79, 141, 255, 0.28);
}
.ds-btn-primary:active { transform: translateY(0); }
.ds-btn-primary:focus-visible {
    outline: none;
    box-shadow: 0 0 0 3px var(--accent-ring), var(--shadow-glow);
}

.ds-btn-solid {
    background: var(--primary);
    color: var(--primary-fg);
    box-shadow: var(--shadow-sm);
}
.ds-btn-solid:hover { background: var(--primary-hover); box-shadow: var(--shadow-md); }

.ds-btn-ghost {
    border: 1px solid var(--line);
    background: transparent;
    color: var(--ink-dim);
}
.ds-btn-ghost:hover {
    background: rgba(255, 255, 255, 0.04);
    border-color: var(--line-hover);
    color: var(--ink);
}

.ds-btn-chip {
    padding: 0.375rem 0.875rem;
    font-size: var(--text-xs);
    font-weight: 500;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-2);
    border-radius: var(--radius-full);
    display: inline-flex; align-items: center; gap: var(--sp-2);
    transition: border-color var(--tr), background var(--tr);
    cursor: pointer;
    text-decoration: none;
}
.ds-btn-chip:hover { border-color: var(--line-hover); background: rgba(255, 255, 255, 0.05); }
.ds-btn-chip.is-active {
    border-color: var(--accent-ring);
    background: var(--accent-soft);
    color: var(--accent-hover);
}

.ds-btn-sm    { padding: 0.55rem 1.1rem; font-size: var(--text-xs); }
.ds-btn-xs    { padding: 0.375rem 0.75rem; font-size: var(--text-xs); }
.ds-btn-lg    { padding: 0.85rem 1.75rem; font-size: var(--text-base); }
.ds-btn-block { width: 100%; }

.ds-btn-danger:hover { background: rgba(244, 63, 94, 0.25); border-color: rgba(244, 63, 94, 0.55); }

.ds-btn-success:hover { background: rgba(16, 185, 129, 0.25); border-color: rgba(16, 185, 129, 0.55); }

.ds-btn-icon:hover { border-color: var(--line-hover); background: rgba(255, 255, 255, 0.10); }

/* ── 6.4 Badges & Tags ───────────────────────────────────────── */
.badge-pill {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-3);
    font-size: 0.7rem;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.08em;
}

.studio-chip {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: 0.55rem 0.95rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--muted);
}

.studio-icon-bubble {
    display: inline-flex;
    align-items: center; justify-content: center;
    height: 42px; width: 42px;
    border-radius: var(--radius-full);
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: var(--card);
}

/* ── 6.5 Alerts (unified) ────────────────────────────────────── */
.alert-error,
.alert-success,
.alert-info {
    border-radius: var(--radius-2xl);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--text-sm);
    border-width: 1px;
    border-style: solid;
}
.alert-error   { border-color: var(--danger-border);  background: var(--danger-bg);  color: #ffe4e6; }
.alert-success { border-color: var(--success-border); background: var(--success-bg); color: #d1fae5; }
.alert-info    { border-color: var(--info-border);    background: var(--info-bg);    color: var(--ink-dim); }

/* ── 6.7 Legacy aliases ──────────────────────────────────────── */
.studio-input {
    background: rgba(0, 0, 0, 0.35);
    border: 1px solid rgba(255, 255, 255, 0.14);
    border-radius: var(--radius-2xl);
    padding: var(--sp-3) var(--sp-4);
    font-size: var(--text-sm);
    color: var(--ink);
    outline: none;
    transition: border-color var(--tr), box-shadow var(--tr);
}
.studio-input:focus {
    border-color: var(--line-focus);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.10);
}

.form-input:focus {
    border-color: var(--line-focus);
    box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.10);
}

.studio-btn {
    position: relative; overflow: hidden;
    background: var(--accent);
    color: #FFFFFF;
    box-shadow: var(--shadow-glow);
    transition: background var(--tr), transform var(--tr-fast), box-shadow var(--tr);
}
.studio-btn:hover { background: var(--accent-hover); transform: translateY(-1px); box-shadow: 0 0 0 1px rgba(79, 141, 255, 0.32), 0 12px 32px rgba(79, 141, 255, 0.28); }
.studio-btn:active { transform: translateY(0); }

.studio-btn-ghost {
    border: 1px solid var(--line);
    background: transparent;
    color: var(--ink-dim);
    transition: background var(--tr), border-color var(--tr), color var(--tr);
}
.studio-btn-ghost:hover { background: rgba(255, 255, 255, 0.04); border-color: var(--line-hover); color: var(--ink); }

/* ── 7. Preview / Before-After Slider ────────────────────────── */
.preview-btn {
    font-size: 14px;
    padding: 6px 14px;
    border-radius: var(--radius-full);
    border: 1px solid rgba(255, 255, 255, 0.12);
    background: rgba(255, 255, 255, 0.04);
    color: rgba(255, 255, 255, 0.7);
    transition: var(--tr-slow);
}
.preview-btn:hover { background: rgba(255, 255, 255, 0.08); color: white; }
.preview-btn.active {
    background: rgba(79, 141, 255, 0.10);
    color: #FFFFFF;
    border: 1px solid rgba(79, 141, 255, 0.35);
    box-shadow: 0 0 12px rgba(79, 141, 255, 0.25);
}

/* ── 8. Custom Styles Page (btn-purple) ──────────────────────── */
.btn-purple {
    background: linear-gradient(135deg, #4F8DFF 0%, #3B7DFF 50%, #2C6FE8 100%);
    border: 1px solid rgba(79, 141, 255, 0.3);
    color: #FFFFFF;
    box-shadow: 0 2px 8px rgba(79, 141, 255, 0.4);
    transition: box-shadow var(--tr), transform var(--tr-fast), background var(--tr);
}
.btn-purple:hover {
    background: linear-gradient(135deg, #6BA1FF 0%, #4F8DFF 50%, #3B7DFF 100%);
    box-shadow: 0 0 18px rgba(79, 141, 255, 0.5), 0 4px 16px rgba(79, 141, 255, 0.5);
    transform: translateY(-1px);
    border-color: rgba(79, 141, 255, 0.4);
}
.btn-purple:active { transform: translateY(0); box-shadow: 0 0 8px rgba(79, 141, 255, 0.3); }

/* ── 9. Favorite cards ───────────────────────────────────────── */
.fav-style-card {
    position: relative; overflow: hidden; display: block;
    border-radius: var(--radius-2xl);
    border: 1px solid rgba(255, 255, 255, 0.05);
    background-color: rgba(0, 0, 0, 0.4);
    background-size: cover;
    background-position: center;
    transition: border-color var(--tr);
}
.fav-style-card:hover { border-color: var(--line-hover); }
.fav-style-card .aspect-box { aspect-ratio: 3/4; }

.fav-style-card .overlay {
    position: absolute; inset: 0;
    border-radius: var(--radius-2xl);
    background: linear-gradient(to top, rgba(0,0,0,0.85) 0%, rgba(0,0,0,0.35) 55%, transparent 100%);
    transition: background var(--tr);
}
.fav-style-card:hover .overlay {
    background: linear-gradient(to top, rgba(0,0,0,0.65) 0%, rgba(0,0,0,0.25) 55%, transparent 100%);
}

.fav-style-card .card-body {
    pointer-events: none;
    position: absolute; inset: 0;
    display: flex; flex-direction: column; justify-content: flex-end;
    padding: var(--sp-5);
    color: var(--ink-dim);
    gap: var(--sp-3);
}

.fav-style-card .accent-bar {
    position: absolute; inset-inline: 0; bottom: 0; height: 4px; opacity: 0.8;
}

.fav-corner-btn {
    position: absolute; top: 0.75rem; right: 0.75rem; z-index: 10;
    display: flex; align-items: center; justify-content: center; gap: 0.35rem;
    padding: 0.35rem 0.6rem;
    font-size: 10px; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.05em;
    border-radius: var(--radius-full);
    background: rgba(0, 0, 0, 0.55);
    backdrop-filter: blur(6px);
    border: 1px solid rgba(79, 141, 255, 0.35);
    color: #FFFFFF; cursor: pointer;
    transition: background var(--tr), box-shadow var(--tr), transform var(--tr-fast), border-color var(--tr);
}
.fav-corner-btn:hover {
    background: rgba(79, 141, 255, 0.35);
    box-shadow: 0 0 12px rgba(79, 141, 255, 0.4);
    border-color: rgba(79, 141, 255, 0.55);
    transform: scale(1.1);
}

/* ── Favorite heart icon (filled state) ─────────────────────── */
.favorite-btn svg.fill-rose-400,
.favorite-btn svg.fill-rose-400 path {
    fill: #fb7185 !important;
    stroke: #fb7185 !important;
}
/* ── Lucide filled icons (generic) ──────────────────────────── */
svg[class*="fill-rose"] path { fill: inherit; }

/* ── 10. Ribbon ──────────────────────────────────────────────── */
/*  A clean horizontal "POPULAR" badge in the top-right of the card,
    using the accent color. Replaces the old rotated ribbon for a
    more refined SaaS look. */
.studio-ribbon {
    position: absolute; top: 1.25rem; right: 1.25rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--accent-ring);
    background: var(--accent-soft);
    color: #FFFFFF;
    font-size: 0.65rem; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
}

/* ── 11. Animations ──────────────────────────────────────────── */
@media (prefers-reduced-motion: no-preference) {
    .studio-float { animation: studioFloat 7s ease-in-out infinite; }
    @keyframes studioFloat {
        0%, 100% { transform: translateY(0); }
        50%      { transform: translateY(-8px); }
    }
}

/* ── 12. Utilities ───────────────────────────────────────────── */
.scrollbar-hide::-webkit-scrollbar { display: none; }
.scrollbar-hide { -ms-overflow-style: none; scrollbar-width: none; }
.is-hidden { display: none !important; }

/* ══════════════════════════════════════════════════════════════
 * 12b. Generation Overlay
 * ══════════════════════════════════════════════════════════════ */
.gen-overlay {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(5, 5, 7, 0.82);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    transition: opacity 0.3s ease;
}
.gen-overlay.is-hidden {
    display: none !important;
}
.gen-overlay-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sp-6);
    text-align: center;
    padding: var(--sp-6);
}
.gen-overlay-spinner {
    width: 5rem;
    height: 5rem;
    color: #6BA1FF;
    animation: spin 1s linear infinite;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}
.gen-overlay-title {
    font-size: var(--text-xl);
    font-weight: 600;
    color: #fff;
}
.gen-overlay-sub {
    font-size: var(--text-sm);
    color: var(--muted-2);
}

/* ══════════════════════════════════════════════════════════════
 * 13. Result Page — full-bleed viewer (variant C)
 *
 * The frame IS the page. The file is CONTAINED (never cropped) over a
 * blurred copy of itself, actions float over it in one dock, and the
 * read-once metadata lives in a drawer instead of a permanent 340px
 * column — that column is what squeezed the image down to ~300px in a
 * 1366px window while two thirds of the viewport stayed empty.
 *
 * Large forms (Animate, marketplace post) are `.res-panel` sections BELOW
 * the frame, in the normal page flow — deliberately not a bottom sheet.
 * The marketplace block is a workbench (fields, then a LIST of generated
 * posts with their own expanders and exports); scrolling that inside a
 * sheet, over an image nobody needs at that moment, is worse than the
 * layout it replaces.
 *
 * THE STAGE STAYS DARK IN BOTH THEMES, on purpose: it is a media viewer,
 * like a lightbox, and photos read against black. So the chips, dock and
 * drawer need no light-theme layer — only the panels below the frame do,
 * and those follow the ordinary tokens.
 * ══════════════════════════════════════════════════════════════ */
.result-page { padding: var(--sp-8) 0 var(--sp-12); }
.res-shell { max-width: 72rem; margin: 0 auto; padding: 0 var(--sp-6); }

.res-sr-only {
    position: absolute; width: 1px; height: 1px;
    padding: 0; margin: -1px; overflow: hidden;
    clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ── The frame ────────────────────────────────────────────────── */
.res-viewer {
    position: relative;
    height: clamp(20rem, 68vh, 45rem);
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    overflow: hidden;
    background: #000;
    isolation: isolate;
}
.res-viewer__bg {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    filter: blur(38px) brightness(0.32);
    transform: scale(1.15);
    z-index: 0;
}
/* Solid scrim for the states that paint copy over the backdrop. */
.res-viewer__scrim {
    position: absolute; inset: 0; z-index: 1;
    background: rgba(0, 0, 0, 0.62);
}
.res-viewer__stage { position: absolute; inset: 0; z-index: 2; }
.res-viewer__media {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
}
.res-viewer__media--placeholder {
    filter: blur(8px);
    transition: opacity 0.5s;
}
/* Press-and-hold comparison overlay.
   OPAQUE and `display`-toggled, both deliberate. With a transparent overlay
   the result stayed visible around a differently-shaped original, which reads
   as the original pasted on top of it rather than as a comparison — holding
   has to REPLACE the frame. And no transition: a comparison wants an instant
   swap, not a cross-fade. */
.res-viewer__orig {
    position: absolute; inset: 0;
    width: 100%; height: 100%;
    object-fit: contain;
    background: #0A0A0A;
    display: none;
    z-index: 3;
    pointer-events: none;
}
.res-viewer.is-comparing .res-viewer__orig { display: block; }

/* ── Gallery arrows ───────────────────────────────────────────── */
.res-nav {
    position: absolute; top: 50%;
    transform: translateY(-50%);
    z-index: 6;
    width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(18, 18, 20, 0.72);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    color: rgba(255, 255, 255, 0.9);
    text-decoration: none;
    transition: background var(--tr), border-color var(--tr), color var(--tr);
}
.res-nav:hover { background: rgba(18, 18, 20, 0.92); border-color: rgba(255, 255, 255, 0.30); color: #fff; }
.res-nav i, .res-nav svg { width: 22px; height: 22px; }
.res-nav--prev { left: var(--sp-3); }
.res-nav--next { right: var(--sp-3); }
/* While holding "Original" the arrows would sit on top of the comparison. */
.res-viewer.is-comparing .res-nav { opacity: 0; pointer-events: none; }

/* Generated video — fills the frame, no native controls, click to play. */
.res-viewer__video { position: absolute; inset: 0; cursor: pointer; }
.res-viewer__playicon {
    position: absolute; top: 50%; left: 50%;
    transform: translate(-50%, -50%);
    width: 72px; height: 72px;
    display: flex; align-items: center; justify-content: center;
    border-radius: 50%;
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.25);
    color: #fff;
    opacity: 0;                 /* hidden while playing */
    transition: opacity var(--tr-slow);
    pointer-events: none;       /* clicks pass through to the stage */
    z-index: 4;
}
.res-viewer__playicon i, .res-viewer__playicon svg { width: 32px; height: 32px; }
.res-viewer__video.is-paused .res-viewer__playicon { opacity: 1; }

/* Gradients so the overlay bar and dock stay legible over any photo. */
.res-viewer__fade { position: absolute; left: 0; right: 0; z-index: 5; pointer-events: none; }
.res-viewer__fade--top    { top: 0; height: 6rem; background: linear-gradient(to bottom, rgba(0,0,0,0.72), transparent); }
.res-viewer__fade--bottom { bottom: 0; height: 7.5rem; background: linear-gradient(to top, rgba(0,0,0,0.75), transparent); }

/* ── Top bar ──────────────────────────────────────────────────── */
.res-topbar {
    position: absolute; top: 0; left: 0; right: 0; z-index: 6;
    display: flex; align-items: center; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-4);
    flex-wrap: wrap;
}
.res-topbar__link {
    display: inline-flex; align-items: center; gap: 0.375rem;
    font-size: var(--text-sm); font-weight: 500;
    color: rgba(255, 255, 255, 0.82);
    text-decoration: none;
    transition: color var(--tr);
}
.res-topbar__link:hover { color: #fff; }
.res-topbar__link i, .res-topbar__link svg { width: 15px; height: 15px; }

.res-chips { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; margin-left: auto; }
.res-chip {
    display: inline-flex; align-items: center; gap: 0.3rem;
    padding: 0.25rem 0.65rem;
    border-radius: var(--radius-full);
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: rgba(0, 0, 0, 0.5);
    font-size: 0.72rem; font-weight: 500;
    color: rgba(255, 255, 255, 0.88);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.res-chip--style::before {
    content: '';
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 8px var(--glow);
}
/* Retention urgency. --warning / --danger are used as text on near-black
   here, which is the direction they are contrast-safe in. */
.res-chip--warn   { border-color: rgba(251, 191, 36, 0.40); background: rgba(251, 191, 36, 0.16); color: #FBBF24; }
.res-chip--danger { border-color: rgba(248, 113, 113, 0.42); background: rgba(248, 113, 113, 0.18); color: #FCA5A5; }

/* ── Action dock ──────────────────────────────────────────────── */
.res-dock {
    position: absolute; left: 50%; bottom: var(--sp-4);
    transform: translateX(-50%);
    z-index: 7;
    display: flex; align-items: center; gap: 2px;
    max-width: calc(100% - var(--sp-8));
    padding: 6px;
    border-radius: var(--radius-full);
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(18, 18, 20, 0.86);
    backdrop-filter: blur(12px);
    box-shadow: 0 12px 40px rgba(0, 0, 0, 0.55);
    overflow-x: auto;
    scrollbar-width: none;
}
.res-dock::-webkit-scrollbar { display: none; }
.res-dock__btn {
    display: inline-flex; align-items: center; gap: 0.4rem;
    flex: 0 0 auto;
    padding: 0.5rem 0.8rem;
    border: none; background: transparent;
    border-radius: var(--radius-full);
    font-family: var(--font-sans);
    font-size: var(--text-sm); font-weight: 500; line-height: 1.2;
    color: rgba(255, 255, 255, 0.86);
    text-decoration: none; cursor: pointer; white-space: nowrap;
    transition: background var(--tr), color var(--tr);
    -webkit-user-select: none; user-select: none;
    -webkit-touch-callout: none;
    touch-action: manipulation;
}
.res-dock__btn:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }
.res-dock__btn i, .res-dock__btn svg { width: 15px; height: 15px; flex-shrink: 0; }
.res-dock__btn[aria-pressed="true"],
.res-dock__btn[aria-expanded="true"] { background: rgba(255, 255, 255, 0.14); color: #fff; }
.res-dock__btn--icon { padding: 0.5rem 0.6rem; }
.res-dock__btn--primary {
    background: var(--accent); color: #0A0A0A; font-weight: 600;
}
.res-dock__btn--primary:hover { background: var(--accent-hover); color: #0A0A0A; }
.res-dock__sep {
    flex: 0 0 auto;
    width: 1px; height: 1.25rem;
    background: rgba(255, 255, 255, 0.16);
    margin: 0 2px;
}

/* ── Details drawer ───────────────────────────────────────────── */
.res-drawer {
    position: absolute; top: 0; right: 0; bottom: 0;
    width: 19rem; max-width: 100%;
    z-index: 8;
    display: flex; flex-direction: column;
    background: rgba(12, 12, 14, 0.95);
    backdrop-filter: blur(20px);
    border-left: 1px solid var(--line);
    transform: translateX(100%);
    transition: transform 0.22s ease;
}
.res-drawer.is-open { transform: none; }
.res-drawer__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--sp-3);
    padding: var(--sp-4);
    border-bottom: 1px solid var(--line);
    flex-shrink: 0;
}
.res-drawer .res-drawer__title { font-size: var(--text-sm); font-weight: 600; color: #fff; }
.res-drawer__close {
    background: none; border: none; cursor: pointer;
    color: rgba(255, 255, 255, 0.6);
    padding: 4px; line-height: 1;
    transition: color var(--tr);
}
.res-drawer__close:hover { color: #fff; }
.res-drawer__close i, .res-drawer__close svg { width: 16px; height: 16px; }
/* flex:1 + min-height:0 are both load-bearing: a flex item's default
   min-height is auto, so without them the body grows to its content instead
   of scrolling, and the overflow lands outside the viewer's own clip — the
   report button at the end of the list became unreachable that way. */
.res-drawer__body {
    flex: 1 1 auto; min-height: 0;
    padding: var(--sp-4);
    display: flex; flex-direction: column; gap: 0.7rem;
    overflow-y: auto;
}
.res-row {
    display: flex; justify-content: space-between; gap: var(--sp-3);
    font-size: 0.78rem;
}
.res-row > span:first-child { color: rgba(255, 255, 255, 0.55); }
.res-row > span:last-child  { color: rgba(255, 255, 255, 0.92); text-align: right; }
.res-row.is-hidden { display: none; }
.res-drawer__hr { height: 1px; background: var(--line); }
/* Scoped through .res-drawer because `body.studio-body p` (0,0,1,2) beats a
   bare class — the same trap that made the pending title grey. */
.res-drawer .res-drawer__note {
    font-size: 0.72rem; line-height: 1.55;
    color: rgba(255, 255, 255, 0.58);
    margin: 0;
}
.res-badge-row { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.res-drawer__btn {
    display: inline-flex; align-items: center; justify-content: center; gap: 0.4rem;
    width: 100%;
    padding: 0.55rem 0.9rem;
    border-radius: var(--radius-full);
    border: 1px solid rgba(255, 255, 255, 0.16);
    background: rgba(255, 255, 255, 0.06);
    font-family: var(--font-sans);
    font-size: var(--text-xs); font-weight: 600;
    color: #fff; cursor: pointer;
    transition: background var(--tr), border-color var(--tr);
}
.res-drawer__btn:hover { background: rgba(255, 255, 255, 0.12); border-color: rgba(255, 255, 255, 0.28); }
.res-drawer__btn i, .res-drawer__btn svg { width: 14px; height: 14px; }
.res-drawer__link {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2);
    font-size: 0.72rem; color: rgba(255, 255, 255, 0.58);
}
.res-drawer__link a { color: rgba(255, 255, 255, 0.78); text-decoration: underline; word-break: break-all; }
.res-drawer__link.is-hidden { display: none; }

/* ── Notice states inside the frame (expired / failed) ────────── */
.res-notice {
    position: absolute; inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    gap: var(--sp-3);
    padding: var(--sp-6);
    text-align: center;
}
.res-notice__icon {
    width: 48px; height: 48px;
    border-radius: var(--radius-lg);
    display: flex; align-items: center; justify-content: center;
    background: rgba(255, 255, 255, 0.06);
    border: 1px solid rgba(255, 255, 255, 0.14);
    color: rgba(255, 255, 255, 0.8);
}
.res-notice__icon i, .res-notice__icon svg { width: 24px; height: 24px; }
.res-notice__icon--warn   { color: #FBBF24; background: rgba(251, 191, 36, 0.12); border-color: rgba(251, 191, 36, 0.28); }
.res-notice__icon--danger { color: #FCA5A5; background: rgba(248, 113, 113, 0.12); border-color: rgba(248, 113, 113, 0.30); }
/* Two classes (0,2,0) to clear `body.studio-body p` (0,0,1,2) — see the
   .result-pending-title note below; same trap, same fix. */
.res-notice .res-notice__title { font-size: var(--text-lg); font-weight: 700; color: #fff; }
.res-notice .res-notice__body  { font-size: var(--text-sm); line-height: 1.55; color: rgba(255, 255, 255, 0.82); max-width: 34rem; }
.res-notice .res-notice__hint  { font-size: var(--text-xs); color: rgba(255, 255, 255, 0.6); }

/* Failed-state error details toggle (admin only). */
.result-error-toggle {
    font-size: var(--text-xs); color: rgba(255, 255, 255, 0.6);
    background: none; border: none; cursor: pointer; text-decoration: underline;
    padding: 0;
}
.result-error-toggle:hover { color: #fff; }
.result-error-detail {
    font-size: var(--text-xs); color: rgba(255, 255, 255, 0.7);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    text-align: left;
    word-break: break-word; white-space: pre-wrap;
    background: rgba(0, 0, 0, 0.45); border: 1px solid var(--line);
    border-radius: var(--radius-md); padding: var(--sp-3);
    max-width: 34rem; max-height: 12rem; overflow: auto;
}

/* ── Pending state inside the frame ───────────────────────────── */
.result-pending-inner {
    position: absolute; inset: 0;
    display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    text-align: center; gap: var(--sp-3);
    padding: var(--sp-6);
}
.result-pending-spinner {
    width: 56px; height: 56px; color: var(--accent);
    /* `spin` is declared once, in section 6 with the generation overlay's
       spinner. It was declared a second time right here — keyframes are global,
       so the copy did nothing but wait to drift from the original. */
    animation: spin 1s linear infinite;
}
/* Scoped through .result-pending-inner ON PURPOSE, and these are all <p>.
   `body.studio-body p` (further down this file) sets color: var(--ink-3),
   and one class plus two elements (0,1,2) beats a bare class (0,1,0) — so
   plain white below would never apply. Two classes (0,2,0) win. Same trap
   as .history-card-title. */
.result-pending-inner .result-pending-title { font-size: var(--text-lg); font-weight: 700; color: #fff; text-shadow: 0 1px 3px rgba(0,0,0,0.6); }
.result-pending-inner .result-pending-sub   { font-size: var(--text-sm); color: rgba(255,255,255,0.85); max-width: 32rem; text-shadow: 0 1px 2px rgba(0,0,0,0.5); }

/* ── Buttons shared with the panels below the frame ───────────── */
.result-btn {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    padding: 0.65rem 1.35rem;
    border-radius: var(--radius-full);
    font-family: var(--font-sans);
    font-size: var(--text-sm); font-weight: 600;
    cursor: pointer; border: none; text-decoration: none; line-height: 1.4;
    transition: filter var(--tr), transform var(--tr-fast), background var(--tr), border-color var(--tr), box-shadow var(--tr);
}
.result-btn svg, .result-btn i { width: 16px; height: 16px; flex-shrink: 0; }
.result-btn-primary { background: var(--gradient-brand); color: #FFFFFF; box-shadow: var(--shadow-glow); }
.result-btn-primary:hover { filter: brightness(1.05); transform: translateY(-1px); box-shadow: 0 8px 30px rgba(79, 141, 255, 0.25); }
.result-btn-primary:active { transform: translateY(0); }
.result-btn-ghost {
    border: 1px solid rgba(255, 255, 255, 0.14);
    background: rgba(255, 255, 255, 0.04);
    color: var(--ink);
}
.result-btn-ghost:hover { background: rgba(255, 255, 255, 0.07); border-color: var(--line-hover); }
/* Still live: the per-post export/copy buttons in the marketplace panel are
   built in JS (result.php → makePostExportRow) with this class. */
.result-btn-sm { padding: 0.55rem 1.1rem; font-size: var(--text-xs); }

/* ── Share controls (live in the drawer, kept on their own classes
      because result-page.js toggles them by id) ────────────────── */
.result-public-badge {
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.25rem 0.65rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--success-border);
    background: var(--success-bg);
    font-size: 0.7rem; font-weight: 600; color: var(--success);
}
.result-public-badge svg, .result-public-badge i { width: 13px; height: 13px; }
.result-public-badge.is-hidden { display: none; }

.result-revoke-btn {
    display: inline-flex; align-items: center; gap: 0.25rem;
    padding: 0.25rem 0.65rem;
    border-radius: var(--radius-full);
    border: 1px solid rgba(239, 68, 68, 0.35);
    background: rgba(239, 68, 68, 0.12);
    font-size: 0.7rem; font-weight: 600; color: #FCA5A5;
    cursor: pointer; font-family: var(--font-sans); white-space: nowrap;
    transition: background var(--tr), border-color var(--tr), opacity var(--tr);
}
.result-revoke-btn:hover { background: rgba(239, 68, 68, 0.22); border-color: rgba(239, 68, 68, 0.55); }
.result-revoke-btn svg, .result-revoke-btn i { width: 12px; height: 12px; }
.result-revoke-btn.is-hidden { display: none; }

.result-share-copy-btn {
    display: inline-flex; align-items: center; gap: 4px;
    padding: 0.2rem 0.55rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--success-border);
    background: var(--success-bg);
    font-size: 0.66rem; font-weight: 600; color: var(--success);
    cursor: pointer; font-family: var(--font-sans);
    transition: background var(--tr), border-color var(--tr);
}
.result-share-copy-btn:hover { background: rgba(16, 185, 129, 0.25); border-color: rgba(16, 185, 129, 0.55); }
.result-share-copy-btn svg, .result-share-copy-btn i { width: 12px; height: 12px; }

/* ── Panels below the frame (Animate, marketplace post) ───────── */
.res-panel {
    margin-top: var(--sp-5);
    padding: var(--sp-6);
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    background: linear-gradient(180deg, var(--card), var(--card-alt));
}
/* Spelled out rather than left to Tailwind's .hidden: app.css loads BEFORE
   this file, so a utility of the same specificity loses to whatever is
   written here. Toggle .is-hidden, which is `display:none !important`
   further up in section 6. */
.res-panel.is-hidden { display: none; }

@media (prefers-reduced-motion: no-preference) {
    @keyframes resultSlideIn {
        from { opacity: 0; transform: translateY(18px); }
        to   { opacity: 1; transform: translateY(0); }
    }
    .res-viewer { animation: resultSlideIn 0.5s ease-out both; }
}

@media (max-width: 900px) {
    .res-viewer { height: clamp(18rem, 60vh, 32rem); }
    .res-drawer { width: 100%; }
    .res-nav { width: 38px; height: 38px; }
    .res-nav i, .res-nav svg { width: 19px; height: 19px; }
    /* Labels off, glyphs only — the dock must not outgrow the frame. */
    .res-dock__btn span { display: none; }
    .res-dock__btn { padding: 0.55rem 0.7rem; }
    .res-dock__btn--primary { padding: 0.55rem 0.8rem; }
}
@media (max-width: 640px) {
    .res-shell { padding: 0 var(--sp-4); }
    .res-panel { padding: var(--sp-5) var(--sp-4); }
    .res-topbar { gap: var(--sp-2); padding: var(--sp-3); }
    /* The chips wrap under the links instead of squeezing them. */
    .res-chips { margin-left: 0; flex-basis: 100%; }
}

/* ── 13c. Custom-style media toggle + VIDEO badge (Session 4) ──── */

/* Image/Video segmented toggle on the "create style" form. Self-contained
   (no Tailwind peer utilities) so it works without a CSS rebuild. */
.ds-media-toggle {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--sp-2);
}
.ds-media-opt { cursor: pointer; }
.ds-media-opt input { position: absolute; opacity: 0; pointer-events: none; }
.ds-media-opt > span {
    display: flex; align-items: center; justify-content: center; gap: var(--sp-2);
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    background: rgba(255,255,255,0.05);
    padding: 0.75rem 1rem;
    font-size: var(--text-sm); font-weight: 600;
    color: var(--ink-3);
    transition: border-color var(--tr-slow), background var(--tr-slow), color var(--tr-slow);
}
.ds-media-opt > span i, .ds-media-opt > span svg { width: 16px; height: 16px; }
.ds-media-opt input:checked + span {
    border-color: var(--accent-ring);
    background: var(--accent-soft);
    color: #fff;
}
.ds-media-opt input:focus-visible + span {
    box-shadow: 0 0 0 3px var(--accent-ring);
}

/* VIDEO badge shown on style cards / pages whose media_type is video. */
.style-video-badge {
    position: absolute; top: var(--sp-3); left: var(--sp-3);
    z-index: 6;
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.25rem 0.6rem;
    border-radius: var(--radius-lg);
    background: rgba(0,0,0,0.55);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(255,255,255,0.18);
    color: #fff;
    font-size: 11px; font-weight: 700;
    letter-spacing: 0.05em; text-transform: uppercase;
    pointer-events: none;
}
.style-video-badge i, .style-video-badge svg { width: 13px; height: 13px; }
.style-video-badge-lg {
    top: var(--sp-4); left: var(--sp-4);
    padding: 0.35rem 0.75rem; font-size: 12px;
}

/* PRODUCT badge shown on style cards / pages whose is_product flag is set.
   Ділить кістяк зі .style-video-badge (позиція, розміри, радіус, типографіка) —
   різниця лише в кольорі. Тримати їх нарізно тим і зручно, що бейджі можуть
   зустрітись на одній картці (відео-стиль для товару): video ліворуч зверху,
   product — під ним. */
.style-product-badge {
    position: absolute; top: var(--sp-3); left: var(--sp-3);
    z-index: 6;
    display: inline-flex; align-items: center; gap: 0.35rem;
    padding: 0.25rem 0.6rem;
    border-radius: var(--radius-lg);
    background: rgba(139, 92, 246, 0.22);
    backdrop-filter: blur(10px);
    border: 1px solid rgba(167, 139, 250, 0.45);
    color: #ddd0fe;
    font-size: 11px; font-weight: 700;
    letter-spacing: 0.05em; text-transform: uppercase;
    pointer-events: none;
}
.style-product-badge i, .style-product-badge svg { width: 13px; height: 13px; }
.style-product-badge-lg {
    top: var(--sp-4); left: var(--sp-4);
    padding: 0.35rem 0.75rem; font-size: 12px;
}

/* Обидва бейджі на одній картці — товарний зсувається під відео-бейдж. */
.style-video-badge + .style-product-badge { top: calc(var(--sp-3) + 30px); }
.style-video-badge-lg + .style-product-badge-lg { top: calc(var(--sp-4) + 34px); }

/* Світла тема: на світлому прев'ю прозорий фіолет губиться — робимо щільніше. */
html[data-theme="light"] .style-product-badge {
    background: rgba(139, 92, 246, 0.92);
    border-color: rgba(109, 40, 217, 0.55);
    color: #fff;
}

/* ── 14. RTL Support ─────────────────────────────────────────── */
[dir="rtl"] body,
[dir="rtl"] p, [dir="rtl"] h1, [dir="rtl"] h2, [dir="rtl"] h3, [dir="rtl"] h4,
[dir="rtl"] li, [dir="rtl"] label, [dir="rtl"] span, [dir="rtl"] a {
    text-align: right;
}
[dir="rtl"] input, [dir="rtl"] textarea, [dir="rtl"] select {
    direction: rtl; text-align: right;
}


/* ══════════════════════════════════════════════════════════════
 * 7b. Before / After Slider
 * ══════════════════════════════════════════════════════════════ */

/* ── Stage wrapper ─────────────────────────────────────────── */
.bas-stage {
    position: relative;
    box-sizing: border-box;
    max-width: 100%;
    padding: 20px;
    border-radius: 28px;
    background:
        radial-gradient(ellipse 80% 60% at 50% 0%, rgba(79, 141, 255, 0.08), transparent 70%),
        radial-gradient(ellipse 100% 100% at 50% 50%, var(--card) 40%, transparent 100%),
        linear-gradient(180deg, var(--card), var(--card-alt));
    box-shadow:
        inset 0 1px 0 rgba(255, 255, 255, 0.06),
        0 0 0 1px rgba(255, 255, 255, 0.04),
        0 0 40px 8px rgba(79, 141, 255, 0.04),
        0 30px 80px -20px rgba(0, 0, 0, 0.6),
        0 10px 30px -10px rgba(0, 0, 0, 0.4);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
}
.bas-stage,
.bas-stage > * { min-width: 0; }
.bas-stage *,
.bas-stage *::before,
.bas-stage *::after { box-sizing: border-box; }

/* Decorative top light line */
.bas-stage::before {
    content: "";
    position: absolute;
    top: -1px; left: 20%; right: 20%;
    height: 1px;
    background: linear-gradient(90deg, transparent, rgba(140, 170, 255, 0.5), transparent);
    pointer-events: none;
}

/* ── Frame ─────────────────────────────────────────────────── */
/* Keep portrait faces in frame when cropped to the wide ratio */
#basAfter, #basBefore { object-position: center 30%; }

.bas-frame {
    cursor: col-resize;
    aspect-ratio: 16 / 9;
    min-height: 220px;
    min-width: 0;
    max-width: 100%;
    border-radius: 22px;
    background: var(--card);
    border: 1px solid var(--line);
    box-shadow: 0 20px 50px -15px rgba(0, 0, 0, 0.5);
}
.bas-frame:focus-visible {
    outline: 2px solid rgba(140, 170, 255, 0.7);
    outline-offset: 3px;
}
/* ── Divider line ──────────────────────────────────────────── */
/* ── Corner badges (Before / After labels) ────────────────── */
.bas-label-after {
    padding: 4px 10px;
    border-radius: 8px;
    background: linear-gradient(90deg, rgba(140,110,255,.90), rgba(80,220,255,.90));
    backdrop-filter: blur(6px);
    font-size: 10px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .10em;
    color: #FFFFFF;
}

/* ── Live drag handle ──────────────────────────────────────── */
.bas-handle {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 52px;
    height: 52px;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 2px;
    pointer-events: none;
    z-index: 20;
    color: #fff;
    background: rgba(255, 255, 255, 0.15);
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    border: 1.5px solid rgba(255, 255, 255, 0.85);
    box-shadow:
        0 4px 20px rgba(0, 0, 0, 0.5),
        inset 0 0 10px rgba(255, 255, 255, 0.2);
    animation: bas-breathe 2.8s ease-in-out infinite;
    transition: transform 200ms ease-out, box-shadow 200ms ease-out;
}
.bas-handle svg {
    position: relative;
    z-index: 2;
    filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.5));
    animation: bas-arrow-left 2.4s ease-in-out infinite;
}
.bas-handle svg:last-of-type {
    animation-name: bas-arrow-right;
    animation-delay: 0.15s;
}
.bas-handle-ring {
    position: absolute;
    inset: -4px;
    border-radius: 50%;
    border: 1.5px solid rgba(180, 200, 255, 0.55);
    animation: bas-ring 2.4s ease-out infinite;
    pointer-events: none;
}
.bas-handle-glow {
    position: absolute;
    inset: -10px;
    border-radius: 50%;
    background: radial-gradient(circle, rgba(140, 170, 255, 0.35) 0%, rgba(140, 170, 255, 0) 65%);
    filter: blur(4px);
    animation: bas-glow 2.8s ease-in-out infinite;
    pointer-events: none;
    z-index: 0;
}
.bas-handle.is-dragging {
    transform: translate(-50%, -50%) scale(1.12);
    box-shadow:
        0 6px 28px rgba(120, 170, 255, 0.55),
        inset 0 0 14px rgba(255, 255, 255, 0.35);
    animation-play-state: paused;
}
.bas-handle.is-dragging svg,
.bas-handle.is-dragging .bas-handle-ring,
.bas-handle.is-dragging .bas-handle-glow {
    animation-play-state: paused;
}

@keyframes bas-breathe {
    0%, 100% {
        transform: translate(-50%, -50%) scale(1);
        box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5), inset 0 0 10px rgba(255, 255, 255, 0.2);
    }
    50% {
        transform: translate(-50%, -50%) scale(1.06);
        box-shadow: 0 6px 26px rgba(120, 170, 255, 0.45), inset 0 0 14px rgba(255, 255, 255, 0.3);
    }
}
@keyframes bas-ring {
    0%        { transform: scale(1);   opacity: 0.7; }
    80%, 100% { transform: scale(1.5); opacity: 0;   }
}
@keyframes bas-glow {
    0%, 100% { opacity: 0.55; transform: scale(1);    }
    50%      { opacity: 0.95; transform: scale(1.15); }
}
@keyframes bas-arrow-left {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(-2px); }
}
@keyframes bas-arrow-right {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(2px); }
}

/* ── Autoplay progress bar ─────────────────────────────────── */
.bas-autoplay-bar {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 2px;
    background: rgba(255, 255, 255, 0.08);
    z-index: 15;
    pointer-events: none;
    transition: opacity 300ms ease;
}
.bas-autoplay-bar.is-hidden { opacity: 0; }
.bas-autoplay-bar span {
    display: block;
    width: 100%;
    height: 100%;
    transform: scaleX(0);
    transform-origin: left center;
    background: linear-gradient(90deg, rgba(79, 141, 255, 0.9), rgba(79, 141, 255, 0.9));
    box-shadow: 0 0 8px rgba(79, 141, 255, 0.5);
}

/* ── Thumbnail buttons ─────────────────────────────────────── */
.preview-btn.bas-thumb {
    position: relative;
    padding: 2px;
    flex: 0 1 64px;
    min-width: 0;
    aspect-ratio: 16 / 9;
    border-radius: 11px;
    overflow: hidden;
    background: transparent;
    border: 1px solid var(--line);
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    transition:
        transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
        border-color 0.4s ease,
        box-shadow 0.4s cubic-bezier(0.16, 1, 0.3, 1);
}
.preview-btn.bas-thumb:hover {
    background: transparent;
    transform: translateY(-2px);
    border-color: var(--line-hover);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.35);
}
.preview-btn.bas-thumb.active {
    background: transparent;
    border-color: rgba(140, 170, 255, 0.55);
    box-shadow:
        0 0 0 2px rgba(79, 141, 255, 0.18),
        0 10px 28px rgba(0, 0, 0, 0.35);
}
.preview-btn.bas-thumb:focus-visible {
    outline: 2px solid rgba(140, 170, 255, 0.7);
    outline-offset: 2px;
}

/* Shine sweep on hover */
.preview-btn.bas-thumb::before {
    content: "";
    position: absolute;
    top: 3px; bottom: 3px; left: -100%;
    width: 50%;
    background: linear-gradient(to right, transparent, rgba(255, 255, 255, 0.25), transparent);
    transform: skewX(-20deg);
    z-index: 2;
    transition: left 0.6s cubic-bezier(0.16, 1, 0.3, 1);
    pointer-events: none;
}
.preview-btn.bas-thumb:hover::before { left: 200%; }

/* Bottom gradient for label legibility */
.preview-btn.bas-thumb::after {
    content: "";
    position: absolute;
    left: 3px; right: 3px; bottom: 3px;
    height: 45%;
    border-radius: 0 0 11px 11px;
    background: linear-gradient(to top, rgba(0, 0, 0, 0.75) 0%, transparent 100%);
    pointer-events: none;
    z-index: 1;
}

.preview-btn.bas-thumb img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    border-radius: 11px;
    filter: brightness(0.8) saturate(0.95);
    transition: transform 0.6s cubic-bezier(0.16, 1, 0.3, 1), filter 0.4s ease;
}
.preview-btn.bas-thumb:hover img {
    transform: scale(1.05);
    filter: brightness(1) saturate(1.05);
}
.preview-btn.bas-thumb.active img {
    filter: brightness(1.05) saturate(1.1);
}

.bas-thumb-label {
    position: absolute;
    left: 3px; right: 3px;
    bottom: 9px;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.12em;
    color: rgba(255, 255, 255, 0.95);
    text-shadow: 0 2px 5px rgba(0, 0, 0, 0.9);
    pointer-events: none;
    z-index: 3;
}

/* ── Responsive ────────────────────────────────────────────── */
@media (max-width: 640px) {
    .bas-stage { padding: 12px; border-radius: 20px; }
    .bas-frame { min-height: 240px; }
}
@media (max-width: 480px) {
    .bas-stage { padding: 10px; border-radius: 18px; }
    .bas-frame { min-height: 200px; }
    #basTabs { gap: 8px; }
    .preview-btn.bas-thumb { flex-basis: 56px; }
    .bas-thumb-label { font-size: 9px; bottom: 4px; letter-spacing: 0.1em; }
}
@media (max-width: 380px) {
    #basTabs { gap: 6px; }
    .preview-btn.bas-thumb { width: 64px; height: 48px; }
}

/* ── Reduced motion ────────────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
    .bas-handle,
    .bas-handle svg,
    .bas-handle-ring,
    .bas-handle-glow { animation: none !important; }
    .preview-btn.bas-thumb { transition: none; }
    .preview-btn.bas-thumb:hover { transform: none; }
}

/* ── Image zoom on hover ─────────────────────────────────── */
/* SHORT ON PURPOSE — it used to be 1.2s. In Firefox a transformed layer is
   composited by stretching the raster it already has, rather than redrawing
   the image at the new size, and on a detailed photo that cheap filtering
   shimmers. It is NOT a performance problem: measured over 53 frames at the
   real card width, this animation drops ZERO frames and averages 16.7ms.
   Nothing here is slow; the pixels are just resampled badly while moving.

   Everything was tried, at identical size, on the machine that shows it:
   no promotion, will-change, translateZ, the `scale` property, promoting the
   container instead, removing the rounded clip — all shimmer identically.
   Animating width/height instead makes the browser redraw at the exact size
   and is genuinely sharp in theory, but measured 14 dropped frames out of 53
   and shifts the image sideways (a percentage margin resolves against width,
   so it cannot stay centred). Chrome filters properly and shows none of this.

   So the only lever that helped was TIME: the same zoom over 0.2s scored the
   same 0/53 frames and was the least visible of everything tested. Shorter is
   the fix here; do not lengthen this without re-testing in Firefox. */
.preset-zoom-img {
    transition: transform 0.25s ease-out;
}
.group:hover .preset-zoom-img {
    transform: scale(1.08);
}
@media (prefers-reduced-motion: reduce) {
    .preset-zoom-img { transition: none; }
    .group:hover .preset-zoom-img { transform: none; }
}

/* Background layer for fav-style-card (custom.php) */
.fav-card-bg {
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background-size: cover;
    background-position: center;
    z-index: 0;
}

/* ══════════════════════════════════════════════════════════════
 *  17. OxieAI Premium Refresh — additive component utilities
 * ══════════════════════════════════════════════════════════════
 *  All classes below are NEW. They do not override existing styles
 *  and are safe to opt-into per-element. Drop them onto markup
 *  where you want the refreshed treatment.
 * ── 17.1 Credit pill (header balance display) ──────────────── */
.ds-credit-pill {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.4rem 0.85rem 0.4rem 0.7rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-dim);
    font-size: var(--text-xs);
    font-weight: 600;
    line-height: 1;
    letter-spacing: 0.01em;
    transition: border-color var(--tr), background var(--tr), color var(--tr);
    text-decoration: none;
    white-space: nowrap;
}
.ds-credit-pill:hover {
    border-color: var(--accent-ring);
    background: var(--accent-soft);
    color: var(--ink);
}
.ds-credit-pill__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.125rem;
    height: 1.125rem;
    color: var(--accent);
}
.ds-credit-pill__icon svg,
.ds-credit-pill__icon i { width: 14px; height: 14px; }
.ds-credit-pill__value {
    color: var(--ink);
    font-variant-numeric: tabular-nums;
}
.ds-credit-pill__label {
    color: var(--ink-3);
    font-weight: 500;
}
/* ── 17.2 Section breathing room ───────────────────────────── */
/*  Apply .ds-section to top-level marketing/page sections to give
    them generous vertical rhythm. Pairs naturally with max-w-6xl. */
.ds-section { padding-top: 5rem; padding-bottom: 5rem; }
@media (min-width: 768px)  { .ds-section { padding-top: 6rem;   padding-bottom: 6rem; } }
@media (min-width: 1024px) { .ds-section { padding-top: 7.5rem; padding-bottom: 7.5rem; } }

/* ── 17.3 Eyebrow / kicker text above headlines ────────────── */
.ds-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.3rem 0.75rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-3);
    font-size: 0.7rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

/* ── 17.4 Hairline frame around hero gallery / sliders ─────── */
/*  The interface is a minimal frame — the image is the hero. */
/* ── 17.5 Smooth focus ring for accessibility ──────────────── */
.ds-focus-ring:focus-visible,
a:focus-visible, button:focus-visible {
    outline: none;
    box-shadow: 0 0 0 2px var(--bg-0), 0 0 0 4px var(--accent-ring);
    border-radius: inherit;
}

/* ── 17.6 Body type sets the calm muted gray per brief ─────── */
body.studio-body { color: var(--ink-2); }
body.studio-body p { color: var(--ink-3); }
body.studio-body h1,
body.studio-body h2,
body.studio-body h3 { color: var(--ink); letter-spacing: -0.02em; }

/* ── 17.7 Tailwind primary-utility overrides ─────────────────
 *  The Tailwind static build still ships compiled `bg-primary` /
 *  `text-primary` / `text-primary-foreground` utilities pointing at
 *  the legacy purple (#7c3aed). Since rebuilding Tailwind on the
 *  server isn't an option, we re-map them here to the new blue accent
 *  using CSS variables. This file loads AFTER app.css so these win. */
.bg-primary { background-color: var(--accent) !important; }
.bg-primary\/10 { background-color: rgba(79, 141, 255, 0.10) !important; }
.bg-primary\/20 { background-color: rgba(79, 141, 255, 0.20) !important; }
.bg-primary\/30 { background-color: rgba(79, 141, 255, 0.30) !important; }
.text-primary { color: var(--accent) !important; }
.text-primary-foreground { color: #FFFFFF !important; }
.ring-primary { --tw-ring-color: var(--accent) !important; }
.hover\:bg-primary:hover { background-color: var(--accent-hover) !important; }

/* ── 17.8 Hero grid (replaces Tailwind arbitrary value) ────────
 *  Tailwind classes like lg:grid-cols-[1fr_420px] only get compiled
 *  if they appear in source at JIT-build time. To avoid silent
 *  breakage when adding new arbitrary values, the hero grid is
 *  defined here as a stable class. */
.ds-hero-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 3rem;
    align-items: start;
}
@media (min-width: 1024px) {
    .ds-hero-grid {
        grid-template-columns: minmax(0, 1fr) 460px;
        gap: 4rem;
        align-items: start;
    }
}
@media (min-width: 1280px) {
    .ds-hero-grid {
        grid-template-columns: minmax(0, 1fr) 500px;
    }
}

/* ── 17.9 Stable spacing utilities (Tailwind-compile-proof) ────
 *  When the production Tailwind build can't be regenerated, any
 *  utility class that wasn't present at JIT-build time will be
 *  silently dropped from the final CSS. To stay independent of
 *  the build pipeline, the spacing primitives below are defined
 *  here as plain CSS — they cannot be lost. */

/*  Inline flex with gap — used inside hero meta strip, badges,
    chips, anywhere where a Lucide icon sits next to a label. */
.ds-inline-row {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;        /* 8px between icon and label */
    white-space: nowrap;
}

/*  Horizontal stack with consistent column gap and row wrap. */
.ds-hstack {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 1.5rem;  /* 24px between items */
    row-gap: 0.5rem;     /* 8px when wrapped */
}

.ds-hstack--tight   { column-gap: 0.75rem; row-gap: 0.5rem; }
/*  Vertical stack with consistent gap (replaces fragile space-y / gap-N). */
.ds-vstack          { display: flex; flex-direction: column; gap: 1.5rem; }
.ds-vstack--xl      { gap: 3.5rem; }
/*  Buttons: any direct-child icon gets natural breathing room
    so we never have to remember an explicit gap class on the
    button itself. */
.ds-btn > svg,
.ds-btn > i,
.ds-btn > [data-lucide] {
    margin-inline-start: 0.5rem;
}
.ds-btn > svg:first-child,
.ds-btn > i:first-child,
.ds-btn > [data-lucide]:first-child {
    margin-inline-start: 0;
    margin-inline-end: 0.5rem;
}

/*  Hero grid: bullets need air between rows on desktop. */
.ds-hero-bullets {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.75rem 2rem;
}

/* ── "How it works" — compact version inside the hero left column ── */
.ds-mini-steps {
    display: flex;
    flex-direction: column;
    gap: 0.625rem;
    margin: 0;
    padding: 0;
    list-style: none;
}
.ds-mini-steps > li {
    display: flex;
    align-items: flex-start;
    gap: 0.625rem;
    font-size: 0.85rem;
    color: var(--ink-3, #b9c0cc);
}
.ds-mini-steps .ds-mini-num { margin-top: 1px; }
.ds-mini-num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.4rem;
    height: 1.4rem;
    flex-shrink: 0;
    border-radius: 9999px;
    background: color-mix(in srgb, var(--accent, #4F8DFF) 18%, transparent);
    color: var(--accent, #4F8DFF);
    font-size: 0.72rem;
    font-weight: 700;
}
.ds-mini-step-video {
    color: var(--accent, #4F8DFF);
    font-weight: 600;
}

/* ── Slider prev/next round buttons ─────────────────────────── */
.bas-nav-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.75rem;
    height: 2rem;
    border-radius: 9999px;
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-2, #e5e7eb);
    cursor: pointer;
    transition: background .15s ease, border-color .15s ease, color .15s ease, transform .1s ease;
}
@media (max-width: 640px) {
    .bas-nav-btn { width: 3.25rem; height: 2.5rem; }
}
.bas-nav-btn:hover {
    background: color-mix(in srgb, var(--accent, #4F8DFF) 16%, var(--card));
    border-color: color-mix(in srgb, var(--accent, #4F8DFF) 50%, transparent);
    color: #fff;
}
.bas-nav-btn:active { transform: scale(0.92); }

/* ── Animated CTA arrow (Start creating) ────────────────────── */
@keyframes ds-arrow-nudge {
    0%, 100% { transform: translateX(0); }
    50%      { transform: translateX(4px); }
}
.ds-cta-arrow {
    animation: ds-arrow-nudge 1.3s ease-in-out infinite;
    transition: transform 0.15s ease;
}
.ds-btn:hover .ds-cta-arrow {
    animation: none;
    transform: translateX(5px);
}
@media (prefers-reduced-motion: reduce) {
    .ds-cta-arrow { animation: none; }
}
@media (min-width: 640px) {
    .ds-hero-bullets { grid-template-columns: 1fr 1fr; }
}
.ds-hero-bullets > li {
    display: inline-flex;
    align-items: flex-start;
    gap: 0.625rem;
}

/* ── 17.10 CTA pair — mobile-asymmetric, desktop auto-width ────
 *  Used for hero/landing CTAs where two buttons sit side-by-side.
 *  On mobile the primary CTA gets more width (60%) than the
 *  secondary (40%) so the main label never gets clipped. On
 *  ≥640px both snap back to natural width. */
.ds-cta-pair {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.625rem;
    width: 100%;
}
.ds-cta-pair > .ds-btn {
    min-width: 0;
    width: 100%;
    justify-content: center;
    padding-left: 1.25rem;
    padding-right: 1.25rem;
    white-space: nowrap;
}
@media (min-width: 640px) {
    .ds-cta-pair {
        flex-direction: row;
        flex-wrap: wrap;
        width: auto;
        gap: 0.75rem;
    }
    .ds-cta-pair > .ds-btn {
        flex: 0 0 auto;
        width: auto;
        padding-left: 1.75rem;
        padding-right: 1.75rem;
    }
}

/* ── 17.11 Soften legacy harsh-bordered cards ──────────────────
 *  Some partials (and content blocks added through CMS/lang files
 *  that are not in version control) use opaque black backgrounds
 *  with strong white borders like border-white/15 or border-white/20.
 *  Those clash with the hairline aesthetic everywhere else on the
 *  site. We knock them down to the same hairline border + subtle
 *  card surface used by .ds-card / .studio-surface. */

.border-white\/15,
.border-white\/20,
.border-white\/25,
.border-white\/\[0\.15\],
.border-white\/\[0\.20\],
.border-white\/\[0\.25\] {
    border-color: var(--line) !important;
}
.border-white\/15:hover,
.border-white\/20:hover,
.border-white\/\[0\.15\]:hover,
.border-white\/\[0\.20\]:hover {
    border-color: var(--line-hover) !important;
}

/*  Solid-black card backgrounds → soften to our translucent surface
    so the cards sit on top of the page wash instead of looking like
    cutouts. Targets the most common Tailwind black-bg patterns. */
.bg-black,
.bg-black\/40,
.bg-black\/50,
.bg-black\/60,
.bg-black\/70,
.bg-\[\#000\],
.bg-\[\#000000\],
.bg-\[\#0a0a0a\],
.bg-\[\#0A0A0A\],
.bg-\[\#0a0a0d\] {
    background-color: var(--surface-2) !important;
}

/*  Light theme: above overrides should use the light-theme card token. */
html[data-theme="light"] .border-white\/15,
html[data-theme="light"] .border-white\/20,
html[data-theme="light"] .border-white\/25,
html[data-theme="light"] .border-white\/\[0\.15\],
html[data-theme="light"] .border-white\/\[0\.20\],
html[data-theme="light"] .border-white\/\[0\.25\] {
    border-color: var(--line) !important;
}
html[data-theme="light"] .bg-black,
html[data-theme="light"] .bg-black\/40,
html[data-theme="light"] .bg-black\/50,
html[data-theme="light"] .bg-black\/60,
html[data-theme="light"] .bg-black\/70 {
    background-color: var(--card) !important;
}

/* ── 17.12 Round icon button — matches credit pill aesthetic ───
 *  Used for header icons (bell, etc) so they sit harmoniously next
 *  to .ds-credit-pill. Same border + background + transitions, just
 *  square aspect ratio with a circular border-radius. */
.ds-icon-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;        /* 36px — matches credit-pill height */
    height: 2.25rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--card);
    color: var(--ink-2);
    transition: border-color var(--tr), background var(--tr), color var(--tr);
    text-decoration: none;
    flex-shrink: 0;
}
.ds-icon-btn:hover {
    border-color: var(--line-hover);
    background: var(--surface-2);
    color: var(--ink);
}

/* ── 17.13 Meta strip — icon + label rows under hero CTAs ──────
 *  On desktop: items sit on one row separated by spacing.
 *  On mobile: items stack vertically; the label inside each item
 *  is allowed to wrap so long sentences don't push the layout
 *  past the viewport. */
.ds-meta-strip {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    align-items: flex-start;
}
.ds-meta-strip > .ds-meta-item {
    display: inline-flex;
    align-items: flex-start;
    gap: 0.5rem;
    /* allow wrap inside; don't force nowrap like ds-inline-row does */
    white-space: normal;
}
.ds-meta-strip > .ds-meta-item > i,
.ds-meta-strip > .ds-meta-item > svg,
.ds-meta-strip > .ds-meta-item > [data-lucide] {
    flex-shrink: 0;
    margin-top: 2px;          /* baseline-align with first text line */
}
@media (min-width: 640px) {
    .ds-meta-strip {
        flex-direction: row;
        flex-wrap: wrap;
        column-gap: 1.75rem;
        row-gap: 0.5rem;
    }
}

/* ============================================================
 * Phase D2 hotfix v4 — Rail card dimensions
 *
 * Explicit width + height for rail cards. Replaces the earlier
 * `aspect-ratio` approach which had inconsistent results when
 * the flex item width was constrained only by min-width and the
 * card content was fully absolute-positioned.
 *
 * Card geometry chosen to closely match the picker-form preset
 * cards (240/280px wide on mobile/desktop) but at 3:4 portrait
 * instead of 1:1 square.
 *
 *   240w × 320h on mobile  (3:4)
 *   280w × 373h on >=640px (3:4)
 * ============================================================ */
.rail-card-link {
    flex: 0 0 auto;
    width: 240px;
    height: 320px;
}
@media (min-width: 640px) {
    .rail-card-link {
        width: 280px;
        height: 373px;
    }
}


/* ============================================================
   Product block (result page) — AI-detected name/description,
   editable. Marketplace post UI is added in a later step.
   ============================================================ */
.product-block {
    margin-top: 1.25rem;
    border-radius: 1.25rem;
    border: 1px solid rgba(255, 255, 255, 0.10);
    background: linear-gradient(180deg, rgba(255,255,255,0.05), rgba(255,255,255,0.015));
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    padding: 1.25rem 1.35rem;
}
.product-block-head {
    display: flex;
    align-items: flex-start;
    gap: .75rem;
    margin-bottom: 1rem;
}
.product-block-icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 2.25rem;
    height: 2.25rem;
    flex: 0 0 auto;
    border-radius: .75rem;
    background: var(--accent-soft);
    color: var(--accent);
}
.product-block-icon svg { width: 1.15rem; height: 1.15rem; }
.product-block-title {
    font-size: 1rem;
    font-weight: 600;
    color: #fff;
    letter-spacing: -0.01em;
}
.product-block-sub {
    margin-top: .15rem;
    font-size: .8rem;
    color: rgba(226, 232, 240, 0.7);
}
.product-field-label {
    display: block;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: rgba(148, 163, 184, 0.9);
    margin: .75rem 0 .35rem;
}
.product-input {
    width: 100%;
    border-radius: .85rem;
    border: 1px solid rgba(255, 255, 255, 0.10);
    background: rgba(0, 0, 0, 0.30);
    padding: .7rem .9rem;
    font-size: .9rem;
    color: #f1f5f9;
    outline: none;
    transition: border-color .2s ease, box-shadow .2s ease;
}
.product-input:focus {
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-ring);
}
.product-textarea { resize: vertical; min-height: 4.5rem; line-height: 1.5; }
.product-block-actions {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin-top: 1rem;
}
.product-save-status { font-size: .8rem; color: rgba(148, 163, 184, 0.9); }
.product-save-ok  { color: #4ade80; }
.product-save-err { color: #f87171; }

/* Product post controls (result page) */
.product-post-controls {
    display: flex;
    align-items: flex-end;
    gap: .75rem;
    margin-top: 1rem;
    flex-wrap: wrap;
}
.product-post-btn {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
    white-space: nowrap;
}
.product-post-cost {
    font-size: .72rem;
    font-weight: 600;
    padding: .1rem .45rem;
    border-radius: 999px;
    background: rgba(0,0,0,0.25);
}
.product-post-output {
    margin-top: 1rem;
    border-top: 1px solid rgba(255,255,255,0.08);
    padding-top: 1rem;
}
.product-post-output-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: .5rem;
}
.product-post-text {
    white-space: pre-wrap;
    word-break: break-word;
    font-family: inherit;
    font-size: .85rem;
    line-height: 1.6;
    color: #e2e8f0;
    background: rgba(0,0,0,0.30);
    border: 1px solid rgba(255,255,255,0.08);
    border-radius: .85rem;
    padding: .9rem 1rem;
    margin: 0;
    max-height: 30rem;
    overflow-y: auto;
}

/* Structured product post blocks */
.product-post-block { margin-top: 1.1rem; }
.product-post-block-head {
    display: flex; align-items: center; justify-content: space-between;
    gap: .75rem; margin-bottom: .4rem;
}
.product-copy-btn { padding: .25rem .6rem; font-size: .78rem; }
.product-specs { display: flex; flex-direction: column; gap: .4rem; }
.product-spec-row { display: flex; gap: .5rem; }
.product-spec-key { flex: 0 0 38%; }
.product-spec-val { flex: 1 1 auto; }
.product-info-card {
    border: 1px solid var(--accent-ring);
    background: var(--accent-soft);
    border-radius: .9rem;
    padding: .85rem 1rem;
}
.product-info-row {
    display: flex; justify-content: space-between; gap: 1rem;
    padding: .3rem 0; font-size: .85rem;
}
.product-info-key { color: rgba(226,232,240,0.75); }
.product-info-val { color: #fff; font-weight: 600; text-align: right; }
.product-info-price { color: var(--accent); font-size: 1rem; }
.product-info-note {
    margin-top: .5rem; font-size: .72rem; font-style: italic;
    color: rgba(226,232,240,0.6);
}

/* Collapsible generated-post cards */
.product-post-card {
    border: 1px solid rgba(255,255,255,0.10);
    border-radius: 1rem;
    background: rgba(0,0,0,0.20);
    margin-top: .75rem;
    overflow: hidden;
}
.product-post-card-head {
    display: flex;
    align-items: center;
    gap: .75rem;
    width: 100%;
    padding: .85rem 1rem;
    background: transparent;
    border: 0;
    cursor: pointer;
    text-align: left;
    color: #fff;
}
.product-post-card-head:hover { background: rgba(255,255,255,0.03); }
.product-post-badge {
    flex: 0 0 auto;
    font-size: .72rem;
    font-weight: 700;
    padding: .15rem .55rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    text-transform: uppercase;
    letter-spacing: .03em;
}
.product-post-card-title {
    flex: 1 1 auto;
    min-width: 0;
    font-size: .9rem;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    color: #e2e8f0;
}
.product-post-chevron {
    flex: 0 0 auto;
    width: 1.1rem; height: 1.1rem;
    color: rgba(226,232,240,0.6);
    transition: transform .2s ease;
}
.product-post-card.is-open .product-post-chevron { transform: rotate(180deg); }
.product-post-card-body {
    display: none;
    padding: 0 1rem 1rem;
}
.product-post-card.is-open .product-post-card-body { display: block; }
.product-post-body .product-post-block:first-child { margin-top: .25rem; }

/* Product export bar */
.product-export-bar {
    display: flex; align-items: center; gap: .5rem;
    flex-wrap: wrap;
    margin-top: 1rem;
    padding-top: 1rem;
    border-top: 1px solid rgba(255,255,255,0.08);
}
.product-export-label {
    font-size: .75rem; text-transform: uppercase; letter-spacing: .04em;
    color: rgba(148,163,184,0.9); margin-right: .25rem;
}


/* ══════════════════════════════════════════════════════════════
 * REPORT MODAL  (partials/report_style_modal.php)
 *
 * Built entirely from design tokens, so the light theme needs NO
 * override block of its own — the first version of this modal used
 * hardcoded darks (bg-[#141416], text-white, border-white/10) and
 * stayed black-on-black when the site was switched to light.
 *
 * The card uses --bg-1 rather than --card: --card is a translucent
 * white wash meant to sit on the page background, and over a dim
 * backdrop it reads as a grey ghost. --bg-1 is opaque in both themes.
 * ══════════════════════════════════════════════════════════════ */

/* Trigger — a bordered pill with a red flag, not a grey text link.
   It has to be findable by someone who arrived to report a photo of
   themselves, which the previous muted-slate wording was not. */
.report-trigger {
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    border-radius: var(--radius-full);
    border: 1px solid var(--danger-border);
    background: var(--danger-bg);
    padding: 0.375rem var(--sp-3);
    font-size: var(--text-xs);
    font-weight: 500;
    color: var(--ink-2);
    transition: border-color var(--tr), background-color var(--tr), color var(--tr);
    cursor: pointer;
}

.report-trigger:hover {
    border-color: var(--danger);
    background: var(--danger-bg);
    color: var(--ink);
}

.report-trigger svg,
.report-trigger i {
    color: var(--danger);
    flex-shrink: 0;
}

.report-modal-backdrop {
    position: fixed;
    inset: 0;
    z-index: 9999;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    background: rgba(0, 0, 0, 0.6);
    backdrop-filter: blur(2px);
    -webkit-backdrop-filter: blur(2px);
}

.report-modal-card {
    width: 100%;
    max-width: 28rem;
    border-radius: var(--radius-2xl);
    border: 1px solid var(--line);
    background: var(--bg-1);
    padding: var(--sp-6);
    box-shadow: var(--shadow-lg);
    color: var(--ink-dim);
}

.report-modal-title {
    font-size: var(--text-lg);
    font-weight: 600;
    color: var(--ink);
}

.report-modal-sub {
    margin-top: var(--sp-1);
    font-size: var(--text-sm);
    color: var(--muted);
}

.report-modal-reasons {
    margin-top: var(--sp-4);
    display: flex;
    flex-direction: column;
    gap: var(--sp-2);
}

.report-reason {
    display: flex;
    align-items: center;
    gap: 0.625rem;
    border-radius: var(--radius-xl);
    border: 1px solid var(--line);
    background: var(--card);
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--text-sm);
    color: var(--ink-2);
    cursor: pointer;
    transition: border-color var(--tr), background-color var(--tr);
}

.report-reason:hover { border-color: var(--line-hover); }

.report-reason:has(:checked) {
    border-color: var(--danger-border);
    background: var(--danger-bg);
    color: var(--ink);
}

.report-reason input { accent-color: var(--danger); }

.report-modal-note {
    margin-top: var(--sp-3);
    width: 100%;
    border-radius: var(--radius-xl);
    border: 1px solid var(--line);
    background: var(--card);
    padding: var(--sp-2) var(--sp-3);
    font-size: var(--text-sm);
    font-family: inherit;
    color: var(--ink-dim);
    outline: none;
    resize: vertical;
    transition: border-color var(--tr);
}

.report-modal-note:focus { border-color: var(--line-focus); }
.report-modal-note::placeholder { color: var(--muted-2); }

.report-modal-actions {
    margin-top: var(--sp-5);
    display: flex;
    justify-content: flex-end;
    gap: var(--sp-2);
}

.report-modal-cancel {
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: transparent;
    padding: var(--sp-2) var(--sp-4);
    font-size: var(--text-sm);
    color: var(--ink-3);
    cursor: pointer;
    transition: border-color var(--tr), background-color var(--tr);
}

.report-modal-cancel:hover {
    border-color: var(--line-hover);
    background: var(--card);
}

.report-modal-submit {
    border-radius: var(--radius-full);
    border: 1px solid transparent;
    background: var(--danger);
    padding: var(--sp-2) var(--sp-4);
    font-size: var(--text-sm);
    font-weight: 500;
    color: #ffffff;
    cursor: pointer;
    transition: opacity var(--tr), filter var(--tr);
}

.report-modal-submit:hover:not(:disabled) { filter: brightness(1.08); }
.report-modal-submit:disabled { opacity: 0.5; cursor: not-allowed; }

.report-modal-done { text-align: center; }
.report-modal-done-icon { margin: 0 auto; color: var(--success); }

/* ── History card: generation state ──────────────────────────────
   Colours for the placeholder shown where a preview would be, per
   generation state (see edit_job_display_state()).

   These live here rather than as utility classes on purpose. Tailwind's
   output (public/assets/app.css) is a build artifact that is NOT
   regenerated on deploy, so a class the build has never seen silently
   does not exist — which is how the first version of this shipped with
   no background on the video caption at all. design-system.css is
   hand-written and shipped as-is, and it gets a light-theme layer for
   free.

   Dark values match the status dots already used in the card
   (#FBBF24 / #F87171), so the placeholder and the dot agree. */
.history-state-pending { color: #FBBF24; }
.history-state-failed  { color: #F87171; }
.history-state-expired { color: var(--muted); }
/* NOT --muted-3: on the #121212 card that is 2.4:1, i.e. decorative
   rather than readable. #8E8E96 gives 5.8:1 and still reads as
   secondary next to the label above it. */
.history-state-hint    { color: #8E8E96; }

/* The label is the thing a user reads at a glance; keep it from
   inheriting the card's smaller muted treatment. */
.history-state-label { font-size: var(--text-xs, 0.75rem); font-weight: 500; }

/* ── Mobile navigation panel ─────────────────────────────────────
   The panel is a child of the STICKY header, so where it lands
   depends on the header still being stuck. When that failed on a
   scrolled page the panel rendered off-screen while the body scroll
   lock was already applied: nothing visible, nothing scrollable, and
   — because the only ways to close it were a tap on a link inside it,
   Escape, or the burger itself — nothing tappable either. A reload was
   the only way out.

   Pinning it to the viewport removes the dependency: --mobile-nav-top
   is set from JS to the bar's bottom edge and CLAMPED to >= 0, so even
   if the header is not where it should be, the panel is on screen.

   No `display` here: the open/closed state stays with the .hidden
   utility the markup and JS already use. */
@media (max-width: 767px) {
    .mobile-nav-panel {
        position: fixed;
        top: var(--mobile-nav-top, 64px);
        left: 0;
        right: 0;
        z-index: 250;
        max-height: calc(100vh - var(--mobile-nav-top, 64px));
        max-height: calc(100dvh - var(--mobile-nav-top, 64px));
        overflow-y: auto;
        -webkit-overflow-scrolling: touch;
        background: rgba(10, 10, 10, 0.97);
        border-bottom: 1px solid var(--line);
    }

    /* Tap anywhere outside to close — the recovery path that did not
       exist. Sits under the panel, over everything else. */
    .mobile-nav-backdrop {
        position: fixed;
        inset: 0;
        z-index: 240;
        background: rgba(0, 0, 0, 0.55);
    }
}

/* ── Captcha status ──────────────────────────────────────────────
   Turnstile runs invisibly, so the only sign anything was happening
   was a line of text that silently vanished on success — easy to miss
   on the auth pages, and on the admin login there was no feedback at
   all. This gives the wait a moving indicator and makes the outcome
   explicit.

   Hand-written rather than utility classes: public/assets/app.css is a
   build artifact that is not regenerated on deploy, so new Tailwind
   classes would not exist in the shipped CSS. */
.captcha-status {
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.875rem;
    font-weight: 500;
    color: var(--muted);
}

.captcha-status-icon { display: inline-flex; flex-shrink: 0; }
.captcha-status-icon svg { width: 1rem; height: 1rem; }

/* The pending state renders no icon at all — the dots are the only
   moving thing. An empty slot must not leave a flex gap behind. */
.captcha-status-icon:empty { display: none; }

/* The three dots. aria-hidden in the markup — the state text already
   carries the meaning for a screen reader, and a ticking ellipsis
   announced repeatedly would be hostile. */
.captcha-dots { display: inline-flex; gap: 0.15rem; }
.captcha-dots span {
    width: 0.25rem;
    height: 0.25rem;
    border-radius: 9999px;
    background: currentColor;
    opacity: 0.25;
    animation: captcha-dot 1.2s ease-in-out infinite;
}
.captcha-dots span:nth-child(2) { animation-delay: 0.18s; }
.captcha-dots span:nth-child(3) { animation-delay: 0.36s; }
@keyframes captcha-dot {
    0%, 60%, 100% { opacity: 0.25; transform: translateY(0); }
    30%           { opacity: 1;    transform: translateY(-0.15rem); }
}

.captcha-status[data-state="done"]    { color: var(--success); }
.captcha-status[data-state="error"],
.captcha-status[data-state="expired"] { color: var(--danger); }
.captcha-status[data-state="done"] .captcha-dots,
.captcha-status[data-state="error"] .captcha-dots,
.captcha-status[data-state="expired"] .captcha-dots { display: none; }

/* Respect a user who has asked for less motion: keep the state, drop
   the movement. */
@media (prefers-reduced-motion: reduce) {
    .captcha-dots span { animation: none; }
    .captcha-dots span { opacity: 0.6; }
}

/* ── History gallery card ────────────────────────────────────────
   The card used to be a boxed panel: image on top, then a title, a
   divider and a row of buttons — roughly half its height was chrome.
   This turns it into a gallery tile where the image IS the card and
   everything else appears over it on demand.

   Nothing about the ACTIONS changed: history-page.js still finds them
   by their data-history-action / .delete-job-btn hooks inside
   [data-history-card]. Only where they sit moved. */
/* Masonry, second attempt — and this time both reasons it was dropped are
   answered rather than argued with.

   1. "The thumbnails it needs to vary are all the same shape." Generated
      results now keep their own proportions (ThumbnailShaper::SHAPE_FIT), and
      the ratio is stored on the row, so the layout knows every tile's shape
      before a single image has loaded.
   2. "Column flow reads top-to-bottom, which fights newest-first." That was
      CSS `columns`, and it is exactly why new generations used to pile up in
      the LEFT column. This is not CSS columns: history-page.js walks the
      cards in DOM order and drops each into the shortest column, so the first
      few fill the TOP row and new work appears where it should.

   The plain grid below stays as the no-JS layout — the masonry pass only adds
   .is-masonry. Do not put anything in .is-masonry that the fallback needs. */
.history-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(var(--masonry-col), 1fr));
    gap: var(--masonry-gap);
    align-items: start;
    /* Read by history-page.js, so the breakpoint lives here in CSS and the
       layouter has no numbers of its own. */
    --masonry-col: 200px;
    --masonry-gap: 0.75rem;
}

/* ── Filter bar ────────────────────────────────────────────────────
   Three dropdowns, one per filter dimension. They replaced the month group
   headings (which answered only "when", and chopped the masonry into short
   blocks with ragged bottom edges), and then a row of chips plus a native
   <select>, which stood four rows and 233px tall on a phone.

   <details>/<summary> rather than a scripted popup: the browser already knows
   how to open it, so the control works with JavaScript disabled and needs no
   focus management. history-page.js only adds closing on an outside click and
   on Escape, which the element does not do by itself.

   A native <select> could not have carried the icons — option elements render
   text only. That is the whole reason this is a custom control.

   Hand-written rather than Tailwind utilities: public/assets/app.css is a build
   artifact and is not regenerated on deploy, so a class the build has never
   seen does not exist. This also fixed the chips' active state, which was
   `text-white` — light theme overrides that to near-black with !important, so
   active and idle measured an identical 14.34:1 and the only signal left was a
   15% wash. */
.history-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-2); }

.history-drop { position: relative; }

.history-drop__btn {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.5rem 0.9rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--bg-1);
    font-family: var(--font-sans);
    font-size: var(--text-sm); font-weight: 500;
    color: var(--ink-3);
    cursor: pointer; white-space: nowrap;
    /* summary defaults to display:list-item; both of these are needed to be
       rid of the disclosure triangle across browsers. */
    list-style: none;
    transition: border-color var(--tr), background var(--tr), color var(--tr);
}
.history-drop__btn::-webkit-details-marker { display: none; }
.history-drop__btn:hover { border-color: var(--line-hover); color: var(--ink); }
.history-drop__btn:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 1px; }
.history-drop__btn i, .history-drop__btn svg { width: 1rem; height: 1rem; flex-shrink: 0; }
/* The dimension's name is the quiet half; the chosen value is the loud one. */
.history-drop__title { color: var(--muted); }
.history-drop__value { color: var(--ink); font-weight: 600; }
.history-drop__count {
    font-size: var(--text-xs); opacity: 0.65;
    font-variant-numeric: tabular-nums;
}
.history-drop__caret { opacity: 0.6; transition: transform var(--tr); }
.history-drop[open] .history-drop__caret { transform: rotate(180deg); }

/* A dimension that is actually filtering says so without being opened. */
.history-drop.is-filtered .history-drop__btn {
    border-color: var(--accent-ring);
    background: var(--accent-soft);
    color: var(--ink);
}
.history-drop.is-filtered .history-drop__btn > i:first-child { color: var(--accent); }

.history-drop__menu {
    position: absolute;
    top: calc(100% + 0.4rem);
    /* Logical, so the menu hangs off the correct edge in the RTL locale. */
    inset-inline-start: 0;
    z-index: 30;
    min-width: 100%;
    max-height: 60vh; overflow-y: auto;
    padding: 0.35rem;
    display: flex; flex-direction: column; gap: 0.15rem;
    border-radius: var(--radius-lg);
    border: 1px solid var(--line);
    background: var(--bg-1);
    box-shadow: var(--shadow-lg);
}
.history-drop__opt {
    display: flex; align-items: center; gap: 0.5rem;
    padding: 0.45rem 0.6rem;
    border-radius: var(--radius-md);
    font-size: var(--text-sm); font-weight: 500;
    color: var(--ink-3); text-decoration: none; white-space: nowrap;
    transition: background var(--tr), color var(--tr);
}
.history-drop__opt:hover { background: var(--card); color: var(--ink); }
.history-drop__opt i, .history-drop__opt svg { width: 0.95rem; height: 0.95rem; flex-shrink: 0; opacity: 0.7; }
.history-drop__opt > span:first-of-type { flex: 1 1 auto; }
.history-drop__opt.is-active { color: var(--ink); font-weight: 600; background: var(--accent-soft); }
.history-drop__opt.is-active i, .history-drop__opt.is-active svg { color: var(--accent); opacity: 1; }

/* ── "Show more" ───────────────────────────────────────────────────
   Replaces the page-by-page nav. The link is real (?page=N+1), so it works
   with no JS; history-page.js intercepts it and appends instead. */
.history-more {
    display: flex; flex-direction: column; align-items: center; gap: var(--sp-2);
    padding-top: var(--sp-2);
}
.history-more__btn {
    display: inline-flex; align-items: center; gap: 0.45rem;
    padding: 0.6rem 1.4rem;
    border-radius: var(--radius-full);
    border: 1px solid var(--line);
    background: var(--bg-1);
    font-family: var(--font-sans);
    font-size: var(--text-sm); font-weight: 600;
    color: var(--ink); text-decoration: none; cursor: pointer;
    transition: border-color var(--tr), background var(--tr);
}
.history-more__btn:hover { border-color: var(--line-hover); background: var(--card); }
.history-more__btn i, .history-more__btn svg { width: 1rem; height: 1rem; }
/* Spins the chevron while the fetch is in flight. The class is added and
   removed by the click handler, so it needs no state of its own. */
.history-more__btn.is-loading { pointer-events: none; opacity: 0.7; }
.history-more__btn.is-loading i, .history-more__btn.is-loading svg { animation: spin 0.9s linear infinite; }
.history-more__count {
    font-size: var(--text-xs); color: var(--muted);
    font-variant-numeric: tabular-nums;
}

/* Two columns on a phone rather than one: at 200px a 343px-wide content
   column fits exactly one tile, which turns the gallery into a single file of
   huge cards. */
@media (max-width: 640px) {
    .history-grid { --masonry-col: 150px; --masonry-gap: 0.5rem; }

    /* One line at 375px: each button shows the ONE half that carries meaning.
       A dimension that is filtering shows its value ("Photo · 14") and drops
       its name; a dimension that is not shows its name ("Date") and drops the
       value, which would only have said "All". Both halves at once is 371px of
       button in a 327px row; this is 306px.

       Note this row deliberately does NOT scroll horizontally the way the chips
       it replaced did — a scroll container would clip the open menus.

       Every selector here is scoped to .history-drop__btn on purpose. The
       count element appears on the button AND on each row of the menu, so an
       unscoped rule hid the numbers inside the menu too — which is what made
       a phone look like it had no counts until something was filtered. */
    .history-drop.is-filtered .history-drop__btn .history-drop__title { display: none; }
    .history-drop:not(.is-filtered) .history-drop__btn .history-drop__value,
    .history-drop:not(.is-filtered) .history-drop__btn .history-drop__count { display: none; }
    .history-drop__btn { padding: 0.5rem 0.7rem; gap: 0.35rem; }

    /* Menus span the whole filter bar instead of hanging off their own button.
       Anchoring the last one to the right edge was the first attempt and is
       wrong as soon as that button wraps to the start of a second row: its
       right edge is then near the left of the screen, and the menu opened at
       left: -29px. Full width cannot go off either side, needs no knowledge of
       where the button ended up, and is what a phone expects anyway.

       Safe because only one menu is ever open — history-page.js closes the
       others — so they cannot overlap each other. */
    .history-filters { position: relative; }
    .history-drop { position: static; }
    .history-drop__menu { inset-inline: 0; top: calc(100% + 0.4rem); }
}

/* Absolutely positioned tiles, placed by JS. Order in the DOM stays
   chronological — only where each tile SITS moves, which keeps reading and
   tab order correct. */
.history-grid.is-masonry {
    display: block;
    position: relative;
}
.history-grid.is-masonry .history-card {
    position: absolute;
    top: 0;
    left: 0;
    margin: 0;
}

.history-card {
    position: relative;
    overflow: hidden;
    border-radius: 1rem;
    background: var(--bg-1);
    border: 1px solid var(--line);
}

.history-card-media {
    display: block;
    position: relative;
    width: 100%;
    /* The tile's own proportions, set per card by the view from
       result_width/result_height. 3:4 is the fallback shape for videos and for
       rows that predate those columns. */
    aspect-ratio: var(--ar, 3 / 4);
    background: var(--bg-2, rgba(127, 127, 127, 0.06));
    overflow: hidden;
}

.history-card-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    /* 0.2s, not the 0.5s this started at — see the note on .preset-zoom-img.
       Firefox stretches the existing raster of a transformed layer instead of
       redrawing the image, which shimmers on a photo; the shorter the zoom
       runs, the less of it there is to see. No promotion here on purpose:
       will-change and translateZ were both measured at this exact card width
       and changed nothing, so they were only costing layer memory. */
    transition: transform 0.2s ease-out;
}

/* Only where a pointer can actually hover — on a touch screen this
   would stick after a tap. */
@media (hover: hover) {
    .history-card:hover .history-card-media img { transform: scale(1.04); }
}

/* Touch gets the same zoom, driven by .is-open rather than :hover.
   The distinction matters: a tap on a style card navigates away, so the
   emulated :hover there only flashes before the page changes — but a tap
   here does NOT navigate (history-page.js calls preventDefault and reveals
   the overlay instead), so an emulated :hover would sit zoomed until the
   user tapped somewhere else. Hanging the transform off the class the JS
   already toggles gives the movement without the stuck state: tapping away
   removes .is-open and the image settles back. */
.history-card.is-open .history-card-media img { transform: scale(1.04); }

/* Always-on markers, kept small so the image stays the subject. */
.history-card-badge {
    position: absolute;
    top: 0.5rem;
    right: 0.5rem;
    z-index: 3;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
    padding: 0.2rem 0.4rem;
    border-radius: 0.5rem;
    background: rgba(0, 0, 0, 0.55);
    color: #fff;
    /* The blur stays. It was removed for a while on the theory that it cost
       a re-filter per frame while the image behind it zoomed — plausible,
       but measurement said otherwise: the zoom drops zero frames out of 53
       either way. It was a design change made to fix nothing. */
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
}
.history-card-badge svg { width: 0.8rem; height: 0.8rem; }

.history-card-dot {
    position: absolute;
    bottom: 0.55rem;
    right: 0.55rem;
    z-index: 3;
    width: 0.5rem;
    height: 0.5rem;
    border-radius: 9999px;
    box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.6);
}

/* The play glyph for a finished video. */
.history-card-play {
    position: absolute;
    inset: 0;
    z-index: 2;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
}
.history-card-play span {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 3rem;
    height: 3rem;
    border-radius: 9999px;
    /* Opaque enough to read without a backdrop blur — and this element
       scales on hover, so a blur here would cost a re-filter every frame
       for the same reason it was dropped from the overlay. The static
       badge above keeps its blur precisely because it never moves. */
    background: rgba(0, 0, 0, 0.55);
    border: 1px solid rgba(255, 255, 255, 0.4);
    /* Same reasoning as the image: no promotion inside a rounded, clipped
       container. */
    transition: transform 0.3s ease;
}
@media (hover: hover) {
    .history-card:hover .history-card-play span { transform: scale(1.1); }
}
/* Same on tap, for the same reason as the image above — the glyph sits in
   the middle of an image that is now zooming, and leaving it the one static
   thing on the card reads as a missed frame rather than a choice. */
.history-card.is-open .history-card-play span { transform: scale(1.1); }
.history-card-play svg { width: 1.4rem; height: 1.4rem; color: #fff; }

/* ── The overlay ─────────────────────────────────────────────────
   Sits over the photo in BOTH themes, so it is deliberately dark in
   both and needs no light-theme layer — the only part of this design
   that gets that for free. */
.history-card-overlay {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 4;
    padding: 0.75rem;
    display: flex;
    flex-direction: column;
    gap: 0.55rem;
    color: #fff;
    /* The title sits at the TOP of the overlay, which is exactly where the
       old gradient had already faded out — on a bright photo it turned
       grey and nearly vanished. The dark band now covers the whole
       overlay and only fades in its last fifth, with a blur behind it so
       readability does not depend on what the photo happens to look like. */
    background: linear-gradient(to top,
        rgba(0, 0, 0, 0.95) 0%,
        rgba(0, 0, 0, 0.92) 60%,
        rgba(0, 0, 0, 0.86) 88%,
        rgba(0, 0, 0, 0) 100%);
    /* No backdrop-filter, and that one is purely a look decision: the
       gradient already sits at 0.86-0.95 alpha, so a blur behind it would
       not show. It is NOT a performance measure — an earlier note here
       claimed it was, and the frame counts say there was nothing to save. */
    transform: translateY(101%);
    opacity: 0;
    /* No will-change either. It was here to "promote up front", from the
       same round of guesses as the image zoom above; measuring the hover
       showed zero dropped frames out of 53 with or without it, so all it
       bought was a compositor layer per card. */
    transition: transform 0.26s cubic-bezier(0.22, 0.61, 0.36, 1),
                opacity 0.2s ease-out;
}

@media (hover: hover) {
    .history-card:hover .history-card-overlay,
    .history-card:focus-within .history-card-overlay {
        transform: translateY(0);
        opacity: 1;
    }
}

/* Touch: revealed by a tap, since there is no hover to rely on. */
.history-card.is-open .history-card-overlay {
    transform: translateY(0);
    opacity: 1;
}

/* Keyboard users must reach the actions without a pointer. */
.history-card-overlay:focus-within { transform: translateY(0); opacity: 1; }

/* Everything inside the overlay is white, stated once and with enough
   specificity to stay that way. The overlay always sits on a dark
   gradient over a photo, in BOTH themes, so nothing in it should ever
   pick up a theme text colour — and twice now a global rule has reached
   in and done exactly that. A blanket rule here is cheaper than
   discovering the next one element at a time. */
.history-card-overlay,
.history-card-overlay p,
.history-card-overlay a,
.history-card-overlay span,
.history-card-overlay button { color: #fff; }

/* The share link keeps its green, being a deliberate signal. */
.history-card-overlay .history-card-link-row a { color: #6EE7B7; }

/* Scoped through the overlay ON PURPOSE. The global `body.studio-body p`
   rule sets color: var(--ink-3) and its specificity (0,0,1,2) beats a bare
   class (0,0,1,0), so .history-card-title alone never won: the title came
   out grey on dark and near-black on light — over a dark overlay in both
   cases. Two classes (0,0,2,0) take precedence over one class plus two
   elements. This is what the text actually needed; strengthening the
   gradient earlier had only been compensating for it. */
.history-card-overlay .history-card-title {
    margin: 0;
    color: #fff;
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
    font-size: 0.82rem;
    font-weight: 600;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.history-card-overlay .history-card-meta {
    margin: 0;
    font-size: 0.68rem;
    color: rgba(255, 255, 255, 0.88);
    text-shadow: 0 1px 3px rgba(0, 0, 0, 0.85);
}

/* Someone who has asked for less motion gets the states, not the movement.
   The .is-open pairs have to be listed too, not just the :hover ones —
   `transition: none` alone would leave the transform applying INSTANTLY on
   tap, which is a snap rather than an absence of motion, and worse than the
   animation it was meant to remove. */
@media (prefers-reduced-motion: reduce) {
    .history-card-media img,
    .history-card-overlay,
    .history-card-play span { transition: none; }
    .history-card:hover .history-card-media img,
    .history-card.is-open .history-card-media img { transform: none; }
    .history-card:hover .history-card-play span,
    .history-card.is-open .history-card-play span { transform: none; }
}

.history-card-actions {
    display: flex;
    align-items: center;
    gap: 0.45rem;
}

/* app.css is loaded BEFORE this file and .hidden{display:none} has the
   same specificity as the display rules below, so the later ones were
   winning: an unshared generation still showed the green "shared"
   button and its link row, making everything look public. Two classes
   beat one, and this sits after them either way. */
.history-card-btn.hidden,
.history-card-view.hidden,
.history-card-link-row.hidden,
.history-card-actions.hidden { display: none; }

.history-card-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.9rem;
    height: 1.9rem;
    border-radius: 9999px;
    border: 1px solid rgba(255, 255, 255, 0.18);
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    transition: background 0.15s ease, color 0.15s ease;
}
.history-card-btn:hover { background: rgba(255, 255, 255, 0.24); }
.history-card-btn svg { width: 0.85rem; height: 0.85rem; }
.history-card-btn-danger:hover { background: rgba(248, 113, 113, 0.28); }
.history-card-btn-shared { background: rgba(52, 211, 153, 0.22); color: #6EE7B7; }

.history-card-view {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
    /* Its own row above the icons rather than crammed in beside them. */
    align-self: stretch;
    padding: 0.4rem 0.7rem;
    border-radius: 9999px;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    font-size: 0.72rem;
    font-weight: 600;
}
.history-card-view:hover { background: rgba(255, 255, 255, 0.28); }
.history-card-view svg { width: 0.8rem; height: 0.8rem; }

.history-card-link-row {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    padding: 0.3rem 0.45rem;
    border-radius: 0.6rem;
    background: rgba(52, 211, 153, 0.16);
    border: 1px solid rgba(52, 211, 153, 0.3);
    font-size: 0.62rem;
}
.history-card-link-row a { color: #6EE7B7; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Smaller copy button, to fit the link row's own line height. */
.history-card-btn--xs { width: 1.4rem; height: 1.4rem; }

/* ── Short tiles: the overlay collapses to two lines ───────────────
   With tiles taking their own proportions, a wide result is a short tile: at
   a 200px column a 2:1 image is 100px tall, and the full overlay needs ~150px
   for title, meta, the "view" link, the action row and the public-link row.
   Rather than crop those tiles harder, the overlay drops to the title on one
   line and every button on the next — about 67px, which fits anything the
   aspect clamp can produce.

   It was ONE line at first, title and buttons side by side, and that clipped
   the last button on a phone: a 163px column leaves 147px of content, and
   five 30px buttons with their gaps are 176px. Buttons get their own full
   width now, and the overlay wraps rather than clips if it ever runs out
   anyway.

   `display: contents` on the link row is what keeps the COPY button: the row
   stops being a box and its children join the overlay's flex line directly,
   so the button lands beside the action row while the icon and the long URL —
   the parts that need a line of their own — are hidden. Nothing is moved in
   the DOM, and no action is lost.

   history-page.js sets .is-short by comparing the FULL overlay height with
   the tile's. */
.history-card.is-short .history-card-overlay {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.5rem;
}
/* The title claims the whole first line, which is what pushes the buttons on
   to the second one. */
.history-card.is-short .history-card-overlay > div:first-child { flex: 1 1 100%; min-width: 0; }
.history-card.is-short .history-card-title {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.history-card.is-short .history-card-meta { display: none; }
/* The "view" link is dropped on a short tile. It was kept for a round as an
   icon-only pill, on the argument that a tile needs a VISIBLE way through —
   but tapping the image twice is that way on every tile, and this pill was a
   fifth button in a line with room for four. Dropping it also buys back 36px
   of width for the ones that do something the image cannot. */
.history-card.is-short .history-card-view { display: none; }
.history-card.is-short .history-card-link-row {
    display: contents;
    /* The box's own padding/border/background go with it. */
}
.history-card.is-short .history-card-link-row > i,
.history-card.is-short .history-card-link-row > a { display: none; }
/* Promoted into the row, the copy button matches the other buttons — and
   nothing in the row may shrink: as a flex item with the default shrink it
   measured 15px wide against its neighbours' 30px. */
.history-card.is-short .history-card-btn,
.history-card.is-short .history-card-btn--xs { width: 1.9rem; height: 1.9rem; flex: 0 0 auto; }
/* Insurance for the narrowest tile and for whatever action gets added next:
   the buttons wrap to another line instead of being clipped by the card. */
.history-card.is-short .history-card-actions { flex-wrap: wrap; }

/* Tiniest tiles: actions only. Even the two-line overlay covers 74% of a 16:9
   tile on a phone and 82% of the widest one, so the title line goes as well —
   see the second pass in history-page.js. The image is its own identity at
   this size; what it cannot do is share, download or delete itself. */
.history-card.is-tiny .history-card-overlay > div:first-child { display: none; }
.history-card.is-tiny .history-card-overlay { padding: 0.35rem 0.4rem; }
/* .hidden must keep winning over `display: contents` — the row is hidden
   outright while the result is private. app.css loads first, so spell it out
   here (the same reason .res-panel.is-hidden is spelled out). */
.history-card.is-short .history-card-link-row.hidden { display: none; }

/* ══════════════════════════════════════════════════════════════
 *  Generation workspace — styles/show.php + styles/custom_show.php
 * ══════════════════════════════════════════════════════════════
 *  Полотно ліворуч, рейка керування праворуч. Замінює двоколонкову
 *  сітку, де прев'ю стилю було карткою на 520 px у смузі max-w-6xl,
 *  а форма — довгим стосом із дев'яти обведених блоків.
 *
 *  Розмітка: app/Views/partials/generation_form.php.
 *
 *  УВАГА про .hidden: app.css вантажиться ПЕРЕД цим файлом, і
 *  `.hidden{display:none}` має ту саму специфічність, що й правила
 *  нижче — тож без явних `.gen-x.hidden` пізніші правила звідси
 *  мовчки перемагають, і приховані елементи лишаються видимими.
 *  Ми вже на це наступали в картках історії. */

.gen-page { padding: var(--sp-6) 0 var(--sp-10); }
.gen-page__bar {
    max-width: 1152px;
    margin: 0 auto var(--sp-4);
    padding: 0 var(--sp-6);
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    flex-wrap: wrap;
}
.gen-page__back {
    display: inline-flex; align-items: center; gap: var(--sp-2);
    font-size: var(--text-sm); color: var(--ink-3); text-decoration: none;
    transition: color var(--tr);
}
.gen-page__back:hover { color: var(--ink); }
.gen-page__back svg { width: 1rem; height: 1rem; }
.gen-page__bar-right { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }

.gen-plan-pill {
    display: inline-flex; align-items: center; gap: 0.35rem;
    border: 1px solid var(--line); background: var(--surface-1);
    border-radius: var(--radius-full); padding: 0.3rem 0.7rem;
    font-size: var(--text-xs); color: var(--ink-3);
}
.gen-plan-pill svg { width: 0.85rem; height: 0.85rem; }

/* ── Робоча зона ───────────────────────────────────────────── */
.gen-workspace {
    /* Та сама ширина, що й у решти сайту (Tailwind max-w-6xl = 72rem).
       Спершу тут було 1440px — і сторінка стала ширшою за власну шапку,
       через що виглядала не просторою, а зламаною. */
    max-width: 1152px; margin: 0 auto; padding: 0 var(--sp-6);
}
.gen-workspace__form {
    display: flex;
    /* Висота під вікно, але не нижча за робочу і не вища за розумну:
       на 4K рейка інакше розтягується на пів екрана порожнечею. */
    height: clamp(560px, calc(100vh - 190px), 860px);
    border: 1px solid var(--line);
    border-radius: var(--radius-2xl);
    overflow: hidden;
    background: var(--bg-1);
}

/* ── Полотно ───────────────────────────────────────────────── */
.gen-canvas { flex: 1; min-width: 0; padding: var(--sp-4); display: flex; }
.gen-canvas__stage { flex: 1; min-width: 0; display: flex; gap: var(--sp-3); }

.gen-plate {
    flex: 1; min-width: 0; position: relative; overflow: hidden;
    border-radius: var(--radius-xl); background: rgba(0, 0, 0, 0.35);
}
.gen-plate__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.gen-plate__blur {
    position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
    filter: blur(14px); transform: scale(1.06);
}
.gen-plate__tag {
    position: absolute; top: var(--sp-3); left: var(--sp-3); z-index: 2;
    font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
    /* Літерал, не --ink-2. Полотно лежить на фотографії й лишається темним у
       ОБИДВОХ темах, а --ink-* у світлій перевертається на майже чорний —
       тобто текст зникав саме там, де фон не змінювався. */
    color: #E4E4E7; background: rgba(0, 0, 0, 0.6);
    border: 1px solid var(--line); border-radius: var(--radius-full);
    padding: 0.25rem 0.6rem;
}
.gen-plate__meta {
    position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
    padding: var(--sp-6);
    background: linear-gradient(to top, rgba(0,0,0,0.92), rgba(0,0,0,0.62) 45%, rgba(0,0,0,0.15) 80%, transparent);
}
.gen-plate__title { margin: 0; font-size: var(--text-2xl); font-weight: 600; color: var(--ink); letter-spacing: -0.02em; }
.gen-plate__desc { margin: var(--sp-2) 0 0; font-size: var(--text-sm); line-height: 1.6; color: var(--ink-3); max-width: 60ch; }
.gen-plate__tags { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: var(--sp-3); }
.gen-tag {
    border: 1px solid rgba(255, 255, 255, 0.16); background: rgba(0, 0, 0, 0.45);
    border-radius: var(--radius-full); padding: 0.15rem 0.5rem;
    font-size: 11px; color: #CBD5E1;
}

/* ── Рейка ─────────────────────────────────────────────────── */
.gen-rail {
    width: 356px; flex: none; display: flex; flex-direction: column;
    border-left: 1px solid var(--line); background: var(--bg-0);
}
.gen-rail__head { padding: var(--sp-4) var(--sp-5) var(--sp-3); border-bottom: 1px solid var(--line); }
.gen-rail__title { margin: 0; font-size: var(--text-base); font-weight: 600; color: var(--ink); }
.gen-rail__sub { margin: 0.2rem 0 0; font-size: var(--text-xs); color: var(--muted-2); }
.gen-rail__scroll { flex: 1; overflow-y: auto; padding: var(--sp-4) var(--sp-5) var(--sp-5); }
.gen-rail__foot { border-top: 1px solid var(--line); padding: var(--sp-4) var(--sp-5); background: var(--bg-0); }

/* ── Завантаження ──────────────────────────────────────────── */
.gen-drop {
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 0.3rem; text-align: center; cursor: pointer;
    border: 1px dashed var(--line-hover); border-radius: var(--radius-lg);
    background: rgba(0, 0, 0, 0.3); padding: var(--sp-5) var(--sp-4);
    transition: border-color var(--tr), background var(--tr);
}
.gen-drop:hover, .gen-drop:focus-within { border-color: var(--accent); background: var(--accent-soft); }
.gen-drop.is-dragging { border-color: var(--success); background: var(--success-bg); }
.gen-drop__icon { width: 1.4rem; height: 1.4rem; color: var(--ink-3); margin-bottom: 0.35rem; }
.gen-drop__lead { font-size: var(--text-sm); font-weight: 500; color: var(--ink-2); }
.gen-drop__sub { font-size: var(--text-xs); color: var(--muted-2); }

.gen-consent { display: flex; gap: 0.6rem; margin-top: var(--sp-3); font-size: var(--text-xs); color: var(--ink-3); line-height: 1.5; }
.gen-consent__box { width: 1rem; height: 1rem; flex: none; margin-top: 0.15rem; accent-color: var(--accent); }
.gen-consent a { color: var(--success); }

/* ── Стрічка попередніх фото ───────────────────────────────── */
.gen-strip-head { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-2); margin-top: var(--sp-5); }
.gen-strip-count { font-size: 10.5px; color: var(--muted-2); font-variant-numeric: tabular-nums; }
.gen-strip-wrap { position: relative; margin-top: var(--sp-2); }
.gen-strip {
    display: flex; gap: 0.5rem; overflow-x: auto;
    scroll-snap-type: x mandatory; cursor: grab;
    scrollbar-width: none; -ms-overflow-style: none; padding-bottom: 2px;
}
.gen-strip::-webkit-scrollbar { display: none; }
.gen-strip.is-dragging { cursor: grabbing; scroll-snap-type: none; }
/* Виділення й нативний drag зображень вимикаємо разом: перше підсвічує
   плитки під час протягу, друге тягне привид картинки за курсором І
   ковтає mouseup, через що стрічка їде після відпускання кнопки. */
.gen-strip { user-select: none; -webkit-user-select: none; }
.gen-strip__item img { -webkit-user-drag: none; user-select: none; pointer-events: none; }
.gen-strip__item {
    width: 64px; height: 64px; flex: none; padding: 0; overflow: hidden;
    border: 1px solid var(--line); border-radius: var(--radius-md);
    background: var(--surface-1); cursor: pointer; scroll-snap-align: start;
    transition: border-color var(--tr);
}
.gen-strip__item img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gen-strip__item:hover, .gen-strip__item:focus-visible { border-color: var(--line-focus); outline: none; }
/* Обране фото позначає ГАЛОЧКА, а не рамка: у стрічці з чотирнадцяти
   мініатюр підсвічений кант читається як «наведення», а не як «вибрано»,
   і на строкатих фото його майже не видно. */
.gen-strip__item { position: relative; }
.gen-strip__item.is-active img { opacity: 0.5; }
.gen-strip__check {
    position: absolute; inset: 0; display: none;
    align-items: center; justify-content: center; pointer-events: none;
}
.gen-strip__item.is-active .gen-strip__check { display: flex; }
.gen-strip__check svg {
    width: 26px; height: 26px; color: #fff;
    background: var(--accent); border-radius: 50%; padding: 5px;
}
/* Край наступного фото має визирати — інакше про решту стрічки ніхто не
   дізнається. Маска гасне, коли гортати вже нікуди. */
.gen-strip-fade {
    position: absolute; top: 0; right: -1px; bottom: 0; width: 44px;
    pointer-events: none; transition: opacity var(--tr);
    background: linear-gradient(to left, var(--bg-0) 12%, transparent);
}
.gen-strip-fade.is-hidden { opacity: 0; }

/* ── Налаштування ──────────────────────────────────────────── */
.gen-settings { margin-top: var(--sp-5); border-top: 1px solid var(--line); }
.gen-block { padding: var(--sp-4) 0; border-bottom: 1px solid var(--line); }
.gen-block__label { margin: 0; font-size: var(--text-sm); color: var(--ink-2); }
.gen-block__hint { margin: 0.15rem 0 0; font-size: 11px; color: var(--muted-2); }
.gen-block__hint--cost { font-weight: 600; font-size: var(--text-xs); }

.gen-ratios { display: grid; grid-template-columns: repeat(5, 1fr); gap: 0.375rem; margin-top: var(--sp-3); }
.gen-ratio { position: relative; }
.gen-ratio__input { position: absolute; opacity: 0; width: 0; height: 0; }
.gen-ratio__tile {
    display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.45rem;
    height: 70px; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius-md);
    background: var(--surface-1);
    transition: border-color var(--tr), background var(--tr);
}
.gen-ratio__tile:hover { border-color: var(--line-hover); }
.gen-ratio__input:checked + .gen-ratio__tile { border-color: var(--accent); background: var(--accent-soft); }
.gen-ratio__input:focus-visible + .gen-ratio__tile { outline: 2px solid var(--accent); outline-offset: 2px; }
.gen-ratio__shape { border: 1.5px solid var(--ink-3); border-radius: 4px; }
.gen-ratio__shape--auto { border-style: dashed; }
.gen-ratio__input:checked + .gen-ratio__tile .gen-ratio__shape { border-color: var(--accent); }
.gen-ratio__name { font-size: 10px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.gen-ratio__input:checked + .gen-ratio__tile .gen-ratio__name { color: var(--ink); }

.gen-row {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-4);
    padding: var(--sp-4) 0; border-bottom: 1px solid var(--line);
}
.gen-row--switch { cursor: pointer; }
.gen-row--locked { align-items: flex-start; cursor: default; }
.gen-row__icon { width: 1rem; height: 1rem; color: var(--muted-2); flex: none; margin-top: 0.15rem; }
.gen-row__label { margin: 0; font-size: var(--text-sm); color: var(--ink-2); }
.gen-row__hint { margin: 0.15rem 0 0; font-size: 11px; color: var(--muted-2); line-height: 1.5; }

.gen-switch {
    width: 38px; height: 21px; flex: none; position: relative; border-radius: var(--radius-full);
    background: rgba(255, 255, 255, 0.14); transition: background var(--tr);
}
.gen-switch__knob {
    position: absolute; top: 3px; left: 3px; width: 15px; height: 15px;
    border-radius: 50%; background: #fff; transition: left var(--tr);
}
.gen-switch.is-on { background: var(--accent); }
.gen-switch.is-on .gen-switch__knob { left: 20px; }

.gen-seg { display: inline-flex; border: 1px solid var(--line); border-radius: var(--radius-md); overflow: hidden; margin-top: var(--sp-3); }
.gen-seg__opt {
    font: inherit; font-size: var(--text-xs); padding: 0.4rem 0.8rem;
    background: transparent; border: none; color: var(--ink-3); cursor: pointer;
    transition: background var(--tr), color var(--tr);
}
.gen-seg__opt:hover { color: var(--ink-2); }
.gen-seg__opt.is-active { background: rgba(255, 255, 255, 0.08); color: var(--ink); }

/* ── Помилка, нота, підвал ─────────────────────────────────── */
.gen-error {
    display: flex; align-items: flex-start; gap: 0.6rem; margin-top: var(--sp-4);
    border: 1px solid var(--danger-border); background: var(--danger-bg);
    border-radius: var(--radius-md); padding: var(--sp-3);
    font-size: var(--text-xs); color: var(--ink-2);
}
.gen-error svg { width: 1rem; height: 1rem; color: var(--danger); flex: none; }
.gen-error p { margin: 0; }

.gen-cost { margin: 0 0 var(--sp-3); font-size: var(--text-xs); color: var(--ink-3); }
.gen-cost strong { color: var(--success); font-weight: 600; }
.gen-submit {
    display: flex; align-items: center; justify-content: center; gap: 0.5rem;
    width: 100%; font-size: var(--text-sm); font-weight: 600;
    color: var(--primary-fg); background: var(--primary); border: none;
    border-radius: var(--radius-full); padding: 0.75rem var(--sp-5);
    cursor: pointer; text-decoration: none; transition: background var(--tr);
}
.gen-submit:hover { background: var(--primary-hover); }
.gen-submit:disabled { opacity: 0.55; cursor: not-allowed; }
.gen-submit span { display: inline-flex; align-items: center; gap: 0.5rem; }
.gen-submit svg { width: 1rem; height: 1rem; }

/* ── Керування власним стилем (під робочою зоною) ──────────── */
.gen-manage { max-width: 1152px; margin: var(--sp-6) auto 0; padding: 0 var(--sp-6); display: grid; gap: var(--sp-3); }
.gen-manage__block { border: 1px solid var(--line); border-radius: var(--radius-xl); background: var(--surface-1); }
.gen-manage__block--pad { padding: var(--sp-4) var(--sp-5); display: grid; gap: var(--sp-3); }
.gen-manage__block > summary {
    display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
    padding: var(--sp-3) var(--sp-5); cursor: pointer;
    font-size: var(--text-xs); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3);
}
.gen-manage__block > summary span { display: inline-flex; align-items: center; gap: 0.45rem; }
.gen-manage__block > summary svg { width: 0.85rem; height: 0.85rem; }
.gen-manage__block[open] > summary .gen-manage__toggle { font-size: 0; }
.gen-manage__block[open] > summary .gen-manage__toggle::after { content: attr(data-open-label); font-size: var(--text-xs); }
.gen-manage__body { border-top: 1px solid var(--line); padding: var(--sp-4) var(--sp-5); display: grid; gap: var(--sp-3); justify-items: start; }
.gen-manage__body pre {
    margin: 0; white-space: pre-wrap; word-break: break-word;
    font-family: var(--font-sans); font-size: var(--text-sm); line-height: 1.6; color: var(--ink-3);
}
.gen-manage__row { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); font-size: var(--text-xs); color: var(--ink-2); flex-wrap: wrap; }
.gen-manage__row span { display: inline-flex; align-items: center; gap: 0.4rem; }
.gen-manage__row svg { width: 0.9rem; height: 0.9rem; }
.gen-manage__muted { color: var(--muted-2); }
.gen-manage__actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; }
.gen-manage__btn {
    display: inline-flex; align-items: center; gap: 0.4rem;
    border: 1px solid var(--line); background: var(--surface-2); color: var(--ink-2);
    border-radius: var(--radius-full); padding: 0.4rem 0.9rem;
    font: inherit; font-size: var(--text-xs); cursor: pointer; transition: border-color var(--tr), background var(--tr);
}
.gen-manage__btn:hover { border-color: var(--line-hover); background: rgba(255, 255, 255, 0.07); }
.gen-manage__btn:disabled { opacity: 0.5; cursor: not-allowed; }
.gen-manage__btn svg { width: 0.85rem; height: 0.85rem; }
.gen-manage__btn--danger { border-color: var(--danger-border); color: var(--danger); }
.gen-manage__link { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; font-size: var(--text-xs); }
.gen-manage__link a { color: var(--success); word-break: break-all; flex: 1; min-width: 12rem; }
.gen-manage__link svg { width: 0.85rem; height: 0.85rem; color: var(--success); }
.gen-manage__alert {
    display: flex; gap: 0.6rem; border: 1px solid var(--danger-border); background: var(--danger-bg);
    border-radius: var(--radius-md); padding: var(--sp-3); font-size: var(--text-xs); color: var(--ink-2);
}
.gen-manage__alert svg { width: 1rem; height: 1rem; color: var(--danger); flex: none; }
.gen-manage__alert span { display: block; margin-top: 0.25rem; }

/* app.css вантажиться першим, тож .hidden програє за порядком усім
   display-правилам вище. Повертаємо йому владу явно. */
.gen-manage__btn.hidden,
.gen-manage__link.hidden,
.gen-manage__row svg.hidden,
.gen-plan-pill.hidden { display: none; }

/* ── Вузькі екрани: рейка йде під полотно ──────────────────── */
@media (max-width: 900px) {
    .gen-workspace__form { flex-direction: column; height: auto; }
    /* flex:1 задає flex-basis:0%, який у колонковому контейнері перебиває
       height — тож саме flex:none, інакше полотно схлопнеться. */
    .gen-canvas { flex: none; height: 46vh; min-height: 260px; padding: var(--sp-3); }
    .gen-rail { width: 100%; border-left: none; border-top: 1px solid var(--line); }
    .gen-rail__scroll { overflow-y: visible; }
    .gen-plate__meta { padding: var(--sp-4); }
    .gen-plate__title { font-size: var(--text-xl); }
    .gen-plate__desc { display: none; }
}
@media (max-width: 560px) {
    .gen-page__bar, .gen-workspace, .gen-manage { padding-left: var(--sp-4); padding-right: var(--sp-4); }
    .gen-ratios { gap: 0.3rem; }
}

@media (prefers-reduced-motion: reduce) {
    .gen-strip { scroll-behavior: auto; }
    .gen-switch, .gen-switch__knob, .gen-strip-fade,
    .gen-drop, .gen-ratio__tile, .gen-seg__opt { transition: none; }
}

/* Кольори тексту для <p> у робочій зоні — проскоповані НАВМИСНО.
   `body.studio-body p { color: var(--ink-3) }` має специфічність (0,1,2) і
   б'є будь-який голий клас (0,1,0), тож усі підписи нижче інакше
   отримали б --ink-3: сірий на темній темі (майже непомітно) і темно-синій
   на світлій. Рівно так уже ламалися заголовок картки історії та текст
   лоадера на сторінці результату — тут закриваємо одразу.
   Два класи (0,2,0) виграють. */
.gen-workspace .gen-rail__sub,
.gen-workspace .gen-block__hint,
.gen-workspace .gen-row__hint { color: var(--muted-2); }
/* Доплата мусить бути НЕ там, де решта модифікаторів: правило вище має
   (0,2,0) і б'є голий `.gen-block__hint--cost` (0,1,0), через що «+5
   кредитів» лишалися сірими. Той самий каскад, лише на рівень глибше —
   тому модифікатор живе тут, одразу після базового кольору. */
.gen-workspace .gen-block__hint--cost { color: var(--success); }
.gen-workspace .gen-block__label,
.gen-workspace .gen-row__label { color: var(--ink-2); }
.gen-workspace .gen-cost { color: var(--ink-3); }
.gen-workspace .gen-error p { color: var(--ink-2); }

/* Полотно темне в ОБИДВОХ темах — воно лежить на фотографії. Тому підпис
   тут не має слідувати темі взагалі. */
.gen-plate__meta .gen-plate__title { color: #FFFFFF; }
.gen-plate__meta .gen-plate__desc { color: #E4E4E7; }
