@import url('https://fonts.googleapis.com/css2?family=Sora:wght@500;600;700;800&family=Plus+Jakarta+Sans:wght@400;500;600;700&display=swap');

:root {
  --bg: #060606;
  --bg-elevated: #0d0d0d;
  --surface: rgba(255, 255, 255, 0.045);
  --surface-hover: rgba(255, 255, 255, 0.08);
  --border: rgba(255, 255, 255, 0.09);
  --border-soft: rgba(255, 255, 255, 0.055);
  --text: #ffffff;
  --text-dim: rgba(255, 255, 255, 0.56);
  --text-faint: rgba(255, 255, 255, 0.34);
  /* One accent, used sparingly for focus/active states — not a decorative gradient. */
  --accent: #ffffff;
  --accent-soft: rgba(255, 255, 255, 0.12);
  --danger: #f87171;
  --warning: #fbbf24;
  --success: #4ade80;
  --radius-lg: 20px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --tg-bg: var(--bg);
  --tg-text: var(--text);
  --tg-hint: var(--text-dim);
  --tg-button: #ffffff;
  --tg-button-text: #000000;
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --kb-inset: 0px; /* live on-screen-keyboard height, tracked in app.js (Telegram viewportChanged) — not currently read by any rule here, kept for future use */
  --tabbar-h: 67px; /* real measured .tabbar height, kept live by syncTabbarHeight() in app.js */
  --topbar-h: 62px; /* real measured .topbar height, kept live by syncTopbarHeight() in app.js — used to size .view-agent's fixed flex column below it */
  --lime: #ffffff;
  --lime-hover: #e6e6e6;
  --panel-2: #1a1a1a;
  --panel-3: #1f1f1f;
  --swatch-bg: #2a2a2a;
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  background-image: radial-gradient(circle at 50% -10%, rgba(255, 255, 255, 0.06), transparent 45%);
  background-attachment: fixed;
  color: var(--text);
  font-family: 'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}
h1, h2, h3, .brand, .num { font-family: 'Sora', 'Plus Jakarta Sans', sans-serif; }

[dir='rtl'] { direction: rtl; }

a { color: inherit; text-decoration: none; }
button { font-family: inherit; }

#app {
  max-width: 560px;
  margin: 0 auto;
  min-height: 100vh;
  padding-bottom: calc(var(--tabbar-h) + 16px + var(--safe-bottom));
  position: relative;
}

/* ── Top bar ─────────────────────────────────────────────── */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 18px 8px;
  gap: 10px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 700;
  font-size: 17px;
}
.brand-mark {
  width: 34px; height: 34px;
  border-radius: 10px;
  background: #000000;
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  flex-shrink: 0;
}
.brand-mark svg, .brand-mark img { width: 18px; height: 18px; display: block; }
#brand-name.hidden { display: none; }

.topbar-actions { display: flex; align-items: center; gap: 6px; }
.credit-pill {
  display: flex; align-items: center; gap: 5px;
  background: var(--surface);
  border: 1px solid var(--border);
  padding: 5px 10px 5px 6px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.15s, transform 0.1s;
}
.credit-pill:active { transform: scale(0.96); }
.credit-pill:hover { background: var(--surface-hover); }
.credit-pill .dot {
  width: 16px; height: 16px; border-radius: 50%;
  background: #ffffff;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px;
  color: #000;
}
.credit-pill .dot svg { stroke: #000 !important; width: 9px; height: 9px; }
#lang-btn { width: 28px; height: 28px; border-radius: 9px; }
#lang-btn svg { width: 14px; height: 14px; }
.icon-btn {
  width: 36px; height: 36px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--surface);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  color: var(--text-dim);
  transition: background 0.15s, color 0.15s;
}
.icon-btn:hover { background: var(--surface-hover); color: var(--text); }
.icon-btn svg { width: 17px; height: 17px; }

/* ── Views ───────────────────────────────────────────────── */
.view { padding: 4px 18px 24px; animation: fadein 0.22s ease; }
.view.hidden { display: none; }
@keyframes fadein { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.section-title {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
  margin: 22px 2px 12px;
}
.section-title:first-child { margin-top: 6px; }

/* ── Tool grid ───────────────────────────────────────────── */
.tool-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}
.tool-card {
  /* Deliberately white, not the app's usual dark --surface — these are the
     Studio home tiles the user wants readable as plain white cards, not
     backed by generation media (see tool-grid comment in app.js). */
  background: #ffffff;
  border: 1px solid rgba(0,0,0,0.08);
  border-radius: var(--radius-lg);
  padding: 12px;
  min-height: 132px;
  display: flex; flex-direction: column; justify-content: flex-end;
  cursor: pointer;
  text-align: start;
  transition: transform 0.12s, border-color 0.15s, background 0.15s;
  position: relative;
  overflow: hidden;
}
.tool-card:active { transform: scale(0.97); }
.tool-card:hover { border-color: rgba(0,0,0,0.16); background: #f6f6f6; }


.tool-card h3 { margin: 0 0 4px; font-size: 14.5px; font-weight: 700; color: #0a0a0a; position: relative; z-index: 2; }
.tool-card .cost {
  font-size: 11.5px;
  color: rgba(0,0,0,0.5);
  font-weight: 600;
  position: relative; z-index: 2;
}
.tool-card .cost.free { color: var(--success); }
.tool-card-badge {
  position: absolute; top: 12px; right: 12px;
  background: var(--lime); color: #000;
  font-size: 8.5px; font-weight: 800;
  letter-spacing: 0.04em;
  padding: 2px 6px;
  border-radius: 6px;
  box-shadow: 0 2px 8px -2px rgba(255,255,255,0.6);
  z-index: 3;
}

/* Tool identity now comes from the icon glyph, not a rainbow of tinted
   backgrounds — and, on the white tile background above, a dark icon
   rather than white so it stays visible. */
.tc-violet .icon-wrap, .tc-cyan .icon-wrap, .tc-gold .icon-wrap, .tc-green .icon-wrap, .tc-lime .icon-wrap {
  background: rgba(0,0,0,0.055); color: #111;
}

/* ── Buttons ─────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none; border-radius: var(--radius-sm);
  padding: 13px 18px;
  font-weight: 700; font-size: 14.5px;
  cursor: pointer;
  transition: transform 0.1s, opacity 0.15s, filter 0.15s;
  width: 100%;
}
.btn:active { transform: scale(0.98); }
.btn:disabled { opacity: 0.4; cursor: not-allowed; }
.btn-primary { background: #ffffff; color: #000000; }
.btn-primary:hover:not(:disabled) { background: rgba(255,255,255,0.88); }
.btn-ghost { background: var(--surface); color: var(--text); border: 1px solid var(--border); }
.btn-ghost:hover { background: var(--surface-hover); }
.btn-sm { padding: 9px 14px; font-size: 13px; width: auto; }

/* ── Tool detail / generator flow ───────────────────────────*/
.flow-header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.flow-header h2 { margin: 0; font-size: 18px; font-weight: 700; }
/* The tool-detail title is redundant with the composer itself now — drop
   the header bar entirely rather than leave an empty strip of padding. */
#view-tool .flow-header { display: none; }

.dropzone {
  border: 1.5px dashed var(--border);
  border-radius: var(--radius-md);
  padding: 28px 16px;
  text-align: center;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  background: var(--bg-elevated);
}
.dropzone.drag { border-color: rgba(255,255,255,0.5); background: rgba(255,255,255,0.04); }
.dropzone svg { width: 30px; height: 30px; color: var(--text-faint); margin-bottom: 8px; }
.dropzone p { margin: 0; font-size: 13px; color: var(--text-dim); }
.dropzone .fname { color: var(--text); font-weight: 600; margin-top: 6px; }
.preview-img {
  width: 100%; max-height: 260px; object-fit: contain;
  border-radius: var(--radius-md);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
}

textarea.field, input.field, select.field {
  width: 100%;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  color: var(--text);
  padding: 12px 14px;
  font-size: 14px;
  font-family: inherit;
  resize: vertical;
  margin-top: 10px;
}
textarea.field:focus, input.field:focus, select.field:focus { outline: none; border-color: rgba(255,255,255,0.45); }
textarea.field::placeholder, input.field::placeholder { color: var(--text-faint); }
/* <option> styling only reliably applies in Chromium-based engines (which
   Telegram's WebView is), but costs nothing to set — a browser that ignores
   it just falls back to its own native picker rendering instead of ours. */
select.field option { background: var(--bg-elevated); color: var(--text); }

/* Prompt boxes: compact by default, user can still drag to grow if they want more room. */
textarea.field#prompt { height: 84px; }

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.chip {
  padding: 8px 13px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  color: var(--text-dim);
  transition: all 0.15s;
}
.chip.active { background: #ffffff; color: #000; border-color: transparent; }
.chip:hover:not(.active) { border-color: rgba(255,255,255,0.4); color: var(--text); }

/* ── Generation config row (model / resolution / aspect ratio / batch) ──
   Sits directly above the cost row + Generate button, tightly grouped —
   and every dropdown panel opens UPWARD (bottom: 100%) so it can never
   cover the Generate button sitting right below this row. */
.gen-config-row { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; position: relative; z-index: 5; }

.gen-dropdown { position: relative; flex: 1 1 auto; min-width: 0; }
.gen-dd-btn {
  width: 100%;
  display: flex; align-items: center; justify-content: space-between; gap: 5px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text);
  font-size: 11px;
  font-weight: 600;
  font-family: inherit;
  padding: 6px 9px;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  white-space: nowrap;
  overflow: hidden;
}
.gen-dd-btn .gen-dd-label { overflow: hidden; text-overflow: ellipsis; }
.gen-dd-btn .gen-dd-label b { color: var(--text); font-weight: 700; }
.gen-dd-btn svg { flex-shrink: 0; opacity: 0.6; transition: transform 0.15s; }
.gen-dropdown:hover .gen-dd-btn { border-color: rgba(255,255,255,0.35); background: var(--surface-hover); }

.gen-dd-panel {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  min-width: 100%;
  max-height: 220px;
  overflow-y: auto;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 -12px 32px rgba(0,0,0,0.55);
  padding: 6px;
  z-index: 20;
  animation: gen-dd-in 0.14s cubic-bezier(.2,.8,.2,1);
}
.gen-dd-panel.hidden { display: none; }
@keyframes gen-dd-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
.gen-dd-option {
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.12s, color 0.12s;
}
.gen-dd-option:hover { background: var(--surface-hover); color: var(--text); }
.gen-dd-row { display: flex; align-items: center; gap: 10px; padding: 7px 8px; border-radius: 10px; cursor: pointer; transition: background 0.12s; }
.gen-dd-row:hover { background: var(--surface-hover); }
.gen-dd-row .gen-dd-option { padding: 0; border-radius: 0; background: none !important; flex: 1; }
.gen-dd-row .gen-dd-option.selected { color: var(--lime); }
.gen-dd-row .gen-dd-option.selected::after { content: '✓'; float: right; font-weight: 800; }

.gen-stepper {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 4px 8px;
  flex: 0 0 auto;
}
.gen-step-btn {
  width: 20px; height: 20px;
  border-radius: 50%;
  border: none;
  background: rgba(255,255,255,0.1);
  color: var(--text);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.15s;
}
.gen-step-btn svg { width: 10px; height: 10px; }
.gen-step-btn:hover { background: rgba(255,255,255,0.18); }
.gen-step-btn:active { transform: scale(0.92); }
.gen-step-val { font-size: 12.5px; font-weight: 700; min-width: 22px; text-align: center; }

/* minmax(0, 1fr), not bare 1fr — a bare 1fr track's minimum size defaults to
   its content's min-content width, so one wide item (e.g. .bulk-bar's row of
   buttons) can force that whole column wide and squash the other column to
   almost nothing. minmax(0, 1fr) pins the floor to 0 so both columns always
   split the row evenly regardless of what's inside a given cell. */
.batch-results { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px; margin-top: 10px; }
.batch-results .result-card { margin-top: 0; }

/* ── Playground-style composer (Image / Video tools) — kept deliberately
   compact (one tight card, ~4 short rows) so a generated result is visible
   without scrolling past a tall input form. ── */
/* Pinned above the tabbar — same fixed/centered technique as .tabbar / .agent-inputbar,
   so the composer stays a small dock at the bottom and .results-area (the generated
   images) gets the rest of the screen instead of being pushed below a tall form. */
.composer-dock {
  position: fixed;
  left: 0; right: 0;
  bottom: calc(var(--tabbar-h) + var(--safe-bottom));
  max-width: 560px;
  margin: 0 auto;
  padding: 0 18px;
  z-index: 25;
}
.composer-card {
  background: var(--panel-2);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius-sm);
  padding: 6px 8px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.45);
}
.results-area { padding-bottom: 160px; min-height: 45vh; }

/* Mode-toggle icons + reference-photo strip share one row. */
.composer-toprow { display: flex; align-items: center; gap: 5px; }

.mode-toggle-rail { display: flex; gap: 5px; flex-shrink: 0; }
.mode-toggle-btn {
  width: 22px; height: 22px;
  border-radius: 7px;
  border: 1px solid var(--border-soft);
  background: rgba(255,255,255,0.05);
  color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: all 0.15s;
}
.mode-toggle-btn svg { width: 11px; height: 11px; }
.mode-toggle-btn:hover { color: var(--text); background: rgba(255,255,255,0.09); }
.mode-toggle-btn.active { border-color: rgba(255,255,255,0.5); color: var(--lime); background: rgba(255,255,255,0.08); }

.ref-strip { display: flex; flex-wrap: wrap; gap: 5px; flex: 1; min-height: 22px; border-radius: 8px; transition: background 0.15s, outline 0.15s; }
.ref-strip.drag { outline: 1.5px dashed rgba(255,255,255,0.55); outline-offset: 4px; background: rgba(255,255,255,0.04); }
.ref-thumb { position: relative; width: 22px; height: 22px; border-radius: 7px; overflow: hidden; border: 1px solid var(--border); background: var(--bg-elevated); flex-shrink: 0; }
.ref-thumb img, .ref-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; }
.ref-thumb-remove {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.55);
  border: none; color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  opacity: 0; transition: opacity 0.15s;
}
.ref-thumb-remove svg { width: 9px; height: 9px; }
.ref-thumb:hover .ref-thumb-remove, .ref-thumb:active .ref-thumb-remove { opacity: 1; }
.ref-thumb-add {
  width: 22px; height: 22px;
  border-radius: 7px;
  border: 1.5px dashed var(--border);
  background: transparent;
  color: var(--text-faint);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  flex-shrink: 0;
  overflow: hidden;
  transition: border-color 0.15s, color 0.15s;
}
.ref-thumb-add:hover { border-color: rgba(255,255,255,0.5); color: var(--lime); }
.ref-thumb-add.has-file { border-style: solid; padding: 0; }
.ref-thumb-add img { width: 100%; height: 100%; object-fit: cover; }
.ref-thumb-add svg { width: 10px; height: 10px; }
.dz-hint.hidden { display: none; }

.composer-prompt {
  width: 100%;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: 12px;
  font-family: inherit;
  resize: none;
  padding: 4px 0 2px;
  min-height: 22px;
}
.composer-prompt:focus { outline: none; }
.composer-prompt::placeholder { color: var(--text-faint); }

.model-swatch {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px;
  border-radius: 5px;
  font-size: 6.5px; font-weight: 800;
  background: var(--swatch-bg);
  color: var(--text);
  flex-shrink: 0;
  letter-spacing: -0.02em;
}
.model-swatch--nb { background: var(--lime); color: #000; }
.model-swatch--sd { background: #6366f1; color: #fff; }
.model-swatch--ai { background: #38bdf8; color: #000; }
.model-swatch--kl { background: #a855f7; color: #fff; }
.model-swatch--wan { background: linear-gradient(135deg,#f97316,#fb923c); color: #fff; }
.model-swatch--sc { background: #f43f5e; color: #fff; }
.model-swatch--default { background: var(--swatch-bg); color: var(--text-dim); }

.ratio-icon { display: inline-block; border: 1.6px solid currentColor; border-radius: 2px; flex-shrink: 0; }

.generate-btn-lime {
  background: var(--lime);
  color: #000;
  padding: 8px 14px;
  font-size: 13px;
  display: flex; align-items: center; justify-content: space-between;
  margin-top: 2px;
}
.generate-btn-lime:hover:not(:disabled) { background: var(--lime-hover); }
.generate-btn-lime .price { font-weight: 800; }
.generate-btn-lime .spinner { border-top-color: #000; }

/* ── Results grid (image/video tool-detail flow) — pending/done/error ── */
.result-slot { position: relative; aspect-ratio: 3/4; border-radius: var(--radius-md); overflow: hidden; border: 1px solid var(--border-soft); background: var(--swatch-bg); animation: reveal 0.3s cubic-bezier(.2,.8,.2,1); }
/* `height: 100%` against a parent whose height comes ONLY from
   aspect-ratio (no explicit height) is unreliable for a replaced element
   (<img>/<video>) — percentage heights only resolve cleanly against a
   containing block with a definite height, and engines differ on whether
   aspect-ratio counts as "definite enough" for that. It showed up as a bare
   swatch-colored strip above the actual image/video, worse for video (whose
   own intrinsic size is ALSO unknown until metadata loads) but confirmed to
   happen for plain images too. inset:0 sidesteps percentage-height
   resolution entirely — it positions against the containing block directly
   — so it fills the tile regardless of either quirk. */
.result-slot img, .result-slot video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* Grid-tile videos have no native controls (see resultGridCard) so tapping
   anywhere always opens the lightbox — this badge is the only hint that the
   tile is a video at all, sitting centered over the muted preview frame. */
.result-play-badge {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none;
}
.result-play-badge svg { width: 44px; height: 44px; color: rgba(255,255,255,0.9); filter: drop-shadow(0 2px 10px rgba(0,0,0,0.5)); }
.result-audio { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-faint); background: var(--swatch-bg); }
.result-audio .i-lg { width: 30px; height: 30px; }
.result-slot.pending { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px; padding: 10px; text-align: center; }
.result-slot.pending .ring { width: 26px; height: 26px; border-radius: 50%; border: 2.5px solid rgba(255,255,255,0.18); border-top-color: var(--lime); animation: spin 0.7s linear infinite; }
.result-slot.pending .stage { font-size: 11px; font-weight: 700; color: var(--lime); }
.result-slot .count-badge { position: absolute; top: 8px; left: 8px; background: rgba(0,0,0,0.55); backdrop-filter: blur(4px); color: #fff; font-size: 10px; font-weight: 700; padding: 3px 7px; border-radius: 8px; z-index: 2; }
.result-download-btn {
  position: absolute; bottom: 8px; right: 8px;
  width: 28px; height: 28px;
  border-radius: 50%;
  border: none;
  background: rgba(255,255,255,0.2);
  backdrop-filter: blur(4px);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.result-download-btn:hover { background: rgba(255,255,255,0.35); }
.result-download-btn svg { width: 13px; height: 13px; }

/* ── Multi-select ────────────────────────────────────────── */
.result-slot.done { cursor: pointer; transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s; }
.result-slot.done.pressing { transform: scale(0.93); box-shadow: 0 0 0 2px rgba(255,255,255,0.25); }
.result-select {
  position: absolute; top: 8px; left: 8px;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 2px solid rgba(255,255,255,0.6);
  background: rgba(0,0,0,0.3);
  backdrop-filter: blur(4px);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  z-index: 2;
  transition: background 0.15s, border-color 0.15s, transform 0.15s;
}
.result-select svg { width: 11px; height: 11px; color: #000; opacity: 0; transition: opacity 0.1s; }
.result-slot.selected { border-color: var(--lime); box-shadow: 0 0 0 2px rgba(255,255,255,0.35); transform: scale(0.97); }
.result-slot.selected .result-select { background: var(--lime); border-color: var(--lime); transform: scale(1.08); }
.result-slot.selected .result-select svg { opacity: 1; }

.bulk-bar {
  grid-column: 1 / -1;
  position: sticky; top: 0; z-index: 6;
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  background: var(--panel-2);
  border: 1px solid rgba(255,255,255,0.3);
  border-radius: 999px;
  padding: 8px 8px 8px 16px;
  margin-bottom: 10px;
  box-shadow: 0 10px 28px rgba(0,0,0,0.4);
  animation: reveal 0.2s cubic-bezier(.2,.8,.2,1);
}
.bulk-bar .count { font-size: 12.5px; font-weight: 700; color: var(--lime); white-space: nowrap; }
.bulk-bar .actions { display: flex; align-items: center; gap: 4px; }
.bulk-bar button {
  display: flex; align-items: center; gap: 5px;
  background: rgba(255,255,255,0.07);
  border: none; color: var(--text);
  font-size: 12px; font-weight: 600; font-family: inherit;
  padding: 8px 12px;
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.15s;
}
.bulk-bar button svg { width: 13px; height: 13px; }
.bulk-bar button:hover { background: rgba(255,255,255,0.14); }
.bulk-bar button.danger:hover { background: rgba(248,113,113,0.18); color: #ffb3b3; }

/* ── Lightbox — full-size preview with delete/download ─────── */
.lightbox {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.9);
  backdrop-filter: blur(6px);
  z-index: 60;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  padding: 24px 16px calc(24px + var(--safe-bottom));
  animation: fadein 0.18s ease;
}
.lightbox.hidden { display: none; }
.lightbox-media { max-width: 100%; max-height: 72vh; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,0.6); }
.lightbox-close {
  position: absolute; top: calc(16px + var(--safe-bottom)); right: 16px;
  width: 38px; height: 38px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: rgba(255,255,255,0.08);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
}
.lightbox-actions { display: flex; gap: 10px; margin-top: 18px; }
.lightbox-actions button {
  display: flex; align-items: center; gap: 7px;
  background: rgba(255,255,255,0.1);
  border: none; color: #fff;
  font-size: 13px; font-weight: 700; font-family: inherit;
  padding: 11px 18px;
  border-radius: 999px;
  cursor: pointer;
}
.lightbox-actions button svg { width: 15px; height: 15px; }
.lightbox-actions button:hover { background: rgba(255,255,255,0.18); }
.lightbox-actions button.danger { background: rgba(248,113,113,0.16); color: #ffb3b3; }
.lightbox-actions button.danger:hover { background: rgba(248,113,113,0.26); }
.result-slot.error { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 12px; text-align: center; background: rgba(248,113,113,0.08); }
.result-slot.error .tag { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.04em; color: #ffb3b3; background: rgba(248,113,113,0.14); border: 1px solid rgba(248,113,113,0.28); padding: 4px 9px; border-radius: 8px; }
.result-slot.error .msg { font-size: 11px; color: var(--text-dim); line-height: 1.4; }

/* NSFW / flagged variant — a blurred noise pattern (its own layer, behind
   the text) standing in for the redacted image; there's nothing real to
   blur since the request never finished generating. Tag recolored amber
   instead of red so it reads as "hidden", not "broken". */
.result-slot.error.nsfw {
  position: relative;
  background: linear-gradient(160deg, rgba(251,191,36,0.14), rgba(0,0,0,0.35));
  overflow: hidden;
}
.result-slot.error.nsfw::before {
  content: '';
  position: absolute; inset: -12px;
  background: repeating-linear-gradient(135deg, rgba(255,255,255,0.09) 0 3px, transparent 3px 7px);
  filter: blur(7px);
}
.result-slot.error.nsfw > * { position: relative; z-index: 1; }
.result-slot.error.nsfw .i-lg { color: rgba(255,255,255,0.55); width: 22px; height: 22px; }
.result-slot.error.nsfw .tag { color: #ffd98a; background: rgba(251,191,36,0.16); border-color: rgba(251,191,36,0.32); }

.cost-row {
  display: flex; align-items: center; justify-content: space-between;
  margin: 16px 0 10px;
  font-size: 13px;
  color: var(--text-dim);
}
.cost-row b { color: var(--text); }

.result-card {
  margin-top: 16px;
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--border);
  background: var(--bg-elevated);
}
.result-card img, .result-card video { width: 100%; display: block; }
.result-card audio { width: 100%; padding: 14px; }

.spinner {
  width: 18px; height: 18px;
  border: 2.5px solid rgba(255,255,255,0.25);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.status-banner {
  display: flex; align-items: center; gap: 10px;
  padding: 13px 14px;
  border-radius: var(--radius-sm);
  font-size: 13.5px;
  margin-top: 12px;
  animation: banner-in 0.28s cubic-bezier(.2,.8,.2,1);
  border: 1px solid transparent;
}
@keyframes banner-in { from { opacity: 0; transform: translateY(4px) scale(0.98); } to { opacity: 1; transform: none; } }

.status-banner.processing {
  background: linear-gradient(100deg, rgba(255,255,255,0.05) 0%, rgba(255,255,255,0.12) 50%, rgba(255,255,255,0.05) 100%);
  background-size: 220% 100%;
  animation: banner-in 0.28s cubic-bezier(.2,.8,.2,1), sweep 2.2s ease-in-out infinite;
  color: rgba(255,255,255,0.85);
  border-color: var(--border);
}
@keyframes sweep { 0% { background-position: 0% 0; } 100% { background-position: -220% 0; } }

.status-banner.error { background: rgba(248,113,113,0.12); color: #ffb3b3; border-color: rgba(248,113,113,0.22); }
.status-banner.flagged { background: rgba(251,191,36,0.12); color: #ffd98a; border-color: rgba(251,191,36,0.28); }
.status-banner.success {
  background: rgba(74,222,128,0.12);
  color: #b3f2cc;
  border-color: rgba(74,222,128,0.25);
  animation: banner-in 0.28s cubic-bezier(.2,.8,.2,1), pop 0.4s cubic-bezier(.34,1.56,.64,1) 0.05s both;
}
@keyframes pop { 0% { transform: scale(0.9); } 60% { transform: scale(1.015); } 100% { transform: scale(1); } }

/* Animated processing rail — thin bar that fills while a job runs */
.progress-rail {
  height: 3px;
  border-radius: 3px;
  background: var(--border-soft);
  overflow: hidden;
  margin-top: 10px;
}
.progress-rail::after {
  content: '';
  display: block;
  height: 100%;
  width: 40%;
  background: #ffffff;
  border-radius: 3px;
  animation: rail 1.3s ease-in-out infinite;
}
@keyframes rail { 0% { transform: translateX(-100%); } 100% { transform: translateX(350%); } }

/* Draw-in check for the success icon */
.status-banner.success .i svg path { stroke-dasharray: 24; stroke-dashoffset: 24; animation: draw 0.5s ease forwards 0.15s; }
@keyframes draw { to { stroke-dashoffset: 0; } }

/* Shake for a flagged / rejected result */
.status-banner.flagged { animation: banner-in 0.28s cubic-bezier(.2,.8,.2,1), shake 0.4s ease 0.28s; }
@keyframes shake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-3px); } 75% { transform: translateX(3px); } }

/* Result card reveal */
.result-card { animation: reveal 0.35s cubic-bezier(.2,.8,.2,1); }
@keyframes reveal { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: none; } }

/* Tool card and button micro-interactions */
.tool-card, .pkg-card, .gallery-item { transition: transform 0.15s cubic-bezier(.2,.8,.2,1), border-color 0.15s, background 0.15s, box-shadow 0.2s; }
.tool-card:hover { box-shadow: 0 10px 28px rgba(0,0,0,0.5); }
.btn-primary { transition: transform 0.1s, opacity 0.15s, filter 0.15s, box-shadow 0.2s; }
.btn-primary:hover:not(:disabled) { box-shadow: 0 8px 26px rgba(255,255,255,0.18); }
.spinner { animation: spin 0.7s linear infinite, spinfade 1.4s ease-in-out infinite; }
@keyframes spinfade { 0%,100% { opacity: 1; } 50% { opacity: 0.6; } }

/* ── Wallet — lime-accented to match the rest of the redesigned app,
   instead of the plain monochrome cards this section had before. ── */
#view-wallet .section-title { margin: 16px 2px 8px; font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-faint); }
#view-wallet .history-item { padding: 8px 0; font-size: 12.5px; }

.balance-hero {
  background: linear-gradient(165deg, var(--panel-2) 0%, #050505 100%);
  border: 1px solid rgba(255,255,255,0.14);
  border-radius: var(--radius-lg);
  padding: 18px 18px;
  color: #fff;
  position: relative;
  overflow: hidden;
  box-shadow: 0 14px 34px -18px rgba(0,0,0,0.7), inset 0 1px 0 rgba(255,255,255,0.05);
}
.balance-hero::before {
  content: '';
  position: absolute; top: -70%; right: -25%;
  width: 220px; height: 220px;
  background: radial-gradient(circle, rgba(255,255,255,0.16), transparent 70%);
  pointer-events: none;
}
.balance-hero .label {
  font-size: 10px; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--text-faint); font-weight: 800; position: relative;
  display: flex; align-items: center; gap: 6px;
}
.balance-hero .label::before {
  content: ''; width: 6px; height: 6px; border-radius: 50%;
  background: var(--lime); box-shadow: 0 0 10px rgba(255,255,255,0.8);
  animation: pulse-dot 2s ease-in-out infinite;
}
@keyframes pulse-dot { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.balance-hero .num {
  font-size: 34px; font-weight: 800; font-family: 'Sora', sans-serif;
  margin: 6px 0 14px; letter-spacing: -0.02em; position: relative;
  font-variant-numeric: tabular-nums;
  background: linear-gradient(135deg, #ffffff 40%, var(--lime) 130%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.balance-stats { display: flex; gap: 0; border-top: 1px solid rgba(255,255,255,0.08); padding-top: 12px; position: relative; }
.balance-stats > div { flex: 1; font-size: 10px; color: var(--text-faint); position: relative; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; }
.balance-stats > div:not(:last-child)::after {
  content: ''; position: absolute; right: 0; top: 1px; bottom: 1px; width: 1px; background: var(--border-soft);
}
.balance-stats b { display: block; font-size: 14px; font-weight: 800; color: #fff; margin-bottom: 3px; font-family: 'Sora', sans-serif; text-transform: none; letter-spacing: normal; }

.topup-card {
  background: var(--panel-2);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: var(--radius-lg);
  padding: 16px;
  margin-top: 8px;
  box-shadow: 0 10px 26px -18px rgba(0,0,0,0.6);
}
.topup-input-row {
  display: flex; align-items: center; gap: 8px;
  background: var(--bg-elevated);
  border: 1.5px solid var(--border);
  border-radius: 999px;
  height: 48px;
  padding: 0 16px;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.topup-input-row:focus-within { border-color: rgba(255,255,255,0.5); box-shadow: 0 0 0 3px rgba(255,255,255,0.08); }
.topup-dollar { font-size: 18px; font-weight: 800; color: var(--lime); font-family: 'Sora', sans-serif; line-height: 1; }
/* input.field (base .field rule) and .topup-input have equal specificity
   (0,1,1 each) — the element-qualified selector here is what actually lets
   this win over the base rule's own border/background/padding/margin
   instead of losing the cascade and showing a boxed input nested inside
   the pill. */
input.topup-input {
  border: none; background: none; margin: 0; padding: 0;
  font-size: 20px; font-weight: 800; font-family: 'Sora', sans-serif;
  line-height: 1;
  color: var(--text); width: 100%; height: 100%; letter-spacing: -0.01em;
}
input.topup-input:focus { outline: none; }
.topup-input::-webkit-outer-spin-button, .topup-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.topup-min { font-size: 10.5px; color: var(--text-faint); margin-top: 8px; text-align: center; }

#topup-presets { justify-content: center; margin-top: 10px; gap: 6px; }
#topup-presets .chip {
  flex: 1; text-align: center; font-weight: 700;
  padding: 8px 0; border-radius: 999px; font-size: 12px;
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text-dim);
  transition: all 0.15s;
}
#topup-presets .chip:hover:not(.active) { border-color: rgba(255,255,255,0.35); color: var(--text); }
#topup-presets .chip.active {
  background: var(--lime);
  color: #000;
  border-color: transparent;
  box-shadow: 0 4px 16px -4px rgba(255,255,255,0.5);
}

.pay-methods { display: flex; flex-direction: column; gap: 10px; margin-top: 16px; }
.pay-method {
  display: flex; align-items: center; gap: 12px;
  width: 100%;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px;
  cursor: pointer;
  background: var(--bg-elevated);
  transition: transform 0.12s, border-color 0.15s, background 0.15s, box-shadow 0.15s;
  text-align: start;
}
.pay-method:active { transform: scale(0.98); }
.pay-method-badge {
  flex-shrink: 0;
  width: 40px; height: 40px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px;
  overflow: hidden;
}
.pay-method-badge img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pay-method-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.pay-method-text b { font-size: 13.5px; font-weight: 700; color: var(--text); }
.pay-method-text span { font-size: 11px; color: var(--text-faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pay-method-arrow { flex-shrink: 0; width: 16px; height: 16px; color: var(--text-faint); transition: transform 0.15s; }
.pay-method:hover .pay-method-arrow { transform: translateX(3px); color: var(--lime); }

/* Flat neutral row for both methods — same surface every other card in the
   app uses — with color reserved for just the icon badge and the small
   status label, instead of a full tinted gradient wash across the row. */
.pay-method-stars:hover { border-color: rgba(245,158,11,0.45); }
.pay-method-stars .pay-method-badge { background: rgba(245,158,11,0.14); color: #f5b74a; }
.pay-method-stars .pay-method-text span { color: #f5b74a; font-weight: 700; }

.pay-method-crypto:hover { border-color: rgba(255,255,255,0.4); }
.pay-method-crypto .pay-method-badge { background: rgba(255,255,255,0.14); color: var(--lime); }
.pay-method-crypto .pay-method-text span { color: rgba(255,255,255,0.75); font-weight: 700; }

.pay-method-balance:hover { border-color: rgba(255,255,255,0.35); }
.pay-method-balance .pay-method-badge { background: rgba(255,255,255,0.1); color: var(--text); }
.pay-method-balance .pay-method-text span { color: var(--text-dim); font-weight: 700; }
.pay-method-balance:disabled, .pay-method-balance.insufficient { opacity: 0.5; pointer-events: none; }

.history-item {
  display: flex; align-items: center; justify-content: space-between;
  padding: 12px 4px;
  border-radius: var(--radius-sm);
  border-bottom: 1px solid var(--border-soft);
  font-size: 13.5px;
  transition: background 0.15s;
}
.history-item:hover { background: var(--surface); }
.history-item:last-child { border-bottom: none; }
.history-item .reason { color: var(--text-dim); font-size: 12px; }
.history-item .amt { font-weight: 800; font-family: 'Sora', sans-serif; }
.history-item .amt.pos { color: var(--lime); }
.history-item .amt.neg { color: var(--text); }
.history-item .icon-text > .i { background: var(--swatch-bg); border-radius: 9px; padding: 7px; width: 30px; height: 30px; box-sizing: border-box; }

/* ── Gallery ─────────────────────────────────────────────── */
.filter-row { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px; }
.gallery-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-top: 14px;
}
.gallery-item {
  aspect-ratio: 1;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--border);
  position: relative;
}
.gallery-item img, .gallery-item video { width: 100%; height: 100%; object-fit: cover; }
.gallery-item .kind-badge {
  position: absolute; top: 8px; left: 8px;
  background: rgba(0,0,0,0.7);
  backdrop-filter: blur(6px);
  border-radius: 8px;
  padding: 4px 7px;
  font-size: 11px;
}
.empty-state {
  text-align: center;
  padding: 50px 20px;
  color: var(--text-faint);
}
.empty-state svg { width: 44px; height: 44px; margin-bottom: 12px; opacity: 0.6; }

/* ── Profile / settings ──────────────────────────────────── */
.profile-header { display: flex; align-items: center; gap: 14px; padding: 8px 0 18px; }
.avatar {
  width: 54px; height: 54px; border-radius: 16px;
  background: #ffffff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 20px; color: #000;
  font-family: 'Sora', sans-serif;
  overflow: hidden;
}
.avatar.has-photo { background: var(--panel-2); }
.avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.profile-header .name { font-weight: 700; font-size: 16px; }
.profile-header .sub { color: var(--text-faint); font-size: 12.5px; }

.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 4px 0 6px; }
.stat-tile {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 14px 10px;
  text-align: center;
  color: var(--text-dim);
}
.stat-tile .stat-num { font-family: 'Sora', sans-serif; font-size: 19px; font-weight: 800; color: var(--text); margin-top: 6px; }
.stat-tile .stat-label { font-size: 10.5px; color: var(--text-faint); font-weight: 600; margin-top: 2px; text-transform: uppercase; letter-spacing: 0.04em; }

.list-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 15px 4px;
  border-bottom: 1px solid var(--border-soft);
  cursor: pointer;
}
.list-row:last-child { border-bottom: none; }
.list-row .left { display: flex; align-items: center; gap: 12px; }
.list-row .left svg { width: 18px; height: 18px; color: var(--text-dim); }
.list-row .label { font-size: 14px; font-weight: 600; }
.list-row .value { color: var(--text-faint); font-size: 13px; }
.list-row .chev { color: var(--text-faint); }

.referral-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 16px;
  margin-top: 6px;
}
.referral-link {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-size: 12.5px;
  color: var(--text-dim);
  margin: 10px 0;
  overflow-wrap: anywhere;
  font-family: monospace;
}

/* ── Lang sheet ──────────────────────────────────────────── */
.sheet-backdrop {
  position: fixed; inset: 0;
  background: rgba(0,0,0,0.65);
  backdrop-filter: blur(2px);
  z-index: 40;
  display: flex; align-items: flex-end; justify-content: center;
}
.sheet-backdrop.hidden { display: none; }
.sheet {
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  border-radius: 20px 20px 0 0;
  width: 100%; max-width: 560px;
  padding: 18px 18px calc(20px + var(--safe-bottom));
  animation: sheetup 0.22s ease;
}
@keyframes sheetup { from { transform: translateY(24px); opacity: 0; } to { transform: none; opacity: 1; } }
.sheet h3 { margin: 0 0 14px; font-size: 15px; }
.sheet-header-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
.sheet-header-row .icon-btn { width: 30px; height: 30px; }

/* ── Payment History (Profile → Payment History sheet) ── */
#payment-history-list { max-height: 60vh; overflow-y: auto; margin: 0 -4px; }
.pay-history-item {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 4px;
  border-bottom: 1px solid var(--border-soft);
}
.pay-history-item:last-child { border-bottom: none; }
.pay-history-item .icon-text > .i { background: var(--swatch-bg); border-radius: 9px; padding: 7px; width: 30px; height: 30px; box-sizing: border-box; flex-shrink: 0; }
.pay-history-item .info { flex: 1; min-width: 0; }
.pay-history-item .info .method { font-weight: 700; font-size: 13.5px; text-transform: capitalize; }
.pay-history-item .info .date { color: var(--text-faint); font-size: 11.5px; margin-top: 1px; }
.pay-history-item .right { text-align: end; flex-shrink: 0; }
.pay-history-item .paid-amt { font-weight: 800; font-family: 'Sora', sans-serif; font-size: 13.5px; }
.pay-status {
  display: inline-block;
  margin-top: 3px;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
}
.pay-status.paid { background: rgba(255,255,255,0.14); color: var(--lime); }
.pay-status.pending { background: rgba(245,158,11,0.14); color: #f5b74a; }
.pay-status.failed, .pay-status.expired { background: rgba(248,113,113,0.14); color: #ffb3b3; }
.lang-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 13px 4px;
  cursor: pointer;
  border-radius: 10px;
}
.lang-row:hover { background: var(--surface); }
.lang-row .name { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 14.5px; }
.sheet-section-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--text-faint); margin: 0 4px 4px; }

/* ── Workflows — home card tint + picker sheet + composer slots ── */
.tc-lime .icon-wrap { background: rgba(255,255,255,0.12); color: var(--lime); border-color: rgba(255,255,255,0.25); }

.workflow-row { display: flex; align-items: center; gap: 12px; padding: 12px 8px; border-radius: 14px; cursor: pointer; transition: background 0.12s; }
.workflow-row:hover { background: var(--surface); }
.workflow-row .icon-wrap { width: 40px; height: 40px; border-radius: 12px; background: var(--swatch-bg); color: var(--lime); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.workflow-row .info { flex: 1; min-width: 0; }
.workflow-row .info b { font-size: 13.5px; font-weight: 700; color: var(--text); display: block; }
.workflow-row .info span { font-size: 11px; color: var(--text-faint); }

.wf-slot-row { display: flex; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.wf-slot { display: flex; flex-direction: column; align-items: center; gap: 3px; }
.wf-slot-label { font-size: 7.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-faint); }
.wf-slot-tile {
  width: 28px; height: 28px;
  border-radius: 9px;
  border: 1.5px dashed var(--border);
  background: transparent;
  color: var(--text-faint);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: border-color 0.15s, color 0.15s;
}
.wf-slot-tile:hover { border-color: rgba(255,255,255,0.5); color: var(--lime); }
.wf-slot-tile.has-file { border-style: solid; }
.wf-slot-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wf-slot-remove {
  position: absolute; inset: 0;
  background: rgba(0,0,0,0.55);
  display: flex; align-items: center; justify-content: center;
  color: #fff;
  opacity: 0; transition: opacity 0.15s;
}
.wf-slot-tile.has-file:hover .wf-slot-remove, .wf-slot-tile.has-file:active .wf-slot-remove { opacity: 1; }

.hs-grid { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 5px; max-height: 130px; overflow-y: auto; margin: 6px 0; }
.hs-chip {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 5px 2px;
  border-radius: 9px;
  background: var(--surface);
  border: 1px solid var(--border);
  cursor: pointer;
  font-size: 6.5px; font-weight: 600;
  color: var(--text-dim);
  text-align: center;
  line-height: 1.15;
}
.hs-chip .emoji { font-size: 13px; }
.hs-chip.active { background: var(--lime); color: #000; border-color: transparent; }

.wf-select-row { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 5px; margin-top: 6px; }
.wf-select-row .gen-dd-btn { font-size: 10.5px; padding: 7px 8px; }
select.field {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23888' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 8px center;
  background-size: 12px;
  padding-right: 26px;
  cursor: pointer;
}

.wf-autoprompt-btn {
  display: flex; align-items: center; justify-content: center; gap: 5px;
  width: 100%;
  background: rgba(255,255,255,0.1);
  border: 1px solid rgba(255,255,255,0.3);
  color: var(--lime);
  font-size: 10.5px; font-weight: 700; font-family: inherit;
  padding: 7px;
  border-radius: 999px;
  cursor: pointer;
  margin-top: 8px;
  transition: background 0.15s;
}
.wf-autoprompt-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.wf-autoprompt-btn:hover:not(:disabled) { background: rgba(255,255,255,0.18); }
.wf-autoprompt-btn svg { width: 13px; height: 13px; }

/* ── Sound toggle (video models that support it only) ─────────── */
.sound-toggle-row {
  display: flex; align-items: center; justify-content: space-between;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  margin-top: 6px;
  cursor: pointer;
}
.sound-toggle-row .label { display: flex; align-items: center; gap: 6px; font-size: 11px; font-weight: 700; color: var(--text-dim); }
.sound-toggle-row .label svg { width: 13px; height: 13px; }

/* ── Lip Sync composer — deliberately compact. The audio section is one row
   that expands on tap (.lipsync-summary → .lipsync-audio-body). ── */
.lipsync-summary {
  display: flex; align-items: center; gap: 8px;
  width: 100%;
  padding: 10px 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  color: var(--text);
  font: inherit; font-size: 12px; font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.lipsync-summary svg:first-child { width: 15px; height: 15px; flex-shrink: 0; color: var(--text-dim); }
.lipsync-summary span { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lipsync-chev { width: 15px; height: 15px; flex-shrink: 0; color: var(--text-faint); transition: transform 0.18s; }
.lipsync-summary.open .lipsync-chev { transform: rotate(180deg); }

.lipsync-audio-body { display: flex; flex-direction: column; gap: 8px; padding-top: 2px; }
.lipsync-audio-body.hidden { display: none; }

/* "Upload" ⇄ "Our voice" segmented toggle */
.lipsync-mode {
  display: flex; gap: 4px;
  padding: 3px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
}
.lipsync-mode-btn {
  flex: 1;
  padding: 6px 10px;
  border: 0; border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font: inherit; font-size: 11px; font-weight: 700;
  cursor: pointer;
}
.lipsync-mode-btn.active { background: var(--panel-2); color: var(--text); box-shadow: 0 1px 4px rgba(0,0,0,0.3); }

.lipsync-panel { display: flex; flex-direction: column; gap: 8px; }
.lipsync-panel.hidden { display: none; }

.lipsync-audio-btn {
  display: flex; align-items: center; gap: 7px;
  width: 100%;
  padding: 9px 12px;
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: 10px;
  color: var(--text-dim);
  font: inherit; font-size: 11px; font-weight: 700;
  text-align: left;
  cursor: pointer;
}
.lipsync-audio-btn svg { width: 13px; height: 13px; flex-shrink: 0; }
.lipsync-audio-btn.has-clip { border-style: solid; color: var(--text); }
.lipsync-audio-btn span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#lipsync-audio-preview { height: 34px; }

/* Voice picker + Preview on one row */
.lipsync-voice-pick { display: flex; align-items: stretch; gap: 6px; }
.lipsync-voice-pick .gen-dropdown { flex: 1; min-width: 0; }
.lipsync-voice-preview { flex: 0 0 auto; display: flex; align-items: center; gap: 5px; white-space: nowrap; }
.lipsync-voice-preview svg { width: 14px; height: 14px; }

.lipsync-voice-text { min-height: 46px; resize: none; font-size: 12.5px; }
.lipsync-voice-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.lipsync-voice-count { font-size: 11px; color: var(--text-faint); }
.lipsync-voice-count.warn { color: var(--warn, #ffcc66); }
.lipsync-voice-actions .btn-sm { display: flex; align-items: center; gap: 6px; }
.lipsync-voice-compose { display: flex; flex-direction: column; gap: 6px; }
.lipsync-voice-result { display: flex; flex-direction: column; gap: 6px; }
.lipsync-voice-result.hidden, .lipsync-voice-compose.hidden { display: none; }
.lipsync-voice-result audio { height: 34px; }
.lipsync-voice-result-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; font-size: 11px; color: var(--text-dim); font-weight: 700; }
.switch { position: relative; width: 34px; height: 20px; flex-shrink: 0; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch .track { position: absolute; inset: 0; background: rgba(255,255,255,0.14); border-radius: 999px; transition: background 0.15s; }
.switch .track::before { content: ''; position: absolute; width: 16px; height: 16px; left: 2px; top: 2px; background: #fff; border-radius: 50%; transition: transform 0.15s; }
.switch input:checked + .track { background: var(--lime); }
.switch input:checked + .track::before { transform: translateX(14px); }

/* ── Agent chat ──────────────────────────────────────────── */
/* A self-contained, fixed-height flex column — deliberately NOT part of the
   page's own scroll flow the way every other .view is. This is the actual
   fix for the recurring keyboard/scroll bugs the old position:fixed-overlay
   compose bar kept hitting: .agent-scroll below is the ONLY thing that ever
   scrolls, the compose bar is a plain flex child (no viewport-tracking math
   needed), and .view-agent's own height already shrinks for the keyboard —
   so the page itself never moves, and a WebView's "scroll to reveal
   already-visible input" heuristic has nothing to trigger on. Same shape
   any real chat app uses. `height` is declared twice on purpose: the first
   (100vh) is what a browser without dvh support keeps; the second (100dvh)
   overrides it in every browser that understands the newer unit — dvh is
   specifically designed to track exactly this on-screen-keyboard resize. */
.view-agent {
  position: fixed;
  left: 0; right: 0;
  top: var(--topbar-h, 62px);
  height: calc(100vh - var(--topbar-h, 62px) - var(--tabbar-h));
  height: calc(100dvh - var(--topbar-h, 62px) - var(--tabbar-h));
  max-width: 560px;
  margin: 0 auto;
  padding: 0;
  display: flex;
  flex-direction: column;
  z-index: 15;
  background: var(--bg);
  animation: none; /* .view's fadein keyframe transforms — irrelevant here and would only fight the fixed positioning during the brief transition */
}
body.kb-open .view-agent { height: calc(100vh - var(--topbar-h, 62px)); }
body.kb-open .view-agent { height: calc(100dvh - var(--topbar-h, 62px)); }

.agent-header { justify-content: space-between; flex: none; margin: 0; padding: 4px 18px 12px; }
.agent-clear-btn { transition: background 0.15s, color 0.15s, border-color 0.15s; }
.agent-clear-btn:hover { color: #ffb3b3; background: rgba(248,113,113,0.1); border-color: rgba(248,113,113,0.28); }

.agent-scroll {
  flex: 1;
  min-height: 0; /* lets a flex child actually shrink/scroll instead of overflowing its parent */
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  padding: 0 18px;
}
.agent-scroll.hidden { display: none; }
.agent-messages {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 100%;
  padding-bottom: 14px;
}
.agent-messages.hidden { display: none; }
.agent-inputbar.hidden { display: none; }

/* ── AI Agent subscription lock screen ── */
.agent-locked.hidden { display: none; }
.agent-locked {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 20px;
  animation: msg-in 0.35s cubic-bezier(.2,.8,.2,1) both;
}
.agent-locked-badge {
  width: 56px; height: 56px;
  border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(155deg, rgba(255,255,255,0.16), rgba(255,255,255,0.02));
  border: 1px solid var(--border);
  color: var(--lime);
  margin-bottom: 16px;
}
.agent-locked-badge svg { width: 26px; height: 26px; }
.agent-locked h3 { margin: 0 0 8px; font-size: 17px; font-weight: 700; }
.agent-locked .sub { margin: 0 0 22px; color: var(--text-dim); font-size: 13px; line-height: 1.55; max-width: 300px; }
.agent-locked .btn { min-width: 180px; }

/* Consecutive messages from the same sender sit closer together and only
   the last bubble in the run keeps the "tail" corner + timestamp — the
   grouping behavior is toggled by JS (appendAgentMessage / showAgentTyping). */
.msg-row {
  display: flex;
  flex-direction: column;
  animation: msg-in 0.32s cubic-bezier(.2,.8,.2,1) both;
}
.msg-row.user { align-items: flex-end; }
.msg-row.assistant { align-items: flex-start; }
.msg-row.grouped { margin-top: -8px; }
/* The tightened -8px grouping gap above was designed for two small text
   bubbles sitting close together — applied to a big rounded media tile
   (the pending-generation card, or a finished image/video result) it was
   visually pulling the tile up far enough to overlap whatever the PREVIOUS
   row's own -in-progress entrance animation (msg-in below, still animating
   in from translateY(10px) at that exact moment) hadn't finished settling
   into place yet — a brief but real overlap glitch, not just a screenshot
   timing coincidence. A media tile always gets normal spacing instead. */
.msg-row.grouped:has(.msg-bubble-media) { margin-top: 8px; }
@keyframes msg-in { from { opacity: 0; transform: translateY(10px) scale(0.97); } to { opacity: 1; transform: none; } }
/* A media tile (pending-generation spinner, or a finished result) fades in
   in place — no translate/scale motion to ever visually overlap the row
   above it while still settling, unlike a small text bubble where that
   motion is subtle enough not to matter. */
.msg-row:has(> .msg-bubble-media) { animation: msg-in-fade 0.32s ease both; }
@keyframes msg-in-fade { from { opacity: 0; } to { opacity: 1; } }

.msg-bubble {
  max-width: 78%;
  padding: 11px 14px;
  border-radius: 18px;
  font-size: 13.5px;
  line-height: 1.48;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}
.msg-bubble.user {
  background: #ffffff;
  color: #000000;
  border-end-end-radius: 6px;
  box-shadow: 0 4px 14px rgba(255,255,255,0.06);
}
.msg-row.user.grouped .msg-bubble.user { border-start-end-radius: 6px; }
.msg-bubble.assistant {
  background: rgba(255,255,255,0.055);
  backdrop-filter: blur(18px);
  -webkit-backdrop-filter: blur(18px);
  border: 1px solid var(--border);
  color: var(--text);
  border-end-start-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.28);
}
.msg-row.assistant.grouped .msg-bubble.assistant { border-start-start-radius: 6px; }
.msg-bubble.msg-bubble-media {
  background: transparent;
  border: none;
  padding: 0;
  max-width: 85%;
  box-shadow: none;
}

.msg-time {
  font-size: 10px;
  font-weight: 600;
  color: var(--text-faint);
  margin: 5px 5px 0;
  letter-spacing: 0.02em;
}

/* Result card rendered inline in a chat bubble (via the shared
   renderResultPreview) gets a tighter, chat-appropriate treatment than the
   standalone .result-card panel in the tool-detail flow. */
.msg-bubble-media .result-card {
  margin-top: 0;
  max-width: 240px;
  border-radius: 16px;
  box-shadow: 0 10px 28px rgba(0,0,0,0.4);
}
.msg-bubble-media .result-card audio { padding: 10px; }
/* resultGridCard() output, reused verbatim inside a chat bubble — same
   done/error/nsfw states as the Studio results grid, sized for a bubble. */
.msg-bubble-media .result-slot { width: 170px; aspect-ratio: 3/4; margin-top: 0; box-shadow: 0 10px 28px rgba(0,0,0,0.4); }

.msg-bubble-img {
  width: 170px;
  max-width: 100%;
  border-radius: 12px;
  display: block;
}

/* Holds one or several reference-photo thumbnails inside a transparent
   .msg-bubble-media tile — a single photo stays full-size, multiple ones
   shrink into a wrapping row of small squares. */
.msg-photo-strip { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-end; }
.msg-row.assistant .msg-photo-strip { justify-content: flex-start; }
.msg-photo-strip.multi .msg-bubble-img { width: 84px; height: 84px; object-fit: cover; }
/* A photo tile immediately followed by a caption bubble in the same row
   needs its own breathing room since .msg-bubble-media has zero padding. */
.msg-bubble-media + .msg-bubble:not(.msg-bubble-media) { margin-top: 6px; }

/* Animated "thinking" indicator — three dots pulsing in a staggered wave,
   inside an assistant-shaped bubble, instead of a bare spinner. */
.msg-bubble.msg-typing { display: flex; align-items: center; gap: 5px; padding: 14px 18px; }
.agent-typing-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--text-dim);
  animation: agent-dot 1.1s ease-in-out infinite;
}
.agent-typing-dot:nth-child(2) { animation-delay: 0.15s; }
.agent-typing-dot:nth-child(3) { animation-delay: 0.3s; }
@keyframes agent-dot {
  0%, 60%, 100% { transform: scale(0.6); opacity: 0.3; }
  30% { transform: scale(1); opacity: 1; }
}

/* Distinct "generating an image/video" indicator — swapped in for the
   plain typing dots once a tool call actually starts a job, so waiting for
   a 30s image edit reads differently than waiting for a one-line reply. */
.msg-bubble.msg-generating {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-color: rgba(255,255,255,0.18);
}
.msg-generating .ring {
  width: 18px; height: 18px; flex-shrink: 0;
  border-radius: 50%;
  border: 2.5px solid rgba(255,255,255,0.18);
  border-top-color: var(--lime);
  animation: spin 0.7s linear infinite;
}
.msg-generating span { color: var(--lime); font-weight: 700; font-size: 13px; }

/* ── Empty state — designed centerpiece with tappable suggestion chips ── */
.agent-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 54px 18px 20px;
  animation: msg-in 0.35s cubic-bezier(.2,.8,.2,1) both;
}
.agent-empty-badge {
  width: 56px; height: 56px;
  border-radius: 18px;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(155deg, rgba(255,255,255,0.16), rgba(255,255,255,0.02));
  border: 1px solid var(--border);
  color: #fff;
  margin-bottom: 14px;
  position: relative;
  overflow: hidden;
  animation: agent-float 4s ease-in-out infinite;
}
.agent-empty-badge::after {
  content: '';
  position: absolute; inset: 0;
  background: radial-gradient(circle at 28% 20%, rgba(255,255,255,0.3), transparent 60%);
}
.agent-empty-badge svg { width: 26px; height: 26px; position: relative; }
@keyframes agent-float { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-6px); } }

.agent-empty h3 { margin: 0 0 6px; font-size: 16.5px; font-weight: 700; }
.agent-empty p { margin: 0; color: var(--text-dim); font-size: 13px; line-height: 1.55; max-width: 320px; }

.agent-suggest-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin-top: 22px;
}
.agent-suggest-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 9px 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border);
  color: var(--text-dim);
  font-size: 12.5px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s, color 0.15s, transform 0.12s;
}
.agent-suggest-chip svg { width: 14px; height: 14px; flex-shrink: 0; }
.agent-suggest-chip:hover { border-color: rgba(255,255,255,0.4); color: var(--text); background: var(--surface-hover); }
.agent-suggest-chip:active { transform: scale(0.95); }

/* A normal (non-fixed) flex child now — the very last one in .view-agent's
   column, right after .agent-scroll. No viewport/keyboard math needed at
   all: it just sits at the bottom of the flex column, which itself already
   tracks the keyboard via .view-agent's own height (see below). This is
   what actually ended the "compose bar jumps near the header" /
   "chat scrolls the wrong way" saga — those were all symptoms of fighting
   position:fixed against the page's own scroll and an unreliable keyboard
   height signal; a real flex layout just doesn't have that problem. */
.agent-inputbar {
  flex: none;
  background: rgba(6, 6, 6, 0.92);
  backdrop-filter: blur(16px);
  border-top: 1px solid var(--border);
  padding: 12px 14px calc(12px + var(--safe-bottom));
}

/* Queued-photo preview reuses .ref-strip/.ref-thumb (Edit Image tool's own
   reference-photo strip) verbatim — this override just adds the bottom
   margin that strip doesn't need in its original context, and hides it
   when nothing's queued. */
.agent-photo-strip.hidden { display: none; }
.agent-photo-strip { margin: 0 0 8px 2px; }

/* Pill-shaped compose row — attach + text + send read as one bespoke
   control, not a row of generic .icon-btn squares. */
.agent-compose {
  display: flex;
  align-items: center;
  gap: 4px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 5px 5px 6px;
  transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
}
.agent-compose:focus-within {
  border-color: rgba(255,255,255,0.4);
  background: var(--surface-hover);
  box-shadow: 0 0 0 3px rgba(255,255,255,0.06);
}

.agent-attach-btn, .agent-settings-btn {
  width: 34px; height: 34px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 1px solid var(--border-soft);
  background: rgba(255,255,255,0.06);
  color: var(--text-dim);
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: background 0.15s, color 0.15s, transform 0.12s, border-color 0.15s;
}
.agent-attach-btn svg, .agent-settings-btn svg { width: 16px; height: 16px; }
.agent-attach-btn:hover, .agent-settings-btn:hover { background: rgba(255,255,255,0.14); color: var(--text); border-color: rgba(255,255,255,0.3); }
.agent-attach-btn:active { transform: scale(0.88) rotate(-8deg); }
.agent-settings-btn:active { transform: scale(0.88) rotate(45deg); }

.agent-text-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  color: var(--text);
  font-size: 14px;
  font-family: inherit;
  padding: 9px 4px;
}
.agent-text-input:focus { outline: none; }
.agent-text-input::placeholder { color: var(--text-faint); }

/* Send button: circular, reacts on hover/press instead of a flat icon-btn square. */
.agent-send-btn {
  width: 36px; height: 36px;
  flex-shrink: 0;
  border-radius: 50%;
  border: none;
  background: #ffffff;
  color: #000000;
  display: flex; align-items: center; justify-content: center;
  cursor: pointer;
  transition: transform 0.15s cubic-bezier(.34,1.56,.64,1), box-shadow 0.2s, background 0.15s, opacity 0.15s;
}
.agent-send-btn svg { width: 15px; height: 15px; transition: transform 0.15s; }
.agent-send-btn:hover:not(:disabled) { background: rgba(255,255,255,0.9); box-shadow: 0 6px 18px rgba(255,255,255,0.3); transform: scale(1.07); }
.agent-send-btn:hover:not(:disabled) svg { transform: translate(1px, -1px); }
.agent-send-btn:active:not(:disabled) { transform: scale(0.88); }
.agent-send-btn:disabled { opacity: 0.35; cursor: not-allowed; }

/* ── Bottom nav ──────────────────────────────────────────── */
.tabbar {
  position: fixed;
  bottom: 0; left: 0; right: 0;
  max-width: 560px;
  margin: 0 auto;
  background: rgba(6,6,6,0.92);
  backdrop-filter: blur(16px);
  border-top: 1px solid var(--border);
  display: flex;
  padding: 8px 6px calc(8px + var(--safe-bottom));
  z-index: 30;
  transition: transform 0.2s ease;
}
/* While the on-screen keyboard is open, slide the tabbar out of the way and
   let .view-agent reclaim the space it was leaving for it (see .view-agent
   height rules above the Agent chat section) — .composer-dock (the Studio
   tool composer) needs no equivalent since its tools aren't a flex-height
   layout the way the Agent chat now is. */
body.kb-open .tabbar { transform: translateY(100%); }
.tab {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 6px 0;
  cursor: pointer;
  color: var(--text-faint);
  border-radius: 12px;
  transition: color 0.15s;
  background: none;
  border: none;
}
.tab svg { width: 21px; height: 21px; }
.tab .tlabel { font-size: 10.5px; font-weight: 700; }
.tab.active { color: #ffffff; }

/* ── Toast ───────────────────────────────────────────────── */
.toast {
  position: fixed;
  bottom: calc(96px + var(--safe-bottom));
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: var(--bg-elevated);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 11px 18px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  box-shadow: 0 10px 30px rgba(0,0,0,0.6);
  opacity: 0;
  transition: opacity 0.25s, transform 0.25s;
  z-index: 50;
  pointer-events: none;
  max-width: 90%;
  text-align: center;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }

/* ── Loading skeleton ────────────────────────────────────── */
.skeleton {
  background: linear-gradient(90deg, var(--surface) 25%, var(--surface-hover) 37%, var(--surface) 63%);
  background-size: 400% 100%;
  animation: shimmer 1.4s ease infinite;
  border-radius: var(--radius-md);
}
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: -100% 0; } }

/* ── Inline icon utility ─────────────────────────────────── */
.i { width: 15px; height: 15px; display: inline-block; vertical-align: -3px; flex-shrink: 0; }
.i-sm { width: 13px; height: 13px; vertical-align: -2px; }
.i-lg { width: 20px; height: 20px; vertical-align: -5px; }
.icon-text { display: inline-flex; align-items: center; gap: 6px; }
.chip .icon-text, .btn .icon-text { gap: 7px; }

.result-badge {
  position: absolute; top: 8px; left: 8px;
  width: 26px; height: 26px;
  background: rgba(0,0,0,0.75);
  backdrop-filter: blur(8px);
  border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  color: var(--text);
}
[dir='rtl'] .result-badge, [dir='rtl'] .kind-badge { left: auto; right: 8px; }

::-webkit-scrollbar { width: 0; height: 0; }

/* ── AI Influencer workflow panel ──────────────────────────────
   Header row (title + Auto-Prompt) above the reference slots, matching the
   reference site's floating-bar workflow panel. The slots below it stack
   label-over-tiles, and each slot grows horizontally as photos are added
   rather than reflowing the row. */
.wf-panel-head {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding-bottom: 8px; margin-bottom: 8px;
  border-bottom: 1px solid var(--border-soft);
}
.wf-panel-title {
  font-size: 11px; font-weight: 800; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--lime);
  white-space: nowrap;
}
/* In the header the Auto-Prompt button is an inline chip, not the
   full-width pill it is when it sits on its own row. */
.wf-panel-head .wf-autoprompt-btn {
  width: auto; margin-top: 0;
  padding: 5px 10px;
  font-size: 9.5px;
  background: rgba(255,255,255,0.05);
  border-color: rgba(255,255,255,0.1);
  color: var(--text-dim);
}
.wf-panel-head .wf-autoprompt-btn:hover:not(:disabled) {
  background: rgba(255,255,255,0.1); color: var(--text);
}
.wf-panel-head .wf-autoprompt-btn svg { width: 11px; height: 11px; }

/* Multi-photo slot: label above, tiles in a row beneath it. */
.wf-slot--multi { align-items: flex-start; gap: 5px; }
.wf-slot--multi .wf-slot-label { margin-bottom: 1px; }
.wf-slot-tiles { display: flex; gap: 5px; align-items: center; }
/* The add tile keeps the dashed affordance; filled thumbs go solid via the
   shared .has-file rule above. */
.wf-slot-add { border-style: dashed; }

/* ── Character Replacement toggles ─────────────────────────────
   This workflow has no prompt box — the instruction is generated from these
   two switches, so the hint underneath states in plain words what the
   current combination will do. */
.wf-toggle-row { display: flex; flex-wrap: wrap; gap: 8px; margin: 8px 0 6px; }
.wf-toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 6px 10px; border-radius: 999px;
  background: var(--swatch-bg); border: 1px solid var(--border-soft);
  font-size: 11px; font-weight: 600; color: var(--text-dim);
  cursor: pointer; user-select: none;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
}
.wf-toggle:has(input:checked) {
  border-color: rgba(255,255,255,0.45);
  background: rgba(255,255,255,0.1);
  color: var(--lime);
}
.wf-toggle input { accent-color: var(--lime); width: 13px; height: 13px; margin: 0; cursor: pointer; }
.wf-hint { font-size: 10.5px; color: var(--text-faint); margin-bottom: 6px; line-height: 1.45; }

/* ══════════════════════════════════════════════════════════════════════
   LIQUID GLASS
   ──────────────────────────────────────────────────────────────────────
   A frosted-glass pass over the existing dark theme, in the iPhone Pro
   idiom: translucent panes that pick up what is behind them, a bright
   specular edge where light catches the rim, and springy, physical motion.

   PERFORMANCE IS PART OF THE DESIGN HERE. `backdrop-filter` is one of the
   most expensive things a mobile browser can do, and putting it on repeated
   scrolling items (every tool card, every gallery tile) visibly tears
   scrolling on a phone. So real blur is reserved for the FIXED chrome —
   topbar, tabbar, composer dock, sheets — which never scrolls and is only a
   handful of layers. Repeating items get the same visual language built
   from gradients and borders, which cost nothing.
   ══════════════════════════════════════════════════════════════════════ */

:root {
  /* Glass panes: a tinted base the blur shows through. */
  --glass-bg: rgba(22, 22, 24, 0.62);
  --glass-bg-strong: rgba(16, 16, 18, 0.82);
  --glass-blur: 28px;
  --glass-saturate: 180%;

  /* The specular rim. Light catches the top edge brightest, wraps the
     sides faintly, and the bottom edge sits in shadow — that asymmetry is
     what reads as "a pane of glass" rather than "a translucent box". */
  --glass-rim-top: rgba(255, 255, 255, 0.28);
  --glass-rim-side: rgba(255, 255, 255, 0.08);
  --glass-rim-bottom: rgba(0, 0, 0, 0.35);

  /* Depth. Two shadows: a tight contact shadow and a wide ambient one. */
  --glass-shadow: 0 1px 2px rgba(0,0,0,0.4), 0 16px 40px -12px rgba(0,0,0,0.7);
  --glass-shadow-lift: 0 2px 6px rgba(0,0,0,0.45), 0 28px 60px -16px rgba(0,0,0,0.8);

  /* Motion: a gentle spring with slight overshoot for entrances, and a
     fast, non-overshooting curve for taps (overshoot on press feels
     mushy rather than responsive). */
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --tap: cubic-bezier(0.4, 0, 0.2, 1);

  --radius-glass: 26px;
}

/* ── Ambient field ────────────────────────────────────────────────────
   Glass needs something behind it to refract, otherwise blurring a flat
   black background produces flat black. This is a fixed, non-scrolling
   mesh of very low-opacity colour that the panes pick up as they pass
   over it. Fixed + pointer-events:none so it costs nothing to scroll. */
body::before {
  content: '';
  position: fixed;
  inset: -20%;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(42rem 42rem at 12% 8%, rgba(255,255,255,0.05), transparent 62%),
    radial-gradient(38rem 38rem at 88% 14%, rgba(255,255,255,0.035), transparent 60%),
    radial-gradient(46rem 46rem at 50% 96%, rgba(255,255,255,0.03), transparent 64%);
  filter: blur(20px);
}
#app { position: relative; z-index: 1; }

/* ── The glass surface itself ─────────────────────────────────────────
   Applied via a shared class so the treatment stays in one place. The
   ::before carries the specular rim as a gradient border (mask-composite
   keeps it a hairline outline rather than filling the shape). */
.topbar,
.tabbar,
.composer-card,
.sheet,
.um-panel {
  /* No `position` here on purpose. .topbar and .tabbar are position:fixed
     and .composer-dock is fixed too; declaring position:relative in this
     shared rule overrode that and dropped the tab bar into the document
     flow directly under the header. Each of these already establishes its
     own containing block, so the ::before rim anchors correctly without it. */
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border: none;
  box-shadow: var(--glass-shadow);
}
.topbar::before,
.tabbar::before,
.composer-card::before,
.sheet::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(
    to bottom,
    var(--glass-rim-top) 0%,
    var(--glass-rim-side) 30%,
    var(--glass-rim-side) 70%,
    var(--glass-rim-bottom) 100%
  );
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 1;
}

/* Fixed chrome sits above the ambient field and blurs the content under it. */
.topbar { border-bottom: none; }
.tabbar { border-top: none; }

.composer-card {
  border-radius: var(--radius-glass);
  box-shadow: var(--glass-shadow-lift);
}
.sheet {
  border-radius: var(--radius-glass) var(--radius-glass) 0 0;
  background: var(--glass-bg-strong);
  box-shadow: var(--glass-shadow-lift);
}

/* ── Repeating items: same language, no backdrop-filter ───────────────
   Tool cards and result tiles can appear dozens at a time inside a
   scroller. They get the rim and the depth, built from a gradient — which
   composites on the GPU for free — instead of a blur. */
.tool-card,
.result-slot,
.um-stat,
.topup-card,
.balance-hero,
.list-row,
.workflow-row {
  position: relative;
  isolation: isolate;
}
.tool-card::after,
.topup-card::after,
.balance-hero::after {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(to bottom, rgba(255,255,255,0.3), rgba(255,255,255,0.04) 45%, rgba(0,0,0,0.25));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  z-index: 3;
}

.balance-hero,
.topup-card {
  background: linear-gradient(160deg, rgba(255,255,255,0.07), rgba(255,255,255,0.02));
  box-shadow: var(--glass-shadow);
}

/* ── Motion ───────────────────────────────────────────────────────────
   Everything tappable compresses slightly and springs back. The scale is
   deliberately small (0.96–0.97): enough to feel like the surface gave
   under the finger, not enough to look like a bounce. */
.tool-card,
.btn,
.chip,
.tab,
.list-row,
.icon-btn,
.pay-method,
.workflow-row,
.wf-slot-tile,
.gen-dd-btn {
  transition:
    transform 0.22s var(--spring),
    background 0.2s var(--tap),
    border-color 0.2s var(--tap),
    box-shadow 0.25s var(--ease-out),
    opacity 0.2s var(--tap);
  will-change: transform;
}
.tool-card:active,
.btn:not(:disabled):active,
.chip:active,
.list-row:active,
.pay-method:active,
.workflow-row:active,
.wf-slot-tile:active,
.gen-dd-btn:active { transform: scale(0.965); }
.icon-btn:active, .tab:active { transform: scale(0.9); }

/* Tool cards lift toward the light on press rather than just shrinking. */
.tool-card:active { box-shadow: var(--glass-shadow-lift); }

/* Tab switch: the active pill settles rather than snapping. */
.tab.active svg { transition: transform 0.3s var(--spring); transform: translateY(-1px) scale(1.06); }

/* ── Entrances ────────────────────────────────────────────────────────
   Views and result tiles rise into place. Staggered so a grid resolves as
   a wave instead of everything appearing at once. */
@keyframes glassRise {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
  to   { opacity: 1; transform: none; }
}
/* Views fade only — NEVER transform. A transform on .view (even the
   identity matrix an animation leaves behind under fill-mode: both) makes
   it the containing block for position:fixed descendants, which pulled the
   composer dock out of the viewport bottom and left it floating mid-screen.
   The rise is applied to the inner content instead, which contains nothing
   fixed. */
@keyframes glassFadeIn { from { opacity: 0; } to { opacity: 1; } }
.view:not(.hidden) { animation: glassFadeIn 0.26s var(--ease-out) both; }
.tool-grid .tool-card { animation: glassRise 0.4s var(--ease-out) both; }
.tool-grid .tool-card:nth-child(1) { animation-delay: 0.02s; }
.tool-grid .tool-card:nth-child(2) { animation-delay: 0.06s; }
.tool-grid .tool-card:nth-child(3) { animation-delay: 0.10s; }
.tool-grid .tool-card:nth-child(4) { animation-delay: 0.14s; }
.tool-grid .tool-card:nth-child(5) { animation-delay: 0.18s; }

/* Sheets slide up from the edge with the spring. */
@keyframes glassSheetIn {
  from { transform: translateY(100%); }
  to   { transform: none; }
}
.sheet-backdrop:not(.hidden) .sheet { animation: glassSheetIn 0.42s var(--ease-out) both; }
.sheet-backdrop:not(.hidden) {
  animation: glassFade 0.25s var(--ease-out) both;
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
@keyframes glassFade { from { opacity: 0; } to { opacity: 1; } }

/* ── Buttons ──────────────────────────────────────────────────────────
   The primary action keeps its lime fill but gains a specular top edge and
   a coloured glow, so it reads as lit glass rather than a flat rectangle. */
.generate-btn-lime, .btn-primary {
  position: relative;
  overflow: hidden;
  box-shadow: 0 1px 0 rgba(255,255,255,0.4) inset, 0 8px 24px -8px rgba(255,255,255,0.55);
}
.generate-btn-lime::after, .btn-primary::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 45%;
  background: linear-gradient(to bottom, rgba(255,255,255,0.22), transparent);
  pointer-events: none;
}
.generate-btn-lime:disabled, .btn-primary:disabled { box-shadow: none; }

/* ── Pending state ────────────────────────────────────────────────────
   A generation in flight gets a slow sheen travelling across the tile, so
   "working" is legible at a glance without a spinner competing with the
   stage text. */
@keyframes glassSheen {
  from { transform: translateX(-120%); }
  to   { transform: translateX(220%); }
}
.result-slot.pending { overflow: hidden; }
.result-slot.pending::after {
  content: '';
  position: absolute;
  inset: 0;
  width: 45%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,0.09), transparent);
  animation: glassSheen 1.9s var(--ease-out) infinite;
  pointer-events: none;
  z-index: 1;
}

/* ── Respect the platform ─────────────────────────────────────────────
   Someone who has asked for less motion gets the glass without the
   movement — and a device that cannot do backdrop-filter falls back to a
   solid tint rather than a transparent, unreadable panel. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar, .tabbar, .composer-card, .sheet, .um-panel {
    background: #141416;
  }
}

/* ── Tool tiles as frosted white glass ────────────────────────────────
   These stay light rather than going dark — they were deliberately white
   (see .tool-card above) and a bright pane is squarely in the iOS glass
   idiom anyway. The change is that they are no longer flat #fff: they are
   translucent white over the ambient field, with a specular rim and a soft
   drop shadow, so they read as lit glass sitting above the background.

   Six tiles is a small enough count for a real backdrop-filter; the same
   treatment is NOT applied to gallery tiles, which can number dozens. */
.tool-card {
  background: linear-gradient(158deg, rgba(255,255,255,0.94), rgba(255,255,255,0.82));
  -webkit-backdrop-filter: blur(18px) saturate(160%);
  backdrop-filter: blur(18px) saturate(160%);
  border: none;
  box-shadow:
    0 1px 1px rgba(0,0,0,0.18),
    0 12px 28px -10px rgba(0,0,0,0.55);
}
.tool-card:hover { background: linear-gradient(158deg, rgba(255,255,255,0.98), rgba(255,255,255,0.88)); }
/* Rim: bright along the top where light lands, dark along the bottom. */
.tool-card::after {
  background: linear-gradient(to bottom, rgba(255,255,255,0.95), rgba(255,255,255,0.25) 40%, rgba(0,0,0,0.14));
}
/* The icon well reads as pressed into the glass rather than sitting on it. */

.tool-card-badge { box-shadow: 0 2px 8px -2px rgba(255,255,255,0.7); }

/* ── Tab bar: the active tab lifts on a lit pill ──────────────────── */
.tab { position: relative; }
.tab.active::before {
  content: '';
  position: absolute;
  inset: 2px 8px;
  border-radius: 14px;
  background: rgba(255,255,255,0.09);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.18);
  z-index: -1;
  animation: glassRise 0.28s var(--spring) both;
}

/* ══ Studio tile artwork ═══════════════════════════════════════════
   Each tool gets its own illustrated cover: a colour wash matched to the
   tool plus a vector motif of what it actually does. Inline SVG data URIs,
   not downloaded photographs — stock imagery carries licensing this project
   cannot verify, an external host would add a request per tile (the exact
   cost just removed from the gallery), and the Telegram WebView CSP blocks
   most third-party asset hosts anyway. Vector art also stays sharp at any
   density and weighs a couple of KB.
   ══════════════════════════════════════════════════════════════════ */
.tool-card {
  min-height: 176px;
  padding: 0;
  justify-content: flex-end;
  overflow: hidden;
}
/* The art sits behind the label, which gets its own scrim so the text
   stays legible over any part of the illustration. */
/* No background of its own. The art box is inset from the card edge, so
   anything painted on it draws a visible rectangle inside the card — a
   tinted 'stage' here read as a black box sitting behind the artwork.
   The card already supplies its own surface and key light. */
.tool-card .tile-art {
  position: absolute;
  inset: 0 0 auto 0;
  height: 108px;
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  z-index: 0;
}
.tool-card .tile-body {
  position: relative;
  z-index: 2;
  padding: 10px 12px 12px;
  width: 100%;
  background: linear-gradient(to top, rgba(255,255,255,0.97) 62%, rgba(255,255,255,0));
}


.tool-card h3 { margin: 0 0 2px; }
/* A slow drift on press makes the art feel like a real surface. */
.tool-card:active .tile-art { transform: scale(1.06); }
.tool-card .tile-art { transition: transform 0.5s var(--ease-out); }


/* ══════════════════════════════════════════════════════════════════════
   LIQUID GLASS — FULL COVERAGE
   ──────────────────────────────────────────────────────────────────────
   The first glass pass covered the shell. This extends the same language
   to every remaining surface: wallet, gallery, profile, agent, the tool
   composer, sheets, controls, inputs and feedback.

   Two rules hold throughout, and they are what keep this from becoming a
   scrolling-performance problem:

     1. Real `backdrop-filter` goes ONLY on surfaces that are few and do
        not scroll — fixed bars, overlays, transient panels. Everything
        that repeats in a list or grid gets the identical look built from
        gradients and borders, which the GPU composites for free.
     2. Anything tappable shares one motion contract: a small compression
        on press with a spring release, so the whole app feels like one
        physical material rather than a set of separately-tuned widgets.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Shared recipes ───────────────────────────────────────────────────
   `--pane` is the flat (cheap) glass used by repeating items; `--pane-lit`
   adds the top-edge highlight for things that should feel raised. */
:root {
  --pane: linear-gradient(158deg, rgba(255,255,255,0.075), rgba(255,255,255,0.028));
  --pane-lit: linear-gradient(158deg, rgba(255,255,255,0.13), rgba(255,255,255,0.045));
  --pane-border: 1px solid rgba(255,255,255,0.1);
  --pane-shadow: 0 1px 1px rgba(0,0,0,0.28), 0 10px 26px -14px rgba(0,0,0,0.75);
  --well: linear-gradient(180deg, rgba(0,0,0,0.34), rgba(0,0,0,0.18));
  --well-inset: inset 0 1px 2px rgba(0,0,0,0.45), inset 0 -1px 0 rgba(255,255,255,0.06);
}

/* ── Panels & cards ───────────────────────────────────────────────── */
.stat-tile,
.referral-card,
.list-row,
.workflow-row,
.lang-row,
.history-item,
.pay-history-item,
.result-card,
.empty-state,
.agent-locked,
.status-banner,
.um-stat,
.gallery-item,
.hs-grid,
.mode-toggle-rail {
  background: var(--pane);
  border: var(--pane-border);
  box-shadow: var(--pane-shadow);
}

/* Rows that sit in a stack read better as one continuous strip of glass
   with hairline separators than as a run of detached cards. */
.list-row, .lang-row, .history-item, .pay-history-item, .workflow-row {
  border-radius: var(--radius-md);
}
.list-row:hover, .lang-row:hover, .workflow-row:hover {
  background: var(--pane-lit);
  border-color: rgba(255,255,255,0.18);
}

.referral-card, .agent-locked, .empty-state {
  background: var(--pane-lit);
  border-radius: var(--radius-glass);
}

/* Stat tiles get the specular rim — they are the hero numbers on Profile. */
.stat-tile { position: relative; overflow: hidden; }
.stat-tile::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
}

/* ── Controls ─────────────────────────────────────────────────────── */
.chip,
.hs-chip,
.btn-ghost,
.icon-btn,
.credit-pill,
.gen-dd-btn,
.gen-stepper,
.gen-step-btn,
.agent-suggest-chip,
.ref-thumb-add,
.wf-slot-tile,
.result-download-btn,
.agent-settings-btn,
.agent-attach-btn {
  background: var(--pane);
  border: var(--pane-border);
  transition:
    transform 0.2s var(--spring),
    background 0.18s var(--tap),
    border-color 0.18s var(--tap),
    box-shadow 0.22s var(--ease-out);
}
.chip:hover, .hs-chip:hover, .btn-ghost:hover, .icon-btn:hover,
.credit-pill:hover, .gen-dd-btn:hover, .agent-suggest-chip:hover,
.ref-thumb-add:hover, .wf-slot-tile:hover {
  background: var(--pane-lit);
  border-color: rgba(255,255,255,0.2);
}
.chip:active, .hs-chip:active, .btn-ghost:active, .gen-dd-btn:active,
.agent-suggest-chip:active, .gen-step-btn:active { transform: scale(0.94); }

/* Selected state is lit from within rather than merely tinted. */
.chip.active, .hs-chip.active {
  background: linear-gradient(158deg, rgba(255,255,255,0.22), rgba(255,255,255,0.08));
  border-color: rgba(255,255,255,0.45);
  color: var(--lime);
  box-shadow: 0 0 0 1px rgba(255,255,255,0.16), 0 6px 18px -8px rgba(255,255,255,0.5);
}

/* The credit pill is a small piece of lit glass in the header. */
.credit-pill {
  border-radius: 999px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.16), 0 4px 14px -6px rgba(0,0,0,0.6);
}

/* Payment methods are the most consequential taps in the app — give them
   real presence and a clear press. */
.pay-method {
  background: var(--pane-lit);
  border: var(--pane-border);
  border-radius: var(--radius-md);
  box-shadow: var(--pane-shadow);
}
.pay-method:hover { border-color: rgba(255,255,255,0.22); }
.pay-method-badge {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.25), 0 4px 12px -4px rgba(0,0,0,0.6);
}

/* Dropdown panels are transient overlays — few, and worth real blur. */
.gen-dd-panel {
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(24px) saturate(170%);
  backdrop-filter: blur(24px) saturate(170%);
  border: var(--pane-border);
  border-radius: var(--radius-md);
  box-shadow: var(--glass-shadow-lift);
  animation: glassRise 0.2s var(--ease-out) both;
}
.gen-dd-option { transition: background 0.14s var(--tap), color 0.14s var(--tap); }
.gen-dd-option:hover { background: rgba(255,255,255,0.08); }
.gen-dd-option.selected {
  background: linear-gradient(90deg, rgba(255,255,255,0.16), transparent);
  color: var(--lime);
}

/* Toggle rails read as a groove with a sliding pane inside it. */
.mode-toggle-rail { background: var(--well); box-shadow: var(--well-inset); }
.mode-toggle-btn { transition: color 0.2s var(--tap); }
.mode-toggle-btn.active {
  background: var(--pane-lit);
  box-shadow: 0 1px 0 rgba(255,255,255,0.16) inset, 0 4px 12px -6px rgba(0,0,0,0.7);
}

/* ── Inputs: recessed wells, not raised panes ─────────────────────── */
.composer-prompt,
.topup-input,
.agent-text-input,
.referral-link {
  background: var(--well);
  border: 1px solid rgba(255,255,255,0.08);
  box-shadow: var(--well-inset);
  transition: border-color 0.2s var(--tap), box-shadow 0.2s var(--ease-out);
}
.composer-prompt:focus,
.topup-input:focus,
.agent-text-input:focus {
  outline: none;
  border-color: rgba(255,255,255,0.4);
  box-shadow: var(--well-inset), 0 0 0 3px rgba(255,255,255,0.1);
}

/* The drop zone is an empty frame waiting to be filled. */
.dropzone, .ref-thumb-add, .wf-slot-add {
  background: var(--well);
  border: 1.5px dashed rgba(255,255,255,0.18);
  transition: border-color 0.2s var(--tap), background 0.2s var(--tap), transform 0.2s var(--spring);
}
.dropzone:hover, .ref-thumb-add:hover, .wf-slot-add:hover {
  border-color: rgba(255,255,255,0.5);
  background: rgba(255,255,255,0.05);
}

/* ── Gallery & results ────────────────────────────────────────────── */
.result-slot, .gallery-item {
  box-shadow: var(--pane-shadow);
  transition: transform 0.25s var(--spring), box-shadow 0.25s var(--ease-out);
}
.result-slot.done:active, .gallery-item:active { transform: scale(0.965); }
.result-badge, .result-play-badge, .count-badge {
  -webkit-backdrop-filter: blur(10px) saturate(160%);
  backdrop-filter: blur(10px) saturate(160%);
  border: 1px solid rgba(255,255,255,0.14);
}
/* Sticky selection bar floats above the grid on its own pane. */
.bulk-bar {
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  backdrop-filter: blur(20px) saturate(170%);
  border: var(--pane-border);
  border-radius: var(--radius-md);
  box-shadow: var(--glass-shadow-lift);
  animation: glassRise 0.25s var(--spring) both;
}

/* Lightbox: the content floats over a deeply blurred field. */
.lightbox {
  -webkit-backdrop-filter: blur(28px) saturate(140%);
  backdrop-filter: blur(28px) saturate(140%);
  background: rgba(4, 4, 6, 0.72);
  animation: glassFade 0.24s var(--ease-out) both;
}
.lightbox-media { animation: glassRise 0.34s var(--spring) both; box-shadow: var(--glass-shadow-lift); }
.lightbox-close, .lightbox-actions .btn {
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
}

/* ── Agent chat ───────────────────────────────────────────────────── */
.agent-inputbar {
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
  border-top: 1px solid rgba(255,255,255,0.09);
}
.agent-compose {
  background: var(--well);
  border: 1px solid rgba(255,255,255,0.09);
  box-shadow: var(--well-inset);
  border-radius: 999px;
}
/* Assistant bubbles are glass; the user's own stay solid so the two are
   instantly distinguishable at a glance. */
.msg-bubble.assistant {
  background: var(--pane-lit);
  border: var(--pane-border);
  box-shadow: var(--pane-shadow);
}
.msg-row { animation: glassRise 0.3s var(--ease-out) both; }
.agent-empty-badge, .agent-locked-badge {
  background: var(--pane-lit);
  border: var(--pane-border);
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 8px 24px -10px rgba(0,0,0,0.8);
}
.agent-send-btn {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.35), 0 6px 18px -6px rgba(255,255,255,0.6);
  transition: transform 0.2s var(--spring), box-shadow 0.2s var(--ease-out);
}
.agent-send-btn:active { transform: scale(0.9); }

/* ── Feedback ─────────────────────────────────────────────────────── */
.toast {
  background: var(--glass-bg-strong);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  backdrop-filter: blur(20px) saturate(170%);
  border: var(--pane-border);
  box-shadow: var(--glass-shadow-lift);
}
.status-banner { border-radius: var(--radius-md); }
.progress-rail { background: var(--well); box-shadow: var(--well-inset); }

/* Skeletons shimmer like light moving across glass rather than a flat pulse. */
.skeleton {
  background: linear-gradient(90deg, rgba(255,255,255,0.04) 25%, rgba(255,255,255,0.1) 50%, rgba(255,255,255,0.04) 75%);
  background-size: 200% 100%;
  animation: glassShimmer 1.4s linear infinite;
}
@keyframes glassShimmer {
  from { background-position: 200% 0; }
  to   { background-position: -200% 0; }
}

/* ── Section headings ─────────────────────────────────────────────── */
.section-title, .sheet-section-label, .wf-slot-label {
  letter-spacing: 0.08em;
}

/* ── View transitions ─────────────────────────────────────────────── */
/* Lists resolve as a short wave rather than all at once. */
.stat-grid .stat-tile,
.pay-methods .pay-method,
#lang-list .lang-row,
#workflow-list .workflow-row {
  animation: glassRise 0.34s var(--ease-out) both;
}
.stat-grid .stat-tile:nth-child(2),
.pay-methods .pay-method:nth-child(2),
#lang-list .lang-row:nth-child(2),
#workflow-list .workflow-row:nth-child(2) { animation-delay: 0.05s; }
.stat-grid .stat-tile:nth-child(3),
.pay-methods .pay-method:nth-child(3),
#lang-list .lang-row:nth-child(3),
#workflow-list .workflow-row:nth-child(3) { animation-delay: 0.1s; }
#lang-list .lang-row:nth-child(n+4),
#workflow-list .workflow-row:nth-child(n+4) { animation-delay: 0.14s; }

/* The composer dock rises with the spring when a tool opens. */
.composer-dock { animation: glassRise 0.36s var(--ease-out) both; }

/* ══════════════════════════════════════════════════════════════════════
   COMPOSER — generation configuration
   ──────────────────────────────────────────────────────────────────────
   This is the densest, most-used surface in the app and it was the least
   considered: native OS checkboxes sitting next to custom glass, reference
   slots too small to see what was in them, and controls at three different
   heights with no shared rhythm.

   Everything here is now built from one set of measurements — a 44px
   control height (the accepted minimum comfortable touch target), 12px
   gutters, and a single radius scale — so the panel reads as one instrument
   rather than a pile of widgets.
   ══════════════════════════════════════════════════════════════════════ */

.composer-card {
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* ── Panel header ─────────────────────────────────────────────────── */
.wf-panel-head {
  padding-bottom: 10px;
  margin-bottom: 2px;
  border-bottom: 1px solid rgba(255,255,255,0.07);
}
.wf-panel-title {
  font-size: 11.5px;
  font-weight: 800;
  letter-spacing: 0.1em;
  /* A soft glow rather than flat lime — it is a heading, not a control. */
  color: var(--lime);
  text-shadow: 0 0 18px rgba(255,255,255,0.35);
}

/* ── Reference slots ──────────────────────────────────────────────── */
.wf-slot-row { gap: 14px; margin-bottom: 2px; }
.wf-slot-label {
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.09em;
  color: rgba(255,255,255,0.42);
  margin-bottom: 6px;
}
.wf-slot-tile {
  width: 54px; height: 54px;
  border-radius: 15px;
  box-shadow: var(--pane-shadow);
}
.wf-slot-tile.has-file {
  border: 1px solid rgba(255,255,255,0.22);
  box-shadow: 0 2px 8px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.18);
}
.wf-slot-add { border-width: 1.5px; }
.wf-slot-add svg { width: 16px; height: 16px; opacity: 0.7; }
.wf-slot-remove {
  background: rgba(0,0,0,0.62);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}

/* ── Toggles: real switches, not OS checkboxes ────────────────────
   The native control was the single ugliest thing on this panel — a
   platform checkbox rendered in the middle of a custom glass UI. The
   input is now visually hidden (still focusable and still the thing
   that holds state, so keyboard and screen readers are unaffected) and
   a switch is drawn from the label. */
.wf-toggle-row { gap: 8px; margin: 2px 0 0; }
.wf-toggle {
  gap: 9px;
  padding: 9px 13px 9px 11px;
  border-radius: 13px;
  font-size: 11.5px;
  background: var(--pane);
  border: var(--pane-border);
  line-height: 1;
}
.wf-toggle input {
  /* Hidden from view, not from assistive tech or the tab order. */
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
  margin: 0;
  pointer-events: none;
}
/* The track. */
.wf-toggle::before {
  content: '';
  width: 30px; height: 18px;
  border-radius: 999px;
  background: var(--well);
  box-shadow: var(--well-inset);
  flex: none;
  transition: background 0.25s var(--tap), box-shadow 0.25s var(--tap);
}
/* The knob, positioned over the track. */
.wf-toggle::after {
  content: '';
  position: absolute;
  left: 13px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: linear-gradient(180deg, #fff, #d8d8d8);
  box-shadow: 0 1px 3px rgba(0,0,0,0.6);
  transition: transform 0.28s var(--spring), background 0.25s var(--tap);
}
.wf-toggle:has(input:checked) {
  background: linear-gradient(158deg, rgba(255,255,255,0.16), rgba(255,255,255,0.05));
  border-color: rgba(255,255,255,0.4);
  color: var(--lime);
}
.wf-toggle:has(input:checked)::before {
  background: linear-gradient(180deg, rgba(255,255,255,0.85), rgba(255,255,255,0.6));
  box-shadow: inset 0 1px 2px rgba(0,0,0,0.25), 0 0 12px rgba(255,255,255,0.4);
}
.wf-toggle:has(input:checked)::after { transform: translateX(12px); }
.wf-toggle:has(input:focus-visible) { outline: 2px solid rgba(255,255,255,0.6); outline-offset: 2px; }
.wf-toggle:active { transform: scale(0.97); }

.wf-hint {
  font-size: 10.5px;
  color: rgba(255,255,255,0.4);
  margin: 0;
  padding-left: 2px;
}

/* ── Config controls: one height, one rhythm ──────────────────────── */
.gen-config-row { gap: 8px; }
.gen-dd-row { gap: 8px; }
.gen-dd-btn {
  height: 44px;
  padding: 0 13px;
  border-radius: 14px;
  font-size: 12px;
  font-weight: 600;
  box-shadow: var(--pane-shadow);
}
.gen-dd-btn b { font-weight: 700; }
.gen-dd-btn svg { opacity: 0.45; transition: transform 0.25s var(--spring), opacity 0.2s; }
.gen-dropdown.open .gen-dd-btn svg { transform: rotate(180deg); opacity: 0.85; }
.gen-dropdown.open .gen-dd-btn {
  border-color: rgba(255,255,255,0.35);
  background: var(--pane-lit);
}
.gen-dd-option { padding: 11px 13px; font-size: 12.5px; border-radius: 10px; }

.gen-stepper { height: 44px; border-radius: 14px; box-shadow: var(--pane-shadow); }
.gen-step-btn { width: 36px; }
.gen-step-val { font-size: 13px; font-weight: 700; }

.model-swatch {
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.28), 0 2px 6px -2px rgba(0,0,0,0.6);
}

/* ── Prompt field ─────────────────────────────────────────────────── */
.composer-prompt {
  min-height: 46px;
  padding: 13px 14px;
  border-radius: 15px;
  font-size: 13.5px;
  line-height: 1.45;
}

/* ── Generate ─────────────────────────────────────────────────────
   The primary action deserves real weight: taller than the controls
   above it, with a lit top edge and a coloured bloom underneath. */
.generate-btn-lime {
  height: 52px;
  padding: 0 18px;
  border-radius: 16px;
  font-size: 14.5px;
  font-weight: 800;
  letter-spacing: 0.01em;
  margin-top: 2px;
}
.generate-btn-lime .price {
  font-size: 13.5px;
  opacity: 0.75;
}
/* The disabled state was a muddy olive that looked like a broken button
   rather than an unavailable one. It is now plainly inert glass. */
.generate-btn-lime:disabled {
  background: rgba(255,255,255,0.055);
  color: rgba(255,255,255,0.3);
  box-shadow: none;
  opacity: 1;
}
.generate-btn-lime:disabled::after { display: none; }
.generate-btn-lime:not(:disabled) {
  background: linear-gradient(180deg, #ffffff, #e4e4e4);
  color: #08080a;
}

/* ── Auto-Prompt chip ─────────────────────────────────────────────── */
.wf-panel-head .wf-autoprompt-btn {
  height: 30px;
  padding: 0 12px;
  border-radius: 999px;
  font-size: 10px;
  font-weight: 700;
  background: var(--pane-lit);
  border: var(--pane-border);
}
.wf-panel-head .wf-autoprompt-btn:hover:not(:disabled) {
  border-color: rgba(255,255,255,0.4);
  color: var(--lime);
}

/* ── Results area breathing room above the dock ───────────────────── */
.results-area { gap: 10px; }

/* ── Narrow phones: keep the panel from crowding ──────────────────── */
@media (max-width: 380px) {
  .composer-card { padding: 12px; gap: 9px; }
  .wf-slot-tile { width: 48px; height: 48px; border-radius: 13px; }
  .gen-dd-btn, .gen-stepper { height: 40px; font-size: 11.5px; }
  .generate-btn-lime { height: 48px; font-size: 13.5px; }
  .wf-toggle { padding: 8px 11px 8px 9px; font-size: 11px; }
}

/* ── Monochrome tile artwork ──────────────────────────────────────
   Same motifs, now white line work on dark glass. Six saturated
   gradients read as a different product once the accent went white, and
   the tiles go dark for the same reason: a white card was the only light
   surface left in the app. */
.tool-card {
  background: var(--pane-lit);
  -webkit-backdrop-filter: blur(18px) saturate(140%);
  backdrop-filter: blur(18px) saturate(140%);
  box-shadow: var(--pane-shadow);
}
.tool-card:hover { background: linear-gradient(158deg, rgba(255,255,255,0.17), rgba(255,255,255,0.06)); }
.tool-card::after {
  background: linear-gradient(to bottom, rgba(255,255,255,0.34), rgba(255,255,255,0.05) 45%, rgba(0,0,0,0.3));
}
/* Labels sit on dark glass now, so they invert to light. */
.tool-card h3 { color: #fff; }
.tool-card .cost { color: rgba(255,255,255,0.5); }
.tool-card .tile-body {
  background: linear-gradient(to top, rgba(10,10,12,0.94) 58%, rgba(10,10,12,0));
}

.tool-card-badge { background: #fff; color: #000; box-shadow: 0 2px 10px -3px rgba(255,255,255,0.55); }


/* ══════════════════════════════════════════════════════════════════════
   COMPOSER — COMPACT
   ──────────────────────────────────────────────────────────────────────
   The previous pass standardised every control to a 44px touch target and
   let each one take its own row. Correct in isolation, wrong in aggregate:
   six stacked rows pushed the panel to roughly two-thirds of the screen on
   a phone, leaving almost no room for the results it is supposed to sit
   beneath.

   This tightens the whole thing back down. Controls pair up two per row,
   heights drop to 36px, and the gaps and padding come in. 36px is below the
   44px ideal, but these controls sit in a wide row with generous horizontal
   hit area, and reclaiming the vertical space matters more here than the
   last few pixels of height.
   ══════════════════════════════════════════════════════════════════════ */

.composer-card { padding: 10px; gap: 7px; }

/* ── Header: one line, never wrapping ─────────────────────────────── */
.wf-panel-head { padding-bottom: 7px; margin-bottom: 0; gap: 6px; }
.wf-panel-title { font-size: 10px; letter-spacing: 0.08em; }
.wf-panel-head .wf-autoprompt-btn {
  height: 26px;
  padding: 0 10px;
  font-size: 9.5px;
  white-space: nowrap;
  flex: none;
}
.wf-panel-head .wf-autoprompt-btn svg { width: 10px; height: 10px; }

/* ── Reference slots ──────────────────────────────────────────────── */
.wf-slot-row { gap: 10px; margin-bottom: 0; }
.wf-slot-label { font-size: 8px; margin-bottom: 4px; letter-spacing: 0.07em; }
.wf-slot-tile { width: 44px; height: 44px; border-radius: 12px; }
.wf-slot-tiles { gap: 4px; }
.wf-slot-add svg { width: 14px; height: 14px; }

/* ── Prompt: two lines, and the placeholder must fit ──────────────── */
.composer-prompt {
  min-height: 40px;
  padding: 10px 12px;
  border-radius: 12px;
  font-size: 12.5px;
  line-height: 1.35;
}

/* ── Config controls: two per row ─────────────────────────────────
   The pieces are flat siblings, so a wrapping flex row pairs them without
   touching the markup. Model keeps a full row — it carries the longest
   label and truncating it is what makes a picker feel cheap. */
.gen-config-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
.gen-config-row > .gen-dropdown,
.gen-config-row > .gen-stepper {
  flex: 1 1 calc(50% - 3px);
  min-width: 0;
}
.gen-config-row > .gen-dropdown[data-key="model"] { flex-basis: 100%; }

.gen-dd-btn {
  height: 36px;
  padding: 0 10px;
  border-radius: 11px;
  font-size: 11px;
  gap: 6px;
}
.gen-dd-btn b { font-weight: 700; }
/* Long model names truncate rather than forcing the row taller. */
.gen-dd-btn > span:first-of-type,
.gen-dd-btn b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gen-dd-btn svg { width: 13px; height: 13px; flex: none; }
.gen-dd-option { padding: 9px 11px; font-size: 12px; }
.gen-dd-panel { max-height: 46vh; }

.gen-stepper { height: 36px; border-radius: 11px; }
.gen-step-btn { width: 30px; }
.gen-step-btn svg { width: 13px; height: 13px; }
.gen-step-val { font-size: 12px; }

.model-swatch { width: 20px; height: 20px; font-size: 8px; flex: none; }

/* ── Toggles ──────────────────────────────────────────────────────── */
.wf-toggle-row { gap: 6px; margin: 0; }
.wf-toggle { padding: 7px 11px 7px 9px; font-size: 10.5px; border-radius: 11px; gap: 7px; }
.wf-toggle::before { width: 26px; height: 15px; }
.wf-toggle::after { width: 11.5px; height: 11.5px; left: 11px; }
.wf-toggle:has(input:checked)::after { transform: translateX(11px); }
.wf-hint { font-size: 9.5px; }

/* ── Generate ─────────────────────────────────────────────────────── */
.generate-btn-lime {
  height: 44px;
  padding: 0 14px;
  border-radius: 13px;
  font-size: 13.5px;
  margin-top: 1px;
}
.generate-btn-lime .price { font-size: 12.5px; }

/* ── Other composer bits ──────────────────────────────────────────── */
.composer-toprow, .cost-row, .sound-toggle-row { gap: 6px; }
.ref-strip { gap: 5px; }
.ref-thumb, .ref-thumb-add { width: 44px; height: 44px; border-radius: 12px; }
.wf-select-row { gap: 6px; }

/* ── Very small phones ────────────────────────────────────────────── */
@media (max-height: 720px) {
  .composer-card { padding: 8px; gap: 5px; }
  .wf-slot-tile, .ref-thumb, .ref-thumb-add { width: 40px; height: 40px; }
  .gen-dd-btn, .gen-stepper { height: 33px; font-size: 10.5px; }
  .generate-btn-lime { height: 40px; font-size: 12.5px; }
  .composer-prompt { min-height: 36px; font-size: 12px; }
}

/* ══════════════════════════════════════════════════════════════════════
   COMPOSER — COMPACT, SECOND PASS
   ──────────────────────────────────────────────────────────────────────
   Still five rows on a phone, two of them almost empty: the `+` tile alone
   on one, the ×1 stepper alone on another. Both now share a row with their
   neighbours, which is worth more than any further shaving of heights.
   ══════════════════════════════════════════════════════════════════════ */

.composer-card { padding: 9px; gap: 6px; }

/* ── Reference tile beside the prompt, not above it ─────────────────
   Wrapping is deliberate: with one or two reference photos the strip sits
   inline, and once enough are attached to squeeze the text field below
   ~150px the prompt drops to its own line instead of being crushed. */
.composer-inputrow {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 6px;
}
.composer-inputrow > .composer-toprow { flex: 0 1 auto; }
.composer-inputrow > .composer-prompt { flex: 1 1 150px; min-width: 0; }

.composer-prompt { min-height: 40px; padding: 11px 12px; font-size: 12.5px; }
.ref-thumb, .ref-thumb-add { width: 40px; height: 40px; border-radius: 11px; }

/* ── Config: model on its own row, the rest share the next ──────────
   Zero flex-basis on the dropdowns lets all three sit on one line and
   split the leftover space evenly; the stepper holds a fixed width since
   its content ("− ×1 +") never changes size. Model keeps a full row —
   it carries the longest label, and truncating it makes a picker feel
   cheap. */
.gen-config-row > .gen-dropdown:not([data-key="model"]) { flex: 1 1 0; min-width: 0; }
.gen-config-row > .gen-stepper { flex: 0 0 84px; }

.gen-dd-btn, .gen-stepper { height: 34px; border-radius: 10px; font-size: 11px; }
.gen-dd-btn { padding: 0 9px; gap: 5px; }
.gen-step-btn { width: 26px; }
.model-swatch { width: 18px; height: 18px; font-size: 7.5px; }

.generate-btn-lime { height: 42px; border-radius: 12px; font-size: 13px; margin-top: 0; }

@media (max-height: 720px) {
  .composer-card { padding: 7px; gap: 5px; }
  .composer-prompt { min-height: 36px; }
  .ref-thumb, .ref-thumb-add, .wf-slot-tile { width: 36px; height: 36px; }
  .gen-dd-btn, .gen-stepper { height: 31px; font-size: 10.5px; }
  .generate-btn-lime { height: 38px; font-size: 12.5px; }
}

/* The workflow panels carry a header and a row of labelled reference slots
   on top of everything the plain tools have, so they need the same trim
   applied to those two extra rows. */
.wf-panel-head { padding-bottom: 6px; }
.wf-panel-title { font-size: 9.5px; }
.wf-panel-head .wf-autoprompt-btn { height: 24px; padding: 0 9px; font-size: 9px; }
.wf-slot-label { font-size: 7.5px; margin-bottom: 3px; }
.wf-slot-tile { width: 40px; height: 40px; border-radius: 11px; }
.wf-slot-row { gap: 9px; }

/* ══════════════════════════════════════════════════════════════════════
   COMPOSER — CONFIG AS PILLS
   ──────────────────────────────────────────────────────────────────────
   Full-width rows of "Model: Nano Banana Pro" / "Quality: 1K" spent the
   whole width restating what each control is, and cost two rows to say it.
   Each control now shrinks to its own value — the lead icon and the swatch
   already say which setting it is — so the whole configuration collapses to
   one row of pills.

   The "Model: " text is hidden, not removed, because it is what a screen
   reader announces; without it every control would read as a bare value
   with no indication of what it sets.
   ══════════════════════════════════════════════════════════════════════ */

.gen-dd-key {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Pills size to their content and wrap as a group. */
.gen-config-row > .gen-dropdown,
.gen-config-row > .gen-dropdown[data-key="model"],
.gen-config-row > .gen-stepper {
  flex: 0 0 auto;
  min-width: 0;
}
.gen-config-row { gap: 5px; align-items: center; }

.gen-dd-btn {
  height: 32px;
  padding: 0 11px;
  border-radius: 999px;
  font-size: 11.5px;
  gap: 6px;
  width: auto;
  justify-content: center;
}
.gen-dd-btn b { font-weight: 600; }
/* The chevron is dropped on the short pills — at this size it was wider
   than the value beside it. Model keeps one: it is the only control whose
   list is long enough that its options are not guessable. */
.gen-config-row > .gen-dropdown:not([data-key="model"]) .gen-dd-btn > svg:last-child {
  display: none;
}
.gen-dd-btn > svg:last-child { width: 12px; height: 12px; }

.gen-stepper {
  height: 32px;
  border-radius: 999px;
  flex: 0 0 auto;
}
.gen-step-btn { width: 24px; }
.gen-step-btn svg { width: 12px; height: 12px; }
.gen-step-val { font-size: 11.5px; min-width: 16px; text-align: center; }

.model-swatch { width: 17px; height: 17px; font-size: 7px; }

/* The dropdown panel is anchored to a pill now, so it must be free to be
   wider than the control that opened it. */
.gen-dropdown { position: relative; }
.gen-dd-panel { min-width: max(100%, 150px); }
.gen-dropdown[data-key="model"] .gen-dd-panel { min-width: 100%; }

/* The quality icon (a small bar chart, one bar per tier) was emitted by
   resolutionIcon() but never had any CSS: the bars carry an inline height
   and nothing else, so with no width and no flex container the whole icon
   collapsed to 0x0 and has been invisible for as long as it has existed.
   ratio-icon next to it was styled correctly, which is why only this one
   looked "missing" rather than obviously broken. */
.res-icon {
  display: inline-flex;
  align-items: flex-end;
  gap: 1.5px;
  height: 11px;
  flex: none;
}
.res-bar {
  width: 2.5px;
  border-radius: 1px;
  background: currentColor;
  opacity: 0.28;
}
.res-bar.filled { opacity: 0.95; }

/* Beat the earlier `:not([data-key="model"])` rule, which is a class plus an
   attribute selector and so outranks a bare class no matter how far down the
   file it sits. Pills size to their content; nothing stretches. */
.gen-config-row > .gen-dropdown,
.gen-config-row > .gen-dropdown:not([data-key="model"]),
.gen-config-row > .gen-dropdown[data-key="model"] {
  flex: 0 0 auto;
  min-width: 0;
}
.gen-config-row > .gen-dd-btn,
.gen-dropdown > .gen-dd-btn { width: auto; }

/* Final sizing so model + quality + ratio + count all sit on one row at
   390px. The stepper stays a stepper rather than becoming another dropdown
   — changing a count is one tap on +, two on a menu — but it shrinks to
   pill scale, and the "×" prefix goes: inside a pill next to − and + the
   number reads as a count on its own. */
.gen-config-row { gap: 4px; }
.gen-dd-btn { padding: 0 9px; gap: 5px; }
.gen-step-btn { width: 21px; }
.gen-step-val { min-width: 12px; font-weight: 700; }

/* On the narrowest phones the model name gives up width first — it is the
   one value long enough to shorten and still be recognisable. */
@media (max-width: 380px) {
  .gen-dropdown[data-key="model"] .gen-dd-label { max-width: 96px; }
}

/* The stepper carried 8px side padding and an 8px gap on each side of the
   number — 32px of air around a single digit, which is what kept the
   configuration row from fitting on one line. */
.gen-stepper { padding: 0 4px; gap: 2px; }

/* Hiding the inline name is only right where something else already
   identifies the control — the swatch on Model, the icons on Quality and
   Ratio. Scope it to those two places: elsewhere the name is the only thing
   that says what the value means. */
.gen-dd-key { position: static; width: auto; height: auto; margin: 0; clip-path: none; }
.gen-config-row .gen-dd-key,
.wf-select .gen-dd-key {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ── Reddit's four paper/pen selects ──────────────────────────────
   One row of four, each captioned above, so the pill itself is free to
   show only its value. */
.wf-select-row { display: flex; gap: 5px; align-items: end; }
.wf-select {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.wf-select-label {
  font-size: 7.5px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: rgba(255,255,255,0.42);
  padding-left: 2px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wf-select .gen-dropdown { width: 100%; }
.wf-select .gen-dd-btn { width: 100%; justify-content: space-between; padding: 0 8px; }
.wf-select .gen-dd-btn b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wf-select .gen-dd-panel { min-width: max(100%, 118px); }

/* ── Dropdown panel ────────────────────────────────────────────────
   The selected-row tick was a `float: right`, which drops onto its own
   line as soon as the label fills the row — exactly what happened to the
   longest model name. It is a flex child pushed to the end instead, so it
   stays on the baseline of the name no matter how long that name is.

   The panel also sizes to its widest option now rather than to the pill
   that opened it: these pills are far narrower than their contents since
   they only show a value. */
.gen-dd-row .gen-dd-option {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
}
.gen-dd-row .gen-dd-option.selected::after {
  content: '✓';
  float: none;
  margin-left: auto;
  padding-left: 4px;
  font-weight: 800;
  flex: none;
}
.gen-dd-panel {
  width: max-content;
  min-width: 100%;
  max-width: min(72vw, 250px);
}
.gen-dd-row { flex-wrap: nowrap; }

/* The tick's glyph is taller than the label's, which made the selected row
   a pixel taller than its neighbours and the list visibly uneven. Pinning
   the line box on both keeps every row identical. */
.gen-dd-row .gen-dd-option { line-height: 1.35; }
.gen-dd-row .gen-dd-option.selected::after { line-height: 1.35; }

/* ══════════════════════════════════════════════════════════════════════
   TOGGLE KNOB — anchoring fix
   ──────────────────────────────────────────────────────────────────────
   The switch is drawn as two pseudo-elements: ::before is the track (a
   normal flex child) and ::after is the knob (absolutely positioned). But
   .wf-toggle was never given `position: relative`, so the knob resolved
   against whatever positioned ancestor happened to be up the tree — the
   composer card — instead of its own label. Every knob in a row therefore
   landed at the same absolute x, stacking on top of each other and sitting
   outside the track it was supposed to be inside.

   The knob is also no longer positioned with a hardcoded left. Its offsets
   derive from the same variables as the padding and the track, so the two
   halves of the switch cannot drift apart again the next time the padding
   is adjusted.
   ══════════════════════════════════════════════════════════════════════ */
.wf-toggle {
  position: relative;
  --sw-pad-l: 9px;
  --sw-track-w: 26px;
  --sw-track-h: 15px;
  --sw-knob: 11.5px;
  --sw-inset: 1.75px;
  padding: 7px 11px 7px var(--sw-pad-l);
}
.wf-toggle::before {
  width: var(--sw-track-w);
  height: var(--sw-track-h);
}
.wf-toggle::after {
  top: 50%;
  left: calc(var(--sw-pad-l) + var(--sw-inset));
  width: var(--sw-knob);
  height: var(--sw-knob);
  /* translateY carries the vertical centring, so the checked state must
     re-state it — a bare translateX would drop the knob half its height. */
  transform: translateY(-50%);
}
.wf-toggle:has(input:checked)::after {
  transform: translate(
    calc(var(--sw-track-w) - var(--sw-knob) - var(--sw-inset) * 2),
    -50%
  );
}

/* ── The config row must never wrap ────────────────────────────────
   Fitting on one line was previously a coincidence of how wide the values
   happened to be: "Nano Banana 2" with "0.5K" and "16:9" is a few pixels
   wider than the model this was measured against, and the counter dropped
   to a second line.

   So the row no longer wraps at all, and the model pill is the one piece
   allowed to shrink — it holds the longest value and is the only one that
   stays recognisable when truncated ("Nano Banana…"). The small pills keep
   their intrinsic width, because clipping "0.5K" or "16:9" destroys them. */
.gen-config-row { flex-wrap: nowrap; }
.gen-config-row > .gen-dropdown[data-key="model"] {
  flex: 0 1 auto;
  min-width: 0;
}
.gen-config-row > .gen-dropdown:not([data-key="model"]),
.gen-config-row > .gen-stepper {
  flex: 0 0 auto;
}
.gen-config-row > .gen-dropdown[data-key="model"] .gen-dd-label {
  overflow: hidden;
  min-width: 0;
}
.gen-config-row > .gen-dropdown[data-key="model"] .gen-dd-btn {
  min-width: 0;
  justify-content: flex-start;
}

/* ══════════════════════════════════════════════════════════════════════
   GROUPED LISTS — menu rows, language picker, agent model picker
   ──────────────────────────────────────────────────────────────────────
   These were the last surfaces still built as detached slabs: each row its
   own bordered card, its own shadow, 13px of padding, stacked with gaps.
   Next to a composer made of 32px pills they read as a different app, and
   they were tall enough that six languages filled most of a phone screen.

   A stack of rows is one object, not six — so it is one pane of glass with
   hairline separators, the way the rest of the app already treats grouped
   content. Dividers hang off border-bottom rather than border-top so that
   a hidden row (the admin entry, which most users never see) leaves no
   stray line above the first visible one.
   ══════════════════════════════════════════════════════════════════════ */
.list-group,
#lang-list,
.agent-model-list {
  border-radius: 14px;
  overflow: hidden;
  background: var(--pane);
  border: var(--pane-border);
  box-shadow: var(--pane-shadow);
}

.list-group .list-row,
#lang-list .lang-row,
.agent-model-list .lang-row {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding: 10px 12px;
  min-height: 44px;
  gap: 10px;
}
.list-group .list-row:not(:last-child),
#lang-list .lang-row:not(:last-child),
.agent-model-list .lang-row:not(:last-child) {
  border-bottom: 1px solid rgba(255,255,255,0.07);
}
.list-group .list-row:active,
#lang-list .lang-row:active,
.agent-model-list .lang-row:active {
  background: rgba(255,255,255,0.09);
  transform: none; /* a row inside a clipped group cannot scale without tearing the divider */
}
.list-group .list-row:hover,
#lang-list .lang-row:hover,
.agent-model-list .lang-row:hover {
  background: rgba(255,255,255,0.06);
  border-color: transparent;
  border-bottom-color: rgba(255,255,255,0.07);
}

/* ── Row contents ─────────────────────────────────────────────────── */
.list-group .list-row .left { gap: 11px; align-items: center; }
.list-group .list-row .left svg {
  width: 15px; height: 15px;
  flex: none;
  opacity: 0.85;
}
.list-group .list-row .label,
#lang-list .lang-row .name,
.agent-model-list .lang-row .name {
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0;
}
.list-group .list-row .chev {
  font-size: 17px;
  line-height: 1;
  opacity: 0.28;
  font-weight: 400;
}
.list-group .list-row .value {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: rgba(255,255,255,0.45);
}

/* The selected row earns the app's accent — white — rather than a tick
   floating in the dark at the end of an otherwise identical row. */
#lang-list .lang-row .name,
.agent-model-list .lang-row .name { gap: 9px; }
/* svg() wraps its icon in <span class="i-sm">, so the tick is a span with
   a bare <svg> inside — matching on svg.i-sm silently selects nothing. */
#lang-list .lang-row:has(> .i-sm),
.agent-model-list .lang-row:has(> .i-sm) {
  background: rgba(255,255,255,0.08);
}
#lang-list .lang-row > .i-sm,
.agent-model-list .lang-row > .i-sm {
  display: inline-flex;
  flex: none;
  color: #fff;
}
#lang-list .lang-row > .i-sm svg,
.agent-model-list .lang-row > .i-sm svg { width: 16px; height: 16px; }

.section-title {
  font-size: 10px;
  letter-spacing: 0.1em;
  margin-bottom: 7px;
}

/* ══════════════════════════════════════════════════════════════════════
   AGENT CHAT
   ──────────────────────────────────────────────────────────────────────
   The chat never got the compact pass the rest of the app did: a single
   line of text sat in a 42px bubble with 18px corners, so four short
   messages filled the screen. Bubbles are the one thing here that should
   look soft, but they were soft AND huge.

   Everything shrinks by about a fifth, the tail corner gets sharper so the
   bubbles read as speech rather than as pills, and the result image is
   finally bounded — it had no max-height at all and grew until the composer
   cut it off.
   ══════════════════════════════════════════════════════════════════════ */

.msg-bubble {
  max-width: 80%;
  padding: 8px 12px;
  border-radius: 15px;
  font-size: 13px;
  line-height: 1.45;
}
.msg-bubble.user { border-end-end-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.35); }
.msg-row.user.grouped .msg-bubble.user { border-start-end-radius: 5px; }
.msg-bubble.assistant { border-end-start-radius: 5px; box-shadow: 0 3px 12px rgba(0,0,0,0.25); }
.msg-row.assistant.grouped .msg-bubble.assistant { border-start-start-radius: 5px; }
.msg-bubble.msg-typing { padding: 11px 14px; }

.msg-row + .msg-row { margin-top: 7px; }
.msg-row.grouped { margin-top: -3px; }
.msg-row.grouped:has(.msg-bubble-media) { margin-top: 7px; }

/* Now that a stamp only appears at the end of a burst it can afford to be
   quiet — it was competing with the messages when it repeated on every row. */
.msg-time {
  font-size: 9.5px;
  font-weight: 600;
  opacity: 0.45;
  margin: 3px 4px 0;
}

/* ── Media ────────────────────────────────────────────────────────
   A generated portrait is 3:4, so an unbounded one runs to ~470px on a
   phone: taller than the space between the header and the composer, which
   is why the result looked cut off rather than framed. */
.msg-bubble.msg-bubble-media { max-width: 74%; }
.msg-bubble-img {
  max-height: 300px;
  width: auto;
  border-radius: 14px;
  display: block;
}
.msg-photo-strip.multi .msg-bubble-img { width: 76px; height: 76px; border-radius: 11px; }
.msg-bubble-media .result-slot { width: 156px; }
.msg-bubble-media .result-card { max-height: 320px; }

/* ── Composer ─────────────────────────────────────────────────────
   The text field had picked up a border and an inset shadow from the
   generic input styling, so a bordered box sat inside the bordered pill
   that already contains it. The pill is the field. */
.agent-compose {
  padding: 4px 4px 4px 5px;
  gap: 3px;
}
.agent-text-input,
.agent-text-input:focus {
  border: none;
  box-shadow: none;
  background: none;
  outline: none;
  border-radius: 0;
  font-size: 13.5px;
  padding: 8px 6px;
}
.agent-attach-btn, .agent-settings-btn {
  width: 30px; height: 30px;
  flex: none;
}
.agent-attach-btn svg, .agent-settings-btn svg { width: 14px; height: 14px; }
.agent-send-btn { width: 32px; height: 32px; }
.agent-send-btn svg { width: 14px; height: 14px; }
.agent-inputbar { padding: 9px 12px; }

/* ── Header ───────────────────────────────────────────────────────── */
.agent-head h2, .agent-title { font-size: 19px; }

/* ══════════════════════════════════════════════════════════════════════
   GENERATED IMAGES — three to a row
   ──────────────────────────────────────────────────────────────────────
   Results were laid out two to a row, and in the Agent chat a single result
   ran to 225px wide and 300px tall — one image occupied more of the screen
   than the whole conversation around it, so a chat with three results in it
   became almost entirely scrolling.

   A generated image is a thumbnail to judge and tap, not the final artefact;
   full size is a tap away in the viewer. Three to a row across the app.
   ══════════════════════════════════════════════════════════════════════ */
.gallery-grid,
.batch-results {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 7px;
}

/* In chat there is no grid to divide, so the bubble takes the share a grid
   column would have: about a third of the conversation's width. */
.msg-bubble.msg-bubble-media { max-width: 34%; }
.msg-bubble-media .msg-photo-strip:not(.multi) { width: 100%; }
.msg-bubble-img {
  width: 100%;
  height: auto;
  max-height: none;
  border-radius: 12px;
}
.msg-bubble-media .result-slot,
.msg-bubble-media .result-card {
  width: 100%;
  max-height: none;
}
/* Reference photos the user attached stay small and square — several of
   them share one row, so they are not competing with the result. */
.msg-photo-strip.multi .msg-bubble-img { width: 62px; height: 62px; }

/* ── Agent batch collage ───────────────────────────────────────────
   A request for several images is one answer, so its tiles share one
   bubble. Four go 2x2 rather than in a line: a 4-wide row on a phone
   leaves each tile too small to judge, which defeats the point of showing
   them at all. */
.agent-result-grid {
  display: grid;
  gap: 5px;
  width: 100%;
}
.agent-result-grid[data-count="1"] { grid-template-columns: 1fr; }
.agent-result-grid[data-count="2"],
.agent-result-grid[data-count="4"] { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.agent-result-grid[data-count="3"],
.agent-result-grid:not([data-count="1"]):not([data-count="2"]):not([data-count="4"]) {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

/* The bubble widens with the collage instead of staying at the single-image
   third, so tiles keep a usable size however many there are. */
/* Same failure as the collage below, which this originally missed: a
   finished single result has no intrinsic width either. .result-slot is an
   aspect-ratio box at width:100% and the image inside is object-fit:cover
   at width:100% — both resolve against the bubble, so with only a max-width
   the bubble collapsed to a ~22px sliver with the image crushed inside it.

   An attached photo does not hit this: a real <img> contributes its own
   intrinsic width, which is why only generated results were affected.

   Declared ahead of the collage widths, which are equally or more specific
   and so still win for multi-image answers. */
/* A media bubble holds a tile, never prose, so it must not inherit the
   pre-wrap that chat text needs. resultGridCard() returns a template literal
   that begins with a newline and indentation; under pre-wrap that leading
   whitespace renders as real blank lines, and the bubble's background
   stretched to cover them — roughly 40px of empty dark box above every
   finished generation. */
.msg-bubble.msg-bubble-media { white-space: normal; }

.msg-bubble-media:has(.result-slot),
.msg-bubble-media:has(.result-card) { width: 34%; }

/* width, not max-width: the bubble is a flex item that shrink-wraps its
   content, and tiles sized by aspect-ratio have no intrinsic width to wrap
   around — with max-width alone the collage collapsed to ~81px tiles
   regardless of how much room it was allowed.

   Order matters too: these all have equal specificity, so the general case
   has to come first or it wins over the counts it falls back for. */
.msg-bubble-media:has(.agent-result-grid) { width: 94%; max-width: 94%; }
.msg-bubble-media:has(.agent-result-grid[data-count="2"]),
.msg-bubble-media:has(.agent-result-grid[data-count="4"]) { width: 64%; max-width: 64%; }
.msg-bubble-media:has(.agent-result-grid[data-count="1"]) { width: 34%; max-width: 34%; }

.agent-result-grid .result-slot,
.agent-result-grid .result-card {
  width: 100%;
  margin: 0;
  aspect-ratio: 3/4;
}
.agent-result-grid .result-slot .stage { font-size: 9.5px; }
.agent-result-grid .result-slot .ring { width: 20px; height: 20px; border-width: 2px; }

/* ── Tool tile artwork — lit, with depth ──────────────────────────
   Flat silhouettes read as clipart. These are lit from the top left:
   surfaces carry a gradient across their face, planes sit back in
   perspective, and anything floating drops a real shadow. Monochrome
   throughout, since white is the app's only accent. */


/* ══════════════════════════════════════════════════════════════════════
   TOOL CARDS — DEPTH
   ──────────────────────────────────────────────────────────────────────
   The artwork inside the cards now has a light source, so the cards had to
   earn one too: a flat pane holding a lit illustration looks like a sticker
   on a wall. Each card gets a layered shadow (a tight contact shadow plus a
   wide ambient one), a lit top edge where the light would actually catch,
   and a press that tilts the card away rather than just shrinking it.
   ══════════════════════════════════════════════════════════════════════ */

.tool-grid { gap: 11px; perspective: 900px; }

.tool-card {
  border-radius: 18px;
  /* Contact shadow, ambient shadow, then a lit inner top edge. Three
     layers is what separates "raised" from "outlined". */
  box-shadow:
    0 1px 2px rgba(0, 0, 0, 0.6),
    0 10px 24px -8px rgba(0, 0, 0, 0.75),
    inset 0 1px 0 rgba(255, 255, 255, 0.14);
  transition:
    transform 0.42s cubic-bezier(0.22, 0.9, 0.28, 1),
    box-shadow 0.42s cubic-bezier(0.22, 0.9, 0.28, 1);
}

/* Tilting away from the pointer reads as a physical push. Scale alone just
   reads as a UI state change. */
.tool-card:active {
  transform: rotateX(6deg) scale(0.975);
  box-shadow:
    0 1px 1px rgba(0, 0, 0, 0.7),
    0 4px 10px -6px rgba(0, 0, 0, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
}

/* The art sits forward of the card face, so the press parallaxes it very
   slightly against the frame instead of moving as one flat piece. */
.tool-card .tile-art {
  transition: transform 0.5s cubic-bezier(0.22, 0.9, 0.28, 1);
}
.tool-card:active .tile-art { transform: scale(1.04) translateY(-2px); }

/* A soft key light falling across the upper-left corner, matching where the
   illustrations are lit from. */
.tool-card::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(120% 90% at 8% -10%, rgba(255, 255, 255, 0.13), transparent 58%);
}

/* The scrim under the label needs to be deep enough to sit the text on, but
   it was cutting a hard horizontal line across the illustration. A longer,
   eased ramp lets the art fade into it. */
.tool-card .tile-body {
  background: linear-gradient(
    to top,
    rgba(8, 8, 10, 0.97) 42%,
    rgba(8, 8, 10, 0.82) 68%,
    rgba(8, 8, 10, 0) 100%
  );
  padding: 12px 12px 12px;
}
.tool-card h3 { font-size: 13.5px; letter-spacing: -0.01em; }
.tool-card .cost { font-size: 10.5px; }

/* ── Badges ───────────────────────────────────────────────────────
   Both badges float above the art, so both cast a shadow — without one
   they look pasted onto the illustration rather than sitting over it. */



.tool-card-badge {
  top: 10px; right: 10px;
  padding: 2px 7px;
  font-size: 8.5px;
  font-weight: 800;
  letter-spacing: 0.08em;
  border-radius: 999px;
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.6),
    0 3px 10px -3px rgba(0, 0, 0, 0.8);
}

/* Hover only exists on pointer devices; on a phone it would stick after a
   tap and leave the card looking permanently lifted. */
@media (hover: hover) {
  .tool-card:hover {
    transform: translateY(-3px) rotateX(-2deg);
    box-shadow:
      0 2px 3px rgba(0, 0, 0, 0.6),
      0 18px 34px -10px rgba(0, 0, 0, 0.85),
      inset 0 1px 0 rgba(255, 255, 255, 0.2);
  }
  .tool-card:hover .tile-art { transform: scale(1.05); }
}

/* Motion here is decoration, not information. */
@media (prefers-reduced-motion: reduce) {
  .tool-card,
  .tool-card .tile-art { transition: none; }
  .tool-card:active,
  .tool-card:hover { transform: none; }
  .tool-card:active .tile-art,
  .tool-card:hover .tile-art { transform: none; }
}



/* ── Tool tile artwork ────────────────────────────────────────────
   The supplied illustrations, white backgrounds knocked out so they sit
   on the tile rather than in a white box.

   The art box runs the height of the card less the label, instead of a
   fixed 108px strip under it — that strip was why the first attempt came
   out cut off. `contain` keeps each composition whole: these illustrate
   a before-and-after, and cropping one to fill the tile removes half the
   story. */

.tool-card .tile-art {
  /* A flex item, not an absolutely-positioned box with a guessed bottom
     inset. The label is one line on most cards and two on Character
     Replacement, so any fixed inset either overlaps the longer label or
     wastes space above the shorter ones. Taking the remaining column height
     adapts to both. */
  position: relative;
  inset: auto;
  flex: 1 1 auto;
  min-height: 0;
  margin: 7px 7px 0;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
  opacity: 1;
  filter: saturate(1.05) contrast(1.02);
}

/* With the artwork no longer running under the label, the scrim only has
   to seat the text — it does not need to hide anything. */
.tool-card .tile-body {
  background: linear-gradient(to top, rgba(8,8,10,0.98) 62%, rgba(8,8,10,0));
}

[data-tool="image"] .tile-art {
  background-image: url("img/tools/image.webp");
}
[data-tool="video"] .tile-art {
  background-image: url("img/tools/video.webp");
}
[data-tool="lipsync"] .tile-art {
  background-image: url("img/tools/video.webp");
}
[data-tool="wf-influencer"] .tile-art {
  background-image: url("img/tools/wf-influencer.webp");
}
[data-tool="wf-charreplace"] .tile-art {
  background-image: url("img/tools/wf-charreplace.webp");
}
[data-tool="wf-reddit"] .tile-art {
  background-image: url("img/tools/wf-reddit.webp");
}
[data-tool="wf-handsign"] .tile-art {
  background-image: url("img/tools/wf-handsign.webp");
}
